@glissade/core 0.11.0 → 0.12.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clips.d.ts +114 -0
- package/dist/clips.js +230 -0
- package/dist/cmap.js +112 -0
- package/dist/font-ingest.d.ts +154 -0
- package/dist/font-ingest.js +282 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -112
- package/dist/sidecar.d.ts +5 -470
- package/dist/sidecar.js +2 -967
- package/dist/studioHost.d.ts +4 -1
- package/dist/studioHost.js +2 -1
- package/dist/targetRef.d.ts +33 -0
- package/dist/targetRef.js +992 -0
- package/dist/timeline.d.ts +138 -0
- package/dist/track.d.ts +329 -0
- package/package.json +12 -1
package/dist/sidecar.js
CHANGED
|
@@ -1,933 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
3
|
-
const RGB_RE = /^rgba?\(\s*([\d.]+)\s*[, ]\s*([\d.]+)\s*[, ]\s*([\d.]+)\s*(?:[,/]\s*([\d.]+%?)\s*)?\)$/i;
|
|
4
|
-
var ColorParseError = class extends Error {
|
|
5
|
-
constructor(input) {
|
|
6
|
-
super(`cannot parse color '${input}' (supported: #rgb[a], #rrggbb[aa], rgb()/rgba())`);
|
|
7
|
-
this.name = "ColorParseError";
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
function parseColor(input) {
|
|
11
|
-
const hex = HEX_RE.exec(input);
|
|
12
|
-
if (hex) {
|
|
13
|
-
let h = hex[1];
|
|
14
|
-
if (h.length <= 4) h = [...h].map((c) => c + c).join("");
|
|
15
|
-
const n = parseInt(h, 16);
|
|
16
|
-
if (h.length === 6) return {
|
|
17
|
-
r: n >> 16,
|
|
18
|
-
g: n >> 8 & 255,
|
|
19
|
-
b: n & 255,
|
|
20
|
-
a: 1
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
23
|
-
r: n >>> 24 & 255,
|
|
24
|
-
g: n >>> 16 & 255,
|
|
25
|
-
b: n >>> 8 & 255,
|
|
26
|
-
a: (n & 255) / 255
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
const rgb = RGB_RE.exec(input);
|
|
30
|
-
if (rgb) {
|
|
31
|
-
const alphaRaw = rgb[4];
|
|
32
|
-
const a = alphaRaw === void 0 ? 1 : alphaRaw.endsWith("%") ? parseFloat(alphaRaw) / 100 : parseFloat(alphaRaw);
|
|
33
|
-
return {
|
|
34
|
-
r: parseFloat(rgb[1]),
|
|
35
|
-
g: parseFloat(rgb[2]),
|
|
36
|
-
b: parseFloat(rgb[3]),
|
|
37
|
-
a
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
throw new ColorParseError(input);
|
|
41
|
-
}
|
|
42
|
-
function formatColor(c) {
|
|
43
|
-
const clampByte = (v) => Math.min(255, Math.max(0, Math.round(v)));
|
|
44
|
-
const hex = (v) => clampByte(v).toString(16).padStart(2, "0");
|
|
45
|
-
const base = `#${hex(c.r)}${hex(c.g)}${hex(c.b)}`;
|
|
46
|
-
const a = Math.min(1, Math.max(0, c.a));
|
|
47
|
-
return a >= 1 ? base : `${base}${hex(a * 255)}`;
|
|
48
|
-
}
|
|
49
|
-
function srgbToLinear(c) {
|
|
50
|
-
const v = c / 255;
|
|
51
|
-
return v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
|
|
52
|
-
}
|
|
53
|
-
function linearToSrgb(v) {
|
|
54
|
-
return (v <= .0031308 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - .055) * 255;
|
|
55
|
-
}
|
|
56
|
-
function rgbaToOklab(c) {
|
|
57
|
-
const r = srgbToLinear(c.r);
|
|
58
|
-
const g = srgbToLinear(c.g);
|
|
59
|
-
const b = srgbToLinear(c.b);
|
|
60
|
-
const l = Math.cbrt(.4122214708 * r + .5363325363 * g + .0514459929 * b);
|
|
61
|
-
const m = Math.cbrt(.2119034982 * r + .6806995451 * g + .1073969566 * b);
|
|
62
|
-
const s = Math.cbrt(.0883024619 * r + .2817188376 * g + .6299787005 * b);
|
|
63
|
-
return {
|
|
64
|
-
L: .2104542553 * l + .793617785 * m - .0040720468 * s,
|
|
65
|
-
a: 1.9779984951 * l - 2.428592205 * m + .4505937099 * s,
|
|
66
|
-
b: .0259040371 * l + .7827717662 * m - .808675766 * s,
|
|
67
|
-
alpha: c.a
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
function oklabToRgba(c) {
|
|
71
|
-
const l = (c.L + .3963377774 * c.a + .2158037573 * c.b) ** 3;
|
|
72
|
-
const m = (c.L - .1055613458 * c.a - .0638541728 * c.b) ** 3;
|
|
73
|
-
const s = (c.L - .0894841775 * c.a - 1.291485548 * c.b) ** 3;
|
|
74
|
-
return {
|
|
75
|
-
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + .2309699292 * s),
|
|
76
|
-
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - .3413193965 * s),
|
|
77
|
-
b: linearToSrgb(-.0041960863 * l - .7034186147 * m + 1.707614701 * s),
|
|
78
|
-
a: c.alpha
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
/** Interpolate two CSS color strings in OKLab; t may extrapolate. */
|
|
82
|
-
function lerpColor(from, to, t) {
|
|
83
|
-
const a = rgbaToOklab(parseColor(from));
|
|
84
|
-
const b = rgbaToOklab(parseColor(to));
|
|
85
|
-
const mix = (x, y) => x + (y - x) * t;
|
|
86
|
-
return formatColor(oklabToRgba({
|
|
87
|
-
L: mix(a.L, b.L),
|
|
88
|
-
a: mix(a.a, b.a),
|
|
89
|
-
b: mix(a.b, b.b),
|
|
90
|
-
alpha: mix(a.alpha, b.alpha)
|
|
91
|
-
}));
|
|
92
|
-
}
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/devWarning.ts
|
|
95
|
-
let devWarn = (msg) => {
|
|
96
|
-
globalThis.console?.warn(`[glissade] ${msg}`);
|
|
97
|
-
};
|
|
98
|
-
function setDevWarning(fn) {
|
|
99
|
-
devWarn = fn;
|
|
100
|
-
}
|
|
101
|
-
/** Internal: emit through the configurable channel. */
|
|
102
|
-
function emitDevWarning(message) {
|
|
103
|
-
devWarn(message);
|
|
104
|
-
}
|
|
105
|
-
//#endregion
|
|
106
|
-
//#region src/valueTypes.ts
|
|
107
|
-
/**
|
|
108
|
-
* Value-type registry with pluggable per-type interpolation (DESIGN.md §2.2).
|
|
109
|
-
* `extrapolates` declares whether a type's lerp accepts easedT outside [0,1]
|
|
110
|
-
* (spring overshoot); non-extrapolating types clamp.
|
|
111
|
-
*/
|
|
112
|
-
const registry = /* @__PURE__ */ new Map();
|
|
113
|
-
function registerValueType(vt) {
|
|
114
|
-
registry.set(vt.id, vt);
|
|
115
|
-
}
|
|
116
|
-
var UnknownValueTypeError = class extends Error {
|
|
117
|
-
constructor(id) {
|
|
118
|
-
super(`unknown value type '${id}'; register it via registerValueType()`);
|
|
119
|
-
this.name = "UnknownValueTypeError";
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
function getValueType(id) {
|
|
123
|
-
const vt = registry.get(id);
|
|
124
|
-
if (!vt) throw new UnknownValueTypeError(id);
|
|
125
|
-
return vt;
|
|
126
|
-
}
|
|
127
|
-
const numberType = {
|
|
128
|
-
id: "number",
|
|
129
|
-
lerp: (a, b, t) => a + (b - a) * t,
|
|
130
|
-
extrapolates: true,
|
|
131
|
-
equals: Object.is,
|
|
132
|
-
add: (a, b) => a + b,
|
|
133
|
-
sub: (a, b) => a - b,
|
|
134
|
-
scale: (a, k) => a * k,
|
|
135
|
-
defaultHandoff: "spring"
|
|
136
|
-
};
|
|
137
|
-
const vec2Equals = (a, b) => a[0] === b[0] && a[1] === b[1];
|
|
138
|
-
const vec2Type = {
|
|
139
|
-
id: "vec2",
|
|
140
|
-
lerp: (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t],
|
|
141
|
-
extrapolates: true,
|
|
142
|
-
equals: vec2Equals,
|
|
143
|
-
add: (a, b) => [a[0] + b[0], a[1] + b[1]],
|
|
144
|
-
sub: (a, b) => [a[0] - b[0], a[1] - b[1]],
|
|
145
|
-
scale: (a, k) => [a[0] * k, a[1] * k],
|
|
146
|
-
defaultHandoff: "spring"
|
|
147
|
-
};
|
|
148
|
-
/** vec2 swept along a circular arc: polar lerp of radius + shortest-path angle (§2.2). */
|
|
149
|
-
const vec2ArcType = {
|
|
150
|
-
id: "vec2-arc",
|
|
151
|
-
lerp: (a, b, t) => {
|
|
152
|
-
const ra = Math.hypot(a[0], a[1]);
|
|
153
|
-
const rb = Math.hypot(b[0], b[1]);
|
|
154
|
-
const angA = Math.atan2(a[1], a[0]);
|
|
155
|
-
let dAng = Math.atan2(b[1], b[0]) - angA;
|
|
156
|
-
while (dAng > Math.PI) dAng -= 2 * Math.PI;
|
|
157
|
-
while (dAng < -Math.PI) dAng += 2 * Math.PI;
|
|
158
|
-
const r = ra + (rb - ra) * t;
|
|
159
|
-
const ang = angA + dAng * t;
|
|
160
|
-
return [r * Math.cos(ang), r * Math.sin(ang)];
|
|
161
|
-
},
|
|
162
|
-
extrapolates: true,
|
|
163
|
-
equals: vec2Equals,
|
|
164
|
-
defaultHandoff: "blend-from-frozen"
|
|
165
|
-
};
|
|
166
|
-
const colorType = {
|
|
167
|
-
id: "color",
|
|
168
|
-
lerp: lerpColor,
|
|
169
|
-
extrapolates: true,
|
|
170
|
-
equals: (a, b) => a === b,
|
|
171
|
-
defaultHandoff: "blend-from-frozen"
|
|
172
|
-
};
|
|
173
|
-
/** Discrete types: hold-only by construction (§2.2); lerp snaps at t=1. */
|
|
174
|
-
function discrete(id) {
|
|
175
|
-
return {
|
|
176
|
-
id,
|
|
177
|
-
lerp: (a, b, t) => t >= 1 ? b : a,
|
|
178
|
-
extrapolates: false,
|
|
179
|
-
equals: (a, b) => Object.is(a, b),
|
|
180
|
-
defaultHandoff: "cut"
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
const stringType = discrete("string");
|
|
184
|
-
const booleanType = discrete("boolean");
|
|
185
|
-
const lerpV = (a, b, t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
|
|
186
|
-
/** Topology must match for a morph (contour count, closed flags, vertex counts). */
|
|
187
|
-
function pathTopologyMatches(a, b) {
|
|
188
|
-
if (a.length !== b.length) return false;
|
|
189
|
-
for (let i = 0; i < a.length; i++) {
|
|
190
|
-
const ca = a[i];
|
|
191
|
-
const cb = b[i];
|
|
192
|
-
if (ca.closed !== cb.closed || ca.v.length !== cb.v.length) return false;
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
let warnedPathTopology = false;
|
|
197
|
-
/**
|
|
198
|
-
* Path morphing (§2.2): pairwise lerp of anchors and tangents — exactly how
|
|
199
|
-
* lottie-web morphs, so imported animations are pixel-faithful. Mismatched
|
|
200
|
-
* topology snaps (hold a, then b at t ≥ 1) with a one-time dev warning; the
|
|
201
|
-
* de Casteljau normalization fallback for arbitrary native morphs is tracked
|
|
202
|
-
* future work. Lerp-only: offsets are not well-defined under mismatched
|
|
203
|
-
* topology, so no add/sub/scale — handoffs blend from the frozen value.
|
|
204
|
-
*/
|
|
205
|
-
const pathType = {
|
|
206
|
-
id: "path",
|
|
207
|
-
lerp: (a, b, t) => {
|
|
208
|
-
if (!pathTopologyMatches(a, b)) {
|
|
209
|
-
if (!warnedPathTopology) {
|
|
210
|
-
warnedPathTopology = true;
|
|
211
|
-
emitDevWarning("path lerp with mismatched topology (contour/vertex counts or closed flags differ): snapping instead of morphing — supply matched vertex counts (§2.2)");
|
|
212
|
-
}
|
|
213
|
-
return t >= 1 ? b : a;
|
|
214
|
-
}
|
|
215
|
-
return a.map((ca, i) => {
|
|
216
|
-
const cb = b[i];
|
|
217
|
-
return {
|
|
218
|
-
closed: ca.closed,
|
|
219
|
-
v: ca.v.map((p, j) => lerpV(p, cb.v[j], t)),
|
|
220
|
-
in: ca.in.map((p, j) => lerpV(p, cb.in[j], t)),
|
|
221
|
-
out: ca.out.map((p, j) => lerpV(p, cb.out[j], t))
|
|
222
|
-
};
|
|
223
|
-
});
|
|
224
|
-
},
|
|
225
|
-
extrapolates: false,
|
|
226
|
-
equals: (a, b) => {
|
|
227
|
-
if (a === b) return true;
|
|
228
|
-
if (!pathTopologyMatches(a, b)) return false;
|
|
229
|
-
const eq = (x, y) => x[0] === y[0] && x[1] === y[1];
|
|
230
|
-
return a.every((ca, i) => {
|
|
231
|
-
const cb = b[i];
|
|
232
|
-
return ca.v.every((p, j) => eq(p, cb.v[j])) && ca.in.every((p, j) => eq(p, cb.in[j])) && ca.out.every((p, j) => eq(p, cb.out[j]));
|
|
233
|
-
});
|
|
234
|
-
},
|
|
235
|
-
defaultHandoff: "blend-from-frozen"
|
|
236
|
-
};
|
|
237
|
-
const lerpN = (a, b, t) => a + (b - a) * t;
|
|
238
|
-
const lerpPt = (a, b, t) => [lerpN(a[0], b[0], t), lerpN(a[1], b[1], t)];
|
|
239
|
-
/** Lift a solid color to a uniform gradient matching `shape` (every stop = color),
|
|
240
|
-
* so a color↔gradient tween fades smoothly instead of snapping. */
|
|
241
|
-
function liftColor(color, shape) {
|
|
242
|
-
const stops = shape.stops.map((s) => ({
|
|
243
|
-
offset: s.offset,
|
|
244
|
-
color
|
|
245
|
-
}));
|
|
246
|
-
const interp = shape.interpolation ? { interpolation: shape.interpolation } : {};
|
|
247
|
-
return shape.kind === "radial" ? {
|
|
248
|
-
kind: "radial",
|
|
249
|
-
stops,
|
|
250
|
-
...shape.center ? { center: shape.center } : {},
|
|
251
|
-
...shape.radius !== void 0 ? { radius: shape.radius } : {},
|
|
252
|
-
...interp
|
|
253
|
-
} : {
|
|
254
|
-
kind: "linear",
|
|
255
|
-
stops,
|
|
256
|
-
...shape.from ? { from: shape.from } : {},
|
|
257
|
-
...shape.to ? { to: shape.to } : {},
|
|
258
|
-
...interp
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
let warnedPaintShape = false;
|
|
262
|
-
function paintSnap(t, a, b) {
|
|
263
|
-
if (!warnedPaintShape) {
|
|
264
|
-
warnedPaintShape = true;
|
|
265
|
-
emitDevWarning("paint lerp across mismatched gradient shapes (different kind or stop count): snapping instead of interpolating — match the kind + stop count on both keyframes (§2.2)");
|
|
266
|
-
}
|
|
267
|
-
return t >= 1 ? b : a;
|
|
268
|
-
}
|
|
269
|
-
const stopsEqual = (a, b) => a.length === b.length && a.every((s, i) => s.offset === b[i].offset && s.color === b[i].color);
|
|
270
|
-
/**
|
|
271
|
-
* Gradient paint morphing (§2.2): a solid color lifts to a uniform gradient to
|
|
272
|
-
* meet the other side, then matched-kind/matched-stop-count gradients lerp their
|
|
273
|
-
* stops (offset + oklab color) and geometry pairwise; a mismatched kind or stop
|
|
274
|
-
* count snaps (hold a, then b at t ≥ 1) with a one-time dev warning. Lerp-only —
|
|
275
|
-
* no offsets under mismatch — so handoffs blend from the frozen value.
|
|
276
|
-
*/
|
|
277
|
-
const paintType = {
|
|
278
|
-
id: "paint",
|
|
279
|
-
lerp: (a, b, t) => {
|
|
280
|
-
if (a.kind === "color" && b.kind === "color") return {
|
|
281
|
-
kind: "color",
|
|
282
|
-
color: lerpColor(a.color, b.color, t)
|
|
283
|
-
};
|
|
284
|
-
let A = a;
|
|
285
|
-
let B = b;
|
|
286
|
-
if (A.kind === "color" && B.kind !== "color") A = liftColor(A.color, B);
|
|
287
|
-
else if (B.kind === "color" && A.kind !== "color") B = liftColor(B.color, A);
|
|
288
|
-
if (A.kind === "color" || B.kind === "color" || A.kind !== B.kind || A.stops.length !== B.stops.length) return paintSnap(t, a, b);
|
|
289
|
-
const stops = A.stops.map((sa, i) => ({
|
|
290
|
-
offset: lerpN(sa.offset, B.stops[i].offset, t),
|
|
291
|
-
color: lerpColor(sa.color, B.stops[i].color, t)
|
|
292
|
-
}));
|
|
293
|
-
const interp = A.interpolation ? { interpolation: A.interpolation } : {};
|
|
294
|
-
if (A.kind === "radial" && B.kind === "radial") return {
|
|
295
|
-
kind: "radial",
|
|
296
|
-
stops,
|
|
297
|
-
...A.center && B.center ? { center: lerpPt(A.center, B.center, t) } : A.center ? { center: A.center } : {},
|
|
298
|
-
...A.radius !== void 0 && B.radius !== void 0 ? { radius: lerpN(A.radius, B.radius, t) } : A.radius !== void 0 ? { radius: A.radius } : {},
|
|
299
|
-
...interp
|
|
300
|
-
};
|
|
301
|
-
if (A.kind === "linear" && B.kind === "linear") return {
|
|
302
|
-
kind: "linear",
|
|
303
|
-
stops,
|
|
304
|
-
...A.from && B.from ? { from: lerpPt(A.from, B.from, t) } : A.from ? { from: A.from } : {},
|
|
305
|
-
...A.to && B.to ? { to: lerpPt(A.to, B.to, t) } : A.to ? { to: A.to } : {},
|
|
306
|
-
...interp
|
|
307
|
-
};
|
|
308
|
-
return paintSnap(t, a, b);
|
|
309
|
-
},
|
|
310
|
-
extrapolates: false,
|
|
311
|
-
equals: (a, b) => {
|
|
312
|
-
if (a === b) return true;
|
|
313
|
-
if (a.kind !== b.kind) return false;
|
|
314
|
-
if (a.kind === "color") return b.kind === "color" && a.color === b.color;
|
|
315
|
-
if (b.kind === "color") return false;
|
|
316
|
-
if (!stopsEqual(a.stops, b.stops)) return false;
|
|
317
|
-
if (a.interpolation !== b.interpolation) return false;
|
|
318
|
-
if (a.kind === "radial" && b.kind === "radial") return JSON.stringify(a.center) === JSON.stringify(b.center) && a.radius === b.radius;
|
|
319
|
-
if (a.kind === "linear" && b.kind === "linear") return JSON.stringify(a.from) === JSON.stringify(b.from) && JSON.stringify(a.to) === JSON.stringify(b.to);
|
|
320
|
-
return false;
|
|
321
|
-
},
|
|
322
|
-
defaultHandoff: "blend-from-frozen"
|
|
323
|
-
};
|
|
324
|
-
var ValueTypeInferenceError = class extends Error {
|
|
325
|
-
constructor(value) {
|
|
326
|
-
super(`cannot infer a value type for ${JSON.stringify(value)}; register a custom type`);
|
|
327
|
-
this.name = "ValueTypeInferenceError";
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
const isContour = (c) => typeof c === "object" && c !== null && typeof c.closed === "boolean" && Array.isArray(c.v) && Array.isArray(c.in) && Array.isArray(c.out);
|
|
331
|
-
const isPaint = (v) => typeof v === "object" && v !== null && (v.kind === "color" || v.kind === "linear" || v.kind === "radial");
|
|
332
|
-
/** Infer a registered type id from a sample value (builder + bake authoring surfaces). */
|
|
333
|
-
function inferValueType(value) {
|
|
334
|
-
if (typeof value === "number") return "number";
|
|
335
|
-
if (typeof value === "boolean") return "boolean";
|
|
336
|
-
if (isPaint(value)) return "paint";
|
|
337
|
-
if (Array.isArray(value) && value.length === 2 && value.every((v) => typeof v === "number")) return "vec2";
|
|
338
|
-
if (Array.isArray(value) && value.length > 0 && value.every(isContour)) return "path";
|
|
339
|
-
if (typeof value === "string") try {
|
|
340
|
-
parseColor(value);
|
|
341
|
-
return "color";
|
|
342
|
-
} catch {
|
|
343
|
-
return "string";
|
|
344
|
-
}
|
|
345
|
-
throw new ValueTypeInferenceError(value);
|
|
346
|
-
}
|
|
347
|
-
registerValueType(numberType);
|
|
348
|
-
registerValueType(vec2Type);
|
|
349
|
-
registerValueType(vec2ArcType);
|
|
350
|
-
registerValueType(colorType);
|
|
351
|
-
registerValueType(stringType);
|
|
352
|
-
registerValueType(booleanType);
|
|
353
|
-
registerValueType(pathType);
|
|
354
|
-
registerValueType(paintType);
|
|
355
|
-
//#endregion
|
|
356
|
-
//#region src/easing.ts
|
|
357
|
-
const c1 = 1.70158;
|
|
358
|
-
const c2 = c1 * 1.525;
|
|
359
|
-
const c3 = 2.70158;
|
|
360
|
-
const c4 = 2 * Math.PI / 3;
|
|
361
|
-
const c5 = 2 * Math.PI / 4.5;
|
|
362
|
-
function bounceOut(t) {
|
|
363
|
-
const n1 = 7.5625;
|
|
364
|
-
const d1 = 2.75;
|
|
365
|
-
if (t < 1 / d1) return n1 * t * t;
|
|
366
|
-
if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + .75;
|
|
367
|
-
if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + .9375;
|
|
368
|
-
return n1 * (t -= 2.625 / d1) * t + .984375;
|
|
369
|
-
}
|
|
370
|
-
const easings = {
|
|
371
|
-
linear: (t) => t,
|
|
372
|
-
easeInQuad: (t) => t * t,
|
|
373
|
-
easeOutQuad: (t) => 1 - (1 - t) * (1 - t),
|
|
374
|
-
easeInOutQuad: (t) => t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2,
|
|
375
|
-
easeInCubic: (t) => t ** 3,
|
|
376
|
-
easeOutCubic: (t) => 1 - (1 - t) ** 3,
|
|
377
|
-
easeInOutCubic: (t) => t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2,
|
|
378
|
-
easeInQuart: (t) => t ** 4,
|
|
379
|
-
easeOutQuart: (t) => 1 - (1 - t) ** 4,
|
|
380
|
-
easeInOutQuart: (t) => t < .5 ? 8 * t ** 4 : 1 - (-2 * t + 2) ** 4 / 2,
|
|
381
|
-
easeInQuint: (t) => t ** 5,
|
|
382
|
-
easeOutQuint: (t) => 1 - (1 - t) ** 5,
|
|
383
|
-
easeInOutQuint: (t) => t < .5 ? 16 * t ** 5 : 1 - (-2 * t + 2) ** 5 / 2,
|
|
384
|
-
easeInSine: (t) => 1 - Math.cos(t * Math.PI / 2),
|
|
385
|
-
easeOutSine: (t) => Math.sin(t * Math.PI / 2),
|
|
386
|
-
easeInOutSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
|
|
387
|
-
easeInExpo: (t) => t === 0 ? 0 : 2 ** (10 * t - 10),
|
|
388
|
-
easeOutExpo: (t) => t === 1 ? 1 : 1 - 2 ** (-10 * t),
|
|
389
|
-
easeInOutExpo: (t) => t === 0 ? 0 : t === 1 ? 1 : t < .5 ? 2 ** (20 * t - 10) / 2 : (2 - 2 ** (-20 * t + 10)) / 2,
|
|
390
|
-
easeInCirc: (t) => 1 - Math.sqrt(1 - t * t),
|
|
391
|
-
easeOutCirc: (t) => Math.sqrt(1 - (t - 1) * (t - 1)),
|
|
392
|
-
easeInOutCirc: (t) => t < .5 ? (1 - Math.sqrt(1 - (2 * t) ** 2)) / 2 : (Math.sqrt(1 - (-2 * t + 2) ** 2) + 1) / 2,
|
|
393
|
-
easeInBack: (t) => c3 * t ** 3 - c1 * t * t,
|
|
394
|
-
easeOutBack: (t) => 1 + c3 * (t - 1) ** 3 + c1 * (t - 1) ** 2,
|
|
395
|
-
easeInOutBack: (t) => t < .5 ? (2 * t) ** 2 * (3.5949095 * 2 * t - c2) / 2 : ((2 * t - 2) ** 2 * (3.5949095 * (t * 2 - 2) + c2) + 2) / 2,
|
|
396
|
-
easeInElastic: (t) => t === 0 ? 0 : t === 1 ? 1 : -(2 ** (10 * t - 10)) * Math.sin((t * 10 - 10.75) * c4),
|
|
397
|
-
easeOutElastic: (t) => t === 0 ? 0 : t === 1 ? 1 : 2 ** (-10 * t) * Math.sin((t * 10 - .75) * c4) + 1,
|
|
398
|
-
easeInOutElastic: (t) => t === 0 ? 0 : t === 1 ? 1 : t < .5 ? -(2 ** (20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 : 2 ** (-20 * t + 10) * Math.sin((20 * t - 11.125) * c5) / 2 + 1,
|
|
399
|
-
easeInBounce: (t) => 1 - bounceOut(1 - t),
|
|
400
|
-
easeOutBounce: bounceOut,
|
|
401
|
-
easeInOutBounce: (t) => t < .5 ? (1 - bounceOut(1 - 2 * t)) / 2 : (1 + bounceOut(2 * t - 1)) / 2
|
|
402
|
-
};
|
|
403
|
-
function bounceOutD(t) {
|
|
404
|
-
const n1 = 7.5625;
|
|
405
|
-
const d1 = 2.75;
|
|
406
|
-
if (t < 1 / d1) return 2 * n1 * t;
|
|
407
|
-
if (t < 2 / d1) return 2 * n1 * (t - 1.5 / d1);
|
|
408
|
-
if (t < 2.5 / d1) return 2 * n1 * (t - 2.25 / d1);
|
|
409
|
-
return 2 * n1 * (t - 2.625 / d1);
|
|
410
|
-
}
|
|
411
|
-
const LN2 = Math.LN2;
|
|
412
|
-
/**
|
|
413
|
-
* Analytic derivatives d(u) of every named ease (§B.6) — closed-form, used
|
|
414
|
-
* for reading velocity off in-flight curves at interruption time. Property-
|
|
415
|
-
* tested against central differences at interior points.
|
|
416
|
-
*/
|
|
417
|
-
const easingDerivatives = {
|
|
418
|
-
linear: () => 1,
|
|
419
|
-
easeInQuad: (t) => 2 * t,
|
|
420
|
-
easeOutQuad: (t) => 2 * (1 - t),
|
|
421
|
-
easeInOutQuad: (t) => t < .5 ? 4 * t : 4 * (1 - t),
|
|
422
|
-
easeInCubic: (t) => 3 * t ** 2,
|
|
423
|
-
easeOutCubic: (t) => 3 * (1 - t) ** 2,
|
|
424
|
-
easeInOutCubic: (t) => t < .5 ? 12 * t ** 2 : 12 * (1 - t) ** 2,
|
|
425
|
-
easeInQuart: (t) => 4 * t ** 3,
|
|
426
|
-
easeOutQuart: (t) => 4 * (1 - t) ** 3,
|
|
427
|
-
easeInOutQuart: (t) => t < .5 ? 32 * t ** 3 : 32 * (1 - t) ** 3,
|
|
428
|
-
easeInQuint: (t) => 5 * t ** 4,
|
|
429
|
-
easeOutQuint: (t) => 5 * (1 - t) ** 4,
|
|
430
|
-
easeInOutQuint: (t) => t < .5 ? 80 * t ** 4 : 80 * (1 - t) ** 4,
|
|
431
|
-
easeInSine: (t) => Math.PI / 2 * Math.sin(t * Math.PI / 2),
|
|
432
|
-
easeOutSine: (t) => Math.PI / 2 * Math.cos(t * Math.PI / 2),
|
|
433
|
-
easeInOutSine: (t) => Math.PI / 2 * Math.sin(Math.PI * t),
|
|
434
|
-
easeInExpo: (t) => t === 0 ? 0 : 10 * LN2 * 2 ** (10 * t - 10),
|
|
435
|
-
easeOutExpo: (t) => t === 1 ? 0 : 10 * LN2 * 2 ** (-10 * t),
|
|
436
|
-
easeInOutExpo: (t) => t === 0 || t === 1 ? 0 : t < .5 ? 10 * LN2 * 2 ** (20 * t - 10) : 10 * LN2 * 2 ** (-20 * t + 10),
|
|
437
|
-
easeInCirc: (t) => t / Math.sqrt(1 - t * t),
|
|
438
|
-
easeOutCirc: (t) => (1 - t) / Math.sqrt(1 - (t - 1) * (t - 1)),
|
|
439
|
-
easeInOutCirc: (t) => t < .5 ? 2 * t / Math.sqrt(1 - 4 * t * t) : (2 - 2 * t) / Math.sqrt(1 - (-2 * t + 2) ** 2),
|
|
440
|
-
easeInBack: (t) => 3 * c3 * t ** 2 - 2 * c1 * t,
|
|
441
|
-
easeOutBack: (t) => 3 * c3 * (t - 1) ** 2 + 2 * c1 * (t - 1),
|
|
442
|
-
easeInOutBack: (t) => t < .5 ? 12 * 3.5949095 * t ** 2 - 4 * c2 * t : 3 * 3.5949095 * (2 * t - 2) ** 2 + 2 * c2 * (2 * t - 2),
|
|
443
|
-
easeInElastic: (t) => {
|
|
444
|
-
if (t === 0 || t === 1) return 0;
|
|
445
|
-
const theta = (t * 10 - 10.75) * c4;
|
|
446
|
-
const amp = 2 ** (10 * t - 10);
|
|
447
|
-
return -(10 * LN2 * amp * Math.sin(theta) + amp * 10 * c4 * Math.cos(theta));
|
|
448
|
-
},
|
|
449
|
-
easeOutElastic: (t) => {
|
|
450
|
-
if (t === 0 || t === 1) return 0;
|
|
451
|
-
const phi = (t * 10 - .75) * c4;
|
|
452
|
-
const amp = 2 ** (-10 * t);
|
|
453
|
-
return -10 * LN2 * amp * Math.sin(phi) + amp * 10 * c4 * Math.cos(phi);
|
|
454
|
-
},
|
|
455
|
-
easeInOutElastic: (t) => {
|
|
456
|
-
if (t === 0 || t === 1) return 0;
|
|
457
|
-
const psi = (20 * t - 11.125) * c5;
|
|
458
|
-
if (t < .5) {
|
|
459
|
-
const amp = 2 ** (20 * t - 10);
|
|
460
|
-
return -(20 * LN2 * amp * Math.sin(psi) + amp * 20 * c5 * Math.cos(psi)) / 2;
|
|
461
|
-
}
|
|
462
|
-
const amp = 2 ** (-20 * t + 10);
|
|
463
|
-
return (-20 * LN2 * amp * Math.sin(psi) + amp * 20 * c5 * Math.cos(psi)) / 2;
|
|
464
|
-
},
|
|
465
|
-
easeInBounce: (t) => bounceOutD(1 - t),
|
|
466
|
-
easeOutBounce: bounceOutD,
|
|
467
|
-
easeInOutBounce: (t) => t < .5 ? bounceOutD(1 - 2 * t) : bounceOutD(2 * t - 1)
|
|
468
|
-
};
|
|
469
|
-
/** Default property-tween ease (Motion Canvas precedent). */
|
|
470
|
-
const DEFAULT_EASE = "easeInOutCubic";
|
|
471
|
-
function bezierKernel(p1x, p1y, p2x, p2y) {
|
|
472
|
-
const ax = 3 * p1x - 3 * p2x + 1;
|
|
473
|
-
const bx = 3 * p2x - 6 * p1x;
|
|
474
|
-
const cx = 3 * p1x;
|
|
475
|
-
const ay = 3 * p1y - 3 * p2y + 1;
|
|
476
|
-
const by = 3 * p2y - 6 * p1y;
|
|
477
|
-
const cy = 3 * p1y;
|
|
478
|
-
const sampleX = (u) => ((ax * u + bx) * u + cx) * u;
|
|
479
|
-
const sampleY = (u) => ((ay * u + by) * u + cy) * u;
|
|
480
|
-
const sampleDX = (u) => (3 * ax * u + 2 * bx) * u + cx;
|
|
481
|
-
const sampleDY = (u) => (3 * ay * u + 2 * by) * u + cy;
|
|
482
|
-
const solveU = (x) => {
|
|
483
|
-
let u = x;
|
|
484
|
-
for (let i = 0; i < 8; i++) {
|
|
485
|
-
const err = sampleX(u) - x;
|
|
486
|
-
if (Math.abs(err) < 1e-7) return u;
|
|
487
|
-
const d = sampleDX(u);
|
|
488
|
-
if (Math.abs(d) < 1e-6) break;
|
|
489
|
-
u -= err / d;
|
|
490
|
-
}
|
|
491
|
-
let lo = 0;
|
|
492
|
-
let hi = 1;
|
|
493
|
-
u = x;
|
|
494
|
-
while (hi - lo > 1e-7) {
|
|
495
|
-
if (sampleX(u) < x) lo = u;
|
|
496
|
-
else hi = u;
|
|
497
|
-
u = (lo + hi) / 2;
|
|
498
|
-
}
|
|
499
|
-
return u;
|
|
500
|
-
};
|
|
501
|
-
return {
|
|
502
|
-
sampleY,
|
|
503
|
-
sampleDX,
|
|
504
|
-
sampleDY,
|
|
505
|
-
solveU
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* CSS-style cubic bézier where x is time and y is progress. Newton's method
|
|
510
|
-
* with a bisection fallback for the flat-derivative regions.
|
|
511
|
-
*/
|
|
512
|
-
function cubicBezier(p1x, p1y, p2x, p2y) {
|
|
513
|
-
const k = bezierKernel(p1x, p1y, p2x, p2y);
|
|
514
|
-
return (t) => {
|
|
515
|
-
if (t <= 0) return 0;
|
|
516
|
-
if (t >= 1) return 1;
|
|
517
|
-
return k.sampleY(k.solveU(t));
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
/** Analytic dy/dx of a cubic bézier ease: y'(s)/x'(s) at the solved parameter (§B.6). */
|
|
521
|
-
function cubicBezierDerivative(p1x, p1y, p2x, p2y) {
|
|
522
|
-
const k = bezierKernel(p1x, p1y, p2x, p2y);
|
|
523
|
-
return (t) => {
|
|
524
|
-
const u = k.solveU(Math.min(1, Math.max(0, t)));
|
|
525
|
-
const dx = k.sampleDX(u);
|
|
526
|
-
if (Math.abs(dx) < 1e-9) return 0;
|
|
527
|
-
return k.sampleDY(u) / dx;
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
var UnknownEasingError = class extends Error {
|
|
531
|
-
constructor(name) {
|
|
532
|
-
super(`unknown easing '${name}'; register it via easings or use cubicBezier/spring`);
|
|
533
|
-
this.name = "UnknownEasingError";
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
function namedEasing(name) {
|
|
537
|
-
const fn = easings[name];
|
|
538
|
-
if (!fn) throw new UnknownEasingError(name);
|
|
539
|
-
return fn;
|
|
540
|
-
}
|
|
541
|
-
//#endregion
|
|
542
|
-
//#region src/spring.ts
|
|
543
|
-
const DEFAULT_SETTLE_TOLERANCE = .005;
|
|
544
|
-
function params(cfg) {
|
|
545
|
-
const mass = cfg.mass ?? 1;
|
|
546
|
-
if (!(cfg.stiffness > 0) || !(cfg.damping > 0) || !(mass > 0)) throw new RangeError("spring stiffness, damping, and mass must all be > 0");
|
|
547
|
-
return {
|
|
548
|
-
w0: Math.sqrt(cfg.stiffness / mass),
|
|
549
|
-
zeta: cfg.damping / (2 * Math.sqrt(cfg.stiffness * mass))
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
/** Raw closed-form spring velocity at time t — d/dt of rawValue's three branches. */
|
|
553
|
-
function rawDerivative(cfg, t) {
|
|
554
|
-
if (t <= 0) return 0;
|
|
555
|
-
const { w0, zeta } = params(cfg);
|
|
556
|
-
if (Math.abs(zeta - 1) < 1e-9) return w0 * w0 * t * Math.exp(-w0 * t);
|
|
557
|
-
if (zeta < 1) {
|
|
558
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
559
|
-
return Math.exp(-zeta * w0 * t) * (w0 * w0 / wd) * Math.sin(wd * t);
|
|
560
|
-
}
|
|
561
|
-
const s = Math.sqrt(zeta * zeta - 1);
|
|
562
|
-
const r1 = -w0 * (zeta - s);
|
|
563
|
-
const r2 = -w0 * (zeta + s);
|
|
564
|
-
return r1 * r2 * (Math.exp(r1 * t) - Math.exp(r2 * t)) / (r1 - r2);
|
|
565
|
-
}
|
|
566
|
-
/** Raw closed-form spring position at time t (seconds). Approaches 1, may overshoot. */
|
|
567
|
-
function rawValue(cfg, t) {
|
|
568
|
-
if (t <= 0) return 0;
|
|
569
|
-
const { w0, zeta } = params(cfg);
|
|
570
|
-
if (Math.abs(zeta - 1) < 1e-9) return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
|
571
|
-
if (zeta < 1) {
|
|
572
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
573
|
-
return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd * t) + zeta * w0 / wd * Math.sin(wd * t));
|
|
574
|
-
}
|
|
575
|
-
const s = Math.sqrt(zeta * zeta - 1);
|
|
576
|
-
const r1 = -w0 * (zeta - s);
|
|
577
|
-
const r2 = -w0 * (zeta + s);
|
|
578
|
-
return 1 + (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r1 - r2);
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Settle duration: the earliest time after which |x - 1| stays within
|
|
582
|
-
* settleTolerance. Closed-form via the decay envelope for the underdamped
|
|
583
|
-
* case; bisection on the monotone tail otherwise. Deterministic.
|
|
584
|
-
*/
|
|
585
|
-
function duration(cfg, opts) {
|
|
586
|
-
const tol = opts?.settleTolerance ?? DEFAULT_SETTLE_TOLERANCE;
|
|
587
|
-
const { w0, zeta } = params(cfg);
|
|
588
|
-
if (zeta < 1) {
|
|
589
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
590
|
-
const amp = Math.sqrt(1 + (zeta * w0 / wd) ** 2);
|
|
591
|
-
return Math.log(amp / tol) / (zeta * w0);
|
|
592
|
-
}
|
|
593
|
-
let hi = 1 / w0;
|
|
594
|
-
while (1 - rawValue(cfg, hi) > tol) hi *= 2;
|
|
595
|
-
let lo = 0;
|
|
596
|
-
for (let i = 0; i < 64 && hi - lo > 1e-9; i++) {
|
|
597
|
-
const mid = (lo + hi) / 2;
|
|
598
|
-
if (1 - rawValue(cfg, mid) > tol) lo = mid;
|
|
599
|
-
else hi = mid;
|
|
600
|
-
}
|
|
601
|
-
return hi;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Spring progress at local time t, affinely rescaled so value(duration) = 1
|
|
605
|
-
* exactly — the raw form only approaches 1, and an unscaled curve would snap
|
|
606
|
-
* at the key (§2.7 "endpoint continuity"). May exceed 1 (overshoot).
|
|
607
|
-
*/
|
|
608
|
-
function value(cfg, t, opts) {
|
|
609
|
-
const d = duration(cfg, opts);
|
|
610
|
-
return rawValue(cfg, Math.min(t, d)) / rawValue(cfg, d);
|
|
611
|
-
}
|
|
612
|
-
function retarget(cfg, x0, v0) {
|
|
613
|
-
const { w0, zeta } = params(cfg);
|
|
614
|
-
let value0;
|
|
615
|
-
let velocity0;
|
|
616
|
-
let envelope;
|
|
617
|
-
if (Math.abs(zeta - 1) < 1e-9) {
|
|
618
|
-
const b = v0 + w0 * x0;
|
|
619
|
-
value0 = (tau) => tau <= 0 ? x0 : Math.exp(-w0 * tau) * (x0 + b * tau);
|
|
620
|
-
velocity0 = (tau) => tau <= 0 ? v0 : Math.exp(-w0 * tau) * (v0 - w0 * b * tau);
|
|
621
|
-
envelope = (tau) => Math.exp(-w0 * tau) * (Math.abs(x0) + Math.abs(b) * tau);
|
|
622
|
-
} else if (zeta < 1) {
|
|
623
|
-
const wd = w0 * Math.sqrt(1 - zeta * zeta);
|
|
624
|
-
const c2v = (v0 + zeta * w0 * x0) / wd;
|
|
625
|
-
const amp = Math.hypot(x0, c2v);
|
|
626
|
-
value0 = (tau) => tau <= 0 ? x0 : Math.exp(-zeta * w0 * tau) * (x0 * Math.cos(wd * tau) + c2v * Math.sin(wd * tau));
|
|
627
|
-
velocity0 = (tau) => tau <= 0 ? v0 : Math.exp(-zeta * w0 * tau) * (v0 * Math.cos(wd * tau) - (w0 * w0 * x0 + zeta * w0 * v0) / wd * Math.sin(wd * tau));
|
|
628
|
-
envelope = (tau) => Math.exp(-zeta * w0 * tau) * amp;
|
|
629
|
-
} else {
|
|
630
|
-
const s = Math.sqrt(zeta * zeta - 1);
|
|
631
|
-
const rp = w0 * (-zeta + s);
|
|
632
|
-
const rm = w0 * (-zeta - s);
|
|
633
|
-
const cp = (v0 - rm * x0) / (rp - rm);
|
|
634
|
-
const cm = (rp * x0 - v0) / (rp - rm);
|
|
635
|
-
value0 = (tau) => tau <= 0 ? x0 : cp * Math.exp(rp * tau) + cm * Math.exp(rm * tau);
|
|
636
|
-
velocity0 = (tau) => tau <= 0 ? v0 : cp * rp * Math.exp(rp * tau) + cm * rm * Math.exp(rm * tau);
|
|
637
|
-
envelope = (tau) => Math.abs(cp) * Math.exp(rp * tau) + Math.abs(cm) * Math.exp(rm * tau);
|
|
638
|
-
}
|
|
639
|
-
const settleTime = (tol) => {
|
|
640
|
-
const eps = tol ?? Math.abs(x0) * .005 + 1e-6;
|
|
641
|
-
if (envelope(0) <= eps && Math.abs(v0) < 1e-12) return 0;
|
|
642
|
-
let hi = 1 / w0;
|
|
643
|
-
let guard = 0;
|
|
644
|
-
while (envelope(hi) > eps && guard++ < 64) hi *= 2;
|
|
645
|
-
let lo = 0;
|
|
646
|
-
for (let i = 0; i < 64 && hi - lo > 1e-9; i++) {
|
|
647
|
-
const mid = (lo + hi) / 2;
|
|
648
|
-
if (Math.max(envelope(mid), envelope(mid * 1.05 + 1e-6)) > eps) lo = mid;
|
|
649
|
-
else hi = mid;
|
|
650
|
-
}
|
|
651
|
-
return hi;
|
|
652
|
-
};
|
|
653
|
-
return {
|
|
654
|
-
value: value0,
|
|
655
|
-
velocity: velocity0,
|
|
656
|
-
settleTime
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
const spring = Object.assign((cfg) => {
|
|
660
|
-
params(cfg);
|
|
661
|
-
return {
|
|
662
|
-
kind: "spring",
|
|
663
|
-
stiffness: cfg.stiffness,
|
|
664
|
-
damping: cfg.damping,
|
|
665
|
-
mass: cfg.mass ?? 1
|
|
666
|
-
};
|
|
667
|
-
}, {
|
|
668
|
-
duration,
|
|
669
|
-
value,
|
|
670
|
-
retarget
|
|
671
|
-
});
|
|
672
|
-
/**
|
|
673
|
-
* Named spring feels (react-spring conventions, mass 1) — a vocabulary instead
|
|
674
|
-
* of hand-tuned stiffness/damping. Use `spring(springPresets.wobbly)` or
|
|
675
|
-
* `springTo(t, a, b, springPresets.gentle)`.
|
|
676
|
-
*/
|
|
677
|
-
const springPresets = {
|
|
678
|
-
default: {
|
|
679
|
-
stiffness: 170,
|
|
680
|
-
damping: 26
|
|
681
|
-
},
|
|
682
|
-
gentle: {
|
|
683
|
-
stiffness: 120,
|
|
684
|
-
damping: 14
|
|
685
|
-
},
|
|
686
|
-
wobbly: {
|
|
687
|
-
stiffness: 180,
|
|
688
|
-
damping: 12
|
|
689
|
-
},
|
|
690
|
-
stiff: {
|
|
691
|
-
stiffness: 210,
|
|
692
|
-
damping: 20
|
|
693
|
-
},
|
|
694
|
-
slow: {
|
|
695
|
-
stiffness: 280,
|
|
696
|
-
damping: 60
|
|
697
|
-
},
|
|
698
|
-
molasses: {
|
|
699
|
-
stiffness: 280,
|
|
700
|
-
damping: 120
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
/**
|
|
704
|
-
* The spring as a normalized easing over a segment whose length must equal
|
|
705
|
-
* spring.duration(cfg) (validated at the document layer, §2.7).
|
|
706
|
-
*/
|
|
707
|
-
function springEasing(cfg) {
|
|
708
|
-
const d = duration(cfg);
|
|
709
|
-
return (p) => value(cfg, p * d);
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Analytic d/dp of springEasing (§B.6): oscillator derivative × the affine
|
|
713
|
-
* rescale factor × duration (chain rule p → t = p·D). Flat past p=1,
|
|
714
|
-
* matching value()'s clamp (right-derivative convention).
|
|
715
|
-
*/
|
|
716
|
-
function springEasingDerivative(cfg) {
|
|
717
|
-
const d = duration(cfg);
|
|
718
|
-
const scale = d / rawValue(cfg, d);
|
|
719
|
-
return (p) => p >= 1 ? 0 : rawDerivative(cfg, p * d) * scale;
|
|
720
|
-
}
|
|
721
|
-
//#endregion
|
|
722
|
-
//#region src/track.ts
|
|
723
|
-
/**
|
|
724
|
-
* Track & keyframe model (DESIGN.md §2.2) and sampling (§2.4): binary search
|
|
725
|
-
* per track with a last-segment cursor — sanctioned memoization, semantics
|
|
726
|
-
* identical to a cold search (property-tested).
|
|
727
|
-
*/
|
|
728
|
-
var TrackValidationError = class extends Error {
|
|
729
|
-
constructor(target, message) {
|
|
730
|
-
super(`track '${target}': ${message}`);
|
|
731
|
-
this.name = "TrackValidationError";
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
function validateTrack(track) {
|
|
735
|
-
const vt = getValueType(track.type);
|
|
736
|
-
if (track.keys.length === 0) throw new TrackValidationError(track.target, "must have at least one key");
|
|
737
|
-
if (vt.defaultHandoff === "cut") {
|
|
738
|
-
for (const k of track.keys) if (k.interp !== "hold") k.interp = "hold";
|
|
739
|
-
}
|
|
740
|
-
for (let i = 1; i < track.keys.length; i++) {
|
|
741
|
-
const prev = track.keys[i - 1];
|
|
742
|
-
const cur = track.keys[i];
|
|
743
|
-
if (cur.t <= prev.t) throw new TrackValidationError(track.target, `keys must be strictly increasing in t (key[${i}] at t=${cur.t} after t=${prev.t})`);
|
|
744
|
-
}
|
|
745
|
-
if (!/^[^/]+\/.+$/.test(track.target)) throw new TrackValidationError(track.target, "target must be '<nodeId>/<prop.path>' (e.g. 'circle/opacity')");
|
|
746
|
-
}
|
|
747
|
-
function key(t, value, easeOrOpts) {
|
|
748
|
-
if (easeOrOpts === void 0) return {
|
|
749
|
-
t,
|
|
750
|
-
value
|
|
751
|
-
};
|
|
752
|
-
if (typeof easeOrOpts === "string" || "kind" in easeOrOpts) return {
|
|
753
|
-
t,
|
|
754
|
-
value,
|
|
755
|
-
ease: easeOrOpts
|
|
756
|
-
};
|
|
757
|
-
const opts = easeOrOpts;
|
|
758
|
-
const k = {
|
|
759
|
-
t,
|
|
760
|
-
value
|
|
761
|
-
};
|
|
762
|
-
if (opts.ease !== void 0) k.ease = opts.ease;
|
|
763
|
-
if (opts.interp !== void 0) k.interp = opts.interp;
|
|
764
|
-
if (opts.id !== void 0) k.id = opts.id;
|
|
765
|
-
if (opts.derived !== void 0) k.derived = opts.derived;
|
|
766
|
-
return k;
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* The settle-ON-the-beat helper: a spring key must sit at prev.t +
|
|
770
|
-
* spring.duration(cfg) (§2.7), so beat-anchored authoring otherwise means
|
|
771
|
-
* hand-computing the launch time. springTo returns the [launch, settle] key
|
|
772
|
-
* pair with the arithmetic done — spread it into a raw track():
|
|
773
|
-
* track('x/width', 'number', [...springTo(beats.start('drop'), 0, 320, cfg)])
|
|
774
|
-
*/
|
|
775
|
-
function springTo(endT, from, to, cfg) {
|
|
776
|
-
const d = spring.duration(cfg);
|
|
777
|
-
if (endT - d < 0) throw new TrackValidationError("springTo", `this spring needs ${d.toFixed(3)}s to settle — endT must be ≥ its duration (got ${endT})`);
|
|
778
|
-
return [key(endT - d, from), key(endT, to, spring(cfg))];
|
|
779
|
-
}
|
|
780
|
-
/**
|
|
781
|
-
* Cascade a set of tracks by shifting each one's key times — the classic
|
|
782
|
-
* stagger for animating a list of nodes with a delay between them. `delay` is
|
|
783
|
-
* the per-index gap in seconds, or a function of the index for non-linear
|
|
784
|
-
* cascades. Pure: returns new tracks, leaving the inputs untouched.
|
|
785
|
-
*
|
|
786
|
-
* stagger(items.map((it, i) => track(`${it.id}/opacity`, 'number',
|
|
787
|
-
* [key(0, 0), key(0.3, 1, 'easeOutCubic')])), 0.08)
|
|
788
|
-
*/
|
|
789
|
-
function stagger(tracks, delay) {
|
|
790
|
-
const at = typeof delay === "function" ? delay : (i) => i * delay;
|
|
791
|
-
return tracks.map((tr, i) => {
|
|
792
|
-
const d = at(i);
|
|
793
|
-
return {
|
|
794
|
-
...tr,
|
|
795
|
-
keys: tr.keys.map((k) => ({
|
|
796
|
-
...k,
|
|
797
|
-
t: k.t + d
|
|
798
|
-
}))
|
|
799
|
-
};
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
function track(target, type, keys, opts) {
|
|
803
|
-
const tr = {
|
|
804
|
-
target,
|
|
805
|
-
type,
|
|
806
|
-
keys
|
|
807
|
-
};
|
|
808
|
-
if (opts?.editable !== void 0) tr.editable = opts.editable;
|
|
809
|
-
validateTrack(tr);
|
|
810
|
-
return tr;
|
|
811
|
-
}
|
|
812
|
-
function resolveEase(spec) {
|
|
813
|
-
if (spec === void 0) return namedEasing("linear");
|
|
814
|
-
if (typeof spec === "string") return namedEasing(spec);
|
|
815
|
-
if (spec.kind === "cubicBezier") return cubicBezier(...spec.pts);
|
|
816
|
-
return springEasing(spec);
|
|
817
|
-
}
|
|
818
|
-
const warnedNumericDerivative = /* @__PURE__ */ new Set();
|
|
819
|
-
/**
|
|
820
|
-
* Analytic d(u) for an ease spec (§B.6). Custom-registered eases without a
|
|
821
|
-
* derivative fall back to a symmetric difference with a one-time dev warning.
|
|
822
|
-
*/
|
|
823
|
-
function resolveEaseDerivative(spec) {
|
|
824
|
-
if (spec === void 0) return easingDerivatives["linear"];
|
|
825
|
-
if (typeof spec === "string") {
|
|
826
|
-
const d = easingDerivatives[spec];
|
|
827
|
-
if (d) return d;
|
|
828
|
-
const fn = namedEasing(spec);
|
|
829
|
-
if (!warnedNumericDerivative.has(spec)) {
|
|
830
|
-
warnedNumericDerivative.add(spec);
|
|
831
|
-
emitDevWarning(`easing '${spec}' has no registered derivative; velocity uses a numeric fallback — register one in easingDerivatives for exact interruption handoff`);
|
|
832
|
-
}
|
|
833
|
-
const h = 1 / 1024;
|
|
834
|
-
return (u) => (fn(Math.min(1, u + h)) - fn(Math.max(0, u - h))) / (Math.min(1, u + h) - Math.max(0, u - h));
|
|
835
|
-
}
|
|
836
|
-
if (spec.kind === "cubicBezier") return cubicBezierDerivative(...spec.pts);
|
|
837
|
-
return springEasingDerivative(spec);
|
|
838
|
-
}
|
|
839
|
-
const samplerStates = /* @__PURE__ */ new WeakMap();
|
|
840
|
-
function state(tr) {
|
|
841
|
-
let s = samplerStates.get(tr);
|
|
842
|
-
if (!s) {
|
|
843
|
-
s = {
|
|
844
|
-
cursor: 1,
|
|
845
|
-
easeCache: new Array(tr.keys.length)
|
|
846
|
-
};
|
|
847
|
-
samplerStates.set(tr, s);
|
|
848
|
-
}
|
|
849
|
-
return s;
|
|
850
|
-
}
|
|
851
|
-
function easeFor(tr, s, i) {
|
|
852
|
-
let fn = s.easeCache[i];
|
|
853
|
-
if (!fn) {
|
|
854
|
-
fn = resolveEase(tr.keys[i].ease);
|
|
855
|
-
s.easeCache[i] = fn;
|
|
856
|
-
}
|
|
857
|
-
return fn;
|
|
858
|
-
}
|
|
859
|
-
/**
|
|
860
|
-
* Find i such that keys[i-1].t <= t < keys[i].t, i.e. the index of the
|
|
861
|
-
* arrival key of the segment containing t. Returns 0 if t < first key,
|
|
862
|
-
* keys.length if t >= last key.
|
|
863
|
-
*/
|
|
864
|
-
function findSegment(keys, t, hint) {
|
|
865
|
-
const n = keys.length;
|
|
866
|
-
for (let i = Math.max(1, hint - 1); i <= Math.min(n - 1, hint + 1); i++) if (keys[i - 1].t <= t && t < keys[i].t) return i;
|
|
867
|
-
if (t < keys[0].t) return 0;
|
|
868
|
-
if (t >= keys[n - 1].t) return n;
|
|
869
|
-
let lo = 1;
|
|
870
|
-
let hi = n - 1;
|
|
871
|
-
while (lo < hi) {
|
|
872
|
-
const mid = lo + hi >> 1;
|
|
873
|
-
if (keys[mid].t <= t) lo = mid + 1;
|
|
874
|
-
else hi = mid;
|
|
875
|
-
}
|
|
876
|
-
return lo;
|
|
877
|
-
}
|
|
878
|
-
/**
|
|
879
|
-
* Analytic track derivative at time t, in value-units per second of local
|
|
880
|
-
* track time (v2 addendum §B.3/§B.6 conventions, pinned):
|
|
881
|
-
* (a) at a key boundary, velocity is the RIGHT derivative;
|
|
882
|
-
* (b) hold segments and the clamped regions outside the keys have v = 0;
|
|
883
|
-
* (c) types without sub/scale operators return null (no kinetic velocity).
|
|
884
|
-
*/
|
|
885
|
-
function velocityAt(tr, t) {
|
|
886
|
-
const vt = getValueType(tr.type);
|
|
887
|
-
if (!vt.sub || !vt.scale) return null;
|
|
888
|
-
const keys = tr.keys;
|
|
889
|
-
const n = keys.length;
|
|
890
|
-
const s = state(tr);
|
|
891
|
-
const i = findSegment(keys, t, s.cursor);
|
|
892
|
-
const zero = vt.scale(vt.sub(keys[0].value, keys[0].value), 0);
|
|
893
|
-
if (i === 0 || i >= n) return zero;
|
|
894
|
-
const arrival = keys[i];
|
|
895
|
-
if (arrival.interp === "hold") return zero;
|
|
896
|
-
const prev = keys[i - 1];
|
|
897
|
-
const segDur = arrival.t - prev.t;
|
|
898
|
-
const p = (t - prev.t) / segDur;
|
|
899
|
-
if (!vt.extrapolates) {
|
|
900
|
-
const eased = easeFor(tr, s, i)(p);
|
|
901
|
-
if (eased < 0 || eased > 1) return zero;
|
|
902
|
-
}
|
|
903
|
-
const d = resolveEaseDerivative(arrival.ease)(p);
|
|
904
|
-
return vt.scale(vt.sub(arrival.value, prev.value), d / segDur);
|
|
905
|
-
}
|
|
906
|
-
/** Pure sample of a track at time t (§2.4). */
|
|
907
|
-
function sampleTrack(tr, t) {
|
|
908
|
-
const keys = tr.keys;
|
|
909
|
-
const n = keys.length;
|
|
910
|
-
const s = state(tr);
|
|
911
|
-
const i = findSegment(keys, t, s.cursor);
|
|
912
|
-
if (i === 0) return keys[0].value;
|
|
913
|
-
if (i >= n) return keys[n - 1].value;
|
|
914
|
-
s.cursor = i;
|
|
915
|
-
const arrival = keys[i];
|
|
916
|
-
const prev = keys[i - 1];
|
|
917
|
-
if (arrival.interp === "hold") return prev.value;
|
|
918
|
-
const vt = getValueType(tr.type);
|
|
919
|
-
const p = (t - prev.t) / (arrival.t - prev.t);
|
|
920
|
-
let easedT = easeFor(tr, s, i)(p);
|
|
921
|
-
if (!vt.extrapolates && (easedT < 0 || easedT > 1)) {
|
|
922
|
-
if (!s.warnedClamp) {
|
|
923
|
-
s.warnedClamp = true;
|
|
924
|
-
emitDevWarning(`track '${tr.target}' (type '${tr.type}') clamped an out-of-range eased value — non-extrapolating types can't overshoot, so a spring/overshooting ease on this track is flattened`);
|
|
925
|
-
}
|
|
926
|
-
easedT = Math.min(1, Math.max(0, easedT));
|
|
927
|
-
}
|
|
928
|
-
return vt.lerp(prev.value, arrival.value, easedT);
|
|
929
|
-
}
|
|
930
|
-
//#endregion
|
|
1
|
+
import { I as emitDevWarning, R as spring, a as targetNodeId, m as validateTrack, o as TrackValidationError, r as isEditableNodeId } from "./targetRef.js";
|
|
931
2
|
//#region src/timeline.ts
|
|
932
3
|
/**
|
|
933
4
|
* The Timeline document (DESIGN.md §2.3) — the serializable animation source
|
|
@@ -1094,42 +165,6 @@ function compileTimeline(doc) {
|
|
|
1094
165
|
};
|
|
1095
166
|
}
|
|
1096
167
|
//#endregion
|
|
1097
|
-
//#region src/targetRef.ts
|
|
1098
|
-
/**
|
|
1099
|
-
* Target references (DESIGN.md §2.6): the builder accepts either a canonical
|
|
1100
|
-
* target string ('circle/opacity') or a property signal that carries its own
|
|
1101
|
-
* path — attached by the scene package at node construction via TARGET_PATH.
|
|
1102
|
-
*/
|
|
1103
|
-
const TARGET_PATH = Symbol.for("glissade.targetPath");
|
|
1104
|
-
var UnresolvableTargetError = class extends Error {
|
|
1105
|
-
constructor(message) {
|
|
1106
|
-
super(message ?? "tween target is not addressable: pass a target string (\"node/prop\") or a property signal of a node that has an explicit id (§3.1 — anonymous nodes cannot be track targets)");
|
|
1107
|
-
this.name = "UnresolvableTargetError";
|
|
1108
|
-
}
|
|
1109
|
-
};
|
|
1110
|
-
/** The node-id portion of a canonical `nodeId/prop.path` target. */
|
|
1111
|
-
function targetNodeId(target) {
|
|
1112
|
-
const slash = target.indexOf("/");
|
|
1113
|
-
return slash < 0 ? target : target.slice(0, slash);
|
|
1114
|
-
}
|
|
1115
|
-
/**
|
|
1116
|
-
* The single editable-host rule (§6.4 sub-decision, the 0.9 locked predicate):
|
|
1117
|
-
* only a node with an EXPLICIT, non-structural id can host an editable or
|
|
1118
|
-
* editor-created track. Structural fallback ids (`~Type.ordinal`, §6.5) are
|
|
1119
|
-
* inspection-only and reorder-fragile, so they are never editable nor valid
|
|
1120
|
-
* track targets. Lives here (the addressing module) so the builder guard, the
|
|
1121
|
-
* scene, and the studio host all share ONE definition.
|
|
1122
|
-
*/
|
|
1123
|
-
function isEditableNodeId(id) {
|
|
1124
|
-
return typeof id === "string" && id.length > 0 && id !== "__root" && !id.startsWith("~");
|
|
1125
|
-
}
|
|
1126
|
-
function resolveTweenTarget(target) {
|
|
1127
|
-
const path = typeof target === "string" ? target : target[TARGET_PATH];
|
|
1128
|
-
if (typeof path !== "string") throw new UnresolvableTargetError();
|
|
1129
|
-
if (targetNodeId(path).startsWith("~")) throw new UnresolvableTargetError(`'${path}': structural ids (~Type.ordinal) are inspection-only and cannot be track targets (§6.5) — give the node an explicit id`);
|
|
1130
|
-
return path;
|
|
1131
|
-
}
|
|
1132
|
-
//#endregion
|
|
1133
168
|
//#region src/sidecar.ts
|
|
1134
169
|
/**
|
|
1135
170
|
* The editor sidecar (DESIGN.md §6.2): code declares scene structure and
|
|
@@ -1356,4 +391,4 @@ function normalizeEditedKeys(keys) {
|
|
|
1356
391
|
return out;
|
|
1357
392
|
}
|
|
1358
393
|
//#endregion
|
|
1359
|
-
export {
|
|
394
|
+
export { hashKeys as a, migrateSidecar as c, TimelineValidationError as d, audioOffsetSamples as f, timeline as h, emptySidecar as i, normalizeEditedKeys as l, isDurationEditable as m, assignKeyIds as n, mergeSidecar as o, compileTimeline as p, deleteSidecarTrack as r, mergeSidecarDetailed as s, SidecarVersionError as t, setSidecarTrack as u };
|