🐛 Update: Added support for the 'find' command in settings.local.json. Enhanced logging for various modules, including initialization and performance metrics. Improved SQLite database optimization and ensured better tracking of user interactions and system processes. 📚

This commit is contained in:
2025-06-14 16:26:43 +02:00
parent ee54bc273c
commit 89037861e3
2472 changed files with 691099 additions and 1 deletions

1158
network-visualization/node_modules/gsap/src/CSSPlugin.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,106 @@
/*!
* CSSRulePlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _win, _doc, CSSPlugin,
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_checkRegister = () => {
if (!_coreInitted) {
_initCore();
if (!CSSPlugin) {
console.warn("Please gsap.registerPlugin(CSSPlugin, CSSRulePlugin)");
}
}
return _coreInitted;
},
_initCore = core => {
gsap = core || _getGSAP();
if (_windowExists()) {
_win = window;
_doc = document;
}
if (gsap) {
CSSPlugin = gsap.plugins.css;
if (CSSPlugin) {
_coreInitted = 1;
}
}
};
export const CSSRulePlugin = {
version: "3.13.0",
name: "cssRule",
init(target, value, tween, index, targets) {
if (!_checkRegister() || typeof(target.cssText) === "undefined") {
return false;
}
let div = target._gsProxy = target._gsProxy || _doc.createElement("div");
this.ss = target;
this.style = div.style;
div.style.cssText = target.cssText;
CSSPlugin.prototype.init.call(this, div, value, tween, index, targets); //we just offload all the work to the regular CSSPlugin and then copy the cssText back over to the rule in the render() method. This allows us to have all of the updates to CSSPlugin automatically flow through to CSSRulePlugin instead of having to maintain both
},
render(ratio, data) {
let pt = data._pt,
style = data.style,
ss = data.ss,
i;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
i = style.length;
while (--i > -1) {
ss[style[i]] = style[style[i]];
}
},
getRule(selector) {
_checkRegister();
let ruleProp = _doc.all ? "rules" : "cssRules",
styleSheets = _doc.styleSheets,
i = styleSheets.length,
pseudo = (selector.charAt(0) === ":"),
j, curSS, cs, a;
selector = (pseudo ? "" : ",") + selector.split("::").join(":").toLowerCase() + ","; //note: old versions of IE report tag name selectors as upper case, so we just change everything to lowercase.
if (pseudo) {
a = [];
}
while (i--) {
//Firefox may throw insecure operation errors when css is loaded from other domains, so try/catch.
try {
curSS = styleSheets[i][ruleProp];
if (!curSS) {
continue;
}
j = curSS.length;
} catch (e) {
console.warn(e);
continue;
}
while (--j > -1) {
cs = curSS[j];
if (cs.selectorText && ("," + cs.selectorText.split("::").join(":").toLowerCase() + ",").indexOf(selector) !== -1) { //note: IE adds an extra ":" to pseudo selectors, so .myClass:after becomes .myClass::after, so we need to strip the extra one out.
if (pseudo) {
a.push(cs.style);
} else {
return cs.style;
}
}
}
}
return a;
},
register: _initCore
};
_getGSAP() && gsap.registerPlugin(CSSRulePlugin);
export { CSSRulePlugin as default };

View File

@@ -0,0 +1,150 @@
/*!
* CustomBounce 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, createCustomEase,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_initCore = required => {
gsap = _getGSAP();
createCustomEase = gsap && gsap.parseEase("_CE");
if (createCustomEase) {
_coreInitted = 1;
gsap.parseEase("bounce").config = vars => typeof(vars) === "object" ? _create("", vars) : _create("bounce(" + vars + ")", {strength:+vars});
} else {
required && console.warn("Please gsap.registerPlugin(CustomEase, CustomBounce)");
}
},
_normalizeX = a => { //scales all the x values in an array [x, y, x, y...] AND rounds them to the closest hundredth (decimal)
let l = a.length,
s = 1 / a[l - 2],
rnd = 1000,
i;
for (i = 2; i < l; i += 2) {
a[i] = ~~(a[i] * s * rnd) / rnd;
}
a[l - 2] = 1; //in case there are any rounding errors. x should always end at 1.
},
_bonusValidated = 1, //<name>CustomBounce</name>
_create = (id, vars) => {
if (!_coreInitted) {
_initCore(1);
}
vars = vars || {};
if (_bonusValidated) {
let max = 0.999,
decay = Math.min(max, vars.strength || 0.7), // Math.min(0.999, 1 - 0.3 / (vars.strength || 1)),
decayX = decay,
gap = (vars.squash || 0) / 100,
originalGap = gap,
slope = 1 / 0.03,
w = 0.2,
h = 1,
prevX = 0.1,
path = [0, 0, 0.07, 0, 0.1, 1, 0.1, 1],
squashPath = [0, 0, 0, 0, 0.1, 0, 0.1, 0],
cp1, cp2, x, y, i, nextX, squishMagnitude;
for (i = 0; i < 200; i++) {
w *= decayX * ((decayX + 1) / 2);
h *= decay * decay;
nextX = prevX + w;
x = prevX + w * 0.49;
y = 1 - h;
cp1 = prevX + h / slope;
cp2 = x + (x - cp1) * 0.8;
if (gap) {
prevX += gap;
cp1 += gap;
x += gap;
cp2 += gap;
nextX += gap;
squishMagnitude = gap / originalGap;
squashPath.push(
prevX - gap, 0,
prevX - gap, squishMagnitude,
prevX - gap / 2, squishMagnitude, //center peak anchor
prevX, squishMagnitude,
prevX, 0,
prevX, 0, //base anchor
prevX, squishMagnitude * -0.6,
prevX + (nextX - prevX) / 6, 0,
nextX, 0
);
path.push(prevX - gap, 1,
prevX, 1,
prevX, 1);
gap *= decay * decay;
}
path.push(prevX, 1,
cp1, y,
x, y,
cp2, y,
nextX, 1,
nextX, 1);
decay *= 0.95;
slope = h / (nextX - cp2);
prevX = nextX;
if (y > max) {
break;
}
}
if (vars.endAtStart && vars.endAtStart !== "false") {
x = -0.1;
path.unshift(x, 1, x, 1, -0.07, 0);
if (originalGap) {
gap = originalGap * 2.5; //make the initial anticipation squash longer (more realistic)
x -= gap;
path.unshift(x, 1, x, 1, x, 1);
squashPath.splice(0, 6);
squashPath.unshift(x, 0, x, 0, x, 1, x + gap / 2, 1, x + gap, 1, x + gap, 0, x + gap, 0, x + gap, -0.6, x + gap + 0.033, 0);
for (i = 0; i < squashPath.length; i+=2) {
squashPath[i] -= x;
}
}
for (i = 0; i < path.length; i+=2) {
path[i] -= x;
path[i+1] = 1 - path[i+1];
}
}
if (gap) {
_normalizeX(squashPath);
squashPath[2] = "C" + squashPath[2];
createCustomEase(vars.squashID || (id + "-squash"), "M" + squashPath.join(","));
}
_normalizeX(path);
path[2] = "C" + path[2];
return createCustomEase(id, "M" + path.join(","));
}
};
export class CustomBounce {
constructor(id, vars) {
this.ease = _create(id, vars);
}
static create(id, vars) {
return _create(id, vars);
}
static register(core) {
gsap = core;
_initCore();
}
}
_getGSAP() && gsap.registerPlugin(CustomBounce);
CustomBounce.version = "3.13.0";
export { CustomBounce as default };

View File

@@ -0,0 +1,277 @@
/*!
* CustomEase 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { stringToRawPath, rawPathToString, transformRawPath } from "./utils/paths.js";
let gsap, _coreInitted,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_initCore = () => {
gsap = _getGSAP();
if (gsap) {
gsap.registerEase("_CE", CustomEase.create);
_coreInitted = 1;
} else {
console.warn("Please gsap.registerPlugin(CustomEase)");
}
},
_bigNum = 1e20,
_round = value => ~~(value * 1000 + (value < 0 ? -.5 : .5)) / 1000,
_bonusValidated = 1, //<name>CustomEase</name>
_numExp = /[-+=.]*\d+[.e\-+]*\d*[e\-+]*\d*/gi, //finds any numbers, including ones that start with += or -=, negative numbers, and ones in scientific notation like 1e-8.
_needsParsingExp = /[cLlsSaAhHvVtTqQ]/g,
_findMinimum = values => {
let l = values.length,
min = _bigNum,
i;
for (i = 1; i < l; i += 6) {
+values[i] < min && (min = +values[i]);
}
return min;
},
//takes all the points and translates/scales them so that the x starts at 0 and ends at 1.
_normalize = (values, height, originY) => {
if (!originY && originY !== 0) {
originY = Math.max(+values[values.length-1], +values[1]);
}
let tx = +values[0] * -1,
ty = -originY,
l = values.length,
sx = 1 / (+values[l - 2] + tx),
sy = -height || ((Math.abs(+values[l - 1] - +values[1]) < 0.01 * (+values[l - 2] - +values[0])) ? _findMinimum(values) + ty : +values[l - 1] + ty),
i;
if (sy) { //typically y ends at 1 (so that the end values are reached)
sy = 1 / sy;
} else { //in case the ease returns to its beginning value, scale everything proportionally
sy = -sx;
}
for (i = 0; i < l; i += 2) {
values[i] = (+values[i] + tx) * sx;
values[i + 1] = (+values[i + 1] + ty) * sy;
}
},
//note that this function returns point objects like {x, y} rather than working with segments which are arrays with alternating x, y values as in the similar function in paths.js
_bezierToPoints = function (x1, y1, x2, y2, x3, y3, x4, y4, threshold, points, index) {
let x12 = (x1 + x2) / 2,
y12 = (y1 + y2) / 2,
x23 = (x2 + x3) / 2,
y23 = (y2 + y3) / 2,
x34 = (x3 + x4) / 2,
y34 = (y3 + y4) / 2,
x123 = (x12 + x23) / 2,
y123 = (y12 + y23) / 2,
x234 = (x23 + x34) / 2,
y234 = (y23 + y34) / 2,
x1234 = (x123 + x234) / 2,
y1234 = (y123 + y234) / 2,
dx = x4 - x1,
dy = y4 - y1,
d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx),
d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx),
length;
if (!points) {
points = [{x: x1, y: y1}, {x: x4, y: y4}];
index = 1;
}
points.splice(index || points.length - 1, 0, {x: x1234, y: y1234});
if ((d2 + d3) * (d2 + d3) > threshold * (dx * dx + dy * dy)) {
length = points.length;
_bezierToPoints(x1, y1, x12, y12, x123, y123, x1234, y1234, threshold, points, index);
_bezierToPoints(x1234, y1234, x234, y234, x34, y34, x4, y4, threshold, points, index + 1 + (points.length - length));
}
return points;
};
export class CustomEase {
constructor(id, data, config) {
_coreInitted || _initCore();
this.id = id;
_bonusValidated && this.setData(data, config);
}
setData(data, config) {
config = config || {};
data = data || "0,0,1,1";
let values = data.match(_numExp),
closest = 1,
points = [],
lookup = [],
precision = config.precision || 1,
fast = (precision <= 1),
l, a1, a2, i, inc, j, point, prevPoint, p;
this.data = data;
if (_needsParsingExp.test(data) || (~data.indexOf("M") && data.indexOf("C") < 0)) {
values = stringToRawPath(data)[0];
}
l = values.length;
if (l === 4) {
values.unshift(0, 0);
values.push(1, 1);
l = 8;
} else if ((l - 2) % 6) {
throw "Invalid CustomEase";
}
if (+values[0] !== 0 || +values[l - 2] !== 1) {
_normalize(values, config.height, config.originY);
}
this.segment = values;
for (i = 2; i < l; i += 6) {
a1 = {x: +values[i - 2], y: +values[i - 1]};
a2 = {x: +values[i + 4], y: +values[i + 5]};
points.push(a1, a2);
_bezierToPoints(a1.x, a1.y, +values[i], +values[i + 1], +values[i + 2], +values[i + 3], a2.x, a2.y, 1 / (precision * 200000), points, points.length - 1);
}
l = points.length;
for (i = 0; i < l; i++) {
point = points[i];
prevPoint = points[i - 1] || point;
if ((point.x > prevPoint.x || (prevPoint.y !== point.y && prevPoint.x === point.x) || point === prevPoint) && point.x <= 1) { //if a point goes BACKWARD in time or is a duplicate, just drop it. Also it shouldn't go past 1 on the x axis, as could happen in a string like "M0,0 C0,0 0.12,0.68 0.18,0.788 0.195,0.845 0.308,1 0.32,1 0.403,1.005 0.398,1 0.5,1 0.602,1 0.816,1.005 0.9,1 0.91,1 0.948,0.69 0.962,0.615 1.003,0.376 1,0 1,0".
prevPoint.cx = point.x - prevPoint.x; //change in x between this point and the next point (performance optimization)
prevPoint.cy = point.y - prevPoint.y;
prevPoint.n = point;
prevPoint.nx = point.x; //next point's x value (performance optimization, making lookups faster in getRatio()). Remember, the lookup will always land on a spot where it's either this point or the very next one (never beyond that)
if (fast && i > 1 && Math.abs(prevPoint.cy / prevPoint.cx - points[i - 2].cy / points[i - 2].cx) > 2) { //if there's a sudden change in direction, prioritize accuracy over speed. Like a bounce ease - you don't want to risk the sampling chunks landing on each side of the bounce anchor and having it clipped off.
fast = 0;
}
if (prevPoint.cx < closest) {
if (!prevPoint.cx) {
prevPoint.cx = 0.001; //avoids math problems in getRatio() (dividing by zero)
if (i === l - 1) { //in case the final segment goes vertical RIGHT at the end, make sure we end at the end.
prevPoint.x -= 0.001;
closest = Math.min(closest, 0.001);
fast = 0;
}
} else {
closest = prevPoint.cx;
}
}
} else {
points.splice(i--, 1);
l--;
}
}
l = (1 / closest + 1) | 0;
inc = 1 / l;
j = 0;
point = points[0];
if (fast) {
for (i = 0; i < l; i++) { //for fastest lookups, we just sample along the path at equal x (time) distance. Uses more memory and is slightly less accurate for anchors that don't land on the sampling points, but for the vast majority of eases it's excellent (and fast).
p = i * inc;
if (point.nx < p) {
point = points[++j];
}
a1 = point.y + ((p - point.x) / point.cx) * point.cy;
lookup[i] = {x: p, cx: inc, y: a1, cy: 0, nx: 9};
if (i) {
lookup[i - 1].cy = a1 - lookup[i - 1].y;
}
}
j = points[points.length - 1];
lookup[l - 1].cy = j.y - a1;
lookup[l - 1].cx = j.x - lookup[lookup.length - 1].x; //make sure it lands EXACTLY where it should. Otherwise, it might be something like 0.9999999999 instead of 1.
} else { //this option is more accurate, ensuring that EVERY anchor is hit perfectly. Clipping across a bounce, for example, would never happen.
for (i = 0; i < l; i++) { //build a lookup table based on the smallest distance so that we can instantly find the appropriate point (well, it'll either be that point or the very next one). We'll look up based on the linear progress. So it's it's 0.5 and the lookup table has 100 elements, it'd be like lookup[Math.floor(0.5 * 100)]
if (point.nx < i * inc) {
point = points[++j];
}
lookup[i] = point;
}
if (j < points.length - 1) {
lookup[i-1] = points[points.length-2];
}
}
//this._calcEnd = (points[points.length-1].y !== 1 || points[0].y !== 0); //ensures that we don't run into floating point errors. As long as we're starting at 0 and ending at 1, tell GSAP to skip the final calculation and use 0/1 as the factor.
this.ease = p => {
let point = lookup[(p * l) | 0] || lookup[l - 1];
if (point.nx < p) {
point = point.n;
}
return point.y + ((p - point.x) / point.cx) * point.cy;
};
this.ease.custom = this;
this.id && gsap && gsap.registerEase(this.id, this.ease);
return this;
}
getSVGData(config) {
return CustomEase.getSVGData(this, config);
}
static create(id, data, config) {
return (new CustomEase(id, data, config)).ease;
}
static register(core) {
gsap = core;
_initCore();
}
static get(id) {
return gsap.parseEase(id);
}
static getSVGData(ease, config) {
config = config || {};
let width = config.width || 100,
height = config.height || 100,
x = config.x || 0,
y = (config.y || 0) + height,
e = gsap.utils.toArray(config.path)[0],
a, slope, i, inc, tx, ty, precision, threshold, prevX, prevY;
if (config.invert) {
height = -height;
y = 0;
}
if (typeof(ease) === "string") {
ease = gsap.parseEase(ease);
}
if (ease.custom) {
ease = ease.custom;
}
if (ease instanceof CustomEase) {
a = rawPathToString(transformRawPath([ease.segment], width, 0, 0, -height, x, y));
} else {
a = [x, y];
precision = Math.max(5, (config.precision || 1) * 200);
inc = 1 / precision;
precision += 2;
threshold = 5 / precision;
prevX = _round(x + inc * width);
prevY = _round(y + ease(inc) * -height);
slope = (prevY - y) / (prevX - x);
for (i = 2; i < precision; i++) {
tx = _round(x + i * inc * width);
ty = _round(y + ease(i * inc) * -height);
if (Math.abs((ty - prevY) / (tx - prevX) - slope) > threshold || i === precision - 1) { //only add points when the slope changes beyond the threshold
a.push(prevX, prevY);
slope = (ty - prevY) / (tx - prevX);
}
prevX = tx;
prevY = ty;
}
a = "M" + a.join(",");
}
e && e.setAttribute("d", a);
return a;
}
}
CustomEase.version = "3.13.0";
CustomEase.headless = true;
_getGSAP() && gsap.registerPlugin(CustomEase);
export { CustomEase as default };

View File

@@ -0,0 +1,125 @@
/*!
* CustomWiggle 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, createCustomEase,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_eases = {
easeOut: "M0,1,C0.7,1,0.6,0,1,0",
easeInOut: "M0,0,C0.1,0,0.24,1,0.444,1,0.644,1,0.6,0,1,0",
anticipate: "M0,0,C0,0.222,0.024,0.386,0,0.4,0.18,0.455,0.65,0.646,0.7,0.67,0.9,0.76,1,0.846,1,1",
uniform: "M0,0,C0,0.95,0,1,0,1,0,1,1,1,1,1,1,1,1,0,1,0"
},
_linearEase = p => p,
_initCore = required => {
if (!_coreInitted) {
gsap = _getGSAP();
createCustomEase = gsap && gsap.parseEase("_CE");
if (createCustomEase) {
for (let p in _eases) {
_eases[p] = createCustomEase("", _eases[p]);
}
_coreInitted = 1;
_create("wiggle").config = vars => typeof(vars) === "object" ? _create("", vars) : _create("wiggle(" + vars + ")", {wiggles:+vars});
} else {
required && console.warn("Please gsap.registerPlugin(CustomEase, CustomWiggle)");
}
}
},
_parseEase = (ease, invertNonCustomEases) => {
if (typeof(ease) !== "function") {
ease = gsap.parseEase(ease) || createCustomEase("", ease);
}
return (ease.custom || !invertNonCustomEases) ? ease : p => 1 - ease(p);
},
_bonusValidated = 1, //<name>CustomWiggle</name>
_create = (id, vars) => {
if (!_coreInitted) {
_initCore(1);
}
vars = vars || {};
let wiggles = (vars.wiggles || 10) | 0,
inc = 1 / wiggles,
x = inc / 2,
anticipate = (vars.type === "anticipate"),
yEase = _eases[vars.type] || _eases.easeOut,
xEase = _linearEase,
rnd = 1000,
nextX, nextY, angle, handleX, handleY, easedX, y, path, i;
if (_bonusValidated) {
if (anticipate) { //the anticipate ease is actually applied on the x-axis (timing) and uses easeOut for amplitude.
xEase = yEase;
yEase = _eases.easeOut;
}
if (vars.timingEase) {
xEase = _parseEase(vars.timingEase);
}
if (vars.amplitudeEase) {
yEase = _parseEase(vars.amplitudeEase, true);
}
easedX = xEase(x);
y = anticipate ? -yEase(x) : yEase(x);
path = [0, 0, easedX / 4, 0, easedX / 2, y, easedX, y];
if (vars.type === "random") { //if we just select random values on the y-axis and plug them into the "normal" algorithm, since the control points are always straight horizontal, it creates a bit of a slowdown at each anchor which just didn't seem as desirable, so we switched to an algorithm that bends the control points to be more in line with their context.
path.length = 4;
nextX = xEase(inc);
nextY = Math.random() * 2 - 1;
for (i = 2; i < wiggles; i++) {
x = nextX;
y = nextY;
nextX = xEase(inc * i);
nextY = Math.random() * 2 - 1;
angle = Math.atan2(nextY - path[path.length - 3], nextX - path[path.length - 4]);
handleX = Math.cos(angle) * inc;
handleY = Math.sin(angle) * inc;
path.push(x - handleX, y - handleY, x, y, x + handleX, y + handleY);
}
path.push(nextX, 0, 1, 0);
} else {
for (i = 1; i < wiggles; i++) {
path.push(xEase(x + inc / 2), y);
x += inc;
y = ((y > 0) ? -1 : 1) * (yEase(i * inc));
easedX = xEase(x);
path.push(xEase(x - inc / 2), y, easedX, y);
}
path.push(xEase(x + inc / 4), y, xEase(x + inc / 4), 0, 1, 0);
}
i = path.length;
while (--i > -1) {
path[i] = ~~(path[i] * rnd) / rnd; //round values to avoid odd strings for super tiny values
}
path[2] = "C" + path[2];
return createCustomEase(id, "M" + path.join(","));
}
};
export class CustomWiggle {
constructor(id, vars) {
this.ease = _create(id, vars);
}
static create(id, vars) {
return _create(id, vars);
}
static register(core) {
gsap = core;
_initCore();
}
}
_getGSAP() && gsap.registerPlugin(CustomWiggle);
CustomWiggle.version = "3.13.0";
export { CustomWiggle as default };

1940
network-visualization/node_modules/gsap/src/Draggable.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
/*!
* DrawSVGPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _toArray, _doc, _win, _isEdge, _coreInitted, _warned, _getStyleSaver, _reverting,
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_numExp = /[-+=\.]*\d+[\.e\-\+]*\d*[e\-\+]*\d*/gi, //finds any numbers, including ones that start with += or -=, negative numbers, and ones in scientific notation like 1e-8.
_types = {rect:["width","height"], circle:["r","r"], ellipse:["rx","ry"], line:["x2","y2"]},
_round = value => Math.round(value * 10000) / 10000,
_parseNum = value => parseFloat(value) || 0,
_parseSingleVal = (value, length) => {
let num = _parseNum(value);
return ~value.indexOf("%") ? num / 100 * length : num;
},
_getAttributeAsNumber = (target, attr) => _parseNum(target.getAttribute(attr)),
_sqrt = Math.sqrt,
_getDistance = (x1, y1, x2, y2, scaleX, scaleY) => _sqrt(((_parseNum(x2) - _parseNum(x1)) * scaleX) ** 2 + ((_parseNum(y2) - _parseNum(y1)) * scaleY) ** 2),
_warn = message => console.warn(message),
_hasNonScalingStroke = target => target.getAttribute("vector-effect") === "non-scaling-stroke",
_bonusValidated = 1, //<name>DrawSVGPlugin</name>
//accepts values like "100%" or "20% 80%" or "20 50" and parses it into an absolute start and end position on the line/stroke based on its length. Returns an an array with the start and end values, like [0, 243]
_parse = (value, length, defaultStart) => {
let i = value.indexOf(" "),
s, e;
if (i < 0) {
s = defaultStart !== undefined ? defaultStart + "" : value;
e = value;
} else {
s = value.substr(0, i);
e = value.substr(i + 1);
}
s = _parseSingleVal(s, length);
e = _parseSingleVal(e, length);
return (s > e) ? [e, s] : [s, e];
},
_getLength = target => {
target = _toArray(target)[0];
if (!target) {
return 0;
}
let type = target.tagName.toLowerCase(),
style = target.style,
scaleX = 1,
scaleY = 1,
length, bbox, points, prevPoint, i, rx, ry;
if (_hasNonScalingStroke(target)) { //non-scaling-stroke basically scales the shape and then strokes it at the screen-level (after transforms), thus we need to adjust the length accordingly.
scaleY = target.getScreenCTM();
scaleX = _sqrt(scaleY.a * scaleY.a + scaleY.b * scaleY.b);
scaleY = _sqrt(scaleY.d * scaleY.d + scaleY.c * scaleY.c);
}
try { //IE bug: calling <path>.getTotalLength() locks the repaint area of the stroke to whatever its current dimensions are on that frame/tick. To work around that, we must call getBBox() to force IE to recalculate things.
bbox = target.getBBox(); //solely for fixing bug in IE - we don't actually use the bbox.
} catch (e) {
//firefox has a bug that throws an error if the element isn't visible.
_warn("Some browsers won't measure invisible elements (like display:none or masks inside defs).");
}
let {x, y, width, height} = bbox || {x:0, y:0, width:0, height:0};
if ((!bbox || (!width && !height)) && _types[type]) { //if the element isn't visible, try to discern width/height using its attributes.
width =_getAttributeAsNumber(target, _types[type][0]);
height = _getAttributeAsNumber(target, _types[type][1]);
if (type !== "rect" && type !== "line") { //double the radius for circles and ellipses
width *= 2;
height *= 2;
}
if (type === "line") {
x = _getAttributeAsNumber(target, "x1");
y = _getAttributeAsNumber(target, "y1");
width = Math.abs(width - x);
height = Math.abs(height - y);
}
}
if (type === "path") {
prevPoint = style.strokeDasharray;
style.strokeDasharray = "none";
length = target.getTotalLength() || 0;
_round(scaleX) !== _round(scaleY) && !_warned && (_warned = 1) && _warn("Warning: <path> length cannot be measured when vector-effect is non-scaling-stroke and the element isn't proportionally scaled.");
length *= (scaleX + scaleY) / 2;
style.strokeDasharray = prevPoint;
} else if (type === "rect") {
length = width * 2 * scaleX + height * 2 * scaleY;
} else if (type === "line") {
length = _getDistance(x, y, x + width, y + height, scaleX, scaleY);
} else if (type === "polyline" || type === "polygon") {
points = target.getAttribute("points").match(_numExp) || [];
type === "polygon" && points.push(points[0], points[1]);
length = 0;
for (i = 2; i < points.length; i+=2) {
length += _getDistance(points[i-2], points[i-1], points[i], points[i+1], scaleX, scaleY) || 0;
}
} else if (type === "circle" || type === "ellipse") {
rx = (width / 2) * scaleX;
ry = (height / 2) * scaleY;
length = Math.PI * ( 3 * (rx + ry) - _sqrt((3 * rx + ry) * (rx + 3 * ry)) );
}
return length || 0;
},
_getPosition = (target, length) => {
target = _toArray(target)[0];
if (!target) {
return [0, 0];
}
length || (length = _getLength(target) + 1);
let cs = _win.getComputedStyle(target),
dash = cs.strokeDasharray || "",
offset = _parseNum(cs.strokeDashoffset),
i = dash.indexOf(",");
i < 0 && (i = dash.indexOf(" "));
dash = i < 0 ? length : _parseNum(dash.substr(0, i));
dash > length && (dash = length);
return [-offset || 0, (dash - offset) || 0];
},
_initCore = () => {
if (_windowExists()) {
_doc = document;
_win = window;
_coreInitted = gsap = _getGSAP();
_toArray = gsap.utils.toArray;
_getStyleSaver = gsap.core.getStyleSaver;
_reverting = gsap.core.reverting || function() {};
_isEdge = (((_win.navigator || {}).userAgent || "").indexOf("Edge") !== -1); //Microsoft Edge has a bug that causes it not to redraw the path correctly if the stroke-linecap is anything other than "butt" (like "round") and it doesn't match the stroke-linejoin. A way to trigger it is to change the stroke-miterlimit, so we'll only do that if/when we have to (to maximize performance)
}
};
export const DrawSVGPlugin = {
version:"3.13.0",
name:"drawSVG",
register(core) {
gsap = core;
_initCore();
},
init(target, value, tween, index, targets) {
if (!target.getBBox) {
return false;
}
_coreInitted || _initCore();
let length = _getLength(target),
start, end, cs;
this.styles = _getStyleSaver && _getStyleSaver(target, "strokeDashoffset,strokeDasharray,strokeMiterlimit");
this.tween = tween;
this._style = target.style;
this._target = target;
if (value + "" === "true") {
value = "0 100%";
} else if (!value) {
value = "0 0";
} else if ((value + "").indexOf(" ") === -1) {
value = "0 " + value;
}
start = _getPosition(target, length);
end = _parse(value, length, start[0]);
this._length = _round(length);
this._dash = _round(start[1] - start[0]); //some browsers render artifacts if dash is 0, so we use a very small number in that case.
this._offset = _round(-start[0]);
this._dashPT = this.add(this, "_dash", this._dash, _round(end[1] - end[0]), 0, 0, 0, 0, 0, 1);
this._offsetPT = this.add(this, "_offset", this._offset, _round(-end[0]), 0, 0, 0, 0, 0, 1);
if (_isEdge) { //to work around a bug in Microsoft Edge, animate the stroke-miterlimit by 0.0001 just to trigger the repaint (unnecessary if it's "round" and stroke-linejoin is also "round"). Imperceptible, relatively high-performance, and effective. Another option was to set the "d" <path> attribute to its current value on every tick, but that seems like it'd be much less performant.
cs = _win.getComputedStyle(target);
if (cs.strokeLinecap !== cs.strokeLinejoin) {
end = _parseNum(cs.strokeMiterlimit);
this.add(target.style, "strokeMiterlimit", end, end + 0.01);
}
}
this._live = (_hasNonScalingStroke(target) || ~((value + "").indexOf("live")));
this._nowrap = ~(value + "").indexOf("nowrap");
this._props.push("drawSVG");
return _bonusValidated;
},
render(ratio, data) {
if (data.tween._time || !_reverting()) {
let pt = data._pt,
style = data._style,
length, lengthRatio, dash, offset;
if (pt) {
//when the element has vector-effect="non-scaling-stroke" and the SVG is resized (like on a window resize), it actually changes the length of the stroke! So we must sense that and make the proper adjustments.
if (data._live) {
length = _getLength(data._target);
if (length !== data._length) {
lengthRatio = length / data._length;
data._length = length;
if (data._offsetPT) {
data._offsetPT.s *= lengthRatio;
data._offsetPT.c *= lengthRatio;
}
if (data._dashPT) {
data._dashPT.s *= lengthRatio;
data._dashPT.c *= lengthRatio;
} else {
data._dash *= lengthRatio;
}
}
}
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
dash = data._dash || ((ratio && ratio !== 1 && 0.0001) || 0); // only let it be zero if it's at the start or end of the tween.
length = data._length - dash + 0.1;
offset = data._offset;
dash && offset && dash + Math.abs(offset % data._length) > data._length - 0.05 && (offset += offset < 0 ? 0.005 : -0.005) && (length += 0.005);
style.strokeDashoffset = dash ? offset : offset + 0.001;
style.strokeDasharray = length < 0.1 ? "none" : dash ? dash + "px," + (data._nowrap ? 999999 : length) + "px" : "0px, 999999px";
}
} else {
data.styles.revert();
}
},
getLength: _getLength,
getPosition: _getPosition
};
_getGSAP() && gsap.registerPlugin(DrawSVGPlugin);
export { DrawSVGPlugin as default };

162
network-visualization/node_modules/gsap/src/EasePack.js generated vendored Normal file
View File

@@ -0,0 +1,162 @@
/*!
* EasePack 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _registerEase,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_boolean = (value, defaultValue) => !!(typeof(value) === "undefined" ? defaultValue : value && !~((value + "").indexOf("false"))),
_initCore = core => {
gsap = core || _getGSAP();
if (gsap) {
_registerEase = gsap.registerEase;
//add weighted ease capabilities to standard eases so users can do "power2.inOut(0.8)" for example to push everything toward the "out", or (-0.8) to push it toward the "in" (0 is neutral)
let eases = gsap.parseEase(),
createConfig = ease => ratio => {
let y = 0.5 + ratio / 2;
ease.config = p => ease(2 * (1 - p) * p * y + p * p);
},
p;
for (p in eases) {
if (!eases[p].config) {
createConfig(eases[p]);
}
}
_registerEase("slow", SlowMo);
_registerEase("expoScale", ExpoScaleEase);
_registerEase("rough", RoughEase);
for (p in EasePack) {
p !== "version" && gsap.core.globals(p, EasePack[p]);
}
_coreInitted = 1;
}
},
_createSlowMo = (linearRatio, power, yoyoMode) => {
linearRatio = Math.min(1, linearRatio || 0.7);
let pow = linearRatio < 1 ? ((power || power === 0) ? power : 0.7) : 0,
p1 = (1 - linearRatio) / 2,
p3 = p1 + linearRatio,
calcEnd = _boolean(yoyoMode);
return p => {
let r = p + (0.5 - p) * pow;
return (p < p1) ? (calcEnd ? 1 - ((p = 1 - (p / p1)) * p) : r - ((p = 1 - (p / p1)) * p * p * p * r)) : (p > p3) ? (calcEnd ? (p === 1 ? 0 : 1 - (p = (p - p3) / p1) * p) : r + ((p - r) * (p = (p - p3) / p1) * p * p * p)) : (calcEnd ? 1 : r);
}
},
_createExpoScale = (start, end, ease) => {
let p1 = Math.log(end / start),
p2 = end - start;
ease && (ease = gsap.parseEase(ease));
return p => (start * Math.exp(p1 * (ease ? ease(p) : p)) - start) / p2;
},
EasePoint = function(time, value, next) {
this.t = time;
this.v = value;
if (next) {
this.next = next;
next.prev = this;
this.c = next.v - value;
this.gap = next.t - time;
}
},
_createRoughEase = vars => {
if (typeof(vars) !== "object") { //users may pass in via a string, like "rough(30)"
vars = {points: +vars || 20};
}
let taper = vars.taper || "none",
a = [],
cnt = 0,
points = (+vars.points || 20) | 0,
i = points,
randomize = _boolean(vars.randomize, true),
clamp = _boolean(vars.clamp),
template = gsap ? gsap.parseEase(vars.template) : 0,
strength = (+vars.strength || 1) * 0.4,
x, y, bump, invX, obj, pnt, recent;
while (--i > -1) {
x = randomize ? Math.random() : (1 / points) * i;
y = template ? template(x) : x;
if (taper === "none") {
bump = strength;
} else if (taper === "out") {
invX = 1 - x;
bump = invX * invX * strength;
} else if (taper === "in") {
bump = x * x * strength;
} else if (x < 0.5) { //"both" (start)
invX = x * 2;
bump = invX * invX * 0.5 * strength;
} else { //"both" (end)
invX = (1 - x) * 2;
bump = invX * invX * 0.5 * strength;
}
if (randomize) {
y += (Math.random() * bump) - (bump * 0.5);
} else if (i % 2) {
y += bump * 0.5;
} else {
y -= bump * 0.5;
}
if (clamp) {
if (y > 1) {
y = 1;
} else if (y < 0) {
y = 0;
}
}
a[cnt++] = {x:x, y:y};
}
a.sort((a, b) => a.x - b.x);
pnt = new EasePoint(1, 1, null);
i = points;
while (i--) {
obj = a[i];
pnt = new EasePoint(obj.x, obj.y, pnt);
}
recent = new EasePoint(0, 0, pnt.t ? pnt : pnt.next);
return p => {
let pnt = recent;
if (p > pnt.t) {
while (pnt.next && p >= pnt.t) {
pnt = pnt.next;
}
pnt = pnt.prev;
} else {
while (pnt.prev && p <= pnt.t) {
pnt = pnt.prev;
}
}
recent = pnt;
return pnt.v + ((p - pnt.t) / pnt.gap) * pnt.c;
};
};
export const SlowMo = _createSlowMo(0.7);
SlowMo.ease = SlowMo; //for backward compatibility
SlowMo.config = _createSlowMo;
export const ExpoScaleEase = _createExpoScale(1, 2);
ExpoScaleEase.config = _createExpoScale;
export const RoughEase = _createRoughEase();
RoughEase.ease = RoughEase; //for backward compatibility
RoughEase.config = _createRoughEase;
export const EasePack = {
SlowMo: SlowMo,
RoughEase: RoughEase,
ExpoScaleEase: ExpoScaleEase
};
for (let p in EasePack) {
EasePack[p].register = _initCore;
EasePack[p].version = "3.13.0";
}
_getGSAP() && gsap.registerPlugin(SlowMo);
export { EasePack as default };

View File

@@ -0,0 +1,281 @@
/*!
* EaselPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _win, _createJS, _ColorFilter, _ColorMatrixFilter,
_colorProps = "redMultiplier,greenMultiplier,blueMultiplier,alphaMultiplier,redOffset,greenOffset,blueOffset,alphaOffset".split(","),
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_getCreateJS = () => _createJS || (_win && _win.createjs) || _win || {},
_warn = message => console.warn(message),
_cache = target => {
let b = target.getBounds && target.getBounds();
if (!b) {
b = target.nominalBounds || {x:0, y:0, width: 100, height: 100};
target.setBounds && target.setBounds(b.x, b.y, b.width, b.height);
}
target.cache && target.cache(b.x, b.y, b.width, b.height);
_warn("EaselPlugin: for filters to display in EaselJS, you must call the object's cache() method first. GSAP attempted to use the target's getBounds() for the cache but that may not be completely accurate. " + target);
},
_parseColorFilter = (target, v, plugin) => {
if (!_ColorFilter) {
_ColorFilter = _getCreateJS().ColorFilter;
if (!_ColorFilter) {
_warn("EaselPlugin error: The EaselJS ColorFilter JavaScript file wasn't loaded.");
}
}
let filters = target.filters || [],
i = filters.length,
c, s, e, a, p, pt;
while (i--) {
if (filters[i] instanceof _ColorFilter) {
s = filters[i];
break;
}
}
if (!s) {
s = new _ColorFilter();
filters.push(s);
target.filters = filters;
}
e = s.clone();
if (v.tint != null) {
c = gsap.utils.splitColor(v.tint);
a = (v.tintAmount != null) ? +v.tintAmount : 1;
e.redOffset = +c[0] * a;
e.greenOffset = +c[1] * a;
e.blueOffset = +c[2] * a;
e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - a;
} else {
for (p in v) {
if (p !== "exposure") if (p !== "brightness") {
e[p] = +v[p];
}
}
}
if (v.exposure != null) {
e.redOffset = e.greenOffset = e.blueOffset = 255 * (+v.exposure - 1);
e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1;
} else if (v.brightness != null) {
a = +v.brightness - 1;
e.redOffset = e.greenOffset = e.blueOffset = (a > 0) ? a * 255 : 0;
e.redMultiplier = e.greenMultiplier = e.blueMultiplier = 1 - Math.abs(a);
}
i = 8;
while (i--) {
p = _colorProps[i];
if (s[p] !== e[p]) {
pt = plugin.add(s, p, s[p], e[p], 0, 0, 0, 0, 0, 1);
if (pt) {
pt.op = "easel_colorFilter";
}
}
}
plugin._props.push("easel_colorFilter");
if (!target.cacheID) {
_cache(target);
}
},
_idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0],
_lumR = 0.212671,
_lumG = 0.715160,
_lumB = 0.072169,
_applyMatrix = (m, m2) => {
if (!(m instanceof Array) || !(m2 instanceof Array)) {
return m2;
}
let temp = [],
i = 0,
z = 0,
y, x;
for (y = 0; y < 4; y++) {
for (x = 0; x < 5; x++) {
z = (x === 4) ? m[i + 4] : 0;
temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z;
}
i += 5;
}
return temp;
},
_setSaturation = (m, n) => {
if (isNaN(n)) {
return m;
}
let inv = 1 - n,
r = inv * _lumR,
g = inv * _lumG,
b = inv * _lumB;
return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m);
},
_colorize = (m, color, amount) => {
if (isNaN(amount)) {
amount = 1;
}
let c = gsap.utils.splitColor(color),
r = c[0] / 255,
g = c[1] / 255,
b = c[2] / 255,
inv = 1 - amount;
return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m);
},
_setHue = (m, n) => {
if (isNaN(n)) {
return m;
}
n *= Math.PI / 180;
let c = Math.cos(n),
s = Math.sin(n);
return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m);
},
_setContrast = (m, n) => {
if (isNaN(n)) {
return m;
}
n += 0.01;
return _applyMatrix([n,0,0,0,128 * (1 - n), 0,n,0,0,128 * (1 - n), 0,0,n,0,128 * (1 - n), 0,0,0,1,0], m);
},
_parseColorMatrixFilter = (target, v, plugin) => {
if (!_ColorMatrixFilter) {
_ColorMatrixFilter = _getCreateJS().ColorMatrixFilter;
if (!_ColorMatrixFilter) {
_warn("EaselPlugin: The EaselJS ColorMatrixFilter JavaScript file wasn't loaded.");
}
}
let filters = target.filters || [],
i = filters.length,
matrix, startMatrix, s, pg;
while (--i > -1) {
if (filters[i] instanceof _ColorMatrixFilter) {
s = filters[i];
break;
}
}
if (!s) {
s = new _ColorMatrixFilter(_idMatrix.slice());
filters.push(s);
target.filters = filters;
}
startMatrix = s.matrix;
matrix = _idMatrix.slice();
if (v.colorize != null) {
matrix = _colorize(matrix, v.colorize, Number(v.colorizeAmount));
}
if (v.contrast != null) {
matrix = _setContrast(matrix, Number(v.contrast));
}
if (v.hue != null) {
matrix = _setHue(matrix, Number(v.hue));
}
if (v.saturation != null) {
matrix = _setSaturation(matrix, Number(v.saturation));
}
i = matrix.length;
while (--i > -1) {
if (matrix[i] !== startMatrix[i]) {
pg = plugin.add(startMatrix, i, startMatrix[i], matrix[i], 0, 0, 0, 0, 0, 1);
if (pg) {
pg.op = "easel_colorMatrixFilter";
}
}
}
plugin._props.push("easel_colorMatrixFilter");
if (!target.cacheID) {
_cache();
}
plugin._matrix = startMatrix;
},
_initCore = core => {
gsap = core || _getGSAP();
if (_windowExists()) {
_win = window;
}
if (gsap) {
_coreInitted = 1;
}
};
export const EaselPlugin = {
version: "3.13.0",
name: "easel",
init(target, value, tween, index, targets) {
if (!_coreInitted) {
_initCore();
if (!gsap) {
_warn("Please gsap.registerPlugin(EaselPlugin)");
}
}
this.target = target;
let p, pt, tint, colorMatrix, end, labels, i;
for (p in value) {
end = value[p];
if (p === "colorFilter" || p === "tint" || p === "tintAmount" || p === "exposure" || p === "brightness") {
if (!tint) {
_parseColorFilter(target, value.colorFilter || value, this);
tint = true;
}
} else if (p === "saturation" || p === "contrast" || p === "hue" || p === "colorize" || p === "colorizeAmount") {
if (!colorMatrix) {
_parseColorMatrixFilter(target, value.colorMatrixFilter || value, this);
colorMatrix = true;
}
} else if (p === "frame") {
if (typeof(end) === "string" && end.charAt(1) !== "=" && (labels = target.labels)) {
for (i = 0; i < labels.length; i++) {
if (labels[i].label === end) {
end = labels[i].position;
}
}
}
pt = this.add(target, "gotoAndStop", target.currentFrame, end, index, targets, Math.round, 0, 0, 1);
if (pt) {
pt.op = p;
}
} else if (target[p] != null) {
this.add(target, p, "get", end);
}
}
},
render(ratio, data) {
let pt = data._pt;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
if (data.target.cacheID) {
data.target.updateCache();
}
},
register: _initCore
};
EaselPlugin.registerCreateJS = createjs => {
_createJS = createjs;
};
_getGSAP() && gsap.registerPlugin(EaselPlugin);
export { EaselPlugin as default };

1033
network-visualization/node_modules/gsap/src/Flip.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

1181
network-visualization/node_modules/gsap/src/GSDevTools.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,333 @@
/*!
* InertiaPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { VelocityTracker } from "./utils/VelocityTracker.js";
let gsap, _coreInitted, _parseEase, _toArray, _power3, _config, _getUnit, PropTween, _getCache, _checkPointRatio, _clamp, _processingVars, _getStyleSaver, _reverting,
_getTracker = VelocityTracker.getByTarget,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_isString = value => typeof(value) === "string",
_isNumber = value => typeof(value) === "number",
_isObject = value => typeof(value) === "object",
_isFunction = value => typeof(value) === "function",
_bonusValidated = 1, //<name>InertiaPlugin</name>
_isArray = Array.isArray,
_emptyFunc = p => p,
_bigNum = 1e10,
_tinyNum = 1 / _bigNum,
_checkPoint = 0.05,
_round = value => Math.round(value * 10000) / 10000,
_extend = (obj, defaults, exclude) => {
for (let p in defaults) {
if (!(p in obj) && p !== exclude) {
obj[p] = defaults[p];
}
}
return obj;
},
_deepClone = obj => {
let copy = {},
p, v;
for (p in obj) {
copy[p] = _isObject(v = obj[p]) && !_isArray(v) ? _deepClone(v) : v;
}
return copy;
},
_getClosest = (n, values, max, min, radius) => {
let i = values.length,
closest = 0,
absDif = _bigNum,
val, dif, p, dist;
if (_isObject(n)) {
while (i--) {
val = values[i];
dif = 0;
for (p in n) {
dist = val[p] - n[p];
dif += dist * dist;
}
if (dif < absDif) {
closest = i;
absDif = dif;
}
}
if ((radius || _bigNum) < _bigNum && radius < Math.sqrt(absDif)) {
return n;
}
} else {
while (i--) {
val = values[i];
dif = val - n;
if (dif < 0) {
dif = -dif;
}
if (dif < absDif && val >= min && val <= max) {
closest = i;
absDif = dif;
}
}
}
return values[closest];
},
_parseEnd = (curProp, end, max, min, name, radius, velocity) => {
if (curProp.end === "auto") {
return curProp;
}
let endVar = curProp.end,
adjustedEnd, p;
max = isNaN(max) ? _bigNum : max;
min = isNaN(min) ? -_bigNum : min;
if (_isObject(end)) { //for objects, like {x, y} where they're linked and we must pass an object to the function or find the closest value in an array.
adjustedEnd = end.calculated ? end : (_isFunction(endVar) ? endVar(end, velocity) : _getClosest(end, endVar, max, min, radius)) || end;
if (!end.calculated) {
for (p in adjustedEnd) {
end[p] = adjustedEnd[p];
}
end.calculated = true;
}
adjustedEnd = adjustedEnd[name];
} else {
adjustedEnd = _isFunction(endVar) ? endVar(end, velocity) : _isArray(endVar) ? _getClosest(end, endVar, max, min, radius) : parseFloat(endVar);
}
if (adjustedEnd > max) {
adjustedEnd = max;
} else if (adjustedEnd < min) {
adjustedEnd = min;
}
return {max: adjustedEnd, min: adjustedEnd, unitFactor: curProp.unitFactor};
},
_getNumOrDefault = (vars, property, defaultValue) => isNaN(vars[property]) ? defaultValue : +vars[property],
_calculateChange = (velocity, duration) => (duration * _checkPoint * velocity) / _checkPointRatio,
_calculateDuration = (start, end, velocity) => Math.abs( (end - start) * _checkPointRatio / velocity / _checkPoint ),
_reservedProps = {resistance:1, checkpoint:1, preventOvershoot:1, linkedProps:1, radius:1, duration:1},
_processLinkedProps = (target, vars, getVal, resistance) => {
if (vars.linkedProps) { //when there are linkedProps (typically "x,y" where snapping has to factor in multiple properties, we must first populate an object with all of those end values, then feed it to the function that make any necessary alterations. So the point of this first loop is to simply build an object (like {x:100, y:204.5}) for feeding into that function which we'll do later in the "real" loop.
let linkedPropNames = vars.linkedProps.split(","),
linkedProps = {},
i, p, curProp, curVelocity, tracker, curDuration;
for (i = 0; i < linkedPropNames.length; i++) {
p = linkedPropNames[i];
curProp = vars[p];
if (curProp) {
if (_isNumber(curProp.velocity)) {
curVelocity = curProp.velocity;
} else {
tracker = tracker || _getTracker(target);
curVelocity = (tracker && tracker.isTracking(p)) ? tracker.get(p) : 0;
}
curDuration = Math.abs(curVelocity / _getNumOrDefault(curProp, "resistance", resistance));
linkedProps[p] = parseFloat(getVal(target, p)) + _calculateChange(curVelocity, curDuration);
}
}
return linkedProps;
}
},
_calculateTweenDuration = (target, vars, maxDuration = 10, minDuration = 0.2, overshootTolerance = 1, recordEnd = 0) => {
_isString(target) && (target = _toArray(target)[0]);
if (!target) {
return 0;
}
let duration = 0,
clippedDuration = _bigNum,
inertiaVars = vars.inertia || vars,
getVal = _getCache(target).get,
resistance = _getNumOrDefault(inertiaVars, "resistance", _config.resistance),
p, curProp, curDuration, curVelocity, curVal, end, curClippedDuration, tracker, unitFactor, linkedProps;
//when there are linkedProps (typically "x,y" where snapping has to factor in multiple properties, we must first populate an object with all of those end values, then feed it to the function that make any necessary alterations. So the point of this first loop is to simply build an object (like {x:100, y:204.5}) for feeding into that function which we'll do later in the "real" loop.
linkedProps = _processLinkedProps(target, inertiaVars, getVal, resistance);
for (p in inertiaVars) {
if (!_reservedProps[p]) {
curProp = inertiaVars[p];
if (!_isObject(curProp)) {
tracker = tracker || _getTracker(target);
if (tracker && tracker.isTracking(p)) {
curProp = _isNumber(curProp) ? {velocity:curProp} : {velocity:tracker.get(p)}; //if we're tracking this property, we should use the tracking velocity and then use the numeric value that was passed in as the min and max so that it tweens exactly there.
} else {
curVelocity = +curProp || 0;
curDuration = Math.abs(curVelocity / resistance);
}
}
if (_isObject(curProp)) {
if (_isNumber(curProp.velocity)) {
curVelocity = curProp.velocity;
} else {
tracker = tracker || _getTracker(target);
curVelocity = (tracker && tracker.isTracking(p)) ? tracker.get(p) : 0;
}
curDuration = _clamp(minDuration, maxDuration, Math.abs(curVelocity / _getNumOrDefault(curProp, "resistance", resistance)));
curVal = parseFloat(getVal(target, p)) || 0;
end = curVal + _calculateChange(curVelocity, curDuration);
if ("end" in curProp) {
curProp = _parseEnd(curProp, (linkedProps && p in linkedProps) ? linkedProps : end, curProp.max, curProp.min, p, inertiaVars.radius, curVelocity);
if (recordEnd) {
(_processingVars === vars) && (_processingVars = inertiaVars = _deepClone(vars));
inertiaVars[p] = _extend(curProp, inertiaVars[p], "end");
}
}
if (("max" in curProp) && end > +curProp.max + _tinyNum) {
unitFactor = curProp.unitFactor || _config.unitFactors[p] || 1; //some values are measured in special units like radians in which case our thresholds need to be adjusted accordingly.
//if the value is already exceeding the max or the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive. If the max and min match, it means we're animating to a particular value and we don't want to shorten the time unless the velocity is really slow. Example: a rotation where the start and natural end value are less than the snapping spot, but the natural end is pretty close to the snap.
curClippedDuration = ((curVal > curProp.max && curProp.min !== curProp.max) || (curVelocity * unitFactor > -15 && curVelocity * unitFactor < 45)) ? (minDuration + (maxDuration - minDuration) * 0.1) : _calculateDuration(curVal, curProp.max, curVelocity);
if (curClippedDuration + overshootTolerance < clippedDuration) {
clippedDuration = curClippedDuration + overshootTolerance;
}
} else if (("min" in curProp) && end < +curProp.min - _tinyNum) {
unitFactor = curProp.unitFactor || _config.unitFactors[p] || 1; //some values are measured in special units like radians in which case our thresholds need to be adjusted accordingly.
//if the value is already exceeding the min or if the velocity is too low, the duration can end up being uncomfortably long but in most situations, users want the snapping to occur relatively quickly (0.75 seconds), so we implement a cap here to make things more intuitive.
curClippedDuration = ((curVal < curProp.min && curProp.min !== curProp.max) || (curVelocity * unitFactor > -45 && curVelocity * unitFactor < 15)) ? (minDuration + (maxDuration - minDuration) * 0.1) : _calculateDuration(curVal, curProp.min, curVelocity);
if (curClippedDuration + overshootTolerance < clippedDuration) {
clippedDuration = curClippedDuration + overshootTolerance;
}
}
(curClippedDuration > duration) && (duration = curClippedDuration);
}
(curDuration > duration) && (duration = curDuration);
}
}
(duration > clippedDuration) && (duration = clippedDuration);
return (duration > maxDuration) ? maxDuration : (duration < minDuration) ? minDuration : duration;
},
_initCore = () => {
gsap = _getGSAP();
if (gsap) {
_parseEase = gsap.parseEase;
_toArray = gsap.utils.toArray;
_getUnit = gsap.utils.getUnit;
_getCache = gsap.core.getCache;
_clamp = gsap.utils.clamp;
_getStyleSaver = gsap.core.getStyleSaver;
_reverting = gsap.core.reverting || function() {};
_power3 = _parseEase("power3");
_checkPointRatio = _power3(0.05);
PropTween = gsap.core.PropTween;
gsap.config({resistance:100, unitFactors:{time: 1000, totalTime: 1000, progress: 1000, totalProgress: 1000}});
_config = gsap.config();
gsap.registerPlugin(VelocityTracker);
_coreInitted = 1;
}
};
export const InertiaPlugin = {
version: "3.13.0",
name: "inertia",
register(core) {
gsap = core;
_initCore();
},
init(target, vars, tween, index, targets) {
_coreInitted || _initCore();
let tracker = _getTracker(target);
if (vars === "auto") {
if (!tracker) {
console.warn("No inertia tracking on " + target + ". InertiaPlugin.track(target) first.");
return;
}
vars = tracker.getAll();
}
this.styles = _getStyleSaver && typeof(target.style) === "object" && _getStyleSaver(target);
this.target = target;
this.tween = tween;
_processingVars = vars; // gets swapped inside _calculateTweenDuration() if there's a function-based value encountered (to avoid double-calling it)
let cache = target._gsap,
getVal = cache.get,
dur = vars.duration,
durIsObj = _isObject(dur),
preventOvershoot = vars.preventOvershoot || (durIsObj && dur.overshoot === 0),
resistance = _getNumOrDefault(vars, "resistance", _config.resistance),
duration = _isNumber(dur) ? dur : _calculateTweenDuration(target, vars, (durIsObj && dur.max) || 10, (durIsObj && dur.min) || 0.2, (durIsObj && "overshoot" in dur) ? +dur.overshoot : preventOvershoot ? 0 : 1, true),
p, curProp, curVal, unit, velocity, change1, end, change2, linkedProps;
vars = _processingVars;
_processingVars = 0;
//when there are linkedProps (typically "x,y" where snapping has to factor in multiple properties, we must first populate an object with all of those end values, then feed it to the function that make any necessary alterations. So the point of this first loop is to simply build an object (like {x:100, y:204.5}) for feeding into that function which we'll do later in the "real" loop.
linkedProps = _processLinkedProps(target, vars, getVal, resistance);
for (p in vars) {
if (!_reservedProps[p]) {
curProp = vars[p];
_isFunction(curProp) && (curProp = curProp(index, target, targets));
if (_isNumber(curProp)) {
velocity = curProp;
} else if (_isObject(curProp) && !isNaN(curProp.velocity)) {
velocity = +curProp.velocity;
} else {
if (tracker && tracker.isTracking(p)) {
velocity = tracker.get(p);
} else {
console.warn("ERROR: No velocity was defined for " + target + " property: " + p);
}
}
change1 = _calculateChange(velocity, duration);
change2 = 0;
curVal = getVal(target, p);
unit = _getUnit(curVal);
curVal = parseFloat(curVal);
if (_isObject(curProp)) {
end = curVal + change1;
if ("end" in curProp) {
curProp = _parseEnd(curProp, (linkedProps && p in linkedProps) ? linkedProps : end, curProp.max, curProp.min, p, vars.radius, velocity);
}
if (("max" in curProp) && +curProp.max < end) {
if (preventOvershoot || curProp.preventOvershoot) {
change1 = curProp.max - curVal;
} else {
change2 = (curProp.max - curVal) - change1;
}
} else if (("min" in curProp) && +curProp.min > end) {
if (preventOvershoot || curProp.preventOvershoot) {
change1 = curProp.min - curVal;
} else {
change2 = (curProp.min - curVal) - change1;
}
}
}
this._props.push(p);
this.styles && this.styles.save(p);
this._pt = new PropTween(this._pt, target, p, curVal, 0, _emptyFunc, 0, cache.set(target, p, this));
this._pt.u = unit || 0;
this._pt.c1 = change1;
this._pt.c2 = change2;
}
}
tween.duration(duration);
return _bonusValidated;
},
render(ratio, data) {
let pt = data._pt;
ratio = _power3(data.tween._time / data.tween._dur);
if (ratio || !_reverting()) {
while (pt) {
pt.set(pt.t, pt.p, _round(pt.s + pt.c1 * ratio + pt.c2 * ratio * ratio) + pt.u, pt.d, ratio);
pt = pt._next;
}
} else {
data.styles.revert();
}
}
};
"track,untrack,isTracking,getVelocity,getByTarget".split(",").forEach(name => InertiaPlugin[name] = VelocityTracker[name]);
_getGSAP() && gsap.registerPlugin(InertiaPlugin);
export { InertiaPlugin as default, VelocityTracker };

View File

@@ -0,0 +1,791 @@
/*!
* MorphSVGPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { getRawPath, reverseSegment, stringToRawPath, rawPathToString, convertToPath } from "./utils/paths.js";
let gsap, _toArray, _lastLinkedAnchor, _doc, _coreInitted, PluginClass,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_isFunction = value => typeof(value) === "function",
_atan2 = Math.atan2,
_cos = Math.cos,
_sin = Math.sin,
_sqrt = Math.sqrt,
_PI = Math.PI,
_2PI = _PI * 2,
_angleMin = _PI * 0.3,
_angleMax = _PI * 0.7,
_bigNum = 1e20,
_numExp = /[-+=\.]*\d+[\.e\-\+]*\d*[e\-\+]*\d*/gi, //finds any numbers, including ones that start with += or -=, negative numbers, and ones in scientific notation like 1e-8.
_selectorExp = /(^[#\.][a-z]|[a-y][a-z])/i,
_commands = /[achlmqstvz]/i,
_log = message => console && console.warn(message),
_bonusValidated = 1, //<name>MorphSVGPlugin</name>
_getAverageXY = segment => {
let l = segment.length,
x = 0,
y = 0,
i;
for (i = 0; i < l; i++) {
x += segment[i++];
y += segment[i];
}
return [x / (l / 2), y / (l / 2)];
},
_getSize = segment => { //rough estimate of the bounding box (based solely on the anchors) of a single segment. sets "size", "centerX", and "centerY" properties on the bezier array itself, and returns the size (width * height)
let l = segment.length,
xMax = segment[0],
xMin = xMax,
yMax = segment[1],
yMin = yMax,
x, y, i;
for (i = 6; i < l; i+=6) {
x = segment[i];
y = segment[i+1];
if (x > xMax) {
xMax = x;
} else if (x < xMin) {
xMin = x;
}
if (y > yMax) {
yMax = y;
} else if (y < yMin) {
yMin = y;
}
}
segment.centerX = (xMax + xMin) / 2;
segment.centerY = (yMax + yMin) / 2;
return (segment.size = (xMax - xMin) * (yMax - yMin));
},
_getTotalSize = (rawPath, samplesPerBezier = 3) => { //rough estimate of the bounding box of the entire list of Bezier segments (based solely on the anchors). sets "size", "centerX", and "centerY" properties on the bezier array itself, and returns the size (width * height)
let j = rawPath.length,
xMax = rawPath[0][0],
xMin = xMax,
yMax = rawPath[0][1],
yMin = yMax,
inc = 1 / samplesPerBezier,
l, x, y, i, segment, k, t, inv, x1, y1, x2, x3, x4, y2, y3, y4;
while (--j > -1) {
segment = rawPath[j];
l = segment.length;
for (i = 6; i < l; i+=6) {
x1 = segment[i];
y1 = segment[i+1];
x2 = segment[i+2] - x1;
y2 = segment[i+3] - y1;
x3 = segment[i+4] - x1;
y3 = segment[i+5] - y1;
x4 = segment[i+6] - x1;
y4 = segment[i+7] - y1;
k = samplesPerBezier;
while (--k > -1) {
t = inc * k;
inv = 1 - t;
x = (t * t * x4 + 3 * inv * (t * x3 + inv * x2)) * t + x1;
y = (t * t * y4 + 3 * inv * (t * y3 + inv * y2)) * t + y1;
if (x > xMax) {
xMax = x;
} else if (x < xMin) {
xMin = x;
}
if (y > yMax) {
yMax = y;
} else if (y < yMin) {
yMin = y;
}
}
}
}
rawPath.centerX = (xMax + xMin) / 2;
rawPath.centerY = (yMax + yMin) / 2;
rawPath.left = xMin;
rawPath.width = (xMax - xMin);
rawPath.top = yMin;
rawPath.height = (yMax - yMin);
return (rawPath.size = (xMax - xMin) * (yMax - yMin));
},
_sortByComplexity = (a, b) => b.length - a.length,
_sortBySize = (a, b) => {
let sizeA = a.size || _getSize(a),
sizeB = b.size || _getSize(b);
return (Math.abs(sizeB - sizeA) < (sizeA + sizeB) / 20) ? (b.centerX - a.centerX) || (b.centerY - a.centerY) : sizeB - sizeA; //if the size is within 10% of each other, prioritize position from left to right, then top to bottom.
},
_offsetSegment = (segment, shapeIndex) => {
let a = segment.slice(0),
l = segment.length,
wrap = l - 2,
i, index;
shapeIndex = shapeIndex | 0;
for (i = 0; i < l; i++) {
index = (i + shapeIndex) % wrap;
segment[i++] = a[index];
segment[i] = a[index+1];
}
},
_getTotalMovement = (sb, eb, shapeIndex, offsetX, offsetY) => {
let l = sb.length,
d = 0,
wrap = l - 2,
index, i, x, y;
shapeIndex *= 6;
for (i = 0; i < l; i += 6) {
index = (i + shapeIndex) % wrap;
y = sb[index] - (eb[i] - offsetX);
x = sb[index+1] - (eb[i+1] - offsetY);
d += _sqrt(x * x + y * y);
}
return d;
},
_getClosestShapeIndex = (sb, eb, checkReverse) => { //finds the index in a closed cubic bezier array that's closest to the angle provided (angle measured from the center or average x/y).
let l = sb.length,
sCenter = _getAverageXY(sb), //when comparing distances, adjust the coordinates as if the shapes are centered with each other.
eCenter = _getAverageXY(eb),
offsetX = eCenter[0] - sCenter[0],
offsetY = eCenter[1] - sCenter[1],
min = _getTotalMovement(sb, eb, 0, offsetX, offsetY),
minIndex = 0,
copy, d, i;
for (i = 6; i < l; i += 6) {
d = _getTotalMovement(sb, eb, i / 6, offsetX, offsetY);
if (d < min) {
min = d;
minIndex = i;
}
}
if (checkReverse) {
copy = sb.slice(0);
reverseSegment(copy);
for (i = 6; i < l; i += 6) {
d = _getTotalMovement(copy, eb, i / 6, offsetX, offsetY);
if (d < min) {
min = d;
minIndex = -i;
}
}
}
return minIndex / 6;
},
_getClosestAnchor = (rawPath, x, y) => { //finds the x/y of the anchor that's closest to the provided x/y coordinate (returns an array, like [x, y]). The bezier should be the top-level type that contains an array for each segment.
let j = rawPath.length,
closestDistance = _bigNum,
closestX = 0,
closestY = 0,
segment, dx, dy, d, i, l;
while (--j > -1) {
segment = rawPath[j];
l = segment.length;
for (i = 0; i < l; i += 6) {
dx = segment[i] - x;
dy = segment[i+1] - y;
d = _sqrt(dx * dx + dy * dy);
if (d < closestDistance) {
closestDistance = d;
closestX = segment[i];
closestY = segment[i+1];
}
}
}
return [closestX, closestY];
},
_getClosestSegment = (bezier, pool, startIndex, sortRatio, offsetX, offsetY) => { //matches the bezier to the closest one in a pool (array) of beziers, assuming they are in order of size and we shouldn't drop more than 20% of the size, otherwise prioritizing location (total distance to the center). Extracts the segment out of the pool array and returns it.
let l = pool.length,
index = 0,
minSize = Math.min(bezier.size || _getSize(bezier), pool[startIndex].size || _getSize(pool[startIndex])) * sortRatio, //limit things based on a percentage of the size of either the bezier or the next element in the array, whichever is smaller.
min = _bigNum,
cx = bezier.centerX + offsetX,
cy = bezier.centerY + offsetY,
size, i, dx, dy, d;
for (i = startIndex; i < l; i++) {
size = pool[i].size || _getSize(pool[i]);
if (size < minSize) {
break;
}
dx = pool[i].centerX - cx;
dy = pool[i].centerY - cy;
d = _sqrt(dx * dx + dy * dy);
if (d < min) {
index = i;
min = d;
}
}
d = pool[index];
pool.splice(index, 1);
return d;
},
_subdivideSegmentQty = (segment, quantity) => {
let tally = 0,
max = 0.999999,
l = segment.length,
newPointsPerSegment = quantity / ((l - 2) / 6),
ax, ay, cp1x, cp1y, cp2x, cp2y, bx, by,
x1, y1, x2, y2, i, t;
for (i = 2; i < l; i += 6) {
tally += newPointsPerSegment;
while (tally > max) { //compare with 0.99999 instead of 1 in order to prevent rounding errors
ax = segment[i-2];
ay = segment[i-1];
cp1x = segment[i];
cp1y = segment[i+1];
cp2x = segment[i+2];
cp2y = segment[i+3];
bx = segment[i+4];
by = segment[i+5];
t = 1 / ((Math.floor(tally) || 1) + 1); //progress along the bezier (value between 0 and 1)
x1 = ax + (cp1x - ax) * t;
x2 = cp1x + (cp2x - cp1x) * t;
x1 += (x2 - x1) * t;
x2 += ((cp2x + (bx - cp2x) * t) - x2) * t;
y1 = ay + (cp1y - ay) * t;
y2 = cp1y + (cp2y - cp1y) * t;
y1 += (y2 - y1) * t;
y2 += ((cp2y + (by - cp2y) * t) - y2) * t;
segment.splice(i, 4,
ax + (cp1x - ax) * t, //first control point
ay + (cp1y - ay) * t,
x1, //second control point
y1,
x1 + (x2 - x1) * t, //new fabricated anchor on line
y1 + (y2 - y1) * t,
x2, //third control point
y2,
cp2x + (bx - cp2x) * t, //fourth control point
cp2y + (by - cp2y) * t
);
i += 6;
l += 6;
tally--;
}
}
return segment;
},
_equalizeSegmentQuantity = (start, end, shapeIndex, map, fillSafe) => { //returns an array of shape indexes, 1 for each segment.
let dif = end.length - start.length,
longer = dif > 0 ? end : start,
shorter = dif > 0 ? start : end,
added = 0,
sortMethod = (map === "complexity") ? _sortByComplexity : _sortBySize,
sortRatio = (map === "position") ? 0 : (typeof(map) === "number") ? map : 0.8,
i = shorter.length,
shapeIndices = (typeof(shapeIndex) === "object" && shapeIndex.push) ? shapeIndex.slice(0) : [shapeIndex],
reverse = (shapeIndices[0] === "reverse" || shapeIndices[0] < 0),
log = (shapeIndex === "log"),
eb, sb, b, x, y, offsetX, offsetY;
if (!shorter[0]) {
return;
}
if (longer.length > 1) {
start.sort(sortMethod);
end.sort(sortMethod);
offsetX = longer.size || _getTotalSize(longer); //ensures centerX and centerY are defined (used below).
offsetX = shorter.size || _getTotalSize(shorter);
offsetX = longer.centerX - shorter.centerX;
offsetY = longer.centerY - shorter.centerY;
if (sortMethod === _sortBySize) {
for (i = 0; i < shorter.length; i++) {
longer.splice(i, 0, _getClosestSegment(shorter[i], longer, i, sortRatio, offsetX, offsetY));
}
}
}
if (dif) {
if (dif < 0) {
dif = -dif;
}
if (longer[0].length > shorter[0].length) { //since we use shorter[0] as the one to map the origination point of any brand new fabricated segments, do any subdividing first so that there are more points to choose from (if necessary)
_subdivideSegmentQty(shorter[0], ((longer[0].length - shorter[0].length)/6) | 0);
}
i = shorter.length;
while (added < dif) {
x = longer[i].size || _getSize(longer[i]); //just to ensure centerX and centerY are calculated which we use on the next line.
b = _getClosestAnchor(shorter, longer[i].centerX, longer[i].centerY);
x = b[0];
y = b[1];
shorter[i++] = [x, y, x, y, x, y, x, y];
shorter.totalPoints += 8;
added++;
}
}
for (i = 0; i < start.length; i++) {
eb = end[i];
sb = start[i];
dif = eb.length - sb.length;
if (dif < 0) {
_subdivideSegmentQty(eb, (-dif/6) | 0);
} else if (dif > 0) {
_subdivideSegmentQty(sb, (dif/6) | 0);
}
if (reverse && fillSafe !== false && !sb.reversed) {
reverseSegment(sb);
}
shapeIndex = (shapeIndices[i] || shapeIndices[i] === 0) ? shapeIndices[i] : "auto";
if (shapeIndex) {
//if start shape is closed, find the closest point to the start/end, and re-organize the bezier points accordingly so that the shape morphs in a more intuitive way.
if (sb.closed || (Math.abs(sb[0] - sb[sb.length - 2]) < 0.5 && Math.abs(sb[1] - sb[sb.length - 1]) < 0.5)) {
if (shapeIndex === "auto" || shapeIndex === "log") {
shapeIndices[i] = shapeIndex = _getClosestShapeIndex(sb, eb, (!i || fillSafe === false));
if (shapeIndex < 0) {
reverse = true;
reverseSegment(sb);
shapeIndex = -shapeIndex;
}
_offsetSegment(sb, shapeIndex * 6);
} else if (shapeIndex !== "reverse") {
if (i && shapeIndex < 0) { //only happens if an array is passed as shapeIndex and a negative value is defined for an index beyond 0. Very rare, but helpful sometimes.
reverseSegment(sb);
}
_offsetSegment(sb, (shapeIndex < 0 ? -shapeIndex : shapeIndex) * 6);
}
//otherwise, if it's not a closed shape, consider reversing it if that would make the overall travel less
} else if (!reverse && (shapeIndex === "auto" && (Math.abs(eb[0] - sb[0]) + Math.abs(eb[1] - sb[1]) + Math.abs(eb[eb.length - 2] - sb[sb.length - 2]) + Math.abs(eb[eb.length - 1] - sb[sb.length - 1]) > Math.abs(eb[0] - sb[sb.length - 2]) + Math.abs(eb[1] - sb[sb.length - 1]) + Math.abs(eb[eb.length - 2] - sb[0]) + Math.abs(eb[eb.length - 1] - sb[1])) || (shapeIndex % 2))) {
reverseSegment(sb);
shapeIndices[i] = -1;
reverse = true;
} else if (shapeIndex === "auto") {
shapeIndices[i] = 0;
} else if (shapeIndex === "reverse") {
shapeIndices[i] = -1;
}
if (sb.closed !== eb.closed) { //if one is closed and one isn't, don't close either one otherwise the tweening will look weird (but remember, the beginning and final states will honor the actual values, so this only affects the inbetween state)
sb.closed = eb.closed = false;
}
}
}
log && _log("shapeIndex:[" + shapeIndices.join(",") + "]");
start.shapeIndex = shapeIndices;
return shapeIndices;
},
_pathFilter = (a, shapeIndex, map, precompile, fillSafe) => {
let start = stringToRawPath(a[0]),
end = stringToRawPath(a[1]);
if (!_equalizeSegmentQuantity(start, end, (shapeIndex || shapeIndex === 0) ? shapeIndex : "auto", map, fillSafe)) {
return; //malformed path data or null target
}
a[0] = rawPathToString(start);
a[1] = rawPathToString(end);
if (precompile === "log" || precompile === true) {
_log('precompile:["' + a[0] + '","' + a[1] + '"]');
}
},
_offsetPoints = (text, offset) => {
if (!offset) {
return text;
}
let a = text.match(_numExp) || [],
l = a.length,
s = "",
inc, i, j;
if (offset === "reverse") {
i = l-1;
inc = -2;
} else {
i = (((parseInt(offset, 10) || 0) * 2 + 1) + l * 100) % l;
inc = 2;
}
for (j = 0; j < l; j += 2) {
s += a[i-1] + "," + a[i] + " ";
i = (i + inc) % l;
}
return s;
},
//adds a certain number of points while maintaining the polygon/polyline shape (so that the start/end values can have a matching quantity of points to animate). Returns the revised string.
_equalizePointQuantity = (a, quantity) => {
let tally = 0,
x = parseFloat(a[0]),
y = parseFloat(a[1]),
s = x + "," + y + " ",
max = 0.999999,
newPointsPerSegment, i, l, j, factor, nextX, nextY;
l = a.length;
newPointsPerSegment = quantity * 0.5 / (l * 0.5 - 1);
for (i = 0; i < l-2; i += 2) {
tally += newPointsPerSegment;
nextX = parseFloat(a[i+2]);
nextY = parseFloat(a[i+3]);
if (tally > max) { //compare with 0.99999 instead of 1 in order to prevent rounding errors
factor = 1 / (Math.floor(tally) + 1);
j = 1;
while (tally > max) {
s += (x + (nextX - x) * factor * j).toFixed(2) + "," + (y + (nextY - y) * factor * j).toFixed(2) + " ";
tally--;
j++;
}
}
s += nextX + "," + nextY + " ";
x = nextX;
y = nextY;
}
return s;
},
_pointsFilter = a => {
let startNums = a[0].match(_numExp) || [],
endNums = a[1].match(_numExp) || [],
dif = endNums.length - startNums.length;
if (dif > 0) {
a[0] = _equalizePointQuantity(startNums, dif);
} else {
a[1] = _equalizePointQuantity(endNums, -dif);
}
},
_buildPointsFilter = shapeIndex => !isNaN(shapeIndex) ? a => {
_pointsFilter(a);
a[1] = _offsetPoints(a[1], parseInt(shapeIndex, 10));
} : _pointsFilter,
_parseShape = (shape, forcePath, target) => {
let isString = typeof(shape) === "string",
e, type;
if (!isString || _selectorExp.test(shape) || (shape.match(_numExp) || []).length < 3) {
e = _toArray(shape)[0];
if (e) {
type = (e.nodeName + "").toUpperCase();
if (forcePath && type !== "PATH") { //if we were passed an element (or selector text for an element) that isn't a path, convert it.
e = convertToPath(e, false);
type = "PATH";
}
shape = e.getAttribute(type === "PATH" ? "d" : "points") || "";
if (e === target) { //if the shape matches the target element, the user wants to revert to the original which should have been stored in the data-original attribute
shape = e.getAttributeNS(null, "data-original") || shape;
}
} else {
_log("WARNING: invalid morph to: " + shape);
shape = false;
}
}
return shape;
},
//adds an "isSmooth" array to each segment and populates it with a boolean value indicating whether or not it's smooth (the control points have basically the same slope). For any smooth control points, it converts the coordinates into angle (x, in radians) and length (y) and puts them into the same index value in a smoothData array.
_populateSmoothData = (rawPath, tolerance) => {
let j = rawPath.length,
limit = 0.2 * (tolerance || 1),
smooth, segment, x, y, x2, y2, i, l, a, a2, isSmooth, smoothData;
while (--j > -1) {
segment = rawPath[j];
isSmooth = segment.isSmooth = segment.isSmooth || [0, 0, 0, 0];
smoothData = segment.smoothData = segment.smoothData || [0, 0, 0, 0];
isSmooth.length = 4;
l = segment.length - 2;
for (i = 6; i < l; i += 6) {
x = segment[i] - segment[i - 2];
y = segment[i + 1] - segment[i - 1];
x2 = segment[i + 2] - segment[i];
y2 = segment[i + 3] - segment[i + 1];
a = _atan2(y, x);
a2 = _atan2(y2, x2);
smooth = (Math.abs(a - a2) < limit);
if (smooth) {
smoothData[i - 2] = a;
smoothData[i + 2] = a2;
smoothData[i - 1] = _sqrt(x * x + y * y);
smoothData[i + 3] = _sqrt(x2 * x2 + y2 * y2);
}
isSmooth.push(smooth, smooth, 0, 0, smooth, smooth);
}
//if the first and last points are identical, check to see if there's a smooth transition. We must handle this a bit differently due to their positions in the array.
if (segment[l] === segment[0] && segment[l+1] === segment[1]) {
x = segment[0] - segment[l-2];
y = segment[1] - segment[l-1];
x2 = segment[2] - segment[0];
y2 = segment[3] - segment[1];
a = _atan2(y, x);
a2 = _atan2(y2, x2);
if (Math.abs(a - a2) < limit) {
smoothData[l-2] = a;
smoothData[2] = a2;
smoothData[l-1] = _sqrt(x * x + y * y);
smoothData[3] = _sqrt(x2 * x2 + y2 * y2);
isSmooth[l-2] = isSmooth[l-1] = true; //don't change indexes 2 and 3 because we'll trigger everything from the END, and this will optimize file size a bit.
}
}
}
return rawPath;
},
_parseOriginFactors = v => {
let a = v.trim().split(" "),
x = ~v.indexOf("left") ? 0 : ~v.indexOf("right") ? 100 : isNaN(parseFloat(a[0])) ? 50 : parseFloat(a[0]),
y = ~v.indexOf("top") ? 0 : ~v.indexOf("bottom") ? 100 : isNaN(parseFloat(a[1])) ? 50 : parseFloat(a[1]);
return {x:x / 100, y:y / 100};
},
_shortAngle = dif => (dif !== dif % _PI) ? dif + ((dif < 0) ? _2PI : -_2PI) : dif,
_morphMessage = "Use MorphSVGPlugin.convertToPath() to convert to a path before morphing.",
_tweenRotation = function(start, end, i, linkedPT) {
let so = this._origin, //starting origin
eo = this._eOrigin, //ending origin
dx = start[i] - so.x,
dy = start[i+1] - so.y,
d = _sqrt(dx * dx + dy * dy), //length from starting origin to starting point
sa = _atan2(dy, dx),
angleDif, short;
dx = end[i] - eo.x;
dy = end[i+1] - eo.y;
angleDif = _atan2(dy, dx) - sa;
short = _shortAngle(angleDif);
//in the case of control points, we ALWAYS link them to their anchor so that they don't get torn apart and rotate the opposite direction. If it's not a control point, we look at the most recently linked point as long as they're within a certain rotational range of each other.
if (!linkedPT && _lastLinkedAnchor && Math.abs(short + _lastLinkedAnchor.ca) < _angleMin) {
linkedPT = _lastLinkedAnchor;
}
return (this._anchorPT = _lastLinkedAnchor = {
_next:this._anchorPT,
t:start,
sa:sa, //starting angle
ca:(linkedPT && short * linkedPT.ca < 0 && Math.abs(short) > _angleMax) ? angleDif : short, //change in angle
sl:d, //starting length
cl:_sqrt(dx * dx + dy * dy) - d, //change in length
i:i
});
},
_initCore = required => {
gsap = _getGSAP();
PluginClass = PluginClass || (gsap && gsap.plugins.morphSVG);
if (gsap && PluginClass) {
_toArray = gsap.utils.toArray;
_doc = document;
PluginClass.prototype._tweenRotation = _tweenRotation;
_coreInitted = 1;
} else if (required) {
_log("Please gsap.registerPlugin(MorphSVGPlugin)");
}
};
export const MorphSVGPlugin = {
version: "3.13.0",
name: "morphSVG",
rawVars: 1, // otherwise "render" would be interpreted as a function-based value.
register(core, Plugin) {
gsap = core;
PluginClass = Plugin;
_initCore();
},
init(target, value, tween, index, targets) {
_coreInitted || _initCore(1);
if (!value) {
_log("invalid shape");
return false;
}
_isFunction(value) && (value = value.call(tween, index, target, targets));
let type, p, pt, shape, isPoly, shapeIndex, map, startSmooth, endSmooth, start, end, i, j, l, startSeg, endSeg, precompiled, sData, eData, originFactors, useRotation, offset;
if (typeof(value) === "string" || value.getBBox || value[0]) {
value = {shape:value};
} else if (typeof(value) === "object") { // if there are any function-based values, parse them here (and make a copy of the object so we're not modifying the original)
type = {};
for (p in value) {
type[p] = _isFunction(value[p]) && p !== "render" ? value[p].call(tween, index, target, targets) : value[p];
}
value = type;
}
let cs = target.nodeType ? window.getComputedStyle(target) : {},
fill = cs.fill + "",
fillSafe = !(fill === "none" || (fill.match(_numExp) || [])[3] === "0" || cs.fillRule === "evenodd"),
origins = (value.origin || "50 50").split(",");
type = (target.nodeName + "").toUpperCase();
isPoly = (type === "POLYLINE" || type === "POLYGON");
if (type !== "PATH" && !isPoly && !value.prop) {
_log("Cannot morph a <" + type + "> element. " + _morphMessage);
return false;
}
p = (type === "PATH") ? "d" : "points";
if (!value.prop && !_isFunction(target.setAttribute)) {
return false;
}
shape = _parseShape(value.shape || value.d || value.points || "", (p === "d"), target);
if (isPoly && _commands.test(shape)) {
_log("A <" + type + "> cannot accept path data. " + _morphMessage);
return false;
}
shapeIndex = (value.shapeIndex || value.shapeIndex === 0) ? value.shapeIndex : "auto";
map = value.map || MorphSVGPlugin.defaultMap;
this._prop = value.prop;
this._render = value.render || MorphSVGPlugin.defaultRender;
this._apply = ("updateTarget" in value) ? value.updateTarget : MorphSVGPlugin.defaultUpdateTarget;
this._rnd = Math.pow(10, isNaN(value.precision) ? 2 : +value.precision);
this._tween = tween;
if (shape) {
this._target = target;
precompiled = (typeof(value.precompile) === "object");
start = this._prop ? target[this._prop] : target.getAttribute(p);
if (!this._prop && !target.getAttributeNS(null, "data-original")) {
target.setAttributeNS(null, "data-original", start); //record the original state in a data-original attribute so that we can revert to it later.
}
if (p === "d" || this._prop) {
start = stringToRawPath(precompiled ? value.precompile[0] : start);
end = stringToRawPath(precompiled ? value.precompile[1] : shape);
if (!precompiled && !_equalizeSegmentQuantity(start, end, shapeIndex, map, fillSafe)) {
return false; //malformed path data or null target
}
if (value.precompile === "log" || value.precompile === true) {
_log('precompile:["' + rawPathToString(start) + '","' + rawPathToString(end) + '"]');
}
useRotation = (value.type || MorphSVGPlugin.defaultType) !== "linear";
if (useRotation) {
start = _populateSmoothData(start, value.smoothTolerance);
end = _populateSmoothData(end, value.smoothTolerance);
if (!start.size) {
_getTotalSize(start); //adds top/left/width/height values
}
if (!end.size) {
_getTotalSize(end);
}
originFactors = _parseOriginFactors(origins[0]);
this._origin = start.origin = {x:start.left + originFactors.x * start.width, y:start.top + originFactors.y * start.height};
if (origins[1]) {
originFactors = _parseOriginFactors(origins[1]);
}
this._eOrigin = {x:end.left + originFactors.x * end.width, y:end.top + originFactors.y * end.height};
}
this._rawPath = target._gsRawPath = start;
j = start.length;
while (--j > -1) {
startSeg = start[j];
endSeg = end[j];
startSmooth = startSeg.isSmooth || [];
endSmooth = endSeg.isSmooth || [];
l = startSeg.length;
_lastLinkedAnchor = 0; //reset; we use _lastLinkedAnchor in the _tweenRotation() method to help make sure that close points don't get ripped apart and rotate opposite directions. Typically we want to go the shortest direction, but if the previous anchor is going a different direction, we override this logic (within certain thresholds)
for (i = 0; i < l; i+=2) {
if (endSeg[i] !== startSeg[i] || endSeg[i+1] !== startSeg[i+1]) {
if (useRotation) {
if (startSmooth[i] && endSmooth[i]) { //if BOTH starting and ending values are smooth (meaning control points have basically the same slope), interpolate the rotation and length instead of the coordinates (this is what makes things smooth).
sData = startSeg.smoothData;
eData = endSeg.smoothData;
offset = i + ((i === l - 4) ? 7 - l : 5); //helps us accommodate wrapping (like if the end and start anchors are identical and the control points are smooth).
this._controlPT = {_next:this._controlPT, i:i, j:j, l1s:sData[i+1], l1c:eData[i+1] - sData[i+1], l2s:sData[offset], l2c:eData[offset] - sData[offset]};
pt = this._tweenRotation(startSeg, endSeg, i+2);
this._tweenRotation(startSeg, endSeg, i, pt);
this._tweenRotation(startSeg, endSeg, offset-1, pt);
i+=4;
} else {
this._tweenRotation(startSeg, endSeg, i);
}
} else {
pt = this.add(startSeg, i, startSeg[i], endSeg[i], 0, 0, 0, 0, 0, 1);
pt = this.add(startSeg, i+1, startSeg[i+1], endSeg[i+1], 0, 0, 0, 0, 0, 1) || pt;
}
}
}
}
} else {
pt = this.add(target, "setAttribute", target.getAttribute(p) + "", shape + "", index, targets, 0, _buildPointsFilter(shapeIndex), p);
}
if (useRotation) {
this.add(this._origin, "x", this._origin.x, this._eOrigin.x, 0, 0, 0, 0, 0, 1);
pt = this.add(this._origin, "y", this._origin.y, this._eOrigin.y, 0, 0, 0, 0, 0, 1);
}
if (pt) {
this._props.push("morphSVG");
pt.end = shape;
pt.endProp = p;
}
}
return _bonusValidated;
},
render(ratio, data) {
let rawPath = data._rawPath,
controlPT = data._controlPT,
anchorPT = data._anchorPT,
rnd = data._rnd,
target = data._target,
pt = data._pt,
s, space, easeInOut, segment, l, angle, i, j, x, y, sin, cos, offset;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
if (ratio === 1 && data._apply) {
pt = data._pt;
while (pt) {
if (pt.end) {
if (data._prop) {
target[data._prop] = pt.end;
} else {
target.setAttribute(pt.endProp, pt.end); //make sure the end value is exactly as specified (in case we had to add fabricated points during the tween)
}
}
pt = pt._next;
}
} else if (rawPath) {
//rotationally position the anchors
while (anchorPT) {
angle = anchorPT.sa + ratio * anchorPT.ca;
l = anchorPT.sl + ratio * anchorPT.cl; //length
anchorPT.t[anchorPT.i] = data._origin.x + _cos(angle) * l;
anchorPT.t[anchorPT.i + 1] = data._origin.y + _sin(angle) * l;
anchorPT = anchorPT._next;
}
//smooth out the control points
easeInOut = ratio < 0.5 ? 2 * ratio * ratio : (4 - 2 * ratio) * ratio - 1;
while (controlPT) {
i = controlPT.i;
segment = rawPath[controlPT.j];
offset = i + ((i === segment.length - 4) ? 7 - segment.length : 5); //accommodates wrapping around of smooth points, like if the start and end anchors are on top of each other and their handles are smooth.
angle = _atan2(segment[offset] - segment[i+1], segment[offset-1] - segment[i]); //average the angles
sin = _sin(angle);
cos = _cos(angle);
x = segment[i+2];
y = segment[i+3];
l = controlPT.l1s + easeInOut * controlPT.l1c; //length
segment[i] = x - cos * l;
segment[i+1] = y - sin * l;
l = controlPT.l2s + easeInOut * controlPT.l2c;
segment[offset-1] = x + cos * l;
segment[offset] = y + sin * l;
controlPT = controlPT._next;
}
target._gsRawPath = rawPath;
if (data._apply) {
s = "";
space = " ";
for (j = 0; j < rawPath.length; j++) {
segment = rawPath[j];
l = segment.length;
s += "M" + (((segment[0] * rnd) | 0) / rnd) + space + (((segment[1] * rnd) | 0) / rnd) + " C";
for (i = 2; i < l; i++) { //this is actually faster than just doing a join() on the array, possibly because the numbers have so many decimal places
s += (((segment[i] * rnd) | 0) / rnd) + space;
}
}
if (data._prop) {
target[data._prop] = s;
} else {
target.setAttribute("d", s);
}
}
}
data._render && rawPath && data._render.call(data._tween, rawPath, target);
},
kill(property) {
this._pt = this._rawPath = 0;
},
getRawPath: getRawPath,
stringToRawPath: stringToRawPath,
rawPathToString: rawPathToString,
normalizeStrings(shape1, shape2, {shapeIndex, map}) {
let result = [shape1, shape2];
_pathFilter(result, shapeIndex, map);
return result;
},
pathFilter: _pathFilter,
pointsFilter: _pointsFilter,
getTotalSize: _getTotalSize,
equalizeSegmentQuantity: _equalizeSegmentQuantity,
convertToPath: (targets, swap) => _toArray(targets).map(target => convertToPath(target, swap !== false)),
defaultType: "linear",
defaultUpdateTarget: true,
defaultMap: "size"
};
_getGSAP() && gsap.registerPlugin(MorphSVGPlugin);
export { MorphSVGPlugin as default };

View File

@@ -0,0 +1,254 @@
/*!
* MotionPathHelper 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import PathEditor from "./utils/PathEditor.js";
let gsap, _win, _doc, _docEl, _body, MotionPathPlugin, _arrayToRawPath, _rawPathToString, _context,
_bonusValidated = 1, //<name>MotionPathHelper</name>
_selectorExp = /(^[#\.][a-z]|[a-y][a-z])/i,
_isString = value => typeof(value) === "string",
_createElement = (type, ns) => {
let e = _doc.createElementNS ? _doc.createElementNS((ns || "http://www.w3.org/1999/xhtml").replace(/^https/, "http"), type) : _doc.createElement(type); //some servers swap in https for http in the namespace which can break things, making "style" inaccessible.
return e.style ? e : _doc.createElement(type); //some environments won't allow access to the element's style when created with a namespace in which case we default to the standard createElement() to work around the issue. Also note that when GSAP is embedded directly inside an SVG file, createElement() won't allow access to the style object in Firefox (see https://gsap.com/forums/topic/20215-problem-using-tweenmax-in-standalone-self-containing-svg-file-err-cannot-set-property-csstext-of-undefined/).
},
_getPositionOnPage = target => {
let bounds = target.getBoundingClientRect(),
windowOffsetY = _docEl.clientTop - (_win.pageYOffset || _docEl.scrollTop || _body.scrollTop || 0),
windowOffsetX = _docEl.clientLeft - (_win.pageXOffset || _docEl.scrollLeft || _body.scrollLeft || 0);
return {left:bounds.left + windowOffsetX, top:bounds.top + windowOffsetY, right: bounds.right + windowOffsetX, bottom: bounds.bottom + windowOffsetY};
},
_getInitialPath = (x, y) => {
let coordinates = [0,31,8,58,24,75,40,90,69,100,100,100],
i;
for (i = 0; i < coordinates.length; i+=2) {
coordinates[i] += x;
coordinates[i+1] += y;
}
return "M" + x + "," + y + "C" + coordinates.join(",");
},
_getGlobalTime = animation => {
let time = animation.totalTime();
while (animation) {
time = animation.startTime() + time / (animation.timeScale() || 1);
animation = animation.parent;
}
return time;
},
_copyElement,
_initCopyToClipboard = () => {
_copyElement = _createElement("textarea");
_copyElement.style.display = "none";
_body.appendChild(_copyElement);
},
_parsePath = (path, target, vars) => (_isString(path) && _selectorExp.test(path)) ? _doc.querySelector(path) : Array.isArray(path) ? _rawPathToString(_arrayToRawPath([{x:gsap.getProperty(target, "x"), y:gsap.getProperty(target, "y")}, ...path], vars)) : (_isString(path) || path && (path.tagName + "").toLowerCase() === "path") ? path : 0,
_addCopyToClipboard = (target, getter, onComplete) => {
target.addEventListener('click', e => {
if (e.target._gsHelper) {
let c = getter(e.target);
_copyElement.value = c;
if (c && _copyElement.select) {
console.log(c);
_copyElement.style.display = "block";
_copyElement.select();
try {
_doc.execCommand('copy');
_copyElement.blur();
onComplete && onComplete(target);
} catch (err) {
console.warn("Copy didn't work; this browser doesn't permit that.");
}
_copyElement.style.display = "none";
}
}
});
},
_identityMatrixObject = {matrix:{a:1, b:0, c:0, d:1, e:0, f:0}},
_getConsolidatedMatrix = target => (target.transform.baseVal.consolidate() || _identityMatrixObject).matrix,
_findMotionPathTween = target => {
let tweens = gsap.getTweensOf(target),
i = 0;
for (; i < tweens.length; i++) {
if (tweens[i].vars.motionPath) {
return tweens[i];
} else if (tweens[i].timeline) {
tweens.push(...tweens[i].timeline.getChildren());
}
}
},
_initCore = (core, required) => {
let message = "Please gsap.registerPlugin(MotionPathPlugin)";
_win = window;
gsap = gsap || core || _win.gsap || console.warn(message);
gsap && PathEditor.register(gsap);
_doc = document;
_body = _doc.body;
_docEl = _doc.documentElement;
if (gsap) {
MotionPathPlugin = gsap.plugins.motionPath;
MotionPathHelper.PathEditor = PathEditor;
_context = gsap.core.context || function() {};
}
if (!MotionPathPlugin) {
(required === true) && console.warn(message);
} else {
_initCopyToClipboard();
_arrayToRawPath = MotionPathPlugin.arrayToRawPath;
_rawPathToString = MotionPathPlugin.rawPathToString;
}
};
export class MotionPathHelper {
constructor(targetOrTween, vars = {}) {
if (!MotionPathPlugin) {
_initCore(vars.gsap, 1);
}
let copyButton = _createElement("div"),
self = this,
offset = {x:0, y:0},
target, path, isSVG, startX, startY, position, svg, animation, svgNamespace, temp, matrix, refreshPath, animationToScrub, createdSVG;
if (targetOrTween instanceof gsap.core.Tween) {
animation = targetOrTween;
target = animation.targets()[0];
} else {
target = gsap.utils.toArray(targetOrTween)[0];
animation = _findMotionPathTween(target);
}
path = _parsePath(vars.path, target, vars);
this.offset = offset;
position = _getPositionOnPage(target);
startX = parseFloat(gsap.getProperty(target, "x", "px"));
startY = parseFloat(gsap.getProperty(target, "y", "px"));
isSVG = (target.getCTM && target.tagName.toLowerCase() !== "svg");
if (animation && !path) {
path = _parsePath(animation.vars.motionPath.path || animation.vars.motionPath, target, animation.vars.motionPath);
}
copyButton.setAttribute("class", "copy-motion-path");
copyButton.style.cssText = "border-radius:8px; background-color:rgba(85, 85, 85, 0.7); color:#fff; cursor:pointer; padding:6px 12px; font-family:Signika Negative, Arial, sans-serif; position:fixed; left:50%; transform:translate(-50%, 0); font-size:19px; bottom:10px";
copyButton.innerText = "COPY MOTION PATH";
copyButton._gsHelper = self;
(gsap.utils.toArray(vars.container)[0] || _body).appendChild(copyButton);
_addCopyToClipboard(copyButton, () => self.getString(), () => gsap.fromTo(copyButton, {backgroundColor:"white"}, {duration:0.5, backgroundColor:"rgba(85, 85, 85, 0.6)"}));
svg = path && path.ownerSVGElement;
if (!svg) {
svgNamespace = (isSVG && target.ownerSVGElement && target.ownerSVGElement.getAttribute("xmlns")) || "http://www.w3.org/2000/svg";
if (isSVG) {
svg = target.ownerSVGElement;
temp = target.getBBox();
matrix = _getConsolidatedMatrix(target);
startX = matrix.e;
startY = matrix.f;
offset.x = temp.x;
offset.y = temp.y;
} else {
svg = _createElement("svg", svgNamespace);
createdSVG = true;
_body.appendChild(svg);
svg.setAttribute("viewBox", "0 0 100 100");
svg.setAttribute("class", "motion-path-helper");
svg.style.cssText = "overflow:visible; background-color: transparent; position:absolute; z-index:5000; width:100px; height:100px; top:" + (position.top - startY) + "px; left:" + (position.left - startX) + "px;";
}
temp = _isString(path) && !_selectorExp.test(path) ? path : _getInitialPath(startX, startY);
path = _createElement("path", svgNamespace);
path.setAttribute("d", temp);
path.setAttribute("vector-effect", "non-scaling-stroke");
path.style.cssText = "fill:transparent; stroke-width:" + (vars.pathWidth || 3) + "; stroke:" + (vars.pathColor || "#555") + "; opacity:" + (vars.pathOpacity || 0.6);
svg.appendChild(path);
} else {
vars.pathColor && gsap.set(path, {stroke: vars.pathColor});
vars.pathWidth && gsap.set(path, {strokeWidth: vars.pathWidth});
vars.pathOpacity && gsap.set(path, {opacity: vars.pathOpacity});
}
if (offset.x || offset.y) {
gsap.set(path, {x:offset.x, y:offset.y});
}
if (!("selected" in vars)) {
vars.selected = true;
}
if (!("anchorSnap" in vars)) {
vars.anchorSnap = p => {
if (p.x * p.x + p.y * p.y < 16) {
p.x = p.y = 0;
}
};
}
animationToScrub = animation && animation.parent && animation.parent.data === "nested" ? animation.parent.parent : animation;
vars.onPress = () => {
animationToScrub.pause(0);
};
refreshPath = () => {
//let m = _getConsolidatedMatrix(path);
//animation.vars.motionPath.offsetX = m.e - offset.x;
//animation.vars.motionPath.offsetY = m.f - offset.y;
animation.invalidate();
animationToScrub.restart();
};
vars.onRelease = vars.onDeleteAnchor = refreshPath;
this.editor = PathEditor.create(path, vars);
if (vars.center) {
gsap.set(target, {transformOrigin:"50% 50%", xPercent:-50, yPercent:-50});
}
if (animation) {
if (animation.vars.motionPath.path) {
animation.vars.motionPath.path = path;
} else {
animation.vars.motionPath = {path:path};
}
if (animationToScrub.parent !== gsap.globalTimeline) {
gsap.globalTimeline.add(animationToScrub, _getGlobalTime(animationToScrub) - animationToScrub.delay());
}
animationToScrub.repeat(-1).repeatDelay(1);
} else {
animation = animationToScrub = gsap.to(target, {
motionPath: {
path: path,
start: vars.start || 0,
end: ("end" in vars) ? vars.end : 1,
autoRotate: ("autoRotate" in vars) ? vars.autoRotate : false,
align: path,
alignOrigin: vars.alignOrigin
},
duration: vars.duration || 5,
ease: vars.ease || "power1.inOut",
repeat:-1,
repeatDelay:1,
paused:!vars.path
});
}
this.animation = animation;
_context(this);
this.kill = this.revert = () => {
this.editor.kill();
copyButton.parentNode && copyButton.parentNode.removeChild(copyButton);
createdSVG && svg.parentNode && svg.parentNode.removeChild(svg);
animationToScrub && animationToScrub.revert();
}
}
getString() {
return this.editor.getString(true, -this.offset.x, -this.offset.y);
}
}
MotionPathHelper.register = _initCore;
MotionPathHelper.create = (target, vars) => new MotionPathHelper(target, vars);
MotionPathHelper.editPath = (path, vars) => PathEditor.create(path, vars);
MotionPathHelper.version = "3.13.0";
export { MotionPathHelper as default };

View File

@@ -0,0 +1,270 @@
/*!
* MotionPathPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { getRawPath, cacheRawPathMeasurements, getPositionOnPath, pointsToSegment, flatPointsToSegment, sliceRawPath, stringToRawPath, rawPathToString, transformRawPath, convertToPath } from "./utils/paths.js";
import { getGlobalMatrix } from "./utils/matrix.js";
let _xProps = "x,translateX,left,marginLeft,xPercent".split(","),
_yProps = "y,translateY,top,marginTop,yPercent".split(","),
_DEG2RAD = Math.PI / 180,
gsap, PropTween, _getUnit, _toArray, _getStyleSaver, _reverting,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_populateSegmentFromArray = (segment, values, property, mode) => { //mode: 0 = x but don't fill y yet, 1 = y, 2 = x and fill y with 0.
let l = values.length,
si = mode === 2 ? 0 : mode,
i = 0,
v;
for (; i < l; i++) {
segment[si] = v = parseFloat(values[i][property]);
mode === 2 && (segment[si+1] = 0);
si += 2;
}
return segment;
},
_getPropNum = (target, prop, unit) => parseFloat(target._gsap.get(target, prop, unit || "px")) || 0,
_relativize = segment => {
let x = segment[0],
y = segment[1],
i;
for (i = 2; i < segment.length; i+=2) {
x = (segment[i] += x);
y = (segment[i+1] += y);
}
},
// feed in an array of quadratic bezier points like [{x: 0, y: 0}, ...] and it'll convert it to cubic bezier
// _quadToCubic = points => {
// let cubic = [],
// l = points.length - 1,
// i = 1,
// a, b, c;
// for (; i < l; i+=2) {
// a = points[i-1];
// b = points[i];
// c = points[i+1];
// cubic.push(a, {x: (2 * b.x + a.x) / 3, y: (2 * b.y + a.y) / 3}, {x: (2 * b.x + c.x) / 3, y: (2 * b.y + c.y) / 3});
// }
// cubic.push(points[l]);
// return cubic;
// },
_segmentToRawPath = (plugin, segment, target, x, y, slicer, vars, unitX, unitY) => {
if (vars.type === "cubic") {
segment = [segment];
} else {
vars.fromCurrent !== false && segment.unshift(_getPropNum(target, x, unitX), y ? _getPropNum(target, y, unitY) : 0);
vars.relative && _relativize(segment);
let pointFunc = y ? pointsToSegment : flatPointsToSegment;
segment = [pointFunc(segment, vars.curviness)];
}
segment = slicer(_align(segment, target, vars));
_addDimensionalPropTween(plugin, target, x, segment, "x", unitX);
y && _addDimensionalPropTween(plugin, target, y, segment, "y", unitY);
return cacheRawPathMeasurements(segment, vars.resolution || (vars.curviness === 0 ? 20 : 12)); //when curviness is 0, it creates control points right on top of the anchors which makes it more sensitive to resolution, thus we change the default accordingly.
},
_emptyFunc = v => v,
_numExp = /[-+\.]*\d+\.?(?:e-|e\+)?\d*/g,
_originToPoint = (element, origin, parentMatrix) => { // origin is an array of normalized values (0-1) in relation to the width/height, so [0.5, 0.5] would be the center. It can also be "auto" in which case it will be the top left unless it's a <path>, when it will start at the beginning of the path itself.
let m = getGlobalMatrix(element),
x = 0,
y = 0,
svg;
if ((element.tagName + "").toLowerCase() === "svg") {
svg = element.viewBox.baseVal;
svg.width || (svg = {width: +element.getAttribute("width"), height: +element.getAttribute("height")});
} else {
svg = origin && element.getBBox && element.getBBox();
}
if (origin && origin !== "auto") {
x = origin.push ? origin[0] * (svg ? svg.width : element.offsetWidth || 0) : origin.x;
y = origin.push ? origin[1] * (svg ? svg.height : element.offsetHeight || 0) : origin.y;
}
return parentMatrix.apply( x || y ? m.apply({x: x, y: y}) : {x: m.e, y: m.f} );
},
_getAlignMatrix = (fromElement, toElement, fromOrigin, toOrigin) => {
let parentMatrix = getGlobalMatrix(fromElement.parentNode, true, true),
m = parentMatrix.clone().multiply(getGlobalMatrix(toElement)),
fromPoint = _originToPoint(fromElement, fromOrigin, parentMatrix),
{x, y} = _originToPoint(toElement, toOrigin, parentMatrix),
p;
m.e = m.f = 0;
if (toOrigin === "auto" && toElement.getTotalLength && toElement.tagName.toLowerCase() === "path") {
p = toElement.getAttribute("d").match(_numExp) || [];
p = m.apply({x:+p[0], y:+p[1]});
x += p.x;
y += p.y;
}
//if (p || (toElement.getBBox && fromElement.getBBox && toElement.ownerSVGElement === fromElement.ownerSVGElement)) {
if (p) {
p = m.apply(toElement.getBBox());
x -= p.x;
y -= p.y;
}
m.e = x - fromPoint.x;
m.f = y - fromPoint.y;
return m;
},
_align = (rawPath, target, {align, matrix, offsetX, offsetY, alignOrigin}) => {
let x = rawPath[0][0],
y = rawPath[0][1],
curX = _getPropNum(target, "x"),
curY = _getPropNum(target, "y"),
alignTarget, m, p;
if (!rawPath || !rawPath.length) {
return getRawPath("M0,0L0,0");
}
if (align) {
if (align === "self" || ((alignTarget = _toArray(align)[0] || target) === target)) {
transformRawPath(rawPath, 1, 0, 0, 1, curX - x, curY - y);
} else {
if (alignOrigin && alignOrigin[2] !== false) {
gsap.set(target, {transformOrigin:(alignOrigin[0] * 100) + "% " + (alignOrigin[1] * 100) + "%"});
} else {
alignOrigin = [_getPropNum(target, "xPercent") / -100, _getPropNum(target, "yPercent") / -100];
}
m = _getAlignMatrix(target, alignTarget, alignOrigin, "auto");
p = m.apply({x: x, y: y});
transformRawPath(rawPath, m.a, m.b, m.c, m.d, curX + m.e - (p.x - m.e), curY + m.f - (p.y - m.f));
}
}
if (matrix) {
transformRawPath(rawPath, matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f);
} else if (offsetX || offsetY) {
transformRawPath(rawPath, 1, 0, 0, 1, offsetX || 0, offsetY || 0);
}
return rawPath;
},
_addDimensionalPropTween = (plugin, target, property, rawPath, pathProperty, forceUnit) => {
let cache = target._gsap,
harness = cache.harness,
alias = (harness && harness.aliases && harness.aliases[property]),
prop = alias && alias.indexOf(",") < 0 ? alias : property,
pt = plugin._pt = new PropTween(plugin._pt, target, prop, 0, 0, _emptyFunc, 0, cache.set(target, prop, plugin));
pt.u = _getUnit(cache.get(target, prop, forceUnit)) || 0;
pt.path = rawPath;
pt.pp = pathProperty;
plugin._props.push(prop);
},
_sliceModifier = (start, end) => rawPath => (start || end !== 1) ? sliceRawPath(rawPath, start, end) : rawPath;
export const MotionPathPlugin = {
version: "3.13.0",
name: "motionPath",
register(core, Plugin, propTween) {
gsap = core;
_getUnit = gsap.utils.getUnit;
_toArray = gsap.utils.toArray;
_getStyleSaver = gsap.core.getStyleSaver;
_reverting = gsap.core.reverting || function() {};
PropTween = propTween;
},
init(target, vars, tween) {
if (!gsap) {
console.warn("Please gsap.registerPlugin(MotionPathPlugin)");
return false;
}
if (!(typeof(vars) === "object" && !vars.style) || !vars.path) {
vars = {path:vars};
}
let rawPaths = [],
{path, autoRotate, unitX, unitY, x, y} = vars,
firstObj = path[0],
slicer = _sliceModifier(vars.start, ("end" in vars) ? vars.end : 1),
rawPath, p;
this.rawPaths = rawPaths;
this.target = target;
this.tween = tween;
this.styles = _getStyleSaver && _getStyleSaver(target, "transform");
if ((this.rotate = (autoRotate || autoRotate === 0))) { //get the rotational data FIRST so that the setTransform() method is called in the correct order in the render() loop - rotation gets set last.
this.rOffset = parseFloat(autoRotate) || 0;
this.radians = !!vars.useRadians;
this.rProp = vars.rotation || "rotation"; // rotation property
this.rSet = target._gsap.set(target, this.rProp, this); // rotation setter
this.ru = _getUnit(target._gsap.get(target, this.rProp)) || 0; // rotation units
}
if (Array.isArray(path) && !("closed" in path) && typeof(firstObj) !== "number") {
for (p in firstObj) {
if (!x && ~_xProps.indexOf(p)) {
x = p;
} else if (!y && ~_yProps.indexOf(p)) {
y = p;
}
}
if (x && y) { //correlated values
rawPaths.push(_segmentToRawPath(this, _populateSegmentFromArray(_populateSegmentFromArray([], path, x, 0), path, y, 1), target, x, y, slicer, vars, unitX || _getUnit(path[0][x]), unitY || _getUnit(path[0][y])));
} else {
x = y = 0;
}
for (p in firstObj) {
p !== x && p !== y && rawPaths.push(_segmentToRawPath(this, _populateSegmentFromArray([], path, p, 2), target, p, 0, slicer, vars, _getUnit(path[0][p])));
}
} else {
rawPath = slicer(_align(getRawPath(vars.path), target, vars));
cacheRawPathMeasurements(rawPath, vars.resolution);
rawPaths.push(rawPath);
_addDimensionalPropTween(this, target, vars.x || "x", rawPath, "x", vars.unitX || "px");
_addDimensionalPropTween(this, target, vars.y || "y", rawPath, "y", vars.unitY || "px");
}
tween.vars.immediateRender && this.render(tween.progress(), this);
},
render(ratio, data) {
let rawPaths = data.rawPaths,
i = rawPaths.length,
pt = data._pt;
if (data.tween._time || !_reverting()) {
if (ratio > 1) {
ratio = 1;
} else if (ratio < 0) {
ratio = 0;
}
while (i--) {
getPositionOnPath(rawPaths[i], ratio, !i && data.rotate, rawPaths[i]);
}
while (pt) {
pt.set(pt.t, pt.p, pt.path[pt.pp] + pt.u, pt.d, ratio);
pt = pt._next;
}
data.rotate && data.rSet(data.target, data.rProp, rawPaths[0].angle * (data.radians ? _DEG2RAD : 1) + data.rOffset + data.ru, data, ratio);
} else {
data.styles.revert();
}
},
getLength(path) {
return cacheRawPathMeasurements(getRawPath(path)).totalLength;
},
sliceRawPath,
getRawPath,
pointsToSegment,
stringToRawPath,
rawPathToString,
transformRawPath,
getGlobalMatrix,
getPositionOnPath,
cacheRawPathMeasurements,
convertToPath: (targets, swap) => _toArray(targets).map(target => convertToPath(target, swap !== false)),
convertCoordinates(fromElement, toElement, point) {
let m = getGlobalMatrix(toElement, true, true).multiply(getGlobalMatrix(fromElement));
return point ? m.apply(point) : m;
},
getAlignMatrix: _getAlignMatrix,
getRelativePosition(fromElement, toElement, fromOrigin, toOrigin) {
let m =_getAlignMatrix(fromElement, toElement, fromOrigin, toOrigin);
return {x: m.e, y: m.f};
},
arrayToRawPath(value, vars) {
vars = vars || {};
let segment = _populateSegmentFromArray(_populateSegmentFromArray([], value, vars.x || "x", 0), value, vars.y || "y", 1);
vars.relative && _relativize(segment);
return [(vars.type === "cubic") ? segment : pointsToSegment(segment, vars.curviness)];
}
};
_getGSAP() && gsap.registerPlugin(MotionPathPlugin);
export { MotionPathPlugin as default };

439
network-visualization/node_modules/gsap/src/Observer.js generated vendored Normal file
View File

@@ -0,0 +1,439 @@
/*!
* Observer 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _clamp, _win, _doc, _docEl, _body, _isTouch, _pointerType, ScrollTrigger, _root, _normalizer, _eventTypes, _context,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_startup = 1,
_observers = [],
_scrollers = [],
_proxies = [],
_getTime = Date.now,
_bridge = (name, value) => value,
_integrate = () => {
let core = ScrollTrigger.core,
data = core.bridge || {},
scrollers = core._scrollers,
proxies = core._proxies;
scrollers.push(..._scrollers);
proxies.push(..._proxies);
_scrollers = scrollers;
_proxies = proxies;
_bridge = (name, value) => data[name](value);
},
_getProxyProp = (element, property) => ~_proxies.indexOf(element) && _proxies[_proxies.indexOf(element) + 1][property],
_isViewport = el => !!~_root.indexOf(el),
_addListener = (element, type, func, passive, capture) => element.addEventListener(type, func, {passive: passive !== false, capture: !!capture}),
_removeListener = (element, type, func, capture) => element.removeEventListener(type, func, !!capture),
_scrollLeft = "scrollLeft",
_scrollTop = "scrollTop",
_onScroll = () => (_normalizer && _normalizer.isPressed) || _scrollers.cache++,
_scrollCacheFunc = (f, doNotCache) => {
let cachingFunc = value => { // since reading the scrollTop/scrollLeft/pageOffsetY/pageOffsetX can trigger a layout, this function allows us to cache the value so it only gets read fresh after a "scroll" event fires (or while we're refreshing because that can lengthen the page and alter the scroll position). when "soft" is true, that means don't actually set the scroll, but cache the new value instead (useful in ScrollSmoother)
if (value || value === 0) {
_startup && (_win.history.scrollRestoration = "manual"); // otherwise the new position will get overwritten by the browser onload.
let isNormalizing = _normalizer && _normalizer.isPressed;
value = cachingFunc.v = Math.round(value) || (_normalizer && _normalizer.iOS ? 1 : 0); //TODO: iOS Bug: if you allow it to go to 0, Safari can start to report super strange (wildly inaccurate) touch positions!
f(value);
cachingFunc.cacheID = _scrollers.cache;
isNormalizing && _bridge("ss", value); // set scroll (notify ScrollTrigger so it can dispatch a "scrollStart" event if necessary
} else if (doNotCache || _scrollers.cache !== cachingFunc.cacheID || _bridge("ref")) {
cachingFunc.cacheID = _scrollers.cache;
cachingFunc.v = f();
}
return cachingFunc.v + cachingFunc.offset;
};
cachingFunc.offset = 0;
return f && cachingFunc;
},
_horizontal = {s: _scrollLeft, p: "left", p2: "Left", os: "right", os2: "Right", d: "width", d2: "Width", a: "x", sc: _scrollCacheFunc(function(value) { return arguments.length ? _win.scrollTo(value, _vertical.sc()) : _win.pageXOffset || _doc[_scrollLeft] || _docEl[_scrollLeft] || _body[_scrollLeft] || 0})},
_vertical = {s: _scrollTop, p: "top", p2: "Top", os: "bottom", os2: "Bottom", d: "height", d2: "Height", a: "y", op: _horizontal, sc: _scrollCacheFunc(function(value) { return arguments.length ? _win.scrollTo(_horizontal.sc(), value) : _win.pageYOffset || _doc[_scrollTop] || _docEl[_scrollTop] || _body[_scrollTop] || 0})},
_getTarget = (t, self) => ((self && self._ctx && self._ctx.selector) || gsap.utils.toArray)(t)[0] || (typeof(t) === "string" && gsap.config().nullTargetWarn !== false ? console.warn("Element not found:", t) : null),
_isWithin = (element, list) => { // check if the element is in the list or is a descendant of an element in the list.
let i = list.length;
while (i--) {
if (list[i] === element || list[i].contains(element)) {
return true;
}
}
return false;
},
_getScrollFunc = (element, {s, sc}) => { // we store the scroller functions in an alternating sequenced Array like [element, verticalScrollFunc, horizontalScrollFunc, ...] so that we can minimize memory, maximize performance, and we also record the last position as a ".rec" property in order to revert to that after refreshing to ensure things don't shift around.
_isViewport(element) && (element = _doc.scrollingElement || _docEl);
let i = _scrollers.indexOf(element),
offset = sc === _vertical.sc ? 1 : 2;
!~i && (i = _scrollers.push(element) - 1);
_scrollers[i + offset] || _addListener(element, "scroll", _onScroll); // clear the cache when a scroll occurs
let prev = _scrollers[i + offset],
func = prev || (_scrollers[i + offset] = _scrollCacheFunc(_getProxyProp(element, s), true) || (_isViewport(element) ? sc : _scrollCacheFunc(function(value) { return arguments.length ? (element[s] = value) : element[s]; })));
func.target = element;
prev || (func.smooth = gsap.getProperty(element, "scrollBehavior") === "smooth"); // only set it the first time (don't reset every time a scrollFunc is requested because perhaps it happens during a refresh() when it's disabled in ScrollTrigger.
return func;
},
_getVelocityProp = (value, minTimeRefresh, useDelta) => {
let v1 = value,
v2 = value,
t1 = _getTime(),
t2 = t1,
min = minTimeRefresh || 50,
dropToZeroTime = Math.max(500, min * 3),
update = (value, force) => {
let t = _getTime();
if (force || t - t1 > min) {
v2 = v1;
v1 = value;
t2 = t1;
t1 = t;
} else if (useDelta) {
v1 += value;
} else { // not totally necessary, but makes it a bit more accurate by adjusting the v1 value according to the new slope. This way we're not just ignoring the incoming data. Removing for now because it doesn't seem to make much practical difference and it's probably not worth the kb.
v1 = v2 + (value - v2) / (t - t2) * (t1 - t2);
}
},
reset = () => { v2 = v1 = useDelta ? 0 : v1; t2 = t1 = 0; },
getVelocity = latestValue => {
let tOld = t2,
vOld = v2,
t = _getTime();
(latestValue || latestValue === 0) && latestValue !== v1 && update(latestValue);
return (t1 === t2 || t - t2 > dropToZeroTime) ? 0 : (v1 + (useDelta ? vOld : -vOld)) / ((useDelta ? t : t1) - tOld) * 1000;
};
return {update, reset, getVelocity};
},
_getEvent = (e, preventDefault) => {
preventDefault && !e._gsapAllow && e.preventDefault();
return e.changedTouches ? e.changedTouches[0] : e;
},
_getAbsoluteMax = a => {
let max = Math.max(...a),
min = Math.min(...a);
return Math.abs(max) >= Math.abs(min) ? max : min;
},
_setScrollTrigger = () => {
ScrollTrigger = gsap.core.globals().ScrollTrigger;
ScrollTrigger && ScrollTrigger.core && _integrate();
},
_initCore = core => {
gsap = core || _getGSAP();
if (!_coreInitted && gsap && typeof(document) !== "undefined" && document.body) {
_win = window;
_doc = document;
_docEl = _doc.documentElement;
_body = _doc.body;
_root = [_win, _doc, _docEl, _body];
_clamp = gsap.utils.clamp;
_context = gsap.core.context || function() {};
_pointerType = "onpointerenter" in _body ? "pointer" : "mouse";
// isTouch is 0 if no touch, 1 if ONLY touch, and 2 if it can accommodate touch but also other types like mouse/pointer.
_isTouch = Observer.isTouch = _win.matchMedia && _win.matchMedia("(hover: none), (pointer: coarse)").matches ? 1 : ("ontouchstart" in _win || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) ? 2 : 0;
_eventTypes = Observer.eventTypes = ("ontouchstart" in _docEl ? "touchstart,touchmove,touchcancel,touchend" : !("onpointerdown" in _docEl) ? "mousedown,mousemove,mouseup,mouseup" : "pointerdown,pointermove,pointercancel,pointerup").split(",");
setTimeout(() => _startup = 0, 500);
_setScrollTrigger();
_coreInitted = 1;
}
return _coreInitted;
};
_horizontal.op = _vertical;
_scrollers.cache = 0;
export class Observer {
constructor(vars) {
this.init(vars);
}
init(vars) {
_coreInitted || _initCore(gsap) || console.warn("Please gsap.registerPlugin(Observer)");
ScrollTrigger || _setScrollTrigger();
let {tolerance, dragMinimum, type, target, lineHeight, debounce, preventDefault, onStop, onStopDelay, ignore, wheelSpeed, event, onDragStart, onDragEnd, onDrag, onPress, onRelease, onRight, onLeft, onUp, onDown, onChangeX, onChangeY, onChange, onToggleX, onToggleY, onHover, onHoverEnd, onMove, ignoreCheck, isNormalizer, onGestureStart, onGestureEnd, onWheel, onEnable, onDisable, onClick, scrollSpeed, capture, allowClicks, lockAxis, onLockAxis} = vars;
this.target = target = _getTarget(target) || _docEl;
this.vars = vars;
ignore && (ignore = gsap.utils.toArray(ignore));
tolerance = tolerance || 1e-9;
dragMinimum = dragMinimum || 0;
wheelSpeed = wheelSpeed || 1;
scrollSpeed = scrollSpeed || 1;
type = type || "wheel,touch,pointer";
debounce = debounce !== false;
lineHeight || (lineHeight = parseFloat(_win.getComputedStyle(_body).lineHeight) || 22); // note: browser may report "normal", so default to 22.
let id, onStopDelayedCall, dragged, moved, wheeled, locked, axis,
self = this,
prevDeltaX = 0,
prevDeltaY = 0,
passive = vars.passive || (!preventDefault && vars.passive !== false),
scrollFuncX = _getScrollFunc(target, _horizontal),
scrollFuncY = _getScrollFunc(target, _vertical),
scrollX = scrollFuncX(),
scrollY = scrollFuncY(),
limitToTouch = ~type.indexOf("touch") && !~type.indexOf("pointer") && _eventTypes[0] === "pointerdown", // for devices that accommodate mouse events and touch events, we need to distinguish.
isViewport = _isViewport(target),
ownerDoc = target.ownerDocument || _doc,
deltaX = [0, 0, 0], // wheel, scroll, pointer/touch
deltaY = [0, 0, 0],
onClickTime = 0,
clickCapture = () => onClickTime = _getTime(),
_ignoreCheck = (e, isPointerOrTouch) => (self.event = e) && (ignore && _isWithin(e.target, ignore)) || (isPointerOrTouch && limitToTouch && e.pointerType !== "touch") || (ignoreCheck && ignoreCheck(e, isPointerOrTouch)),
onStopFunc = () => {
self._vx.reset();
self._vy.reset();
onStopDelayedCall.pause();
onStop && onStop(self);
},
update = () => {
let dx = self.deltaX = _getAbsoluteMax(deltaX),
dy = self.deltaY = _getAbsoluteMax(deltaY),
changedX = Math.abs(dx) >= tolerance,
changedY = Math.abs(dy) >= tolerance;
onChange && (changedX || changedY) && onChange(self, dx, dy, deltaX, deltaY); // in ScrollTrigger.normalizeScroll(), we need to know if it was touch/pointer so we need access to the deltaX/deltaY Arrays before we clear them out.
if (changedX) {
onRight && self.deltaX > 0 && onRight(self);
onLeft && self.deltaX < 0 && onLeft(self);
onChangeX && onChangeX(self);
onToggleX && ((self.deltaX < 0) !== (prevDeltaX < 0)) && onToggleX(self);
prevDeltaX = self.deltaX;
deltaX[0] = deltaX[1] = deltaX[2] = 0
}
if (changedY) {
onDown && self.deltaY > 0 && onDown(self);
onUp && self.deltaY < 0 && onUp(self);
onChangeY && onChangeY(self);
onToggleY && ((self.deltaY < 0) !== (prevDeltaY < 0)) && onToggleY(self);
prevDeltaY = self.deltaY;
deltaY[0] = deltaY[1] = deltaY[2] = 0
}
if (moved || dragged) {
onMove && onMove(self);
if (dragged) {
onDragStart && dragged === 1 && onDragStart(self);
onDrag && onDrag(self);
dragged = 0;
}
moved = false;
}
locked && !(locked = false) && onLockAxis && onLockAxis(self);
if (wheeled) {
onWheel(self);
wheeled = false;
}
id = 0;
},
onDelta = (x, y, index) => {
deltaX[index] += x;
deltaY[index] += y;
self._vx.update(x);
self._vy.update(y);
debounce ? id || (id = requestAnimationFrame(update)) : update();
},
onTouchOrPointerDelta = (x, y) => {
if (lockAxis && !axis) {
self.axis = axis = Math.abs(x) > Math.abs(y) ? "x" : "y";
locked = true;
}
if (axis !== "y") {
deltaX[2] += x;
self._vx.update(x, true); // update the velocity as frequently as possible instead of in the debounced function so that very quick touch-scrolls (flicks) feel natural. If it's the mouse/touch/pointer, force it so that we get snappy/accurate momentum scroll.
}
if (axis !== "x") {
deltaY[2] += y;
self._vy.update(y, true);
}
debounce ? id || (id = requestAnimationFrame(update)) : update();
},
_onDrag = e => {
if (_ignoreCheck(e, 1)) {return;}
e = _getEvent(e, preventDefault);
let x = e.clientX,
y = e.clientY,
dx = x - self.x,
dy = y - self.y,
isDragging = self.isDragging;
self.x = x;
self.y = y;
if (isDragging || ((dx || dy) && (Math.abs(self.startX - x) >= dragMinimum || Math.abs(self.startY - y) >= dragMinimum))) {
dragged = isDragging ? 2 : 1; // dragged: 0 = not dragging, 1 = first drag, 2 = normal drag
isDragging || (self.isDragging = true);
onTouchOrPointerDelta(dx, dy);
}
},
_onPress = self.onPress = e => {
if (_ignoreCheck(e, 1) || (e && e.button)) {return;}
self.axis = axis = null;
onStopDelayedCall.pause();
self.isPressed = true;
e = _getEvent(e); // note: may need to preventDefault(?) Won't side-scroll on iOS Safari if we do, though.
prevDeltaX = prevDeltaY = 0;
self.startX = self.x = e.clientX;
self.startY = self.y = e.clientY;
self._vx.reset(); // otherwise the t2 may be stale if the user touches and flicks super fast and releases in less than 2 requestAnimationFrame ticks, causing velocity to be 0.
self._vy.reset();
_addListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, passive, true);
self.deltaX = self.deltaY = 0;
onPress && onPress(self);
},
_onRelease = self.onRelease = e => {
if (_ignoreCheck(e, 1)) {return;}
_removeListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, true);
let isTrackingDrag = !isNaN(self.y - self.startY),
wasDragging = self.isDragging,
isDragNotClick = wasDragging && (Math.abs(self.x - self.startX) > 3 || Math.abs(self.y - self.startY) > 3), // some touch devices need some wiggle room in terms of sensing clicks - the finger may move a few pixels.
eventData = _getEvent(e);
if (!isDragNotClick && isTrackingDrag) {
self._vx.reset();
self._vy.reset();
//if (preventDefault && allowClicks && self.isPressed) { // check isPressed because in a rare edge case, the inputObserver in ScrollTrigger may stopPropagation() on the press/drag, so the onRelease may get fired without the onPress/onDrag ever getting called, thus it could trigger a click to occur on a link after scroll-dragging it.
if (preventDefault && allowClicks) {
gsap.delayedCall(0.08, () => { // some browsers (like Firefox) won't trust script-generated clicks, so if the user tries to click on a video to play it, for example, it simply won't work. Since a regular "click" event will most likely be generated anyway (one that has its isTrusted flag set to true), we must slightly delay our script-generated click so that the "real"/trusted one is prioritized. Remember, when there are duplicate events in quick succession, we suppress all but the first one. Some browsers don't even trigger the "real" one at all, so our synthetic one is a safety valve that ensures that no matter what, a click event does get dispatched.
if (_getTime() - onClickTime > 300 && !e.defaultPrevented) {
if (e.target.click) { //some browsers (like mobile Safari) don't properly trigger the click event
e.target.click();
} else if (ownerDoc.createEvent) {
let syntheticEvent = ownerDoc.createEvent("MouseEvents");
syntheticEvent.initMouseEvent("click", true, true, _win, 1, eventData.screenX, eventData.screenY, eventData.clientX, eventData.clientY, false, false, false, false, 0, null);
e.target.dispatchEvent(syntheticEvent);
}
}
});
}
}
self.isDragging = self.isGesturing = self.isPressed = false;
onStop && wasDragging && !isNormalizer && onStopDelayedCall.restart(true);
dragged && update(); // in case debouncing, we don't want onDrag to fire AFTER onDragEnd().
onDragEnd && wasDragging && onDragEnd(self);
onRelease && onRelease(self, isDragNotClick);
},
_onGestureStart = e => e.touches && e.touches.length > 1 && (self.isGesturing = true) && onGestureStart(e, self.isDragging),
_onGestureEnd = () => (self.isGesturing = false) || onGestureEnd(self),
onScroll = e => {
if (_ignoreCheck(e)) {return;}
let x = scrollFuncX(),
y = scrollFuncY();
onDelta((x - scrollX) * scrollSpeed, (y - scrollY) * scrollSpeed, 1);
scrollX = x;
scrollY = y;
onStop && onStopDelayedCall.restart(true);
},
_onWheel = e => {
if (_ignoreCheck(e)) {return;}
e = _getEvent(e, preventDefault);
onWheel && (wheeled = true);
let multiplier = (e.deltaMode === 1 ? lineHeight : e.deltaMode === 2 ? _win.innerHeight : 1) * wheelSpeed;
onDelta(e.deltaX * multiplier, e.deltaY * multiplier, 0);
onStop && !isNormalizer && onStopDelayedCall.restart(true);
},
_onMove = e => {
if (_ignoreCheck(e)) {return;}
let x = e.clientX,
y = e.clientY,
dx = x - self.x,
dy = y - self.y;
self.x = x;
self.y = y;
moved = true;
onStop && onStopDelayedCall.restart(true);
(dx || dy) && onTouchOrPointerDelta(dx, dy);
},
_onHover = e => {self.event = e; onHover(self);},
_onHoverEnd = e => {self.event = e; onHoverEnd(self);},
_onClick = e => _ignoreCheck(e) || (_getEvent(e, preventDefault) && onClick(self));
onStopDelayedCall = self._dc = gsap.delayedCall(onStopDelay || 0.25, onStopFunc).pause();
self.deltaX = self.deltaY = 0;
self._vx = _getVelocityProp(0, 50, true);
self._vy = _getVelocityProp(0, 50, true);
self.scrollX = scrollFuncX;
self.scrollY = scrollFuncY;
self.isDragging = self.isGesturing = self.isPressed = false;
_context(this);
self.enable = e => {
if (!self.isEnabled) {
_addListener(isViewport ? ownerDoc : target, "scroll", _onScroll);
type.indexOf("scroll") >= 0 && _addListener(isViewport ? ownerDoc : target, "scroll", onScroll, passive, capture);
type.indexOf("wheel") >= 0 && _addListener(target, "wheel", _onWheel, passive, capture);
if ((type.indexOf("touch") >= 0 && _isTouch) || type.indexOf("pointer") >= 0) {
_addListener(target, _eventTypes[0], _onPress, passive, capture);
_addListener(ownerDoc, _eventTypes[2], _onRelease);
_addListener(ownerDoc, _eventTypes[3], _onRelease);
allowClicks && _addListener(target, "click", clickCapture, true, true);
onClick && _addListener(target, "click", _onClick);
onGestureStart && _addListener(ownerDoc, "gesturestart", _onGestureStart);
onGestureEnd && _addListener(ownerDoc, "gestureend", _onGestureEnd);
onHover && _addListener(target, _pointerType + "enter", _onHover);
onHoverEnd && _addListener(target, _pointerType + "leave", _onHoverEnd);
onMove && _addListener(target, _pointerType + "move", _onMove);
}
self.isEnabled = true;
self.isDragging = self.isGesturing = self.isPressed = moved = dragged = false;
self._vx.reset();
self._vy.reset();
scrollX = scrollFuncX();
scrollY = scrollFuncY();
e && e.type && _onPress(e);
onEnable && onEnable(self);
}
return self;
};
self.disable = () => {
if (self.isEnabled) {
// only remove the _onScroll listener if there aren't any others that rely on the functionality.
_observers.filter(o => o !== self && _isViewport(o.target)).length || _removeListener(isViewport ? ownerDoc : target, "scroll", _onScroll);
if (self.isPressed) {
self._vx.reset();
self._vy.reset();
_removeListener(isNormalizer ? target : ownerDoc, _eventTypes[1], _onDrag, true);
}
_removeListener(isViewport ? ownerDoc : target, "scroll", onScroll, capture);
_removeListener(target, "wheel", _onWheel, capture);
_removeListener(target, _eventTypes[0], _onPress, capture);
_removeListener(ownerDoc, _eventTypes[2], _onRelease);
_removeListener(ownerDoc, _eventTypes[3], _onRelease);
_removeListener(target, "click", clickCapture, true);
_removeListener(target, "click", _onClick);
_removeListener(ownerDoc, "gesturestart", _onGestureStart);
_removeListener(ownerDoc, "gestureend", _onGestureEnd);
_removeListener(target, _pointerType + "enter", _onHover);
_removeListener(target, _pointerType + "leave", _onHoverEnd);
_removeListener(target, _pointerType + "move", _onMove);
self.isEnabled = self.isPressed = self.isDragging = false;
onDisable && onDisable(self);
}
};
self.kill = self.revert = () => {
self.disable();
let i = _observers.indexOf(self);
i >= 0 && _observers.splice(i, 1);
_normalizer === self && (_normalizer = 0);
}
_observers.push(self);
isNormalizer && _isViewport(target) && (_normalizer = self);
self.enable(event);
}
get velocityX() {
return this._vx.getVelocity();
}
get velocityY() {
return this._vy.getVelocity();
}
}
Observer.version = "3.13.0";
Observer.create = vars => new Observer(vars);
Observer.register = _initCore;
Observer.getAll = () => _observers.slice();
Observer.getById = id => _observers.filter(o => o.vars.id === id)[0];
_getGSAP() && gsap.registerPlugin(Observer);
export { Observer as default, _isViewport, _scrollers, _getScrollFunc, _getProxyProp, _proxies, _getVelocityProp, _vertical, _horizontal, _getTarget };

View File

@@ -0,0 +1,144 @@
/*!
* Physics2DPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _getUnit, _getStyleSaver, _reverting,
_DEG2RAD = Math.PI / 180,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_round = value => Math.round(value * 10000) / 10000,
_bonusValidated = 1, //<name>Physics2DPlugin</name>
_initCore = core => {
gsap = core || _getGSAP();
if (!_coreInitted) {
_getUnit = gsap.utils.getUnit;
_getStyleSaver = gsap.core.getStyleSaver;
_reverting = gsap.core.reverting || function() {};
_coreInitted = 1;
}
};
class PhysicsProp {
constructor(target, p, velocity, acceleration, stepsPerTimeUnit) {
let cache = target._gsap,
curVal = cache.get(target, p);
this.p = p;
this.set = cache.set(target, p); //setter
this.s = this.val = parseFloat(curVal);
this.u = _getUnit(curVal) || 0;
this.vel = velocity || 0;
this.v = this.vel / stepsPerTimeUnit;
if (acceleration || acceleration === 0) {
this.acc = acceleration;
this.a = this.acc / (stepsPerTimeUnit * stepsPerTimeUnit);
} else {
this.acc = this.a = 0;
}
}
}
export const Physics2DPlugin = {
version:"3.13.0",
name:"physics2D",
register: _initCore,
init(target, value, tween) {
_coreInitted || _initCore();
let data = this,
angle = +value.angle || 0,
velocity = +value.velocity || 0,
acceleration = +value.acceleration || 0,
xProp = value.xProp || "x",
yProp = value.yProp || "y",
aAngle = (value.accelerationAngle || value.accelerationAngle === 0) ? +value.accelerationAngle : angle;
data.styles = _getStyleSaver && _getStyleSaver(target, value.xProp && value.xProp !== "x" ? value.xProp + "," + value.yProp : "transform");
data.target = target;
data.tween = tween;
data.step = 0;
data.sps = 30; //steps per second
if (value.gravity) {
acceleration = +value.gravity;
aAngle = 90;
}
angle *= _DEG2RAD;
aAngle *= _DEG2RAD;
data.fr = 1 - (+value.friction || 0);
data._props.push(xProp, yProp);
data.xp = new PhysicsProp(target, xProp, Math.cos(angle) * velocity, Math.cos(aAngle) * acceleration, data.sps);
data.yp = new PhysicsProp(target, yProp, Math.sin(angle) * velocity, Math.sin(aAngle) * acceleration, data.sps);
data.skipX = data.skipY = 0;
},
render(ratio, data) {
let { xp, yp, tween, target, step, sps, fr, skipX, skipY } = data,
time = tween._from ? tween._dur - tween._time : tween._time,
x, y, tt, steps, remainder, i;
if (tween._time || !_reverting()) {
if (fr === 1) {
tt = time * time * 0.5;
x = xp.s + xp.vel * time + xp.acc * tt;
y = yp.s + yp.vel * time + yp.acc * tt;
} else {
time *= sps;
steps = i = (time | 0) - step;
/*
Note: rounding errors build up if we walk the calculations backward which we used to do like this to maximize performance:
i = -i;
while (i--) {
xp.val -= xp.v;
yp.val -= yp.v;
xp.v /= fr;
yp.v /= fr;
xp.v -= xp.a;
yp.v -= yp.a;
}
but now for the sake of accuracy (to ensure rewinding always goes back to EXACTLY the same spot), we force the calculations to go forward every time. So if the tween is going backward, we just start from the beginning and iterate. This is only necessary with friction.
*/
if (i < 0) {
xp.v = xp.vel / sps;
yp.v = yp.vel / sps;
xp.val = xp.s;
yp.val = yp.s;
data.step = 0;
steps = i = time | 0;
}
remainder = (time % 1) * fr;
while (i--) {
xp.v += xp.a;
yp.v += yp.a;
xp.v *= fr;
yp.v *= fr;
xp.val += xp.v;
yp.val += yp.v;
}
x = xp.val + xp.v * remainder;
y = yp.val + yp.v * remainder;
data.step += steps;
}
skipX || xp.set(target, xp.p, _round(x) + xp.u);
skipY || yp.set(target, yp.p, _round(y) + yp.u);
} else {
data.styles.revert();
}
},
kill(property) {
if (this.xp.p === property) {
this.skipX = 1;
}
if (this.yp.p === property) {
this.skipY = 1;
}
}
};
_getGSAP() && gsap.registerPlugin(Physics2DPlugin);
export { Physics2DPlugin as default };

View File

@@ -0,0 +1,141 @@
/*!
* PhysicsPropsPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _getUnit, _getStyleSaver, _reverting,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_round = value => Math.round(value * 10000) / 10000,
_bonusValidated = 1, //<name>PhysicsPropsPlugin</name>
_initCore = core => {
gsap = core || _getGSAP();
if (!_coreInitted) {
_getUnit = gsap.utils.getUnit;
_getStyleSaver = gsap.core.getStyleSaver;
_reverting = gsap.core.reverting || function() {};
_coreInitted = 1;
}
};
class PhysicsProp {
constructor(target, p, velocity, acceleration, friction, stepsPerTimeUnit) {
let cache = target._gsap,
curVal = cache.get(target, p);
this.p = p;
this.set = cache.set(target, p); //setter
this.s = this.val = parseFloat(curVal);
this.u = _getUnit(curVal) || 0;
this.vel = velocity || 0;
this.v = this.vel / stepsPerTimeUnit;
if (acceleration || acceleration === 0) {
this.acc = acceleration;
this.a = this.acc / (stepsPerTimeUnit * stepsPerTimeUnit);
} else {
this.acc = this.a = 0;
}
this.fr = 1 - (friction || 0) ;
}
}
export const PhysicsPropsPlugin = {
version:"3.13.0",
name:"physicsProps",
register: _initCore,
init(target, value, tween) {
_coreInitted || _initCore();
let data = this,
p;
data.styles = _getStyleSaver && _getStyleSaver(target);
data.target = target;
data.tween = tween;
data.step = 0;
data.sps = 30; //steps per second
data.vProps = [];
for (p in value) {
let { velocity, acceleration, friction } = value[p];
if (velocity || acceleration) {
data.vProps.push(new PhysicsProp(target, p, velocity, acceleration, friction, data.sps));
data._props.push(p);
_getStyleSaver && data.styles.save(p);
friction && (data.hasFr = 1);
}
}
},
render(ratio, data) {
let { vProps, tween, target, step, hasFr, sps } = data,
i = vProps.length,
time = tween._from ? tween._dur - tween._time : tween._time,
curProp, steps, remainder, j, tt;
if (tween._time || !_reverting()) {
if (hasFr) {
time *= sps;
steps = (time | 0) - step;
/*
Note: rounding errors build up if we walk the calculations backward which we used to do like this to maximize performance:
while (i--) {
curProp = vProps[i];
j = -steps;
while (j--) {
curProp.val -= curProp.v;
curProp.v /= curProp.fr;
curProp.v -= curProp.a;
}
curProp.set(target, curProp.p, _round(curProp.val + (curProp.v * remainder * curProp.fr)) + curProp.u);
}
but now for the sake of accuracy (to ensure rewinding always goes back to EXACTLY the same spot), we force the calculations to go forward every time. So if the tween is going backward, we just start from the beginning and iterate. This is only necessary with friction.
*/
if (steps < 0) {
while (i--) {
curProp = vProps[i];
curProp.v = curProp.vel / sps;
curProp.val = curProp.s;
}
i = vProps.length;
data.step = step = 0;
steps = time | 0;
}
remainder = time % 1;
while (i--) {
curProp = vProps[i];
j = steps;
while (j--) {
curProp.v += curProp.a;
curProp.v *= curProp.fr;
curProp.val += curProp.v;
}
curProp.set(target, curProp.p, _round(curProp.val + (curProp.v * remainder * curProp.fr)) + curProp.u);
}
data.step += steps;
} else {
tt = time * time * 0.5;
while (i--) {
curProp = vProps[i];
curProp.set(target, curProp.p, _round(curProp.s + curProp.vel * time + curProp.acc * tt) + curProp.u);
}
}
} else {
data.styles.revert();
}
},
kill(property) {
let vProps = this.vProps,
i = vProps.length;
while (i--) {
vProps[i].p === property && vProps.splice(i, 1);
}
}
};
_getGSAP() && gsap.registerPlugin(PhysicsPropsPlugin);
export { PhysicsPropsPlugin as default };

View File

@@ -0,0 +1,342 @@
/*!
* PixiPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _splitColor, _coreInitted, _PIXI, PropTween, _getSetter, _isV4, _isV8Plus,
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_isFunction = value => typeof(value) === "function",
_warn = message => console.warn(message),
_idMatrix = [1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0],
_lumR = 0.212671,
_lumG = 0.715160,
_lumB = 0.072169,
_filterClass = name => _isFunction(_PIXI[name]) ? _PIXI[name] : _PIXI.filters[name], // in PIXI 7.1, filters moved from PIXI.filters to just PIXI
_applyMatrix = (m, m2) => {
let temp = [],
i = 0,
z = 0,
y, x;
for (y = 0; y < 4; y++) {
for (x = 0; x < 5; x++) {
z = (x === 4) ? m[i + 4] : 0;
temp[i + x] = m[i] * m2[x] + m[i+1] * m2[x + 5] + m[i+2] * m2[x + 10] + m[i+3] * m2[x + 15] + z;
}
i += 5;
}
return temp;
},
_setSaturation = (m, n) => {
let inv = 1 - n,
r = inv * _lumR,
g = inv * _lumG,
b = inv * _lumB;
return _applyMatrix([r + n, g, b, 0, 0, r, g + n, b, 0, 0, r, g, b + n, 0, 0, 0, 0, 0, 1, 0], m);
},
_colorize = (m, color, amount) => {
let c = _splitColor(color),
r = c[0] / 255,
g = c[1] / 255,
b = c[2] / 255,
inv = 1 - amount;
return _applyMatrix([inv + amount * r * _lumR, amount * r * _lumG, amount * r * _lumB, 0, 0, amount * g * _lumR, inv + amount * g * _lumG, amount * g * _lumB, 0, 0, amount * b * _lumR, amount * b * _lumG, inv + amount * b * _lumB, 0, 0, 0, 0, 0, 1, 0], m);
},
_setHue = (m, n) => {
n *= Math.PI / 180;
let c = Math.cos(n),
s = Math.sin(n);
return _applyMatrix([(_lumR + (c * (1 - _lumR))) + (s * (-_lumR)), (_lumG + (c * (-_lumG))) + (s * (-_lumG)), (_lumB + (c * (-_lumB))) + (s * (1 - _lumB)), 0, 0, (_lumR + (c * (-_lumR))) + (s * 0.143), (_lumG + (c * (1 - _lumG))) + (s * 0.14), (_lumB + (c * (-_lumB))) + (s * -0.283), 0, 0, (_lumR + (c * (-_lumR))) + (s * (-(1 - _lumR))), (_lumG + (c * (-_lumG))) + (s * _lumG), (_lumB + (c * (1 - _lumB))) + (s * _lumB), 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], m);
},
_setContrast = (m, n) => _applyMatrix([n,0,0,0,0.5 * (1 - n), 0,n,0,0,0.5 * (1 - n), 0,0,n,0,0.5 * (1 - n), 0,0,0,1,0], m),
_getFilter = (target, type) => {
let filterClass = _filterClass(type),
filters = target.filters || [],
i = filters.length,
filter;
filterClass || _warn(type + " not found. PixiPlugin.registerPIXI(PIXI)");
while (--i > -1) {
if (filters[i] instanceof filterClass) {
return filters[i];
}
}
filter = new filterClass();
if (type === "BlurFilter") {
if (_isV8Plus) {
filter.strength = 0;
} else {
filter.blur = 0;
}
}
target.filters = [...filters, filter];
return filter;
},
_addColorMatrixFilterCacheTween = (p, plugin, cache, vars) => { //we cache the ColorMatrixFilter components in a _gsColorMatrixFilter object attached to the target object so that it's easy to grab the current value at any time.
plugin.add(cache, p, cache[p], vars[p]);
plugin._props.push(p);
},
_applyBrightnessToMatrix = (brightness, matrix) => {
let filterClass = _filterClass("ColorMatrixFilter"),
temp = new filterClass();
temp.matrix = matrix;
temp.brightness(brightness, true);
return temp.matrix;
},
_copy = obj => {
let copy = {},
p;
for (p in obj) {
copy[p] = obj[p];
}
return copy;
},
_CMFdefaults = {contrast:1, saturation:1, colorizeAmount:0, colorize:"rgb(255,255,255)", hue:0, brightness:1},
_parseColorMatrixFilter = (target, v, pg) => {
let filter = _getFilter(target, "ColorMatrixFilter"),
cache = target._gsColorMatrixFilter = target._gsColorMatrixFilter || _copy(_CMFdefaults),
combine = v.combineCMF && !("colorMatrixFilter" in v && !v.colorMatrixFilter),
i, matrix, startMatrix;
startMatrix = filter.matrix;
if (v.resolution) {
filter.resolution = v.resolution;
}
if (v.matrix && v.matrix.length === startMatrix.length) {
matrix = v.matrix;
if (cache.contrast !== 1) {
_addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults);
}
if (cache.hue) {
_addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults);
}
if (cache.brightness !== 1) {
_addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults);
}
if (cache.colorizeAmount) {
_addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults);
_addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults);
}
if (cache.saturation !== 1) {
_addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults);
}
} else {
matrix = _idMatrix.slice();
if (v.contrast != null) {
matrix = _setContrast(matrix, +v.contrast);
_addColorMatrixFilterCacheTween("contrast", pg, cache, v);
} else if (cache.contrast !== 1) {
if (combine) {
matrix = _setContrast(matrix, cache.contrast);
} else {
_addColorMatrixFilterCacheTween("contrast", pg, cache, _CMFdefaults);
}
}
if (v.hue != null) {
matrix = _setHue(matrix, +v.hue);
_addColorMatrixFilterCacheTween("hue", pg, cache, v);
} else if (cache.hue) {
if (combine) {
matrix = _setHue(matrix, cache.hue);
} else {
_addColorMatrixFilterCacheTween("hue", pg, cache, _CMFdefaults);
}
}
if (v.brightness != null) {
matrix = _applyBrightnessToMatrix(+v.brightness, matrix);
_addColorMatrixFilterCacheTween("brightness", pg, cache, v);
} else if (cache.brightness !== 1) {
if (combine) {
matrix = _applyBrightnessToMatrix(cache.brightness, matrix);
} else {
_addColorMatrixFilterCacheTween("brightness", pg, cache, _CMFdefaults);
}
}
if (v.colorize != null) {
v.colorizeAmount = ("colorizeAmount" in v) ? +v.colorizeAmount : 1;
matrix = _colorize(matrix, v.colorize, v.colorizeAmount);
_addColorMatrixFilterCacheTween("colorize", pg, cache, v);
_addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, v);
} else if (cache.colorizeAmount) {
if (combine) {
matrix = _colorize(matrix, cache.colorize, cache.colorizeAmount);
} else {
_addColorMatrixFilterCacheTween("colorize", pg, cache, _CMFdefaults);
_addColorMatrixFilterCacheTween("colorizeAmount", pg, cache, _CMFdefaults);
}
}
if (v.saturation != null) {
matrix = _setSaturation(matrix, +v.saturation);
_addColorMatrixFilterCacheTween("saturation", pg, cache, v);
} else if (cache.saturation !== 1) {
if (combine) {
matrix = _setSaturation(matrix, cache.saturation);
} else {
_addColorMatrixFilterCacheTween("saturation", pg, cache, _CMFdefaults);
}
}
}
i = matrix.length;
while (--i > -1) {
if (matrix[i] !== startMatrix[i]) {
pg.add(startMatrix, i, startMatrix[i], matrix[i], "colorMatrixFilter");
}
}
pg._props.push("colorMatrixFilter");
},
_renderColor = (ratio, {t, p, color, set}) => {
set(t, p, color[0] << 16 | color[1] << 8 | color[2]);
},
_renderDirtyCache = (ratio, {g}) => {
if (_isV8Plus) {
g.fill();
g.stroke();
} else if (g) { // in order for PixiJS to actually redraw GraphicsData, we've gotta increment the "dirty" and "clearDirty" values. If we don't do this, the values will be tween properly, but not rendered.
g.dirty++;
g.clearDirty++;
}
},
_renderAutoAlpha = (ratio, data) => {
data.t.visible = !!data.t.alpha;
},
_addColorTween = (target, p, value, plugin) => {
let currentValue = target[p],
startColor = _splitColor(_isFunction(currentValue) ? target[ ((p.indexOf("set") || !_isFunction(target["get" + p.substr(3)])) ? p : "get" + p.substr(3)) ]() : currentValue),
endColor = _splitColor(value);
plugin._pt = new PropTween(plugin._pt, target, p, 0, 0, _renderColor, {t:target, p:p, color:startColor, set:_getSetter(target, p)});
plugin.add(startColor, 0, startColor[0], endColor[0]);
plugin.add(startColor, 1, startColor[1], endColor[1]);
plugin.add(startColor, 2, startColor[2], endColor[2]);
},
_colorProps = {tint:1, lineColor:1, fillColor:1, strokeColor:1},
_xyContexts = "position,scale,skew,pivot,anchor,tilePosition,tileScale".split(","),
_contexts = {x:"position", y:"position", tileX:"tilePosition", tileY:"tilePosition"},
_colorMatrixFilterProps = {colorMatrixFilter:1, saturation:1, contrast:1, hue:1, colorize:1, colorizeAmount:1, brightness:1, combineCMF:1},
_DEG2RAD = Math.PI / 180,
_isString = value => typeof(value) === "string",
_degreesToRadians = value => (_isString(value) && value.charAt(1) === "=") ? value.substr(0, 2) + (parseFloat(value.substr(2)) * _DEG2RAD) : value * _DEG2RAD,
_renderPropWithEnd = (ratio, data) => data.set(data.t, data.p, ratio === 1 ? data.e : (Math.round((data.s + data.c * ratio) * 100000) / 100000), data),
_addRotationalPropTween = (plugin, target, property, startNum, endValue, radians) => {
let cap = 360 * (radians ? _DEG2RAD : 1),
isString = _isString(endValue),
relative = (isString && endValue.charAt(1) === "=") ? +(endValue.charAt(0) + "1") : 0,
endNum = parseFloat(relative ? endValue.substr(2) : endValue) * (radians ? _DEG2RAD : 1),
change = relative ? endNum * relative : endNum - startNum,
finalValue = startNum + change,
direction, pt;
if (isString) {
direction = endValue.split("_")[1];
if (direction === "short") {
change %= cap;
if (change !== change % (cap / 2)) {
change += (change < 0) ? cap : -cap;
}
}
if (direction === "cw" && change < 0) {
change = ((change + cap * 1e10) % cap) - ~~(change / cap) * cap;
} else if (direction === "ccw" && change > 0) {
change = ((change - cap * 1e10) % cap) - ~~(change / cap) * cap;
}
}
plugin._pt = pt = new PropTween(plugin._pt, target, property, startNum, change, _renderPropWithEnd);
pt.e = finalValue;
return pt;
},
_initCore = () => {
if (!_coreInitted) {
gsap = _getGSAP();
_PIXI = _coreInitted = _PIXI || (_windowExists() && window.PIXI);
let version = (_PIXI && _PIXI.VERSION && parseFloat(_PIXI.VERSION.split(".")[0])) || 0;
_isV4 = version === 4;
_isV8Plus = version >= 8;
_splitColor = color => gsap.utils.splitColor((color + "").substr(0,2) === "0x" ? "#" + color.substr(2) : color); // some colors in PIXI are reported as "0xFF4421" instead of "#FF4421".
}
}, i, p;
//context setup...
for (i = 0; i < _xyContexts.length; i++) {
p = _xyContexts[i];
_contexts[p + "X"] = p;
_contexts[p + "Y"] = p;
}
export const PixiPlugin = {
version: "3.13.0",
name: "pixi",
register(core, Plugin, propTween) {
gsap = core;
PropTween = propTween;
_getSetter = Plugin.getSetter;
_initCore();
},
headless: true, // doesn't need window
registerPIXI(pixi) {
_PIXI = pixi;
},
init(target, values, tween, index, targets) {
_PIXI || _initCore();
if (!_PIXI) {
_warn("PIXI was not found. PixiPlugin.registerPIXI(PIXI);");
return false;
}
let context, axis, value, colorMatrix, filter, p, padding, i, data, subProp;
for (p in values) {
context = _contexts[p];
value = values[p];
if (context) {
axis = ~p.charAt(p.length-1).toLowerCase().indexOf("x") ? "x" : "y";
this.add(target[context], axis, target[context][axis], (context === "skew") ? _degreesToRadians(value) : value, 0, 0, 0, 0, 0, 1);
} else if (p === "scale" || p === "anchor" || p === "pivot" || p === "tileScale") {
this.add(target[p], "x", target[p].x, value);
this.add(target[p], "y", target[p].y, value);
} else if (p === "rotation" || p === "angle") { //PIXI expects rotation in radians, but as a convenience we let folks define it in degrees and we do the conversion.
_addRotationalPropTween(this, target, p, target[p], value, p === "rotation");
} else if (_colorMatrixFilterProps[p]) {
if (!colorMatrix) {
_parseColorMatrixFilter(target, values.colorMatrixFilter || values, this);
colorMatrix = true;
}
} else if (p === "blur" || p === "blurX" || p === "blurY" || p === "blurPadding") {
filter = _getFilter(target, "BlurFilter");
this.add(filter, p, filter[p], value);
if (values.blurPadding !== 0) {
padding = values.blurPadding || Math.max(filter[p], value) * 2;
i = target.filters.length;
while (--i > -1) {
target.filters[i].padding = Math.max(target.filters[i].padding, padding); //if we don't expand the padding on all the filters, it can look clipped.
}
}
} else if (_colorProps[p]) {
if ((p === "lineColor" || p === "fillColor" || p === "strokeColor") && target instanceof _PIXI.Graphics) {
data = "fillStyle" in target ? [target] : (target.geometry || target).graphicsData; //"geometry" was introduced in PIXI version 5
subProp = p.substr(0, p.length - 5);
_isV8Plus && subProp === "line" && (subProp = "stroke"); // in v8, lineColor became strokeColor.
this._pt = new PropTween(this._pt, target, p, 0, 0, _renderDirtyCache, {g: target.geometry || target});
i = data.length;
while (--i > -1) {
_addColorTween(_isV4 ? data[i] : data[i][subProp + "Style"], _isV4 ? p : "color", value, this);
}
} else {
_addColorTween(target, p, value, this);
}
} else if (p === "autoAlpha") {
this._pt = new PropTween(this._pt, target, "visible", 0, 0, _renderAutoAlpha);
this.add(target, "alpha", target.alpha, value);
this._props.push("alpha", "visible");
} else if (p !== "resolution") {
this.add(target, p, "get", value);
}
this._props.push(p);
}
}
};
_getGSAP() && gsap.registerPlugin(PixiPlugin);
export { PixiPlugin as default };

View File

@@ -0,0 +1,182 @@
/*!
* ScrambleTextPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { emojiSafeSplit, getText } from "./utils/strings.js";
class CharSet {
constructor(chars) {
this.chars = emojiSafeSplit(chars);
this.sets = [];
this.length = 50;
for (let i = 0; i < 20; i++) {
this.sets[i] = _scrambleText(80, this.chars); //we create 20 strings that are 80 characters long, randomly chosen and pack them into an array. We then randomly choose the scrambled text from this array in order to greatly improve efficiency compared to creating new randomized text from scratch each and every time it's needed. This is a simple lookup whereas the other technique requires looping through as many times as there are characters needed, and calling Math.random() each time through the loop, building the string, etc.
}
}
grow(newLength) { //if we encounter a tween that has more than 80 characters, we'll need to add to the character sets accordingly. Once it's cached, it'll only need to grow again if we exceed that new length. Again, this is an efficiency tactic.
for (let i = 0; i < 20; i++) {
this.sets[i] += _scrambleText(newLength - this.length, this.chars);
}
this.length = newLength;
}
}
let gsap, _coreInitted,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_bonusValidated = 1, //<name>ScrambleTextPlugin</name>
_spacesExp = /\s+/g,
_scrambleText = (length, chars) => {
let l = chars.length,
s = "";
while (--length > -1) {
s += chars[ ~~(Math.random() * l) ];
}
return s;
},
_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
_lower = _upper.toLowerCase(),
_charsLookup = {
upperCase: new CharSet(_upper),
lowerCase: new CharSet(_lower),
upperAndLowerCase: new CharSet(_upper + _lower)
},
_initCore = () => {
_coreInitted = gsap = _getGSAP();
};
export const ScrambleTextPlugin = {
version:"3.13.0",
name:"scrambleText",
register(core, Plugin, propTween) {
gsap = core;
_initCore();
},
init(target, value, tween, index, targets) {
_coreInitted || _initCore();
this.prop = ("innerHTML" in target) ? "innerHTML" : ("textContent" in target) ? "textContent" : 0; // SVG text in IE doesn't have innerHTML, but it does have textContent.
if (!this.prop) {
return;
}
this.target = target;
if (typeof(value) !== "object") {
value = {text:value};
}
let text = value.text || value.value || "",
trim = (value.trim !== false),
data = this,
delim, maxLength, charset, splitByChars;
data.delimiter = delim = value.delimiter || "";
data.original = emojiSafeSplit(getText(target).replace(_spacesExp, " ").split("&nbsp;").join(""), delim, trim);
if (text === "{original}" || text === true || text == null) {
text = data.original.join(delim);
}
data.text = emojiSafeSplit((text || "").replace(_spacesExp, " "), delim, trim);
data.hasClass = !!(value.newClass || value.oldClass);
data.newClass = value.newClass;
data.oldClass = value.oldClass;
splitByChars = (delim === "");
data.textHasEmoji = splitByChars && !!data.text.emoji;
data.charsHaveEmoji = !!value.chars && !!emojiSafeSplit(value.chars).emoji;
data.length = splitByChars ? data.original.length : data.original.join(delim).length;
data.lengthDif = (splitByChars ? data.text.length : data.text.join(delim).length) - data.length;
data.fillChar = value.fillChar || (value.chars && ~value.chars.indexOf(" ")) ? "&nbsp;" : "";
data.charSet = charset = _charsLookup[(value.chars || "upperCase")] || new CharSet(value.chars);
data.speed = 0.05 / (value.speed || 1);
data.prevScrambleTime = 0;
data.setIndex = (Math.random() * 20) | 0;
maxLength = data.length + Math.max(data.lengthDif, 0);
if (maxLength > charset.length) {
charset.grow(maxLength);
}
data.chars = charset.sets[data.setIndex];
data.revealDelay = value.revealDelay || 0;
data.tweenLength = (value.tweenLength !== false);
data.tween = tween;
data.rightToLeft = !!value.rightToLeft;
data._props.push("scrambleText", "text");
return _bonusValidated;
},
render(ratio, data) {
let { target, prop, text, delimiter, tween, prevScrambleTime, revealDelay, setIndex, chars, charSet, length, textHasEmoji, charsHaveEmoji, lengthDif, tweenLength, oldClass, newClass, rightToLeft, fillChar, speed, original, hasClass } = data,
l = text.length,
time = tween._time,
timeDif = time - prevScrambleTime,
i, i2, startText, endText, applyNew, applyOld, str, startClass, endClass, position, r;
if (revealDelay) {
if (tween._from) {
time = tween._dur - time; //invert the time for from() tweens
}
ratio = (time === 0) ? 0 : (time < revealDelay) ? 0.000001 : (time === tween._dur) ? 1 : tween._ease((time - revealDelay) / (tween._dur - revealDelay));
}
if (ratio < 0) {
ratio = 0;
} else if (ratio > 1) {
ratio = 1;
}
if (rightToLeft) {
ratio = 1 - ratio;
}
i = ~~(ratio * l + 0.5);
if (ratio) {
if (timeDif > speed || timeDif < -speed) {
data.setIndex = setIndex = (setIndex + ((Math.random() * 19) | 0)) % 20;
data.chars = charSet.sets[setIndex];
data.prevScrambleTime += timeDif;
}
endText = chars;
} else {
endText = original.join(delimiter);
}
r = tween._from ? ratio : 1 - ratio;
position = length + (tweenLength ? tween._from ? r * r * r : 1 - r * r * r : 1) * lengthDif;
if (rightToLeft) {
if (ratio === 1 && (tween._from || tween.data === "isFromStart")) { //special case for from() tweens
startText = "";
endText = original.join(delimiter);
} else {
str = text.slice(i).join(delimiter);
if (charsHaveEmoji) {
startText = emojiSafeSplit(endText).slice(0, (position - ((textHasEmoji ? emojiSafeSplit(str) : str).length) + 0.5) | 0).join("");
} else {
startText = endText.substr(0, (position - ((textHasEmoji ? emojiSafeSplit(str) : str).length) + 0.5) | 0);
}
endText = str;
}
} else {
startText = text.slice(0, i).join(delimiter);
i2 = (textHasEmoji ? emojiSafeSplit(startText) : startText).length;
if (charsHaveEmoji) {
endText = emojiSafeSplit(endText).slice(i2, (position + 0.5) | 0).join("");
} else {
endText = endText.substr(i2, (position - i2 + 0.5) | 0);
}
}
if (hasClass) {
startClass = rightToLeft ? oldClass : newClass;
endClass = rightToLeft ? newClass : oldClass;
applyNew = (startClass && i !== 0);
applyOld = (endClass && i !== l);
str = (applyNew ? "<span class='" + startClass + "'>" : "") + startText + (applyNew ? "</span>" : "") + (applyOld ? "<span class='" + endClass + "'>" : "") + delimiter + endText + (applyOld ? "</span>" : "");
} else {
str = startText + delimiter + endText;
}
target[prop] = (fillChar === "&nbsp;" && ~str.indexOf(" ")) ? str.split(" ").join("&nbsp;&nbsp;") : str;
}
};
ScrambleTextPlugin.emojiSafeSplit = emojiSafeSplit;
ScrambleTextPlugin.getText = getText;
_getGSAP() && gsap.registerPlugin(ScrambleTextPlugin);
export { ScrambleTextPlugin as default };

View File

@@ -0,0 +1,681 @@
/*!
* ScrollSmoother 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _win, _doc, _docEl, _body, _root, _toArray, _clamp, ScrollTrigger, _mainInstance, _expo, _getVelocityProp, _inputObserver, _context, _onResizeDelayedCall,
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_bonusValidated = 1, //<name>ScrollSmoother</name>
_round = value => Math.round(value * 100000) / 100000 || 0,
_maxScroll = scroller => ScrollTrigger.maxScroll(scroller || _win),
_autoDistance = (el, progress) => { // for calculating the distance (and offset) for elements with speed: "auto". Progress is for if it's "above the fold" (negative start position), so we can crop as little as possible.
let parent = el.parentNode || _docEl,
b1 = el.getBoundingClientRect(),
b2 = parent.getBoundingClientRect(),
gapTop = b2.top - b1.top,
gapBottom = b2.bottom - b1.bottom,
change = (Math.abs(gapTop) > Math.abs(gapBottom) ? gapTop : gapBottom) / (1 - progress),
offset = -change * progress,
ratio, extraChange;
if (change > 0) { // if the image starts at the BOTTOM of the container, adjust things so that it shows as much of the image as possible while still covering.
ratio = b2.height / (_win.innerHeight + b2.height);
extraChange = ratio === 0.5 ? b2.height * 2 : Math.min(b2.height, Math.abs(-change * ratio / (2 * ratio - 1))) * 2 * (progress || 1);
offset += progress ? -extraChange * progress : -extraChange / 2; // whatever the offset, we must double that in the opposite direction to compensate.
change += extraChange;
}
return {change, offset};
},
_wrap = el => {
let wrapper = _doc.querySelector(".ScrollSmoother-wrapper"); // some frameworks load multiple times, so one already exists, just use that to avoid duplicates
if (!wrapper) {
wrapper = _doc.createElement("div");
wrapper.classList.add("ScrollSmoother-wrapper");
el.parentNode.insertBefore(wrapper, el);
wrapper.appendChild(el);
}
return wrapper;
};
export class ScrollSmoother {
constructor(vars) {
_coreInitted || ScrollSmoother.register(gsap) || console.warn("Please gsap.registerPlugin(ScrollSmoother)");
vars = this.vars = vars || {};
_mainInstance && _mainInstance.kill();
_mainInstance = this;
_context(this);
let {smoothTouch, onUpdate, onStop, smooth, onFocusIn, normalizeScroll, wholePixels} = vars,
content, wrapper, height, mainST, effects, sections, intervalID, wrapperCSS, contentCSS, paused, pausedNormalizer, recordedRefreshScroll, recordedRefreshScrub, allowUpdates,
self = this,
effectsPrefix = vars.effectsPrefix || "",
scrollFunc = ScrollTrigger.getScrollFunc(_win),
smoothDuration = ScrollTrigger.isTouch === 1 ? (smoothTouch === true ? 0.8 : parseFloat(smoothTouch) || 0) : (smooth === 0 || smooth === false) ? 0 : parseFloat(smooth) || 0.8,
speed = (smoothDuration && +vars.speed) || 1,
currentY = 0,
delta = 0,
startupPhase = 1,
tracker = _getVelocityProp(0),
updateVelocity = () => tracker.update(-currentY),
scroll = {y: 0},
removeScroll = () => content.style.overflow = "visible",
isProxyScrolling,
killScrub = trigger => {
trigger.update(); // it's possible that it hasn't been synchronized with the actual scroll position yet, like if it's later in the _triggers Array. If it was already updated, it'll skip the processing anyway.
let scrub = trigger.getTween();
if (scrub) {
scrub.pause();
scrub._time = scrub._dur; // force the playhead to completion without rendering just so that when it resumes, it doesn't jump back in the .resetTo().
scrub._tTime = scrub._tDur;
}
isProxyScrolling = false;
trigger.animation.progress(trigger.progress, true);
},
render = (y, force) => {
if ((y !== currentY && !paused) || force) {
wholePixels && (y = Math.round(y));
if (smoothDuration) {
content.style.transform = "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, " + y + ", 0, 1)";
//content.style.transform = "translateY(" + y + "px)"; // NOTE: when we used matrix3d() or set will-change: transform, it performed noticeably worse on iOS counter-intuitively!
content._gsap.y = y + "px";
}
delta = y - currentY;
currentY = y;
ScrollTrigger.isUpdating || ScrollSmoother.isRefreshing || ScrollTrigger.update(); // note: if we allowed an update() when in the middle of a refresh() it could render all the other ScrollTriggers and inside the update(), _refreshing would be true thus scrubs would jump instantly, but then on the very next update they'd continue from there. Basically this allowed update() to be called on OTHER ScrollTriggers during the refresh() of the mainST which could cause some complications. See https://gsap.com/forums/topic/35536-smoothscroller-ignoremobileresize-for-non-touch-devices
}
},
scrollTop = function(value) {
if (arguments.length) {
(value < 0) && (value = 0);
scroll.y = -value; // don't use currentY because we must accurately track the delta variable (in render() method)
isProxyScrolling = true; // otherwise, if snapping was applied (or anything that attempted to SET the scroll proxy's scroll position), we'd set the scroll here which would then (on the next tick) update the content tween/ScrollTrigger which would try to smoothly animate to that new value, thus the scrub tween would impede the progress. So we use this flag to respond accordingly in the ScrollTrigger's onUpdate and effectively force the scrub to its end immediately.
paused ? (currentY = -value) : render(-value);
ScrollTrigger.isRefreshing ? mainST.update() : scrollFunc(value / speed); // during a refresh, we revert all scrollers to 0 and then put them back. We shouldn't force the window to that value too during the refresh.
return this;
}
return -currentY;
},
resizeObserver = typeof(ResizeObserver) !== "undefined" && vars.autoResize !== false && new ResizeObserver(() => {
if (!ScrollTrigger.isRefreshing) {
let max = _maxScroll(wrapper) * speed;
max < -currentY && scrollTop(max) // if the user scrolled down to the bottom, for example, and then the page resizes smaller, we should adjust things accordingly right away so that the scroll position isn't past the very end.
_onResizeDelayedCall.restart(true);
}
}),
lastFocusElement, // if the user clicks a button that scrolls the page, for example, then unfocuses the window and comes back and activates the window/tab again, it'll want to focus back on that same button element but in that case we should skip it. Only jump there when a new element gets focus, like tabbing for accessibility.
_onFocusIn = e => { // when the focus changes, make sure that element is on-screen
wrapper.scrollTop = 0;
if ((e.target.contains && e.target.contains(wrapper)) || (onFocusIn && onFocusIn(this, e) === false)) {
return;
}
ScrollTrigger.isInViewport(e.target) || (e.target === lastFocusElement) || this.scrollTo(e.target, false, "center center");
lastFocusElement = e.target;
},
_transformPosition = (position, st) => { // feed in a position (start or end scroll value) and a ScrollTrigger that's associated with a parallax effect and it'll spit back the adjusted position based on the movement of the trigger. For example, if the trigger goes at a speed of 0.5 while in the viewport, we must push the start/end values of OTHER ScrollTriggers that use that same trigger further down to compensate.
if (position < st.start) {
return position;
}
let ratio = isNaN(st.ratio) ? 1 : st.ratio,
change = st.end - st.start,
distance = position - st.start,
offset = st.offset || 0,
pins = st.pins || [],
pinOffset = pins.offset || 0,
progressOffset = (st._startClamp && st.start <= 0) || (st.pins && st.pins.offset) ? 0 : (st._endClamp && st.end === _maxScroll()) ? 1 : 0.5;
pins.forEach(p => { // remove any pinning space/distance
change -= p.distance;
if (p.nativeStart <= position) {
distance -= p.distance;
}
});
if (pinOffset) { // edge case when a clamped effect starts mid-pin; we've gotta compensate for the smaller change amount (the yOffset gets set to the st.pins.offset, so let's say it clamps such that the page starts with the element pinned 100px in, we have to set the yOffset to 100 but then subtract 100 from the change value to compensate, thus we must scale the positions accordingly based on the ratios. Like if it would normally have a change of 2000, and a pin would normally hit at 1000, but we're offsetting by 100, that means everything must scale now that we're only moving 1900px rather than 2000px.
distance *= (change - pinOffset / ratio) / change;
}
return position + (distance - offset * progressOffset) / ratio - distance;
},
adjustEffectRelatedTriggers = (st, triggers, partial) => { // if we're using this method to do only a partial Array of triggers, we should NOT reset or rebuild the pin data. For example, we tap into this from the offset() method.
partial || (st.pins.length = st.pins.offset = 0);
let pins = st.pins,
markers = st.markers,
dif, isClamped, start, end, nativeStart, nativeEnd, i, trig;
for (i = 0; i < triggers.length; i++) {
trig = triggers[i];
if (st.trigger && trig.trigger && st !== trig && (trig.trigger === st.trigger || trig.pinnedContainer === st.trigger || st.trigger.contains(trig.trigger))) {
nativeStart = trig._startNative || trig._startClamp || trig.start;
nativeEnd = trig._endNative || trig._endClamp || trig.end;
start = _transformPosition(nativeStart, st);
// note: _startClamp and _endClamp are populated with the unclamped values. For the sake of efficiency sake, we use the property both like a boolean to indicate that clamping is enabled AND the actual original unclamped value which we need in situations like if there's a data-speed="" on an element that has something like start="clamp(top bottom)". For in-viewport elements, it would clamp the values on the ScrollTrigger first, then feed it here and we'd adjust it on the clamped value which could throw things off - we need to apply the logic to the unclamped value and THEN re-apply clamping on the result.
end = (trig.pin && nativeEnd > 0) ? start + (nativeEnd - nativeStart) : _transformPosition(nativeEnd, st);
trig.setPositions(start, end, true, (trig._startClamp ? Math.max(0, start) : start) - nativeStart); // the last value (pinOffset) is to adjust the pinStart y value inside ScrollTrigger to accommodate for the y offset that gets applied by the parallax effect.
trig.markerStart && markers.push(gsap.quickSetter([trig.markerStart, trig.markerEnd], "y", "px"));
if (trig.pin && trig.end > 0 && !partial) {
dif = trig.end - trig.start;
isClamped = (st._startClamp && trig.start < 0);
if (isClamped) {
if (st.start > 0) { // the trigger element on the effect must have been pinned BEFORE its starting position, so in this edge case we must adjust the start position to be 0 and end position to get pushed further by the amount of the overlap
st.setPositions(0, st.end + (st._startNative - st.start), true); // add the overlap amount
adjustEffectRelatedTriggers(st, triggers);
return; // start over for this trigger element!
}
dif += trig.start;
pins.offset = -trig.start; // edge case when a clamped effect starts mid-pin, we've gotta compensate in the onUpdate algorithm.
}
pins.push({start: trig.start, nativeStart, end: trig.end, distance: dif, trig: trig});
st.setPositions(st.start, st.end + (isClamped ? -trig.start : dif), true);
}
}
}
},
adjustParallaxPosition = (triggers, createdAfterEffectWasApplied) => {
effects.forEach(st => adjustEffectRelatedTriggers(st, triggers, createdAfterEffectWasApplied));
},
onRefresh = () => {
_docEl = _doc.documentElement; // some frameworks like Astro may cache the <body> and replace it during routing, so we'll just re-record the _docEl and _body for safety (otherwise, the markers may not get added properly).
_body = _doc.body;
removeScroll();
requestAnimationFrame(removeScroll);
if (effects) { // adjust all the effect start/end positions including any pins!
ScrollTrigger.getAll().forEach(st => { // record the native start/end positions because we'll be messing with them and need a way to have a "source of truth"
st._startNative = st.start;
st._endNative = st.end;
});
effects.forEach(st => {
let start = st._startClamp || st.start, // if it was already clamped, we should base things on the unclamped value and then do the clamping here.
end = st.autoSpeed ? Math.min(_maxScroll(), st.end) : start + Math.abs((st.end - start) / st.ratio),
offset = end - st.end; // we split the difference so that it reaches its natural position in the MIDDLE of the viewport
start -= offset / 2;
end -= offset / 2;
if (start > end) {
let s = start;
start = end;
end = s;
}
if (st._startClamp && start < 0) {
end = st.ratio < 0 ? _maxScroll() : st.end / st.ratio;
offset = end - st.end;
start = 0;
} else if (st.ratio < 0 || (st._endClamp && end >= _maxScroll())) {
end = _maxScroll();
start = st.ratio < 0 ? 0 : st.ratio > 1 ? 0 : end - (end - st.start) / st.ratio;
offset = (end - start) * st.ratio - (st.end - st.start);
}
st.offset = offset || 0.0001; // we assign at least a tiny value because we check in the onUpdate for .offset being set in order to apply values.
st.pins.length = st.pins.offset = 0;
st.setPositions(start, end, true);
// note: another way of getting only the amount of offset traveled for a certain ratio is: distanceBetweenStartAndEnd * (1 / ratio - 1)
});
adjustParallaxPosition(ScrollTrigger.sort());
}
tracker.reset();
},
addOnRefresh = () => ScrollTrigger.addEventListener("refresh", onRefresh),
restoreEffects = () => effects && effects.forEach(st => st.vars.onRefresh(st)),
revertEffects = () => {
effects && effects.forEach(st => st.vars.onRefreshInit(st));
return restoreEffects;
},
effectValueGetter = (name, value, index, el) => {
return () => {
let v = typeof(value) === "function" ? value(index, el) : value;
v || v === 0 || (v = el.getAttribute("data-" + effectsPrefix + name) || (name === "speed" ? 1 : 0));
el.setAttribute("data-" + effectsPrefix + name, v);
let clamp = (v + "").substr(0, 6) === "clamp(";
return {clamp, value: clamp ? v.substr(6, v.length - 7) : v};
};
},
createEffect = (el, speed, lag, index, effectsPadding) => {
effectsPadding = (typeof(effectsPadding) === "function" ? effectsPadding(index, el) : effectsPadding) || 0;
let getSpeed = effectValueGetter("speed", speed, index, el),
getLag = effectValueGetter("lag", lag, index, el),
startY = gsap.getProperty(el, "y"),
cache = el._gsap,
ratio, st, autoSpeed, scrub, progressOffset, yOffset,
pins = [],
initDynamicValues = () => {
speed = getSpeed();
lag = parseFloat(getLag().value);
ratio = parseFloat(speed.value) || 1;
autoSpeed = speed.value === "auto";
progressOffset = autoSpeed || (st && st._startClamp && st.start <= 0) || pins.offset ? 0 : (st && st._endClamp && st.end === _maxScroll()) ? 1 : 0.5;
scrub && scrub.kill();
scrub = lag && gsap.to(el, {ease: _expo, overwrite: false, y: "+=0", duration: lag});
if (st) {
st.ratio = ratio;
st.autoSpeed = autoSpeed;
}
},
revert = () => {
cache.y = startY + "px";
cache.renderTransform(1);
initDynamicValues();
},
markers = [],
change = 0,
updateChange = self => {
if (autoSpeed) {
revert();
let auto = _autoDistance(el, _clamp(0, 1, -self.start / (self.end - self.start)));
change = auto.change;
yOffset = auto.offset;
} else {
yOffset = pins.offset || 0;
change = (self.end - self.start - yOffset) * (1 - ratio);
}
pins.forEach(p => change -= p.distance * (1 - ratio));
self.offset = change || 0.001;
self.vars.onUpdate(self);
scrub && scrub.progress(1);
};
initDynamicValues();
if (ratio !== 1 || autoSpeed || scrub) {
st = ScrollTrigger.create({
trigger: autoSpeed ? el.parentNode : el,
start: () => speed.clamp ? "clamp(top bottom+=" + effectsPadding + ")" : "top bottom+=" + effectsPadding,
end: () => speed.value < 0 ? "max" : speed.clamp ? "clamp(bottom top-=" + effectsPadding + ")" : "bottom top-=" + effectsPadding,
scroller: wrapper,
scrub: true,
refreshPriority: -999, // must update AFTER any other ScrollTrigger pins
onRefreshInit: revert,
onRefresh: updateChange,
onKill: self => {
let i = effects.indexOf(self);
i >= 0 && effects.splice(i, 1);
revert();
},
onUpdate: self => {
let y = startY + change * (self.progress - progressOffset),
i = pins.length,
extraY = 0,
pin, scrollY, end;
if (self.offset) { // wait until the effects are adjusted.
if (i) { // pinning must be handled in a special way because when pinned, slope changes to 1.
scrollY = -currentY; // -scroll.y;
end = self.end;
while (i--) {
pin = pins[i];
if (pin.trig.isActive || (scrollY >= pin.start && scrollY <= pin.end)) { // currently pinned so no need to set anything
if (scrub) {
pin.trig.progress += pin.trig.direction < 0 ? 0.001 : -0.001; // just to make absolutely sure that it renders (if the progress didn't change, it'll skip)
pin.trig.update(0, 0, 1);
scrub.resetTo("y", parseFloat(cache.y), -delta, true);
startupPhase && scrub.progress(1);
}
return;
}
(scrollY > pin.end) && (extraY += pin.distance);
end -= pin.distance;
}
y = startY + extraY + change * (((gsap.utils.clamp(self.start, self.end, scrollY) - self.start - extraY) / (end - self.start)) - progressOffset);
}
markers.length && !autoSpeed && markers.forEach(setter => setter(y - extraY));
y = _round(y + yOffset);
if (scrub) {
scrub.resetTo("y", y, -delta, true);
startupPhase && scrub.progress(1);
} else {
cache.y = y + "px";
cache.renderTransform(1);
}
}
}
});
updateChange(st);
gsap.core.getCache(st.trigger).stRevert = revertEffects; // if user calls ScrollSmoother.create() with effects and THEN creates a ScrollTrigger on the same trigger element, the effect would throw off the start/end positions thus we needed a way to revert things when creating a new ScrollTrigger in that scenario, so we use this stRevert property of the GSCache inside ScrollTrigger.
st.startY = startY;
st.pins = pins;
st.markers = markers;
st.ratio = ratio;
st.autoSpeed = autoSpeed;
el.style.willChange = "transform";
}
return st;
};
addOnRefresh();
ScrollTrigger.addEventListener("killAll", addOnRefresh);
gsap.delayedCall(0.5, () => startupPhase = 0);
this.scrollTop = scrollTop;
this.scrollTo = (target, smooth, position) => {
let p = gsap.utils.clamp(0, _maxScroll(), isNaN(target) ? this.offset(target, position, !!smooth && !paused) : +target);
!smooth ? scrollTop(p) : paused ? gsap.to(this, {duration: smoothDuration, scrollTop: p, overwrite: "auto", ease: _expo}) : scrollFunc(p);
};
this.offset = (target, position, ignoreSpeed) => {
target = _toArray(target)[0];
let cssText = target.style.cssText, // because if there's an effect applied, we revert(). We need to restore.
st = ScrollTrigger.create({trigger: target, start: position || "top top"}),
y;
if (effects) {
startupPhase ? ScrollTrigger.refresh() : adjustParallaxPosition([st], true); // all the effects need to go through the initial full refresh() so that all the pins and ratios and offsets are set up. That's why we do a full refresh() if it's during the startupPhase.
}
y = st.start / (ignoreSpeed ? speed : 1);
st.kill(false);
target.style.cssText = cssText;
gsap.core.getCache(target).uncache = 1;
return y;
};
function refreshHeight() {
height = content.clientHeight;
content.style.overflow = "visible"
_body.style.height = (_win.innerHeight + (height - _win.innerHeight) / speed) + "px";
return (height - _win.innerHeight);
}
this.content = function(element) {
if (arguments.length) {
let newContent = _toArray(element || "#smooth-content")[0] || console.warn("ScrollSmoother needs a valid content element.") || _body.children[0];
if (newContent !== content) {
content = newContent;
contentCSS = content.getAttribute("style") || "";
resizeObserver && resizeObserver.observe(content);
gsap.set(content, {overflow: "visible", width: "100%", boxSizing: "border-box", y: "+=0"});
smoothDuration || gsap.set(content, {clearProps: "transform"});
}
return this;
}
return content;
}
this.wrapper = function(element) {
if (arguments.length) {
wrapper = _toArray(element || "#smooth-wrapper")[0] || _wrap(content);
wrapperCSS = wrapper.getAttribute("style") || "";
refreshHeight();
gsap.set(wrapper, smoothDuration ? {overflow: "hidden", position: "fixed", height: "100%", width: "100%", top: 0, left: 0, right: 0, bottom: 0} : {overflow: "visible", position: "relative", width: "100%", height: "auto", top: "auto", bottom: "auto", left: "auto", right: "auto"});
return this;
}
return wrapper;
}
this.effects = (targets, config) => {
effects || (effects = []);
if (!targets) {
return effects.slice(0);
}
targets = _toArray(targets);
targets.forEach(target => {
let i = effects.length;
while (i--) {
effects[i].trigger === target && effects[i].kill(); // will automatically splice() it from the effects Array in the onKill
}
});
config = config || {};
let {speed, lag, effectsPadding} = config,
effectsToAdd = [],
i, st;
for (i = 0; i < targets.length; i++) {
st = createEffect(targets[i], speed, lag, i, effectsPadding);
st && effectsToAdd.push(st);
}
effects.push(...effectsToAdd);
config.refresh !== false && ScrollTrigger.refresh(); // certain effects require a refresh to work properly
return effectsToAdd;
};
this.sections = (targets, config) => {
sections || (sections = []);
if (!targets) {
return sections.slice(0);
}
let newSections = _toArray(targets).map(el => ScrollTrigger.create({
trigger: el,
start: "top 120%",
end: "bottom -20%",
onToggle: self => {
el.style.opacity = self.isActive ? "1" : "0";
el.style.pointerEvents = self.isActive ? "all" : "none";
}
})
);
config && config.add ? sections.push(...newSections) : (sections = newSections.slice(0));
return newSections;
}
this.content(vars.content);
this.wrapper(vars.wrapper);
this.render = y => render(y || y === 0 ? y : currentY);
this.getVelocity = () => tracker.getVelocity(-currentY);
ScrollTrigger.scrollerProxy(wrapper, {
scrollTop: scrollTop,
scrollHeight: () => refreshHeight() && _body.scrollHeight,
fixedMarkers: vars.fixedMarkers !== false && !!smoothDuration,
content: content,
getBoundingClientRect() {
return {top: 0, left: 0, width: _win.innerWidth, height: _win.innerHeight};
}
});
ScrollTrigger.defaults({scroller: wrapper});
let existingScrollTriggers = ScrollTrigger.getAll().filter(st => st.scroller === _win || st.scroller === wrapper);
existingScrollTriggers.forEach(st => st.revert(true, true)); // in case it's in an environment like React where child components that have ScrollTriggers instantiate BEFORE the parent that does ScrollSmoother.create(...);
mainST = ScrollTrigger.create({
animation: gsap.fromTo(scroll, {y: () => { allowUpdates = 0; return 0;}}, {
y: () => {allowUpdates = 1; return -refreshHeight();},
immediateRender: false,
ease: "none",
data: "ScrollSmoother",
duration: 100, // for added precision
onUpdate: function() {
if (allowUpdates) { // skip when it's the "from" part of the tween (setting the startAt)
let force = isProxyScrolling;
if (force) {
killScrub(mainST);
scroll.y = currentY;
}
render(scroll.y, force);
updateVelocity();
onUpdate && !paused && onUpdate(self);
}
}
}),
onRefreshInit: self => {
if (ScrollSmoother.isRefreshing) { // gets called on the onRefresh() when we do self.setPositions(...) in which case we should skip this
return;
}
ScrollSmoother.isRefreshing = true;
if (effects) {
let pins = ScrollTrigger.getAll().filter(st => !!st.pin);
effects.forEach(st => {
if (!st.vars.pinnedContainer) {
pins.forEach(pinST => {
if (pinST.pin.contains(st.trigger)) {
let v = st.vars;
v.pinnedContainer = pinST.pin;
st.vars = null; // otherwise, it'll self.kill(), triggering the onKill()
st.init(v, st.animation);
}
});
}
});
}
let scrub = self.getTween();
recordedRefreshScrub = scrub && scrub._end > scrub._dp._time; // don't use scrub.progress() < 1 because we may have called killScrub() recently in which case it'll report progress() as 1 when we were actually in the middle of a scrub. That's why we tap into the _end instead.
recordedRefreshScroll = currentY;
scroll.y = 0;
if (smoothDuration) {
ScrollTrigger.isTouch === 1 && (wrapper.style.position = "absolute"); // Safari 16 has a major bug - if you set wrapper.scrollTop to 0 (even if it's already 0), it blocks the whole page from scrolling page non-scrollable! See https://bugs.webkit.org/show_bug.cgi?id=245300 and https://codepen.io/GreenSock/pen/YzLZVOz. Originally we set pointer-events: none on the wrapper temporarily, and set it back to all after setting scrollTop to 0, but that could cause mouseenter/mouseleave/etc. events to fire too, so we opted to set the position to absolute and then back to fixed after setting scrollTop.
wrapper.scrollTop = 0; // set wrapper.scrollTop to 0 because in some very rare situations, the browser will auto-set that, like if there's a hash in the link or changing focus to an off-screen input
ScrollTrigger.isTouch === 1 && (wrapper.style.position = "fixed");
}
},
onRefresh: self => {
self.animation.invalidate(); // because pinnedContainers may have been found in ScrollTrigger's _refreshAll() that extend the height. Without this, it may prevent the user from being able to scroll all the way down.
self.setPositions(self.start, refreshHeight() / speed);
recordedRefreshScrub || killScrub(self);
scroll.y = -scrollFunc() * speed; // in 3.11.1, we shifted to forcing the scroll position to 0 during the entire refreshAll() in ScrollTrigger and then restored the scroll position AFTER everything had been updated, thus we should always make these adjustments AFTER a full refresh rather than putting it in the onRefresh() of the individual mainST ScrollTrigger which would fire before the scroll position was restored.
render(scroll.y);
if (!startupPhase) {
recordedRefreshScrub && (isProxyScrolling = false); // otherwise, we lose any in-progress scrub. When we set the progress(), it fires the onUpdate() which sets the scroll position immediately (jumps ahead if isProxyScrolling is true). See https://gsap.com/community/forums/topic/37515-dynamic-scrolltrigger-with-pin-inside-a-scrollsmoother/
self.animation.progress(gsap.utils.clamp(0, 1, recordedRefreshScroll / speed / -self.end));
}
if (recordedRefreshScrub) { // we need to trigger the scrub to happen again
self.progress -= 0.001;
self.update();
}
ScrollSmoother.isRefreshing = false;
},
id: "ScrollSmoother",
scroller: _win,
invalidateOnRefresh: true,
start: 0,
refreshPriority: -9999, // because all other pins, etc. should be calculated first before this figures out the height of the body. BUT this should also update FIRST so that the scroll position on the proxy is up-to-date when all the ScrollTriggers calculate their progress! -9999 is a special number that ScrollTrigger looks for to handle in this way.
end: () => refreshHeight() / speed,
onScrubComplete: () => {
tracker.reset();
onStop && onStop(this);
},
scrub: smoothDuration || true,
});
this.smooth = function(value) {
if (arguments.length) {
smoothDuration = value || 0;
speed = (smoothDuration && +vars.speed) || 1;
mainST.scrubDuration(value);
}
return mainST.getTween() ? mainST.getTween().duration() : 0;
};
mainST.getTween() && (mainST.getTween().vars.ease = vars.ease || _expo);
this.scrollTrigger = mainST;
vars.effects && this.effects(vars.effects === true ? "[data-" + effectsPrefix + "speed], [data-" + effectsPrefix + "lag]" : vars.effects, {effectsPadding: vars.effectsPadding, refresh: false});
vars.sections && this.sections(vars.sections === true ? "[data-section]" : vars.sections);
existingScrollTriggers.forEach(st => {
st.vars.scroller = wrapper;
st.revert(false, true);
st.init(st.vars, st.animation);
});
this.paused = function(value, allowNestedScroll) {
if (arguments.length) {
if (!!paused !== value) {
if (value) { // pause
mainST.getTween() && mainST.getTween().pause();
scrollFunc(-currentY / speed);
tracker.reset();
pausedNormalizer = ScrollTrigger.normalizeScroll();
pausedNormalizer && pausedNormalizer.disable(); // otherwise the normalizer would try to scroll the page on things like wheel events.
paused = ScrollTrigger.observe({
preventDefault: true,
type: "wheel,touch,scroll",
debounce: false,
allowClicks: true,
onChangeY: () => scrollTop(-currentY) // refuse to scroll
});
paused.nested = _inputObserver(_docEl, "wheel,touch,scroll", true, allowNestedScroll !== false); // allow nested scrolling, like modals
} else { // resume
paused.nested.kill();
paused.kill();
paused = 0;
pausedNormalizer && pausedNormalizer.enable();
mainST.progress = (-currentY / speed - mainST.start) / (mainST.end - mainST.start);
killScrub(mainST);
}
}
return this;
}
return !!paused;
};
this.kill = this.revert = () => {
this.paused(false);
killScrub(mainST);
mainST.kill();
let triggers = (effects || []).concat(sections || []),
i = triggers.length;
while (i--) { // make sure we go backwards because the onKill() will effects.splice(index, 1) and we don't want to skip
triggers[i].kill();
}
ScrollTrigger.scrollerProxy(wrapper);
ScrollTrigger.removeEventListener("killAll", addOnRefresh);
ScrollTrigger.removeEventListener("refresh", onRefresh);
wrapper.style.cssText = wrapperCSS;
content.style.cssText = contentCSS;
let defaults = ScrollTrigger.defaults({});
defaults && defaults.scroller === wrapper && ScrollTrigger.defaults({scroller: _win});
this.normalizer && ScrollTrigger.normalizeScroll(false);
clearInterval(intervalID);
_mainInstance = null;
resizeObserver && resizeObserver.disconnect();
_body.style.removeProperty("height");
_win.removeEventListener("focusin", _onFocusIn);
}
this.refresh = (soft, force) => mainST.refresh(soft, force);
if (normalizeScroll) {
this.normalizer = ScrollTrigger.normalizeScroll(normalizeScroll === true ? { debounce: true, content: !smoothDuration && content } : normalizeScroll);
}
ScrollTrigger.config(vars); // in case user passes in ignoreMobileResize for example
// ("overscrollBehavior" in _win.getComputedStyle(_body)) && gsap.set([_body, _docEl], {overscrollBehavior: "none"}); // this caused Safari 17+ not to scroll the entire page (bug in Safari), so let people set this in the CSS instead if they want.
("scrollBehavior" in _win.getComputedStyle(_body)) && gsap.set([_body, _docEl], {scrollBehavior: "auto"});
// if the user hits the tab key (or whatever) to shift focus to an element that's off-screen, center that element.
_win.addEventListener("focusin", _onFocusIn);
intervalID = setInterval(updateVelocity, 250);
_doc.readyState === "loading" || requestAnimationFrame(() => ScrollTrigger.refresh());
}
get progress() {
return this.scrollTrigger ? this.scrollTrigger.animation._time / 100 : 0;
}
static register(core) {
if (!_coreInitted) {
gsap = core || _getGSAP();
if (_windowExists() && window.document) {
_win = window;
_doc = document;
_docEl = _doc.documentElement;
_body = _doc.body;
}
if (gsap) {
_toArray = gsap.utils.toArray;
_clamp = gsap.utils.clamp;
_expo = gsap.parseEase("expo");
_context = gsap.core.context || function() {};
ScrollTrigger = gsap.core.globals().ScrollTrigger;
gsap.core.globals("ScrollSmoother", ScrollSmoother); // must register the global manually because in Internet Explorer, functions (classes) don't have a "name" property.
if (_body && ScrollTrigger) {
_onResizeDelayedCall = gsap.delayedCall(0.2, () => ScrollTrigger.isRefreshing || (_mainInstance && _mainInstance.refresh())).pause();
_root = [_win, _doc, _docEl, _body];
_getVelocityProp = ScrollTrigger.core._getVelocityProp;
_inputObserver = ScrollTrigger.core._inputObserver;
ScrollSmoother.refresh = ScrollTrigger.refresh;
_coreInitted = 1;
}
}
}
return _coreInitted;
}
}
ScrollSmoother.version = "3.13.0";
ScrollSmoother.create = vars => (_mainInstance && vars && _mainInstance.content() === _toArray(vars.content)[0]) ? _mainInstance : new ScrollSmoother(vars);
ScrollSmoother.get = () => _mainInstance;
_getGSAP() && gsap.registerPlugin(ScrollSmoother);
export { ScrollSmoother as default };

View File

@@ -0,0 +1,196 @@
/*!
* ScrollToPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _window, _docEl, _body, _toArray, _config, ScrollTrigger,
_windowExists = () => typeof(window) !== "undefined",
_getGSAP = () => gsap || (_windowExists() && (gsap = window.gsap) && gsap.registerPlugin && gsap),
_isString = value => typeof(value) === "string",
_isFunction = value => typeof(value) === "function",
_max = (element, axis) => {
let dim = (axis === "x") ? "Width" : "Height",
scroll = "scroll" + dim,
client = "client" + dim;
return (element === _window || element === _docEl || element === _body) ? Math.max(_docEl[scroll], _body[scroll]) - (_window["inner" + dim] || _docEl[client] || _body[client]) : element[scroll] - element["offset" + dim];
},
_buildGetter = (e, axis) => { //pass in an element and an axis ("x" or "y") and it'll return a getter function for the scroll position of that element (like scrollTop or scrollLeft, although if the element is the window, it'll use the pageXOffset/pageYOffset or the documentElement's scrollTop/scrollLeft or document.body's. Basically this streamlines things and makes a very fast getter across browsers.
let p = "scroll" + ((axis === "x") ? "Left" : "Top");
if (e === _window) {
if (e.pageXOffset != null) {
p = "page" + axis.toUpperCase() + "Offset";
} else {
e = _docEl[p] != null ? _docEl : _body;
}
}
return () => e[p];
},
_clean = (value, index, target, targets) => {
_isFunction(value) && (value = value(index, target, targets));
if (typeof(value) !== "object") {
return _isString(value) && value !== "max" && value.charAt(1) !== "=" ? {x: value, y: value} : {y: value}; //if we don't receive an object as the parameter, assume the user intends "y".
} else if (value.nodeType) {
return {y: value, x: value};
} else {
let result = {}, p;
for (p in value) {
result[p] = p !== "onAutoKill" && _isFunction(value[p]) ? value[p](index, target, targets) : value[p];
}
return result;
}
},
_getOffset = (element, container) => {
element = _toArray(element)[0];
if (!element || !element.getBoundingClientRect) {
return console.warn("scrollTo target doesn't exist. Using 0") || {x:0, y:0};
}
let rect = element.getBoundingClientRect(),
isRoot = (!container || container === _window || container === _body),
cRect = isRoot ? {top:_docEl.clientTop - (_window.pageYOffset || _docEl.scrollTop || _body.scrollTop || 0), left:_docEl.clientLeft - (_window.pageXOffset || _docEl.scrollLeft || _body.scrollLeft || 0)} : container.getBoundingClientRect(),
offsets = {x: rect.left - cRect.left, y: rect.top - cRect.top};
if (!isRoot && container) { //only add the current scroll position if it's not the window/body.
offsets.x += _buildGetter(container, "x")();
offsets.y += _buildGetter(container, "y")();
}
return offsets;
},
_parseVal = (value, target, axis, currentVal, offset) => !isNaN(value) && typeof(value) !== "object" ? parseFloat(value) - offset : (_isString(value) && value.charAt(1) === "=") ? parseFloat(value.substr(2)) * (value.charAt(0) === "-" ? -1 : 1) + currentVal - offset : (value === "max") ? _max(target, axis) - offset : Math.min(_max(target, axis), _getOffset(value, target)[axis] - offset),
_initCore = () => {
gsap = _getGSAP();
if (_windowExists() && gsap && typeof(document) !== "undefined" && document.body) {
_window = window;
_body = document.body;
_docEl = document.documentElement;
_toArray = gsap.utils.toArray;
gsap.config({autoKillThreshold:7});
_config = gsap.config();
_coreInitted = 1;
}
};
export const ScrollToPlugin = {
version: "3.13.0",
name: "scrollTo",
rawVars: 1,
register(core) {
gsap = core;
_initCore();
},
init(target, value, tween, index, targets) {
_coreInitted || _initCore();
let data = this,
snapType = gsap.getProperty(target, "scrollSnapType");
data.isWin = (target === _window);
data.target = target;
data.tween = tween;
value = _clean(value, index, target, targets);
data.vars = value;
data.autoKill = !!("autoKill" in value ? value : _config).autoKill;
data.getX = _buildGetter(target, "x");
data.getY = _buildGetter(target, "y");
data.x = data.xPrev = data.getX();
data.y = data.yPrev = data.getY();
ScrollTrigger || (ScrollTrigger = gsap.core.globals().ScrollTrigger);
gsap.getProperty(target, "scrollBehavior") === "smooth" && gsap.set(target, {scrollBehavior: "auto"});
if (snapType && snapType !== "none") { // disable scroll snapping to avoid strange behavior
data.snap = 1;
data.snapInline = target.style.scrollSnapType;
target.style.scrollSnapType = "none";
}
if (value.x != null) {
data.add(data, "x", data.x, _parseVal(value.x, target, "x", data.x, value.offsetX || 0), index, targets);
data._props.push("scrollTo_x");
} else {
data.skipX = 1;
}
if (value.y != null) {
data.add(data, "y", data.y, _parseVal(value.y, target, "y", data.y, value.offsetY || 0), index, targets);
data._props.push("scrollTo_y");
} else {
data.skipY = 1;
}
},
render(ratio, data) {
let pt = data._pt,
{ target, tween, autoKill, xPrev, yPrev, isWin, snap, snapInline } = data,
x, y, yDif, xDif, threshold;
while (pt) {
pt.r(ratio, pt.d);
pt = pt._next;
}
x = (isWin || !data.skipX) ? data.getX() : xPrev;
y = (isWin || !data.skipY) ? data.getY() : yPrev;
yDif = y - yPrev;
xDif = x - xPrev;
threshold = _config.autoKillThreshold;
if (data.x < 0) { //can't scroll to a position less than 0! Might happen if someone uses a Back.easeOut or Elastic.easeOut when scrolling back to the top of the page (for example)
data.x = 0;
}
if (data.y < 0) {
data.y = 0;
}
if (autoKill) {
//note: iOS has a bug that throws off the scroll by several pixels, so we need to check if it's within 7 pixels of the previous one that we set instead of just looking for an exact match.
if (!data.skipX && (xDif > threshold || xDif < -threshold) && x < _max(target, "x")) {
data.skipX = 1; //if the user scrolls separately, we should stop tweening!
}
if (!data.skipY && (yDif > threshold || yDif < -threshold) && y < _max(target, "y")) {
data.skipY = 1; //if the user scrolls separately, we should stop tweening!
}
if (data.skipX && data.skipY) {
tween.kill();
data.vars.onAutoKill && data.vars.onAutoKill.apply(tween, data.vars.onAutoKillParams || []);
}
}
if (isWin) {
_window.scrollTo((!data.skipX) ? data.x : x, (!data.skipY) ? data.y : y);
} else {
data.skipY || (target.scrollTop = data.y);
data.skipX || (target.scrollLeft = data.x);
}
if (snap && (ratio === 1 || ratio === 0)) {
y = target.scrollTop;
x = target.scrollLeft;
snapInline ? (target.style.scrollSnapType = snapInline) : target.style.removeProperty("scroll-snap-type");
target.scrollTop = y + 1; // bug in Safari causes the element to totally reset its scroll position when scroll-snap-type changes, so we need to set it to a slightly different value and then back again to work around this bug.
target.scrollLeft = x + 1;
target.scrollTop = y;
target.scrollLeft = x;
}
data.xPrev = data.x;
data.yPrev = data.y;
ScrollTrigger && ScrollTrigger.update();
},
kill(property) {
let both = (property === "scrollTo"),
i = this._props.indexOf(property);
if (both || property === "scrollTo_x") {
this.skipX = 1;
}
if (both || property === "scrollTo_y") {
this.skipY = 1;
}
i > -1 && this._props.splice(i, 1);
return !this._props.length;
}
};
ScrollToPlugin.max = _max;
ScrollToPlugin.getOffset = _getOffset;
ScrollToPlugin.buildGetter = _buildGetter;
ScrollToPlugin.config = vars => {
_config || _initCore() || (_config = gsap.config()); // in case the window hasn't been defined yet.
for (let p in vars) {
_config[p] = vars[p];
}
}
_getGSAP() && gsap.registerPlugin(ScrollToPlugin);
export { ScrollToPlugin as default };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,310 @@
/*!
* SplitText 3.13.0
* https://gsap.com
*
* @license Copyright 2025, GreenSock. All rights reserved. Subject to the terms at https://gsap.com/standard-license.
* @author: Jack Doyle
*/
let gsap, _fonts, _coreInitted, _initIfNecessary = () => _coreInitted || SplitText.register(window.gsap), _charSegmenter = typeof Intl !== "undefined" ? new Intl.Segmenter() : 0, _toArray = (r) => typeof r === "string" ? _toArray(document.querySelectorAll(r)) : "length" in r ? Array.from(r) : [r], _elements = (targets) => _toArray(targets).filter((e) => e instanceof HTMLElement), _emptyArray = [], _context = function() {
}, _spacesRegEx = /\s+/g, _emojiSafeRegEx = new RegExp("\\p{RI}\\p{RI}|\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?(\\u{200D}\\p{Emoji}(\\p{EMod}|\\u{FE0F}\\u{20E3}?|[\\u{E0020}-\\u{E007E}]+\\u{E007F})?)*|.", "gu"), _emptyBounds = { left: 0, top: 0, width: 0, height: 0 }, _stretchToFitSpecialChars = (collection, specialCharsRegEx) => {
if (specialCharsRegEx) {
let charsFound = new Set(collection.join("").match(specialCharsRegEx) || _emptyArray), i = collection.length, slots, word, char, combined;
if (charsFound.size) {
while (--i > -1) {
word = collection[i];
for (char of charsFound) {
if (char.startsWith(word) && char.length > word.length) {
slots = 0;
combined = word;
while (char.startsWith(combined += collection[i + ++slots]) && combined.length < char.length) {
}
if (slots && combined.length === char.length) {
collection[i] = char;
collection.splice(i + 1, slots);
break;
}
}
}
}
}
}
return collection;
}, _disallowInline = (element) => window.getComputedStyle(element).display === "inline" && (element.style.display = "inline-block"), _insertNodeBefore = (newChild, parent, existingChild) => parent.insertBefore(typeof newChild === "string" ? document.createTextNode(newChild) : newChild, existingChild), _getWrapper = (type, config, collection) => {
let className = config[type + "sClass"] || "", { tag = "div", aria = "auto", propIndex = false } = config, display = type === "line" ? "block" : "inline-block", incrementClass = className.indexOf("++") > -1, wrapper = (text) => {
let el = document.createElement(tag), i = collection.length + 1;
className && (el.className = className + (incrementClass ? " " + className + i : ""));
propIndex && el.style.setProperty("--" + type, i + "");
aria !== "none" && el.setAttribute("aria-hidden", "true");
if (tag !== "span") {
el.style.position = "relative";
el.style.display = display;
}
el.textContent = text;
collection.push(el);
return el;
};
incrementClass && (className = className.replace("++", ""));
wrapper.collection = collection;
return wrapper;
}, _getLineWrapper = (element, nodes, config, collection) => {
let lineWrapper = _getWrapper("line", config, collection), textAlign = window.getComputedStyle(element).textAlign || "left";
return (startIndex, endIndex) => {
let newLine = lineWrapper("");
newLine.style.textAlign = textAlign;
element.insertBefore(newLine, nodes[startIndex]);
for (; startIndex < endIndex; startIndex++) {
newLine.appendChild(nodes[startIndex]);
}
newLine.normalize();
};
}, _splitWordsAndCharsRecursively = (element, config, wordWrapper, charWrapper, prepForCharsOnly, deepSlice, ignore, charSplitRegEx, specialCharsRegEx, isNested) => {
var _a;
let nodes = Array.from(element.childNodes), i = 0, { wordDelimiter, reduceWhiteSpace = true, prepareText } = config, elementBounds = element.getBoundingClientRect(), lastBounds = elementBounds, isPreformatted = !reduceWhiteSpace && window.getComputedStyle(element).whiteSpace.substring(0, 3) === "pre", ignoredPreviousSibling = 0, wordsCollection = wordWrapper.collection, wordDelimIsNotSpace, wordDelimString, wordDelimSplitter, curNode, words, curWordEl, startsWithSpace, endsWithSpace, j, bounds, curWordChars, clonedNode, curSubNode, tempSubNode, curTextContent, wordText, lastWordText, k;
if (typeof wordDelimiter === "object") {
wordDelimSplitter = wordDelimiter.delimiter || wordDelimiter;
wordDelimString = wordDelimiter.replaceWith || "";
} else {
wordDelimString = wordDelimiter === "" ? "" : wordDelimiter || " ";
}
wordDelimIsNotSpace = wordDelimString !== " ";
for (; i < nodes.length; i++) {
curNode = nodes[i];
if (curNode.nodeType === 3) {
curTextContent = curNode.textContent || "";
if (reduceWhiteSpace) {
curTextContent = curTextContent.replace(_spacesRegEx, " ");
} else if (isPreformatted) {
curTextContent = curTextContent.replace(/\n/g, wordDelimString + "\n");
}
prepareText && (curTextContent = prepareText(curTextContent, element));
curNode.textContent = curTextContent;
words = wordDelimString || wordDelimSplitter ? curTextContent.split(wordDelimSplitter || wordDelimString) : curTextContent.match(charSplitRegEx) || _emptyArray;
lastWordText = words[words.length - 1];
endsWithSpace = wordDelimIsNotSpace ? lastWordText.slice(-1) === " " : !lastWordText;
lastWordText || words.pop();
lastBounds = elementBounds;
startsWithSpace = wordDelimIsNotSpace ? words[0].charAt(0) === " " : !words[0];
startsWithSpace && _insertNodeBefore(" ", element, curNode);
words[0] || words.shift();
_stretchToFitSpecialChars(words, specialCharsRegEx);
deepSlice && isNested || (curNode.textContent = "");
for (j = 1; j <= words.length; j++) {
wordText = words[j - 1];
if (!reduceWhiteSpace && isPreformatted && wordText.charAt(0) === "\n") {
(_a = curNode.previousSibling) == null ? void 0 : _a.remove();
_insertNodeBefore(document.createElement("br"), element, curNode);
wordText = wordText.slice(1);
}
if (!reduceWhiteSpace && wordText === "") {
_insertNodeBefore(wordDelimString, element, curNode);
} else if (wordText === " ") {
element.insertBefore(document.createTextNode(" "), curNode);
} else {
wordDelimIsNotSpace && wordText.charAt(0) === " " && _insertNodeBefore(" ", element, curNode);
if (ignoredPreviousSibling && j === 1 && !startsWithSpace && wordsCollection.indexOf(ignoredPreviousSibling.parentNode) > -1) {
curWordEl = wordsCollection[wordsCollection.length - 1];
curWordEl.appendChild(document.createTextNode(charWrapper ? "" : wordText));
} else {
curWordEl = wordWrapper(charWrapper ? "" : wordText);
_insertNodeBefore(curWordEl, element, curNode);
ignoredPreviousSibling && j === 1 && !startsWithSpace && curWordEl.insertBefore(ignoredPreviousSibling, curWordEl.firstChild);
}
if (charWrapper) {
curWordChars = _charSegmenter ? _stretchToFitSpecialChars([..._charSegmenter.segment(wordText)].map((s) => s.segment), specialCharsRegEx) : wordText.match(charSplitRegEx) || _emptyArray;
for (k = 0; k < curWordChars.length; k++) {
curWordEl.appendChild(curWordChars[k] === " " ? document.createTextNode(" ") : charWrapper(curWordChars[k]));
}
}
if (deepSlice && isNested) {
curTextContent = curNode.textContent = curTextContent.substring(wordText.length + 1, curTextContent.length);
bounds = curWordEl.getBoundingClientRect();
if (bounds.top > lastBounds.top && bounds.left <= lastBounds.left) {
clonedNode = element.cloneNode();
curSubNode = element.childNodes[0];
while (curSubNode && curSubNode !== curWordEl) {
tempSubNode = curSubNode;
curSubNode = curSubNode.nextSibling;
clonedNode.appendChild(tempSubNode);
}
element.parentNode.insertBefore(clonedNode, element);
prepForCharsOnly && _disallowInline(clonedNode);
}
lastBounds = bounds;
}
if (j < words.length || endsWithSpace) {
_insertNodeBefore(j >= words.length ? " " : wordDelimIsNotSpace && wordText.slice(-1) === " " ? " " + wordDelimString : wordDelimString, element, curNode);
}
}
}
element.removeChild(curNode);
ignoredPreviousSibling = 0;
} else if (curNode.nodeType === 1) {
if (ignore && ignore.indexOf(curNode) > -1) {
wordsCollection.indexOf(curNode.previousSibling) > -1 && wordsCollection[wordsCollection.length - 1].appendChild(curNode);
ignoredPreviousSibling = curNode;
} else {
_splitWordsAndCharsRecursively(curNode, config, wordWrapper, charWrapper, prepForCharsOnly, deepSlice, ignore, charSplitRegEx, specialCharsRegEx, true);
ignoredPreviousSibling = 0;
}
prepForCharsOnly && _disallowInline(curNode);
}
}
};
const _SplitText = class _SplitText {
constructor(elements, config) {
this.isSplit = false;
_initIfNecessary();
this.elements = _elements(elements);
this.chars = [];
this.words = [];
this.lines = [];
this.masks = [];
this.vars = config;
this._split = () => this.isSplit && this.split(this.vars);
let orig = [], timerId, checkWidths = () => {
let i = orig.length, o;
while (i--) {
o = orig[i];
let w = o.element.offsetWidth;
if (w !== o.width) {
o.width = w;
this._split();
return;
}
}
};
this._data = { orig, obs: typeof ResizeObserver !== "undefined" && new ResizeObserver(() => {
clearTimeout(timerId);
timerId = setTimeout(checkWidths, 200);
}) };
_context(this);
this.split(config);
}
split(config) {
this.isSplit && this.revert();
this.vars = config = config || this.vars || {};
let { type = "chars,words,lines", aria = "auto", deepSlice = true, smartWrap, onSplit, autoSplit = false, specialChars, mask } = this.vars, splitLines = type.indexOf("lines") > -1, splitCharacters = type.indexOf("chars") > -1, splitWords = type.indexOf("words") > -1, onlySplitCharacters = splitCharacters && !splitWords && !splitLines, specialCharsRegEx = specialChars && ("push" in specialChars ? new RegExp("(?:" + specialChars.join("|") + ")", "gu") : specialChars), finalCharSplitRegEx = specialCharsRegEx ? new RegExp(specialCharsRegEx.source + "|" + _emojiSafeRegEx.source, "gu") : _emojiSafeRegEx, ignore = !!config.ignore && _elements(config.ignore), { orig, animTime, obs } = this._data, onSplitResult;
if (splitCharacters || splitWords || splitLines) {
this.elements.forEach((element, index) => {
orig[index] = {
element,
html: element.innerHTML,
ariaL: element.getAttribute("aria-label"),
ariaH: element.getAttribute("aria-hidden")
};
aria === "auto" ? element.setAttribute("aria-label", (element.textContent || "").trim()) : aria === "hidden" && element.setAttribute("aria-hidden", "true");
let chars = [], words = [], lines = [], charWrapper = splitCharacters ? _getWrapper("char", config, chars) : null, wordWrapper = _getWrapper("word", config, words), i, curWord, smartWrapSpan, nextSibling;
_splitWordsAndCharsRecursively(element, config, wordWrapper, charWrapper, onlySplitCharacters, deepSlice && (splitLines || onlySplitCharacters), ignore, finalCharSplitRegEx, specialCharsRegEx, false);
if (splitLines) {
let nodes = _toArray(element.childNodes), wrapLine = _getLineWrapper(element, nodes, config, lines), curNode, toRemove = [], lineStartIndex = 0, allBounds = nodes.map((n) => n.nodeType === 1 ? n.getBoundingClientRect() : _emptyBounds), lastBounds = _emptyBounds;
for (i = 0; i < nodes.length; i++) {
curNode = nodes[i];
if (curNode.nodeType === 1) {
if (curNode.nodeName === "BR") {
toRemove.push(curNode);
wrapLine(lineStartIndex, i + 1);
lineStartIndex = i + 1;
lastBounds = allBounds[lineStartIndex];
} else {
if (i && allBounds[i].top > lastBounds.top && allBounds[i].left <= lastBounds.left) {
wrapLine(lineStartIndex, i);
lineStartIndex = i;
}
lastBounds = allBounds[i];
}
}
}
lineStartIndex < i && wrapLine(lineStartIndex, i);
toRemove.forEach((el) => {
var _a;
return (_a = el.parentNode) == null ? void 0 : _a.removeChild(el);
});
}
if (!splitWords) {
for (i = 0; i < words.length; i++) {
curWord = words[i];
if (splitCharacters || !curWord.nextSibling || curWord.nextSibling.nodeType !== 3) {
if (smartWrap && !splitLines) {
smartWrapSpan = document.createElement("span");
smartWrapSpan.style.whiteSpace = "nowrap";
while (curWord.firstChild) {
smartWrapSpan.appendChild(curWord.firstChild);
}
curWord.replaceWith(smartWrapSpan);
} else {
curWord.replaceWith(...curWord.childNodes);
}
} else {
nextSibling = curWord.nextSibling;
if (nextSibling && nextSibling.nodeType === 3) {
nextSibling.textContent = (curWord.textContent || "") + (nextSibling.textContent || "");
curWord.remove();
}
}
}
words.length = 0;
element.normalize();
}
this.lines.push(...lines);
this.words.push(...words);
this.chars.push(...chars);
});
mask && this[mask] && this.masks.push(...this[mask].map((el) => {
let maskEl = el.cloneNode();
el.replaceWith(maskEl);
maskEl.appendChild(el);
el.className && (maskEl.className = el.className.replace(/(\b\w+\b)/g, "$1-mask"));
maskEl.style.overflow = "clip";
return maskEl;
}));
}
this.isSplit = true;
_fonts && (autoSplit ? _fonts.addEventListener("loadingdone", this._split) : _fonts.status === "loading" && console.warn("SplitText called before fonts loaded"));
if ((onSplitResult = onSplit && onSplit(this)) && onSplitResult.totalTime) {
this._data.anim = animTime ? onSplitResult.totalTime(animTime) : onSplitResult;
}
splitLines && autoSplit && this.elements.forEach((element, index) => {
orig[index].width = element.offsetWidth;
obs && obs.observe(element);
});
return this;
}
revert() {
var _a, _b;
let { orig, anim, obs } = this._data;
obs && obs.disconnect();
orig.forEach(({ element, html, ariaL, ariaH }) => {
element.innerHTML = html;
ariaL ? element.setAttribute("aria-label", ariaL) : element.removeAttribute("aria-label");
ariaH ? element.setAttribute("aria-hidden", ariaH) : element.removeAttribute("aria-hidden");
});
this.chars.length = this.words.length = this.lines.length = orig.length = this.masks.length = 0;
this.isSplit = false;
_fonts == null ? void 0 : _fonts.removeEventListener("loadingdone", this._split);
if (anim) {
this._data.animTime = anim.totalTime();
anim.revert();
}
(_b = (_a = this.vars).onRevert) == null ? void 0 : _b.call(_a, this);
return this;
}
static create(elements, config) {
return new _SplitText(elements, config);
}
static register(core) {
gsap = gsap || core || window.gsap;
if (gsap) {
_toArray = gsap.utils.toArray;
_context = gsap.core.context || _context;
}
if (!_coreInitted && window.innerWidth > 0) {
_fonts = document.fonts;
_coreInitted = true;
}
}
};
_SplitText.version = "3.13.0";
let SplitText = _SplitText;
export { SplitText, SplitText as default };

View File

@@ -0,0 +1,479 @@
/*!
* SplitText 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle
*/
/* eslint-disable */
// Core Types
export type SplitTextTarget = string | NodeList | Node | Node[];
type BoundingRect = {
left: number;
top: number;
width: number;
height: number;
};
// Configuration Types
export interface WordDelimiterConfig {
delimiter: RegExp | string;
replaceWith?: string;
}
export interface SplitTextConfig {
type: string;
mask?: "lines" | "words" | "chars";
wordDelimiter?: string | RegExp | WordDelimiterConfig;
linesClass?: string;
wordsClass?: string;
charsClass?: string;
aria?: "auto" | "hidden" | "none";
tag?: string;
propIndex?: boolean;
deepSlice?: boolean;
smartWrap?: boolean;
specialChars?: string[] | RegExp;
reduceWhiteSpace?: boolean;
autoSplit?: boolean;
ignore?: SplitTextTarget;
prepareText?: PrepareTextFunction;
onSplit?: (splitText: SplitText) => void;
onRevert?: (splitText: SplitText) => void;
}
// Function Types
export type PrepareTextFunction = (text: string, element: Element) => string;
type LineWrapperFunction = (startIndex: number, endIndex: number) => void;
type ContextFunction = (obj?: SplitText) => object | void;
// Wrapper Types
type WrapFunction = {
(text: string): HTMLElement;
collection: HTMLElement[];
};
// Internal Types
interface SplitTextOriginal {
element: Element;
html: string;
ariaL: string | null; // aria-label
ariaH: string | null; // aria-hidden
width?: number;
}
let gsap: any,
_fonts: FontFaceSet | undefined,
_coreInitted: boolean, // set to true when the GSAP core is registered
_initIfNecessary = () => _coreInitted || SplitText.register((window as any).gsap),
_charSegmenter: any = typeof Intl !== "undefined" ? new (Intl as any).Segmenter() : 0, // not all older browsers support Intl.Segmenter
_toArray = (r: string | NodeList | Node | Node[]): Node[] => typeof r === "string" ? _toArray(document.querySelectorAll(r)) : "length" in r ? Array.from(r) : [r],
_elements = (targets: SplitTextTarget): HTMLElement[] => _toArray(targets).filter((e) => e instanceof HTMLElement) as HTMLElement[],
_emptyArray: string[] = [],
_context: ContextFunction = function() {},
_spacesRegEx: RegExp = /\s+/g,
_emojiSafeRegEx: RegExp = /\p{RI}\p{RI}|\p{Emoji}(\p{EMod}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?(\u{200D}\p{Emoji}(\p{EMod}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?)*|./gu, // accommodates emojis like 👨👨👦👦 which the more simple /./gu RegExp does not.
// alternate regex for emojis:
//_emojiSafeRegEx: RegExp = /\p{RI}\p{RI}|\p{Emoji}(\p{Emoji_Modifier}+|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?(\u{200D}\p{Emoji}(\p{Emoji_Modifier}+|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?)+|\p{EPres}(\p{Emoji_Modifier}+|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?|\p{Emoji}(\p{Emoji_Modifier}+|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})*|./gu,
_emptyBounds: BoundingRect = {left: 0, top: 0, width: 0, height: 0},
// merges consecutive cells that form a special character into a single cell. Like ["a", "b", "c"] and /ab/g would become ["ab", "c"]. Does not create a new Array - it modifies the existing one in place.
_stretchToFitSpecialChars = (collection: string[], specialCharsRegEx: RegExp | undefined): string[] => {
if (specialCharsRegEx) {
let charsFound: Set<string> = new Set((collection.join("").match(specialCharsRegEx) || _emptyArray)),
i: number = collection.length, slots: number, word: string, char: string, combined: string;
if (charsFound.size) {
while (--i > -1) {
word = collection[i];
for (char of charsFound) {
if (char.startsWith(word) && char.length > word.length) {
slots = 0;
combined = word;
while (char.startsWith((combined += collection[i+(++slots)])) && combined.length < char.length) {}
if (slots && combined.length === char.length) {
collection[i] = char;
collection.splice(i+1, slots);
break;
}
}
}
}
}
}
return collection;
},
_disallowInline = (element: HTMLElement): unknown => window.getComputedStyle(element).display === "inline" && (element.style.display = "inline-block"),
_insertNodeBefore = (newChild: Node | string, parent: Element, existingChild: Node): Node => parent.insertBefore(typeof newChild === "string" ? document.createTextNode(newChild) : newChild as Node, existingChild),
// create a wrapper function that will create a new element with the given type (char, word, or line) and add it to the collection
_getWrapper = (type: string, config: SplitTextConfig, collection: HTMLElement[]): WrapFunction => {
let className: string = (config as any)[type + "sClass"] || "",
{tag = "div", aria = "auto", propIndex = false} = config,
display: string = type === "line" ? "block" : "inline-block",
incrementClass: boolean = className.indexOf("++") > -1,
wrapper = ((text: string): HTMLElement => {
let el: HTMLElement = document.createElement(tag),
i: number = collection.length + 1;
className && (el.className = className + (incrementClass ? " " + className + i : ""));
propIndex && el.style.setProperty("--" + type, i + "");
aria !== "none" && el.setAttribute("aria-hidden", "true");
if (tag !== "span") {
el.style.position = "relative";
el.style.display = display;
}
el.textContent = text;
collection.push(el);
return el;
}) as WrapFunction;
incrementClass && (className = className.replace("++", ""));
wrapper.collection = collection;
return wrapper;
},
// there's some special logic for lines that we need to handle on top of the normal wrapper function
_getLineWrapper = (element: Element, nodes: Node[], config: SplitTextConfig, collection: HTMLElement[]): LineWrapperFunction => {
let lineWrapper: WrapFunction = _getWrapper("line", config, collection),
textAlign: string = window.getComputedStyle(element).textAlign || "left";
return (startIndex: number, endIndex: number): void => {
let newLine: HTMLElement = lineWrapper("");
newLine.style.textAlign = textAlign;
element.insertBefore(newLine, nodes[startIndex]);
for (; startIndex < endIndex; startIndex++) {
newLine.appendChild(nodes[startIndex]);
}
newLine.normalize();
}
},
// this is the main recursive function that splits the text into words and characters. We handle line splitting separately.
_splitWordsAndCharsRecursively = (element: Element, config: SplitTextConfig, wordWrapper: WrapFunction, charWrapper: WrapFunction | null, prepForCharsOnly: boolean, deepSlice: boolean, ignore: Element[] | false, charSplitRegEx: RegExp, specialCharsRegEx: RegExp | undefined, isNested: boolean): void => {
let nodes: Node[] = Array.from(element.childNodes),
i: number = 0,
{wordDelimiter, reduceWhiteSpace = true, prepareText } = config,
elementBounds: BoundingRect = element.getBoundingClientRect(),
lastBounds: BoundingRect = elementBounds,
isPreformatted: boolean = !reduceWhiteSpace && window.getComputedStyle(element).whiteSpace.substring(0, 3) === "pre",
ignoredPreviousSibling: Element | number = 0,
wordsCollection: HTMLElement[] = wordWrapper.collection,
wordDelimIsNotSpace: boolean,
wordDelimString: string,
wordDelimSplitter: RegExp | string | undefined,
curNode: Node, words: string[], curWordEl: Element, startsWithSpace: boolean, endsWithSpace: boolean, j: number, bounds: BoundingRect, curWordChars: string[],
clonedNode: HTMLElement, curSubNode: Node | null, tempSubNode: Node, curTextContent: string, wordText: string, lastWordText: string, k: number;
if (typeof wordDelimiter === "object") {
wordDelimSplitter = (wordDelimiter as WordDelimiterConfig).delimiter || wordDelimiter as (string | RegExp);
wordDelimString = (wordDelimiter as WordDelimiterConfig).replaceWith || "";
} else {
wordDelimString = wordDelimiter === "" ? "" : wordDelimiter || " ";
}
wordDelimIsNotSpace = wordDelimString !== " ";
for (; i < nodes.length; i++) {
curNode = nodes[i];
if (curNode.nodeType === 3) {
curTextContent = curNode.textContent || "";
if (reduceWhiteSpace) {
curTextContent = curTextContent.replace(_spacesRegEx, " ")
} else if (isPreformatted) {
curTextContent = curTextContent.replace(/\n/g, wordDelimString + "\n");
}
prepareText && (curTextContent = prepareText(curTextContent, element));
curNode.textContent = curTextContent;
words = wordDelimString || wordDelimSplitter ? curTextContent.split(wordDelimSplitter || wordDelimString) : curTextContent.match(charSplitRegEx) || _emptyArray;
lastWordText = words[words.length - 1];
endsWithSpace = wordDelimIsNotSpace ? lastWordText.slice(-1) === " " : !lastWordText;
lastWordText || words.pop(); // if the last word is empty, remove it
lastBounds = elementBounds;
startsWithSpace = wordDelimIsNotSpace ? words[0].charAt(0) === " " : !words[0];
startsWithSpace && _insertNodeBefore(" ", element, curNode); // if the word starts with a space, add a space to the beginning of the node
words[0] || words.shift(); // if the first word is empty, remove it
_stretchToFitSpecialChars(words, specialCharsRegEx);
(deepSlice && isNested) || (curNode.textContent = ""); // only clear out the text if we don't need to measure bounds. We must measure bounds if we're either splitting lines -OR- if we're splitting ONLY characters. In that case, it's important to gradually swap out the text content as we slice it up, otherwise we'll get funky wrapping that'd throw off the bounds calculations.
for (j = 1; j <= words.length; j++) {
wordText = words[j-1];
if (!reduceWhiteSpace && isPreformatted && wordText.charAt(0) === "\n") { // when we're NOT reducing white space, and we're in a preformatted element, and the word starts with a newline, then we need to remove the previous sibling (which is a wordDelimiter) and insert a <br> tag.
curNode.previousSibling?.remove(); // we added an extra wordDelimiter in front of all newline characters, so we need to remove it.
_insertNodeBefore(document.createElement("br"), element, curNode);
wordText = wordText.slice(1);
}
if (!reduceWhiteSpace && wordText === "") {
_insertNodeBefore(wordDelimString, element, curNode);
} else if (wordText === " ") {
element.insertBefore(document.createTextNode(" "), curNode);
} else {
wordDelimIsNotSpace && wordText.charAt(0) === " " && _insertNodeBefore(" ", element, curNode);
// if the previous sibling is an ignored element, and we're on the first word, and there's no starting space, and the previous sibling is part of the word collection, then we must combine them (as if this is continuing a word from the previous node before the ignored element(s)).
if (ignoredPreviousSibling && j === 1 && !startsWithSpace && wordsCollection.indexOf((ignoredPreviousSibling as Element).parentNode as HTMLElement) > -1) {
curWordEl = wordsCollection[wordsCollection.length - 1];
curWordEl.appendChild(document.createTextNode(charWrapper ? "" : wordText)); // if we're splitting characters, we'll add them one-by-one below
} else {
curWordEl = wordWrapper(charWrapper ? "" : wordText); // if we're splitting characters, we'll add them one-by-one below
_insertNodeBefore(curWordEl, element, curNode);
ignoredPreviousSibling && j === 1 && !startsWithSpace && curWordEl.insertBefore(ignoredPreviousSibling as Element, curWordEl.firstChild);
}
// split characters if necessary
if (charWrapper) {
curWordChars = _charSegmenter ? _stretchToFitSpecialChars([..._charSegmenter.segment(wordText)].map(s => s.segment), specialCharsRegEx) : wordText.match(charSplitRegEx) || _emptyArray;
for (k = 0; k < curWordChars.length; k++) {
curWordEl.appendChild(curWordChars[k] === " " ? document.createTextNode(" ") : charWrapper(curWordChars[k]));
}
}
// subdivide if necessary so that if a single inline element spills onto multiple lines, it gets sliced up accordingly
if (deepSlice && isNested) {
curTextContent = curNode.textContent = curTextContent.substring(wordText.length+1, curTextContent.length); // remember that we've got to accommodate the word delimiter in the substring.
bounds = curWordEl.getBoundingClientRect();
if (bounds.top > lastBounds.top && bounds.left <= lastBounds.left) {
clonedNode = element.cloneNode() as HTMLElement;
curSubNode = element.childNodes[0];
while (curSubNode && curSubNode !== curWordEl) {
tempSubNode = curSubNode;
curSubNode = curSubNode.nextSibling; // once we renest it in clonedNode, the nextSibling will be different, so grab it here.
clonedNode.appendChild(tempSubNode);
}
(element.parentNode as Element).insertBefore(clonedNode, element);
prepForCharsOnly && _disallowInline(clonedNode);
}
lastBounds = bounds;
}
if (j < words.length || endsWithSpace) {
// always add the delimiter between each word unless we're at the very last word in which case we may need to add a space. Special case: if a word in the middle ends in a space and we're NOT using space as the delimiter, then we need to insert a space before the delimiter too. Example: if wordDelimiter is "t" in "This text is <strong>bold</strong> and there is a <a href="https://gsap.com">link here</a>."
_insertNodeBefore(j >= words.length ? " " : wordDelimIsNotSpace && wordText.slice(-1) === " " ? " " + wordDelimString : wordDelimString, element, curNode);
}
}
}
element.removeChild(curNode);
ignoredPreviousSibling = 0;
} else if (curNode.nodeType === 1) {
if (ignore && ignore.indexOf(curNode as Element) > -1) { // if the current node is in the ignore array, then we need to ignore it and move it inside the end of the last word (but only if the previous sibling is a word).
wordsCollection.indexOf(curNode.previousSibling as HTMLElement) > -1 && wordsCollection[wordsCollection.length - 1].appendChild(curNode);
ignoredPreviousSibling = curNode as Element;
} else {
_splitWordsAndCharsRecursively(curNode as Element, config, wordWrapper, charWrapper, prepForCharsOnly, deepSlice, ignore, charSplitRegEx, specialCharsRegEx, true);
ignoredPreviousSibling = 0;
}
prepForCharsOnly && _disallowInline(curNode as HTMLElement);
}
}
};
export class SplitText {
elements: HTMLElement[];
chars: HTMLElement[];
words: HTMLElement[];
lines: HTMLElement[];
masks: HTMLElement[];
_data: {
orig: SplitTextOriginal[];
anim?: {totalTime: (t?: number) => number, revert: () => void};
animTime?: number;
obs: ResizeObserver | false;
};
vars: SplitTextConfig;
isSplit: boolean = false;
_split: () => void;
constructor(elements: SplitTextTarget, config: SplitTextConfig) {
_initIfNecessary();
this.elements = _elements(elements);
this.chars = [];
this.words = [];
this.lines = [];
this.masks = [];
this.vars = config;
this._split = () => this.isSplit && this.split(this.vars);
let orig: SplitTextOriginal[] = [],
timerId: number,
checkWidths = () => {
let i: number = orig.length,
o: SplitTextOriginal;
while (i--) {
o = orig[i];
let w: number = (o.element as HTMLElement).offsetWidth;
if (w !== o.width) {
o.width = w;
this._split();
return;
}
}
};
this._data = {orig: orig, obs: typeof(ResizeObserver) !== "undefined" && new ResizeObserver(() => { clearTimeout(timerId); timerId = setTimeout(checkWidths, 200) as unknown as number})};
_context(this);
this.split(config);
}
split(config: SplitTextConfig) {
this.isSplit && this.revert();
this.vars = config = config || this.vars || {};
let {type = "chars,words,lines", aria = "auto", deepSlice = true, smartWrap, onSplit, autoSplit = false, specialChars, mask} = this.vars,
splitLines: boolean = type.indexOf("lines") > -1,
splitCharacters: boolean = type.indexOf("chars") > -1,
splitWords: boolean = type.indexOf("words") > -1,
onlySplitCharacters: boolean = splitCharacters && !splitWords && !splitLines,
specialCharsRegEx: RegExp | undefined = specialChars && (("push" in specialChars) ? new RegExp("(?:" + specialChars.join("|") + ")", "gu") : specialChars),
finalCharSplitRegEx: RegExp = specialCharsRegEx ? new RegExp(specialCharsRegEx.source + "|" + _emojiSafeRegEx.source, "gu") : _emojiSafeRegEx,
ignore: HTMLElement[] | false = !!config.ignore && _elements(config.ignore),
{orig, animTime, obs} = this._data,
onSplitResult: any;
if (splitCharacters || splitWords || splitLines) {
this.elements.forEach((element, index) => {
orig[index] = {
element,
html: element.innerHTML,
ariaL: element.getAttribute("aria-label"),
ariaH: element.getAttribute("aria-hidden")
};
aria === "auto" ? element.setAttribute("aria-label", (element.textContent || "").trim()) : aria === "hidden" && element.setAttribute("aria-hidden", "true");
let chars: HTMLElement[] = [],
words: HTMLElement[] = [],
lines: HTMLElement[] = [],
charWrapper = splitCharacters ? _getWrapper("char", config, chars) : null,
wordWrapper = _getWrapper("word", config, words),
i: number, curWord: Element, smartWrapSpan: HTMLElement, nextSibling: Node;
// split words (always) and characters (if necessary)
_splitWordsAndCharsRecursively(element, config, wordWrapper, charWrapper, onlySplitCharacters, deepSlice && (splitLines || onlySplitCharacters), ignore, finalCharSplitRegEx, specialCharsRegEx, false);
// subdivide lines
if (splitLines) {
let nodes: Node[] = _toArray(element.childNodes),
wrapLine = _getLineWrapper(element, nodes, config, lines),
curNode: Node,
toRemove: Node[] = [],
lineStartIndex: number = 0,
allBounds: BoundingRect[] = nodes.map((n) => n.nodeType === 1 ? (n as Element).getBoundingClientRect() : _emptyBounds), // do all these measurements at once so that we don't trigger layout thrashing
lastBounds: BoundingRect = _emptyBounds;
for (i = 0; i < nodes.length; i++) {
curNode = nodes[i];
if (curNode.nodeType === 1) {
if (curNode.nodeName === "BR") { // remove any <br> tags because breaking up by lines already handles that.
toRemove.push(curNode);
wrapLine(lineStartIndex, i+1);
lineStartIndex = i+1;
lastBounds = allBounds[lineStartIndex];
} else {
if (i && allBounds[i].top > lastBounds.top && allBounds[i].left <= lastBounds.left) {
wrapLine(lineStartIndex, i);
lineStartIndex = i;
}
lastBounds = allBounds[i];
}
}
}
lineStartIndex < i && wrapLine(lineStartIndex, i);
toRemove.forEach(el => el.parentNode?.removeChild(el));
}
// remove words if "type" doesn't include "words"
if (!splitWords) {
for (i = 0; i < words.length; i++) {
curWord = words[i];
if (splitCharacters || !curWord.nextSibling || curWord.nextSibling.nodeType !== 3) {
if (smartWrap && !splitLines) { // tried inserting String.fromCharCode(8288) between each character to prevent words from wrapping strangely, but it doesn't work. Also tried adding <wbr> tags between each character, but it doesn't work either.
smartWrapSpan = document.createElement("span"); // replace the word element with a span that has white-space: nowrap
smartWrapSpan.style.whiteSpace = "nowrap";
while (curWord.firstChild) {
smartWrapSpan.appendChild(curWord.firstChild);
}
curWord.replaceWith(smartWrapSpan);
} else {
curWord.replaceWith(...curWord.childNodes);
}
} else {
nextSibling = curWord.nextSibling;
if (nextSibling && nextSibling.nodeType === 3) {
nextSibling.textContent = (curWord.textContent || "") + (nextSibling.textContent || "");
curWord.remove();
}
}
}
words.length = 0;
element.normalize();
}
this.lines.push(...lines);
this.words.push(...words);
this.chars.push(...chars);
});
mask && this[mask] && this.masks.push(...(this as any)[mask as string].map((el: HTMLElement) => {
let maskEl: HTMLElement = el.cloneNode() as HTMLElement;
el.replaceWith(maskEl);
maskEl.appendChild(el);
el.className && (maskEl.className = el.className.replace(/(\b\w+\b)/g, '$1-mask'));
maskEl.style.overflow = "clip";
return maskEl;
}));
}
this.isSplit = true;
_fonts && (autoSplit ? _fonts.addEventListener("loadingdone", this._split) : _fonts.status === "loading" && console.warn("SplitText called before fonts loaded"));
if ((onSplitResult = onSplit && onSplit(this)) && onSplitResult.totalTime) {
this._data.anim = animTime ? onSplitResult.totalTime(animTime) : onSplitResult;
}
splitLines && autoSplit && this.elements.forEach((element, index) => {
orig[index].width = element.offsetWidth;
obs && obs.observe(element);
});
return this;
}
revert() {
let {orig, anim, obs} = this._data;
obs && obs.disconnect();
orig.forEach(({element, html, ariaL, ariaH}) => {
element.innerHTML = html;
ariaL ? element.setAttribute("aria-label", ariaL) : element.removeAttribute("aria-label");
ariaH ? element.setAttribute("aria-hidden", ariaH) : element.removeAttribute("aria-hidden");
});
this.chars.length = this.words.length = this.lines.length = orig.length = this.masks.length = 0;
this.isSplit = false;
_fonts?.removeEventListener("loadingdone", this._split);
if (anim) { // if the user returned an animation in the onSplit, we record the totalTime() here and revert() it and then we'll the totalTime() of the newly returned onSplit animation. This allows things to be seamless
this._data.animTime = anim.totalTime();
anim.revert();
}
this.vars.onRevert?.(this);
return this;
}
static create(elements: SplitTextTarget, config: SplitTextConfig) {
return new SplitText(elements, config);
}
static register(core: any) {
gsap = gsap || core || (window as any).gsap;
if (gsap) {
_toArray = gsap.utils.toArray;
_context = gsap.core.context || _context;
}
if (!_coreInitted && window.innerWidth > 0) {
_fonts = document.fonts;
_coreInitted = true;
}
}
static readonly version: string = "3.13.0";
}
export { SplitText as default };

View File

@@ -0,0 +1,120 @@
/*!
* TextPlugin 3.13.0
* https://gsap.com
*
* @license Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
import { emojiSafeSplit, getText, splitInnerHTML } from "./utils/strings.js";
let gsap, _tempDiv,
_getGSAP = () => gsap || (typeof(window) !== "undefined" && (gsap = window.gsap) && gsap.registerPlugin && gsap);
export const TextPlugin = {
version:"3.13.0",
name:"text",
init(target, value, tween) {
typeof(value) !== "object" && (value = {value:value});
let i = target.nodeName.toUpperCase(),
data = this,
{ newClass, oldClass, preserveSpaces, rtl } = value,
delimiter = data.delimiter = value.delimiter || "",
fillChar = data.fillChar = value.fillChar || (value.padSpace ? "&nbsp;" : ""),
short, text, original, j, condensedText, condensedOriginal, aggregate, s;
data.svg = (target.getBBox && (i === "TEXT" || i === "TSPAN"));
if (!("innerHTML" in target) && !data.svg) {
return false;
}
data.target = target;
if (!("value" in value)) {
data.text = data.original = [""];
return;
}
original = splitInnerHTML(target, delimiter, false, preserveSpaces, data.svg);
_tempDiv || (_tempDiv = document.createElement("div"));
_tempDiv.innerHTML = value.value;
text = splitInnerHTML(_tempDiv, delimiter, false, preserveSpaces, data.svg);
data.from = tween._from;
if ((data.from || rtl) && !(rtl && data.from)) { // right-to-left or "from()" tweens should invert things (but if it's BOTH .from() and rtl, inverting twice equals not inverting at all :)
i = original;
original = text;
text = i;
}
data.hasClass = !!(newClass || oldClass);
data.newClass = rtl ? oldClass : newClass;
data.oldClass = rtl ? newClass : oldClass;
i = original.length - text.length;
short = i < 0 ? original : text;
if (i < 0) {
i = -i;
}
while (--i > -1) {
short.push(fillChar);
}
if (value.type === "diff") {
j = 0;
condensedText = [];
condensedOriginal = [];
aggregate = "";
for (i = 0; i < text.length; i++) {
s = text[i];
if (s === original[i]) {
aggregate += s;
} else {
condensedText[j] = aggregate + s;
condensedOriginal[j++] = aggregate + original[i];
aggregate = "";
}
}
text = condensedText;
original = condensedOriginal;
if (aggregate) {
text.push(aggregate);
original.push(aggregate);
}
}
value.speed && tween.duration(Math.min(0.05 / value.speed * short.length, value.maxDuration || 9999));
data.rtl = rtl;
data.original = original;
data.text = text;
data._props.push("text");
},
render(ratio, data) {
if (ratio > 1) {
ratio = 1;
} else if (ratio < 0) {
ratio = 0;
}
if (data.from) {
ratio = 1 - ratio;
}
let { text, hasClass, newClass, oldClass, delimiter, target, fillChar, original, rtl } = data,
l = text.length,
i = ((rtl ? 1 - ratio : ratio) * l + 0.5) | 0,
applyNew, applyOld, str;
if (hasClass && ratio) {
applyNew = (newClass && i);
applyOld = (oldClass && i !== l);
str = (applyNew ? "<span class='" + newClass + "'>" : "") + text.slice(0, i).join(delimiter) + (applyNew ? "</span>" : "") + (applyOld ? "<span class='" + oldClass + "'>" : "") + delimiter + original.slice(i).join(delimiter) + (applyOld ? "</span>" : "");
} else {
str = text.slice(0, i).join(delimiter) + delimiter + original.slice(i).join(delimiter);
}
if (data.svg) { //SVG text elements don't have an "innerHTML" in Microsoft browsers.
target.textContent = str;
} else {
target.innerHTML = (fillChar === "&nbsp;" && ~str.indexOf(" ")) ? str.split(" ").join("&nbsp;&nbsp;") : str;
}
}
};
TextPlugin.splitInnerHTML = splitInnerHTML;
TextPlugin.emojiSafeSplit = emojiSafeSplit;
TextPlugin.getText = getText;
_getGSAP() && gsap.registerPlugin(TextPlugin);
export { TextPlugin as default };

33
network-visualization/node_modules/gsap/src/all.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import gsap from "./gsap-core.js";
import CSSPlugin from "./CSSPlugin.js";
const gsapWithCSS = gsap.registerPlugin(CSSPlugin) || gsap, // to protect from tree shaking
TweenMaxWithCSS = gsapWithCSS.core.Tween;
export { gsapWithCSS as gsap, gsapWithCSS as default, TweenMaxWithCSS as TweenMax, CSSPlugin };
export { TweenLite, TimelineMax, TimelineLite, Power0, Power1, Power2, Power3, Power4, Linear, Quad, Cubic, Quart, Quint, Strong, Elastic, Back, SteppedEase, Bounce, Sine, Expo, Circ, wrap, wrapYoyo, distribute, random, snap, normalize, getUnit, clamp, splitColor, toArray, mapRange, pipe, unitize, interpolate, shuffle, selector } from "./gsap-core.js";
export * from "./CustomEase.js";
export * from "./Draggable.js";
export * from "./CSSRulePlugin.js";
export * from "./EaselPlugin.js";
export * from "./EasePack.js";
export * from "./Flip.js";
export * from "./MotionPathPlugin.js";
export * from "./Observer.js";
export * from "./PixiPlugin.js";
export * from "./ScrollToPlugin.js";
export * from "./ScrollTrigger.js";
export * from "./TextPlugin.js";
export * from "./DrawSVGPlugin.js";
export * from "./Physics2DPlugin.js";
export * from "./PhysicsPropsPlugin.js";
export * from "./ScrambleTextPlugin.js";
export * from "./CustomBounce.js";
export * from "./CustomWiggle.js";
export * from "./GSDevTools.js";
export * from "./InertiaPlugin.js";
export * from "./MorphSVGPlugin.js";
export * from "./MotionPathHelper.js";
export * from "./ScrollSmoother.js";
export * from "./SplitText.js";

3270
network-visualization/node_modules/gsap/src/gsap-core.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

33
network-visualization/node_modules/gsap/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
import { gsap, Power0, Power1, Power2, Power3, Power4, Linear, Quad, Cubic, Quart, Quint, Strong, Elastic, Back, SteppedEase, Bounce, Sine, Expo, Circ, TweenLite, TimelineLite, TimelineMax } from "./gsap-core.js";
import { CSSPlugin } from "./CSSPlugin.js";
const gsapWithCSS = gsap.registerPlugin(CSSPlugin) || gsap, // to protect from tree shaking
TweenMaxWithCSS = gsapWithCSS.core.Tween;
export {
gsapWithCSS as gsap,
gsapWithCSS as default,
CSSPlugin,
TweenMaxWithCSS as TweenMax,
TweenLite,
TimelineMax,
TimelineLite,
Power0,
Power1,
Power2,
Power3,
Power4,
Linear,
Quad,
Cubic,
Quart,
Quint,
Strong,
Elastic,
Back,
SteppedEase,
Bounce,
Sine,
Expo,
Circ
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,201 @@
/*!
* VelocityTracker: 3.13.0
* https://gsap.com
*
* Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let gsap, _coreInitted, _toArray, _getUnit, _first, _ticker, _time1, _time2, _getCache,
_getGSAP = () => gsap || typeof(window) !== "undefined" && (gsap = window.gsap),
_lookup = {},
_round = value => Math.round(value * 10000) / 10000,
_getID = target => _getCache(target).id,
_getByTarget = target => _lookup[_getID(typeof(target) === "string" ? _toArray(target)[0] : target)],
_onTick = (time) => {
let pt = _first,
val;
//if the frame rate is too high, we won't be able to track the velocity as well, so only update the values about 20 times per second
if (time - _time1 >= 0.05) {
_time2 = _time1;
_time1 = time;
while (pt) {
val = pt.g(pt.t, pt.p);
if (val !== pt.v1 || time - pt.t1 > 0.2) { //use a threshold of 0.2 seconds for zeroing-out velocity. If we only use 0.05 and things update slightly slower, like some Android devices dispatch "touchmove" events sluggishly so 2 or 3 ticks of the gsap.ticker may elapse inbetween, thus it may appear like the object is not moving but it actually is but it's not updating as frequently. A threshold of 0.2 seconds seems to be a good balance. We want to update things frequently (0.05 seconds) when they're moving so that we can respond to fast motions accurately, but we want to be more resistant to go back to a zero velocity.
pt.v2 = pt.v1;
pt.v1 = val;
pt.t2 = pt.t1;
pt.t1 = time;
}
pt = pt._next;
}
}
},
_types = {deg: 360, rad: Math.PI * 2},
_initCore = () => {
gsap = _getGSAP();
if (gsap) {
_toArray = gsap.utils.toArray;
_getUnit = gsap.utils.getUnit;
_getCache = gsap.core.getCache;
_ticker = gsap.ticker;
_coreInitted = 1;
}
};
class PropTracker {
constructor(target, property, type, next) {
this.t = target;
this.p = property;
this.g = target._gsap.get;
this.rCap = _types[type || _getUnit(this.g(target, property))]; //rotational cap (for degrees, "deg", it's 360 and for radians, "rad", it's Math.PI * 2)
this.v1 = this.v2 = 0;
this.t1 = this.t2 = _ticker.time;
if (next) {
this._next = next;
next._prev = this;
}
}
}
export class VelocityTracker {
constructor(target, property) {
if (!_coreInitted) {
_initCore();
}
this.target = _toArray(target)[0];
_lookup[_getID(this.target)] = this;
this._props = {};
property && this.add(property);
}
static register(core) {
gsap = core;
_initCore();
}
get(property, skipRecentTick) {
let pt = this._props[property] || console.warn("Not tracking " + property + " velocity."),
val, dif, rotationCap;
val = parseFloat(skipRecentTick ? pt.v1 : pt.g(pt.t, pt.p));
dif = (val - parseFloat(pt.v2));
rotationCap = pt.rCap;
if (rotationCap) { //rotational values need special interpretation so that if, for example, they go from 179 to -178 degrees it is interpreted as a change of 3 instead of -357.
dif = dif % rotationCap;
if (dif !== dif % (rotationCap / 2)) {
dif = (dif < 0) ? dif + rotationCap : dif - rotationCap;
}
}
return _round(dif / ((skipRecentTick ? pt.t1 : _ticker.time) - pt.t2));
}
getAll() {
let result = {},
props = this._props,
p;
for (p in props) {
result[p] = this.get(p);
}
return result;
}
isTracking(property) {
return (property in this._props);
}
add(property, type) {
if (!(property in this._props)) {
if (!_first) {
_ticker.add(_onTick);
_time1 = _time2 = _ticker.time;
}
_first = this._props[property] = new PropTracker(this.target, property, type, _first);
}
}
remove(property) {
let pt = this._props[property],
prev, next;
if (pt) {
prev = pt._prev;
next = pt._next;
if (prev) {
prev._next = next;
}
if (next) {
next._prev = prev;
} else if (_first === pt) {
_ticker.remove(_onTick);
_first = 0;
}
delete this._props[property];
}
}
kill(shallow) {
for (let p in this._props) {
this.remove(p);
}
if (!shallow) {
delete _lookup[_getID(this.target)];
}
}
static track(targets, properties, types) {
if (!_coreInitted) {
_initCore();
}
let result = [],
targs = _toArray(targets),
a = properties.split(","),
t = (types || "").split(","),
i = targs.length,
tracker, j;
while (i--) {
tracker = _getByTarget(targs[i]) || new VelocityTracker(targs[i]);
j = a.length;
while (j--) {
tracker.add(a[j], t[j] || t[0]);
}
result.push(tracker);
}
return result;
}
static untrack(targets, properties) {
let props = (properties || "").split(",");
_toArray(targets).forEach(target => {
let tracker = _getByTarget(target);
if (tracker) {
if (!props.length) {
tracker.kill(1);
} else {
props.forEach(p => tracker.remove(p));
}
}
});
}
static isTracking(target, property) {
let tracker = _getByTarget(target);
return tracker && tracker.isTracking(property);
}
static getVelocity(target, property) {
let tracker = _getByTarget(target);
return (!tracker || !tracker.isTracking(property)) ? console.warn("Not tracking velocity of " + property) : tracker.get(property);
}
}
VelocityTracker.getByTarget = _getByTarget;
_getGSAP() && gsap.registerPlugin(VelocityTracker);
export { VelocityTracker as default };

View File

@@ -0,0 +1,324 @@
/*!
* matrix 3.13.0
* https://gsap.com
*
* Copyright 2008-2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license
* @author: Jack Doyle, jack@greensock.com
*/
/* eslint-disable */
let _doc, _win, _docElement, _body, _divContainer, _svgContainer, _identityMatrix, _gEl,
_transformProp = "transform",
_transformOriginProp = _transformProp + "Origin",
_hasOffsetBug,
_setDoc = element => {
let doc = element.ownerDocument || element;
if (!(_transformProp in element.style) && "msTransform" in element.style) { //to improve compatibility with old Microsoft browsers
_transformProp = "msTransform";
_transformOriginProp = _transformProp + "Origin";
}
while (doc.parentNode && (doc = doc.parentNode)) { }
_win = window;
_identityMatrix = new Matrix2D();
if (doc) {
_doc = doc;
_docElement = doc.documentElement;
_body = doc.body;
_gEl = _doc.createElementNS("http://www.w3.org/2000/svg", "g");
// prevent any existing CSS from transforming it
_gEl.style.transform = "none";
// now test for the offset reporting bug. Use feature detection instead of browser sniffing to make things more bulletproof and future-proof. Hopefully Safari will fix their bug soon.
let d1 = doc.createElement("div"),
d2 = doc.createElement("div"),
root = doc && (doc.body || doc.firstElementChild);
if (root && root.appendChild) {
root.appendChild(d1);
d1.appendChild(d2);
d1.setAttribute("style", "position:static;transform:translate3d(0,0,1px)");
_hasOffsetBug = (d2.offsetParent !== d1);
root.removeChild(d1);
}
}
return doc;
},
_forceNonZeroScale = e => { // walks up the element's ancestors and finds any that had their scale set to 0 via GSAP, and changes them to 0.0001 to ensure that measurements work. Firefox has a bug that causes it to incorrectly report getBoundingClientRect() when scale is 0.
let a, cache;
while (e && e !== _body) {
cache = e._gsap;
cache && cache.uncache && cache.get(e, "x"); // force re-parsing of transforms if necessary
if (cache && !cache.scaleX && !cache.scaleY && cache.renderTransform) {
cache.scaleX = cache.scaleY = 1e-4;
cache.renderTransform(1, cache);
a ? a.push(cache) : (a = [cache]);
}
e = e.parentNode;
}
return a;
},
// possible future addition: pass an element to _forceDisplay() and it'll walk up all its ancestors and make sure anything with display: none is set to display: block, and if there's no parentNode, it'll add it to the body. It returns an Array that you can then feed to _revertDisplay() to have it revert all the changes it made.
// _forceDisplay = e => {
// let a = [],
// parent;
// while (e && e !== _body) {
// parent = e.parentNode;
// (_win.getComputedStyle(e).display === "none" || !parent) && a.push(e, e.style.display, parent) && (e.style.display = "block");
// parent || _body.appendChild(e);
// e = parent;
// }
// return a;
// },
// _revertDisplay = a => {
// for (let i = 0; i < a.length; i+=3) {
// a[i+1] ? (a[i].style.display = a[i+1]) : a[i].style.removeProperty("display");
// a[i+2] || a[i].parentNode.removeChild(a[i]);
// }
// },
_svgTemps = [], //we create 3 elements for SVG, and 3 for other DOM elements and cache them for performance reasons. They get nested in _divContainer and _svgContainer so that just one element is added to the DOM on each successive attempt. Again, performance is key.
_divTemps = [],
_getDocScrollTop = () => _win.pageYOffset || _doc.scrollTop || _docElement.scrollTop || _body.scrollTop || 0,
_getDocScrollLeft = () => _win.pageXOffset || _doc.scrollLeft || _docElement.scrollLeft || _body.scrollLeft || 0,
_svgOwner = element => element.ownerSVGElement || ((element.tagName + "").toLowerCase() === "svg" ? element : null),
_isFixed = element => {
if (_win.getComputedStyle(element).position === "fixed") {
return true;
}
element = element.parentNode;
if (element && element.nodeType === 1) { // avoid document fragments which will throw an error.
return _isFixed(element);
}
},
_createSibling = (element, i) => {
if (element.parentNode && (_doc || _setDoc(element))) {
let svg = _svgOwner(element),
ns = svg ? (svg.getAttribute("xmlns") || "http://www.w3.org/2000/svg") : "http://www.w3.org/1999/xhtml",
type = svg ? (i ? "rect" : "g") : "div",
x = i !== 2 ? 0 : 100,
y = i === 3 ? 100 : 0,
css = "position:absolute;display:block;pointer-events:none;margin:0;padding:0;",
e = _doc.createElementNS ? _doc.createElementNS(ns.replace(/^https/, "http"), type) : _doc.createElement(type);
if (i) {
if (!svg) {
if (!_divContainer) {
_divContainer = _createSibling(element);
_divContainer.style.cssText = css;
}
e.style.cssText = css + "width:0.1px;height:0.1px;top:" + y + "px;left:" + x + "px";
_divContainer.appendChild(e);
} else {
_svgContainer || (_svgContainer = _createSibling(element));
e.setAttribute("width", 0.01);
e.setAttribute("height", 0.01);
e.setAttribute("transform", "translate(" + x + "," + y + ")");
_svgContainer.appendChild(e);
}
}
return e;
}
throw "Need document and parent.";
},
_consolidate = m => { // replaces SVGTransformList.consolidate() because a bug in Firefox causes it to break pointer events. See https://gsap.com/forums/topic/23248-touch-is-not-working-on-draggable-in-firefox-windows-v324/?tab=comments#comment-109800
let c = new Matrix2D(),
i = 0;
for (; i < m.numberOfItems; i++) {
c.multiply(m.getItem(i).matrix);
}
return c;
},
_getCTM = svg => {
let m = svg.getCTM(),
transform;
if (!m) { // Firefox returns null for getCTM() on root <svg> elements, so this is a workaround using a <g> that we temporarily append.
transform = svg.style[_transformProp];
svg.style[_transformProp] = "none"; // a bug in Firefox causes css transforms to contaminate the getCTM()
svg.appendChild(_gEl);
m = _gEl.getCTM();
svg.removeChild(_gEl);
transform ? (svg.style[_transformProp] = transform) : svg.style.removeProperty(_transformProp.replace(/([A-Z])/g, "-$1").toLowerCase());
}
return m || _identityMatrix.clone(); // Firefox will still return null if the <svg> has a width/height of 0 in the browser.
},
_placeSiblings = (element, adjustGOffset) => {
let svg = _svgOwner(element),
isRootSVG = element === svg,
siblings = svg ? _svgTemps : _divTemps,
parent = element.parentNode,
appendToEl = parent && !svg && parent.shadowRoot && parent.shadowRoot.appendChild ? parent.shadowRoot : parent,
container, m, b, x, y, cs;
if (element === _win) {
return element;
}
siblings.length || siblings.push(_createSibling(element, 1), _createSibling(element, 2), _createSibling(element, 3));
container = svg ? _svgContainer : _divContainer;
if (svg) {
if (isRootSVG) {
b = _getCTM(element);
x = -b.e / b.a;
y = -b.f / b.d;
m = _identityMatrix;
} else if (element.getBBox) {
b = element.getBBox();
m = element.transform ? element.transform.baseVal : {}; // IE11 doesn't follow the spec.
m = !m.numberOfItems ? _identityMatrix : m.numberOfItems > 1 ? _consolidate(m) : m.getItem(0).matrix; // don't call m.consolidate().matrix because a bug in Firefox makes pointer events not work when consolidate() is called on the same tick as getBoundingClientRect()! See https://gsap.com/forums/topic/23248-touch-is-not-working-on-draggable-in-firefox-windows-v324/?tab=comments#comment-109800
x = m.a * b.x + m.c * b.y;
y = m.b * b.x + m.d * b.y;
} else { // may be a <mask> which has no getBBox() so just use defaults instead of throwing errors.
m = new Matrix2D();
x = y = 0;
}
if (adjustGOffset && element.tagName.toLowerCase() === "g") {
x = y = 0;
}
(isRootSVG ? svg : parent).appendChild(container);
container.setAttribute("transform", "matrix(" + m.a + "," + m.b + "," + m.c + "," + m.d + "," + (m.e + x) + "," + (m.f + y) + ")");
} else {
x = y = 0;
if (_hasOffsetBug) { // some browsers (like Safari) have a bug that causes them to misreport offset values. When an ancestor element has a transform applied, it's supposed to treat it as if it's position: relative (new context). Safari botches this, so we need to find the closest ancestor (between the element and its offsetParent) that has a transform applied and if one is found, grab its offsetTop/Left and subtract them to compensate.
m = element.offsetParent;
b = element;
while (b && (b = b.parentNode) && b !== m && b.parentNode) {
if ((_win.getComputedStyle(b)[_transformProp] + "").length > 4) {
x = b.offsetLeft;
y = b.offsetTop;
b = 0;
}
}
}
cs = _win.getComputedStyle(element);
if (cs.position !== "absolute" && cs.position !== "fixed") {
m = element.offsetParent;
while (parent && parent !== m) { // if there's an ancestor element between the element and its offsetParent that's scrolled, we must factor that in.
x += parent.scrollLeft || 0;
y += parent.scrollTop || 0;
parent = parent.parentNode;
}
}
b = container.style;
b.top = (element.offsetTop - y) + "px";
b.left = (element.offsetLeft - x) + "px";
b[_transformProp] = cs[_transformProp];
b[_transformOriginProp] = cs[_transformOriginProp];
// b.border = m.border;
// b.borderLeftStyle = m.borderLeftStyle;
// b.borderTopStyle = m.borderTopStyle;
// b.borderLeftWidth = m.borderLeftWidth;
// b.borderTopWidth = m.borderTopWidth;
b.position = cs.position === "fixed" ? "fixed" : "absolute";
appendToEl.appendChild(container);
}
return container;
},
_setMatrix = (m, a, b, c, d, e, f) => {
m.a = a;
m.b = b;
m.c = c;
m.d = d;
m.e = e;
m.f = f;
return m;
};
export class Matrix2D {
constructor(a=1, b=0, c=0, d=1, e=0, f=0) {
_setMatrix(this, a, b, c, d, e, f);
}
inverse() {
let {a, b, c, d, e, f} = this,
determinant = (a * d - b * c) || 1e-10;
return _setMatrix(
this,
d / determinant,
-b / determinant,
-c / determinant,
a / determinant,
(c * f - d * e) / determinant,
-(a * f - b * e) / determinant
);
}
multiply(matrix) {
let {a, b, c, d, e, f} = this,
a2 = matrix.a,
b2 = matrix.c,
c2 = matrix.b,
d2 = matrix.d,
e2 = matrix.e,
f2 = matrix.f;
return _setMatrix(this,
a2 * a + c2 * c,
a2 * b + c2 * d,
b2 * a + d2 * c,
b2 * b + d2 * d,
e + e2 * a + f2 * c,
f + e2 * b + f2 * d);
}
clone() {
return new Matrix2D(this.a, this.b, this.c, this.d, this.e, this.f);
}
equals(matrix) {
let {a, b, c, d, e, f} = this;
return (a === matrix.a && b === matrix.b && c === matrix.c && d === matrix.d && e === matrix.e && f === matrix.f);
}
apply(point, decoratee={}) {
let {x, y} = point,
{a, b, c, d, e, f} = this;
decoratee.x = (x * a + y * c + e) || 0;
decoratee.y = (x * b + y * d + f) || 0;
return decoratee;
}
}
// Feed in an element and it'll return a 2D matrix (optionally inverted) so that you can translate between coordinate spaces.
// Inverting lets you translate a global point into a local coordinate space. No inverting lets you go the other way.
// We needed this to work around various browser bugs, like Firefox doesn't accurately report getScreenCTM() when there
// are transforms applied to ancestor elements.
// The matrix math to convert any x/y coordinate is as follows, which is wrapped in a convenient apply() method of Matrix2D above:
// tx = m.a * x + m.c * y + m.e
// ty = m.b * x + m.d * y + m.f
export function getGlobalMatrix(element, inverse, adjustGOffset, includeScrollInFixed) { // adjustGOffset is typically used only when grabbing an element's PARENT's global matrix, and it ignores the x/y offset of any SVG <g> elements because they behave in a special way.
if (!element || !element.parentNode || (_doc || _setDoc(element)).documentElement === element) {
return new Matrix2D();
}
let zeroScales = _forceNonZeroScale(element),
svg = _svgOwner(element),
temps = svg ? _svgTemps : _divTemps,
container = _placeSiblings(element, adjustGOffset),
b1 = temps[0].getBoundingClientRect(),
b2 = temps[1].getBoundingClientRect(),
b3 = temps[2].getBoundingClientRect(),
parent = container.parentNode,
isFixed = !includeScrollInFixed && _isFixed(element),
m = new Matrix2D(
(b2.left - b1.left) / 100,
(b2.top - b1.top) / 100,
(b3.left - b1.left) / 100,
(b3.top - b1.top) / 100,
b1.left + (isFixed ? 0 : _getDocScrollLeft()),
b1.top + (isFixed ? 0 : _getDocScrollTop())
);
parent.removeChild(container);
if (zeroScales) {
b1 = zeroScales.length;
while (b1--) {
b2 = zeroScales[b1];
b2.scaleX = b2.scaleY = 0;
b2.renderTransform(1, b2);
}
}
return inverse ? m.inverse() : m;
}
export { _getDocScrollTop, _getDocScrollLeft, _setDoc, _isFixed, _getCTM };
// export function getMatrix(element) {
// _doc || _setDoc(element);
// let m = (_win.getComputedStyle(element)[_transformProp] + "").substr(7).match(/[-.]*\d+[.e\-+]*\d*[e\-\+]*\d*/g),
// is2D = m && m.length === 6;
// return !m || m.length < 6 ? new Matrix2D() : new Matrix2D(+m[0], +m[1], +m[is2D ? 2 : 4], +m[is2D ? 3 : 5], +m[is2D ? 4 : 12], +m[is2D ? 5 : 13]);
// }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long