@idle-screens/schema 3.14.0 → 3.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FORMAT.md +157 -18
- package/dist/index.d.ts +323 -18
- package/dist/index.js +948 -116
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +69 -5
package/dist/index.js
CHANGED
|
@@ -96,6 +96,8 @@ var LIMITS = {
|
|
|
96
96
|
// ms — a `clock`ed layer breathes in unison, so 1 Hz not 2
|
|
97
97
|
minClockRate: 0.1,
|
|
98
98
|
maxClockRate: 4,
|
|
99
|
+
maxWobble: 0.25,
|
|
100
|
+
// min(w,h) units — drift `bob` / rise `sway` above this warn `motion-amplitude` (× referenceViewport in px specs)
|
|
99
101
|
defaultListGap: 0.06,
|
|
100
102
|
// viewport units between list/table cells (40 px in px specs)
|
|
101
103
|
defaultListGapPx: 40,
|
|
@@ -103,6 +105,17 @@ var LIMITS = {
|
|
|
103
105
|
maxPolygonSides: 12,
|
|
104
106
|
maxShapePoints: 24,
|
|
105
107
|
// polygon.points / stroke.points
|
|
108
|
+
maxTextNumberDecimals: 6,
|
|
109
|
+
maxTextAffix: 64,
|
|
110
|
+
// characters of a counter's prefix / suffix
|
|
111
|
+
maxRevealRise: 1,
|
|
112
|
+
// em
|
|
113
|
+
maxRockAmp: 45,
|
|
114
|
+
// degrees either side of rest — a swing, not a spin
|
|
115
|
+
minRockPeriod: 1e3,
|
|
116
|
+
// ms — a calm swing; at most once a second
|
|
117
|
+
maxLifeStagger: 6e4,
|
|
118
|
+
// ms between successive entities' entrances
|
|
106
119
|
minEaseTau: 100,
|
|
107
120
|
// ms
|
|
108
121
|
maxEaseTau: 12e4
|
|
@@ -368,7 +381,7 @@ function lerpPathD(a, b, k) {
|
|
|
368
381
|
}
|
|
369
382
|
function textStringsOf(sprite) {
|
|
370
383
|
if (sprite.kind === "text") return sprite.strings;
|
|
371
|
-
if (sprite.kind === "textBlock") return [sprite.text];
|
|
384
|
+
if (sprite.kind === "textBlock") return [sprite.number ? `${sprite.number.prefix ?? ""}\0${sprite.number.suffix ?? ""}` : sprite.text];
|
|
372
385
|
return null;
|
|
373
386
|
}
|
|
374
387
|
function textStringsDiffer(a, b, i) {
|
|
@@ -462,6 +475,28 @@ function easeSmooth(k) {
|
|
|
462
475
|
const c = Math.max(0, Math.min(1, k));
|
|
463
476
|
return c * c * (3 - 2 * c);
|
|
464
477
|
}
|
|
478
|
+
var SPRING_OMEGA = 8;
|
|
479
|
+
var SPRING_END = 1 - (1 + SPRING_OMEGA) * Math.exp(-SPRING_OMEGA);
|
|
480
|
+
function easeGlide(ease2, k) {
|
|
481
|
+
const c = Math.max(0, Math.min(1, k));
|
|
482
|
+
switch (ease2) {
|
|
483
|
+
case "step":
|
|
484
|
+
return c >= 1 ? 1 : 0;
|
|
485
|
+
case "linear":
|
|
486
|
+
return c;
|
|
487
|
+
case "in":
|
|
488
|
+
return c * c * c;
|
|
489
|
+
case "out": {
|
|
490
|
+
const u = 1 - c;
|
|
491
|
+
return 1 - u * u * u;
|
|
492
|
+
}
|
|
493
|
+
case "spring":
|
|
494
|
+
return c >= 1 ? 1 : (1 - (1 + SPRING_OMEGA * c) * Math.exp(-SPRING_OMEGA * c)) / SPRING_END;
|
|
495
|
+
default:
|
|
496
|
+
return c * c * (3 - 2 * c);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
var STEER_EASES = /* @__PURE__ */ new Set(["in", "out", "spring"]);
|
|
465
500
|
function structuralSignature(spec) {
|
|
466
501
|
return JSON.stringify([
|
|
467
502
|
spec.units,
|
|
@@ -497,7 +532,9 @@ function structuralSignature(spec) {
|
|
|
497
532
|
l.sprite.kind === "emoji" ? [l.sprite.glyphs.length, l.sprite.cycle?.period] : l.sprite.kind === "text" ? [l.sprite.strings.length, l.sprite.cycle?.period] : l.sprite.kind === "textBlock" ? l.sprite.anchor ? [l.sprite.fontSize, l.sprite.anchor] : [l.sprite.fontSize] : void 0,
|
|
498
533
|
// Static rotation is baked into entities (a range draws a seeded
|
|
499
534
|
// angle). Appended only when set — same rule as `anchor` above.
|
|
500
|
-
...l.rotate !== void 0 ? [l.rotate] : []
|
|
535
|
+
...l.rotate !== void 0 ? [l.rotate] : [],
|
|
536
|
+
// `rock` is baked per entity like `pulse` (and draws a seeded phase).
|
|
537
|
+
...l.rock !== void 0 ? [{ rock: l.rock }] : []
|
|
501
538
|
];
|
|
502
539
|
})
|
|
503
540
|
]);
|
|
@@ -613,6 +650,7 @@ function sequenceSwapCompatible(prev, next) {
|
|
|
613
650
|
function transitionKey(tr) {
|
|
614
651
|
if (!tr || tr.type === "cut") return "cut";
|
|
615
652
|
if (tr.type === "morph") return `morph:${tr.dur}:${tr.text ?? "step"}`;
|
|
653
|
+
if (tr.type === "wipe") return `wipe:${tr.dur}:${JSON.stringify([tr.shape ?? "circle", tr.origin ?? null, tr.angle ?? 0, tr.feather ?? 0.08])}`;
|
|
616
654
|
return `fade:${tr.dur}`;
|
|
617
655
|
}
|
|
618
656
|
|
|
@@ -649,10 +687,7 @@ function timelineLocalTime(tl, t) {
|
|
|
649
687
|
return t;
|
|
650
688
|
}
|
|
651
689
|
function ease(k, x) {
|
|
652
|
-
|
|
653
|
-
if (k.ease === "step") return c >= 1 ? 1 : 0;
|
|
654
|
-
if (k.ease === "linear") return c;
|
|
655
|
-
return easeSmooth(c);
|
|
690
|
+
return easeGlide(k.ease, x);
|
|
656
691
|
}
|
|
657
692
|
function evalTrack(keys, start, tt, leaf) {
|
|
658
693
|
let from = start;
|
|
@@ -801,7 +836,10 @@ function strokeSamples(s, halfSize, n) {
|
|
|
801
836
|
if (m < 2) return pts;
|
|
802
837
|
const smooth = s.curve !== "linear" && m >= 3;
|
|
803
838
|
const segs = m - 1;
|
|
804
|
-
|
|
839
|
+
if (n === void 0) {
|
|
840
|
+
n = Math.max(STROKE_SAMPLES, segs * 6 + 1);
|
|
841
|
+
if (!smooth) n = Math.ceil((n - 1) / segs) * segs + 1;
|
|
842
|
+
}
|
|
805
843
|
const at = (k) => pts[Math.max(0, Math.min(m - 1, k))];
|
|
806
844
|
const out = [];
|
|
807
845
|
for (let i = 0; i < n; i++) {
|
|
@@ -820,6 +858,22 @@ function strokeSamples(s, halfSize, n) {
|
|
|
820
858
|
}
|
|
821
859
|
return out;
|
|
822
860
|
}
|
|
861
|
+
function strokeDrawnSamples(s, halfSize) {
|
|
862
|
+
const pts = strokeSamples(s, halfSize);
|
|
863
|
+
const span = drawSpan(s.draw);
|
|
864
|
+
return span ? trimPolyline(pts, span[0], span[1]) : pts;
|
|
865
|
+
}
|
|
866
|
+
function strokeDrawnAt(s, halfSize, drawn) {
|
|
867
|
+
if (!s.draw) return void 0;
|
|
868
|
+
const total = pathLength(strokeSamples(s, halfSize));
|
|
869
|
+
if (!(total > 0)) return void 0;
|
|
870
|
+
const a = drawSpan(s.draw)?.[0] ?? 0;
|
|
871
|
+
let run = 0;
|
|
872
|
+
return drawn.map((p, i) => {
|
|
873
|
+
if (i > 0) run += Math.hypot(p.x - drawn[i - 1].x, p.y - drawn[i - 1].y);
|
|
874
|
+
return a + run / total;
|
|
875
|
+
});
|
|
876
|
+
}
|
|
823
877
|
function pathLength(pts) {
|
|
824
878
|
let l = 0;
|
|
825
879
|
for (let i = 1; i < pts.length; i++) {
|
|
@@ -829,6 +883,83 @@ function pathLength(pts) {
|
|
|
829
883
|
}
|
|
830
884
|
return l;
|
|
831
885
|
}
|
|
886
|
+
function drawSpan(d) {
|
|
887
|
+
if (!d) return null;
|
|
888
|
+
const a = Math.max(0, Math.min(1, d.start ?? 0));
|
|
889
|
+
const b = Math.max(0, Math.min(1, d.end ?? 1));
|
|
890
|
+
return a <= 0 && b >= 1 ? null : [a, b];
|
|
891
|
+
}
|
|
892
|
+
function trimPolyline(pts, a, b) {
|
|
893
|
+
if (pts.length < 2 || b <= a) return [];
|
|
894
|
+
const total = pathLength(pts);
|
|
895
|
+
if (!(total > 0)) return [];
|
|
896
|
+
const from = a * total;
|
|
897
|
+
const to = b * total;
|
|
898
|
+
const out = [];
|
|
899
|
+
let run = 0;
|
|
900
|
+
for (let i = 1; i < pts.length; i++) {
|
|
901
|
+
const p = pts[i - 1];
|
|
902
|
+
const q = pts[i];
|
|
903
|
+
const seg = Math.hypot(q.x - p.x, q.y - p.y);
|
|
904
|
+
const s0 = run;
|
|
905
|
+
const s1 = run + seg;
|
|
906
|
+
run = s1;
|
|
907
|
+
if (s1 < from || seg <= 0) continue;
|
|
908
|
+
if (s0 > to) break;
|
|
909
|
+
const at = (s) => {
|
|
910
|
+
const k = Math.max(0, Math.min(1, (s - s0) / seg));
|
|
911
|
+
return { x: p.x + (q.x - p.x) * k, y: p.y + (q.y - p.y) * k };
|
|
912
|
+
};
|
|
913
|
+
if (out.length === 0) out.push(s0 >= from ? { x: p.x, y: p.y } : at(from));
|
|
914
|
+
out.push(s1 <= to ? { x: q.x, y: q.y } : at(to));
|
|
915
|
+
if (s1 >= to) break;
|
|
916
|
+
}
|
|
917
|
+
return out.length >= 2 ? out : [];
|
|
918
|
+
}
|
|
919
|
+
function trimLines(lines, a, b) {
|
|
920
|
+
const key = `${a}|${b}`;
|
|
921
|
+
let byLines = trimCache.get(lines);
|
|
922
|
+
const hit = byLines?.get(key);
|
|
923
|
+
if (hit) return hit;
|
|
924
|
+
const lens = lines.map((l) => {
|
|
925
|
+
const pts = l.points.map(([x, y]) => ({ x, y }));
|
|
926
|
+
if (l.closed && pts.length > 1) pts.push(pts[0]);
|
|
927
|
+
return pathLength(pts);
|
|
928
|
+
});
|
|
929
|
+
const total = lens.reduce((m, v) => m + v, 0);
|
|
930
|
+
const out = [];
|
|
931
|
+
if (total > 0 && b > a) {
|
|
932
|
+
const from = a * total;
|
|
933
|
+
const to = b * total;
|
|
934
|
+
let run = 0;
|
|
935
|
+
lines.forEach((l, i) => {
|
|
936
|
+
const len = lens[i];
|
|
937
|
+
const s0 = run;
|
|
938
|
+
run += len;
|
|
939
|
+
if (len <= 0 || run < from || s0 > to) return;
|
|
940
|
+
if (s0 >= from && run <= to) {
|
|
941
|
+
out.push(l);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const pts = l.points.map(([x, y]) => ({ x, y }));
|
|
945
|
+
if (l.closed && pts.length > 1) pts.push(pts[0]);
|
|
946
|
+
const cut = trimPolyline(pts, (Math.max(from, s0) - s0) / len, (Math.min(to, run) - s0) / len);
|
|
947
|
+
if (cut.length >= 2) out.push({ points: cut.map((q) => [q.x, q.y]), closed: false });
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
if (!byLines) {
|
|
951
|
+
byLines = /* @__PURE__ */ new Map();
|
|
952
|
+
trimCache.set(lines, byLines);
|
|
953
|
+
}
|
|
954
|
+
if (byLines.size > 64) byLines.clear();
|
|
955
|
+
byLines.set(key, out);
|
|
956
|
+
return out;
|
|
957
|
+
}
|
|
958
|
+
var trimCache = /* @__PURE__ */ new WeakMap();
|
|
959
|
+
function strokeLines(part) {
|
|
960
|
+
const span = drawSpan(part.stroke?.draw);
|
|
961
|
+
return span ? trimLines(part.flat.lines, span[0], span[1]) : part.flat.lines;
|
|
962
|
+
}
|
|
832
963
|
function strokeTaper(u) {
|
|
833
964
|
return Math.max(0.15, Math.sin(Math.PI * Math.max(0, Math.min(1, u))));
|
|
834
965
|
}
|
|
@@ -873,10 +1004,10 @@ function ribbonSides(pts, half) {
|
|
|
873
1004
|
}
|
|
874
1005
|
return { left, right };
|
|
875
1006
|
}
|
|
876
|
-
function strokeOutline(pts, lw, taper = true) {
|
|
1007
|
+
function strokeOutline(pts, lw, taper = true, at) {
|
|
877
1008
|
const n = pts.length;
|
|
878
1009
|
if (n < 2) return [];
|
|
879
|
-
const half = (i) => Math.max(0.5, taper ? lw * strokeTaper(i / (n - 1)) : lw) / 2;
|
|
1010
|
+
const half = (i) => Math.max(0.5, taper ? lw * strokeTaper(at?.[i] ?? i / (n - 1)) : lw) / 2;
|
|
880
1011
|
const dir = (a, b) => {
|
|
881
1012
|
const dx = b.x - a.x;
|
|
882
1013
|
const dy = b.y - a.y;
|
|
@@ -968,7 +1099,7 @@ function partInkArea(part, sx, sy, r) {
|
|
|
968
1099
|
const fill = part.fill ? polygonArea(part.flat.points.map(([x, y]) => ({ x: x * sx, y: y * sy }))) * part.opacity : 0;
|
|
969
1100
|
let stroke = 0;
|
|
970
1101
|
if (part.stroke && part.stroke.width > 0) {
|
|
971
|
-
for (const l of part
|
|
1102
|
+
for (const l of strokeLines(part)) {
|
|
972
1103
|
const pts = l.points.map(([x, y]) => ({ x: x * sx, y: y * sy }));
|
|
973
1104
|
if (l.closed && pts.length > 1) pts.push(pts[0]);
|
|
974
1105
|
stroke += pathLength(pts) * part.stroke.width * r;
|
|
@@ -1295,10 +1426,33 @@ function inside(pt, r) {
|
|
|
1295
1426
|
}
|
|
1296
1427
|
return c;
|
|
1297
1428
|
}
|
|
1429
|
+
var ON_EDGE = 2 * PATH_TOLERANCE;
|
|
1430
|
+
function onEdge(pt, r) {
|
|
1431
|
+
for (let k = 0, j = r.length - 1; k < r.length; j = k++) {
|
|
1432
|
+
const a = r[j], b = r[k];
|
|
1433
|
+
const dx = b[0] - a[0], dy = b[1] - a[1];
|
|
1434
|
+
const len2 = dx * dx + dy * dy;
|
|
1435
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((pt[0] - a[0]) * dx + (pt[1] - a[1]) * dy) / len2)) : 0;
|
|
1436
|
+
const ex = a[0] + dx * t - pt[0], ey = a[1] + dy * t - pt[1];
|
|
1437
|
+
if (ex * ex + ey * ey <= ON_EDGE * ON_EDGE) return true;
|
|
1438
|
+
}
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1298
1441
|
function containsRing(r, rb, sub, sb) {
|
|
1299
|
-
if (sb.x0 < rb.x0 || sb.x1 > rb.x1 || sb.y0 < rb.y0 || sb.y1 > rb.y1) return false;
|
|
1300
|
-
|
|
1301
|
-
|
|
1442
|
+
if (sb.x0 < rb.x0 - ON_EDGE || sb.x1 > rb.x1 + ON_EDGE || sb.y0 < rb.y0 - ON_EDGE || sb.y1 > rb.y1 + ON_EDGE) return false;
|
|
1443
|
+
let strictly = false;
|
|
1444
|
+
for (const p of sub) {
|
|
1445
|
+
if (inside(p, r)) strictly = true;
|
|
1446
|
+
else if (!onEdge(p, r)) return false;
|
|
1447
|
+
}
|
|
1448
|
+
if (strictly) return true;
|
|
1449
|
+
let cx = 0, cy = 0;
|
|
1450
|
+
for (const p of sub) {
|
|
1451
|
+
cx += p[0];
|
|
1452
|
+
cy += p[1];
|
|
1453
|
+
}
|
|
1454
|
+
const c = [cx / sub.length, cy / sub.length];
|
|
1455
|
+
return !onEdge(c, r) && inside(c, r);
|
|
1302
1456
|
}
|
|
1303
1457
|
function boxOf(r) {
|
|
1304
1458
|
const b = { x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity };
|
|
@@ -1447,6 +1601,7 @@ var KNOWN_LAYER = /* @__PURE__ */ new Set([
|
|
|
1447
1601
|
"emit",
|
|
1448
1602
|
"clock",
|
|
1449
1603
|
"rotate",
|
|
1604
|
+
"rock",
|
|
1450
1605
|
"opacity",
|
|
1451
1606
|
"transform",
|
|
1452
1607
|
"group"
|
|
@@ -1457,6 +1612,7 @@ var GROUP_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,31}$/;
|
|
|
1457
1612
|
var KNOWN_POSITION = /* @__PURE__ */ new Set(["x", "y", "dx", "dy"]);
|
|
1458
1613
|
var KNOWN_TIMELINE = /* @__PURE__ */ new Set(["loop", "duration", "keys"]);
|
|
1459
1614
|
var KNOWN_TIMELINE_KEY = /* @__PURE__ */ new Set(["t", "path", "value", "ease", "dur"]);
|
|
1615
|
+
var GLIDE_EASES = /* @__PURE__ */ new Set(["step", "linear", "smooth", "in", "out", "spring"]);
|
|
1460
1616
|
var KNOWN_CIRCLE = /* @__PURE__ */ new Set(["kind", "radius", "color", "soft", "colors", "colorWeights"]);
|
|
1461
1617
|
var KNOWN_RING = /* @__PURE__ */ new Set(["kind", "radius", "color", "width", "colors", "colorWeights"]);
|
|
1462
1618
|
var KNOWN_STREAK = /* @__PURE__ */ new Set(["kind", "length", "color", "width", "colors", "colorWeights"]);
|
|
@@ -1464,28 +1620,89 @@ var KNOWN_RECT = /* @__PURE__ */ new Set(["kind", "width", "aspect", "color", "f
|
|
|
1464
1620
|
var KNOWN_BAR = /* @__PURE__ */ new Set(["kind", "values", "length", "thickness", "color", "max", "direction", "colors", "colorWeights"]);
|
|
1465
1621
|
var KNOWN_PATH_PART = /* @__PURE__ */ new Set(["path", "color", "fillRule", "fill", "gradient", "stroke", "opacity"]);
|
|
1466
1622
|
var KNOWN_POLYGON = /* @__PURE__ */ new Set(["kind", "radius", "color", "sides", "points", "path", "fillRule", "fill", "gradient", "stroke", "parts", "span", "origin", "soft", "colors", "colorWeights"]);
|
|
1467
|
-
var KNOWN_STROKE = /* @__PURE__ */ new Set(["kind", "length", "points", "color", "width", "curve", "taper", "orient", "colors", "colorWeights"]);
|
|
1623
|
+
var KNOWN_STROKE = /* @__PURE__ */ new Set(["kind", "length", "points", "color", "width", "curve", "taper", "orient", "draw", "colors", "colorWeights"]);
|
|
1468
1624
|
var KNOWN_EMOJI = /* @__PURE__ */ new Set(["kind", "glyphs", "cycle"]);
|
|
1469
1625
|
var KNOWN_TEXT = /* @__PURE__ */ new Set(["kind", "strings", "color", "font", "align", "baseline", "maxWidth", "cycle", "role"]);
|
|
1470
|
-
var KNOWN_TEXT_BLOCK = /* @__PURE__ */ new Set(["kind", "text", "maxWidth", "fontSize", "lineHeight", "align", "color", "reveal", "anchor", "font", "opacity", "role"]);
|
|
1626
|
+
var KNOWN_TEXT_BLOCK = /* @__PURE__ */ new Set(["kind", "text", "maxWidth", "fontSize", "lineHeight", "align", "color", "reveal", "anchor", "font", "opacity", "role", "number"]);
|
|
1627
|
+
var KNOWN_TEXT_NUMBER = /* @__PURE__ */ new Set(["value", "decimals", "prefix", "suffix", "grouping"]);
|
|
1471
1628
|
var TEXT_ROLES = /* @__PURE__ */ new Set(["read", "atmosphere"]);
|
|
1472
1629
|
var TEXT_BLOCK_ANCHORS = /* @__PURE__ */ new Set(["top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right"]);
|
|
1473
1630
|
var FONT_SIZE_RE = /\d(?:px|pt|pc|em|rem|ex|ch|vw|vh|vmin|vmax|%)/i;
|
|
1474
|
-
var KNOWN_REVEAL = /* @__PURE__ */ new Set(["progress", "mode", "speed", "caret", "fade"]);
|
|
1631
|
+
var KNOWN_REVEAL = /* @__PURE__ */ new Set(["progress", "mode", "speed", "caret", "fade", "rise"]);
|
|
1632
|
+
var FOREIGN_FIELD_HINTS = {
|
|
1633
|
+
top: {
|
|
1634
|
+
duration: "a scene has no length \u2014 loop authored change with `timeline: { loop: true, duration }`, or give a sequence segment a `duration`",
|
|
1635
|
+
loop: "use `timeline: { loop: true, duration, keys }`",
|
|
1636
|
+
fps: "a scene is a pure function of time \u2014 there is no frame rate to set",
|
|
1637
|
+
frameRate: "a scene is a pure function of time \u2014 there is no frame rate to set",
|
|
1638
|
+
bpm: "there is no tempo unit \u2014 write times in ms (one beat = 60000 / bpm)",
|
|
1639
|
+
tempo: "there is no tempo unit \u2014 write times in ms (one beat = 60000 / bpm)",
|
|
1640
|
+
camera: "use a `groups.<name>.transform` (a camera over its member layers) or a layer `transform`",
|
|
1641
|
+
keyframes: "use `timeline: { keys: [{ t, path, value, dur, ease }] }`",
|
|
1642
|
+
palette: "colours live on sprites (`color`, or `colors` for a seeded per-entity pick)"
|
|
1643
|
+
},
|
|
1644
|
+
layer: {
|
|
1645
|
+
delay: "use `life.enter` (ms before the layer fades in)",
|
|
1646
|
+
stagger: "use `life.stagger` (ms between successive entities' entrances)",
|
|
1647
|
+
duration: "use `life.exit` to end a layer (ms), or a sequence segment's `duration`",
|
|
1648
|
+
ease: "eases live on timeline keys and steers (`ease: 'out'`), or on drift/rise/wander motion (`motion.ease`)",
|
|
1649
|
+
easing: "eases live on timeline keys and steers (`ease: 'out'`), or on drift/rise/wander motion (`motion.ease`)",
|
|
1650
|
+
rotation: "use `rotate` (static degrees), `spin` (degrees/sec) or `rock` (a swing back and forth)",
|
|
1651
|
+
wobble: "use `rock` (a swing back and forth) or the motion's `bob` / `sway`",
|
|
1652
|
+
sway: "use `rock` (a swing back and forth), or `sway` on a rise motion",
|
|
1653
|
+
scale: "use `transform.scale` (paint, glides) or `grow` (breathing)",
|
|
1654
|
+
fade: "use `life.fade` (ms) or the layer's paint `opacity`",
|
|
1655
|
+
blur: "there is no blur \u2014 soften with `soft: true` (circle/polygon), `rect.feather`, `trail` or `ghosting`",
|
|
1656
|
+
glow: "glow is `blend: 'lighter'` over soft circles",
|
|
1657
|
+
keyframes: "use the scene's `timeline` (keys on this layer's paths)",
|
|
1658
|
+
animation: "use the scene's `timeline` (keys on this layer's paths)"
|
|
1659
|
+
},
|
|
1660
|
+
key: {
|
|
1661
|
+
duration: "use `dur` (the glide length, ms)",
|
|
1662
|
+
delay: "use `t` (when the glide starts, ms)",
|
|
1663
|
+
time: "use `t` (when the glide starts, ms)",
|
|
1664
|
+
at: "use `t` (when the glide starts, ms)",
|
|
1665
|
+
start: "use `t` (when the glide starts, ms)",
|
|
1666
|
+
easing: "use `ease` ('smooth' | 'linear' | 'step' | 'in' | 'out' | 'spring')",
|
|
1667
|
+
to: "use `value`",
|
|
1668
|
+
target: "use `value`",
|
|
1669
|
+
from: "a key glides from wherever its path is \u2014 set the starting value on the base spec"
|
|
1670
|
+
},
|
|
1671
|
+
motion: {
|
|
1672
|
+
delay: "use the layer's `life.enter`",
|
|
1673
|
+
easing: "use `ease: { type: 'settle' | 'buoyant', tau }` on drift / rise / wander",
|
|
1674
|
+
duration: "only `path` motion has a `duration` (ms per lap)"
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
function unknownMessage(where, k, plain) {
|
|
1678
|
+
const hint = Object.prototype.hasOwnProperty.call(FOREIGN_FIELD_HINTS[where], k) ? FOREIGN_FIELD_HINTS[where][k] : void 0;
|
|
1679
|
+
return hint ? `${plain} \u2014 ${hint}` : plain;
|
|
1680
|
+
}
|
|
1475
1681
|
var KNOWN_REVEAL_CARET = /* @__PURE__ */ new Set(["blink", "color"]);
|
|
1476
|
-
var
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1682
|
+
var MOTION_FIELDS = {
|
|
1683
|
+
drift: ["speed", "angle", "bidirectional", "bob", "ease"],
|
|
1684
|
+
rise: ["speed", "sway", "ease"],
|
|
1685
|
+
bounce: ["speed"],
|
|
1686
|
+
static: [],
|
|
1687
|
+
orbit: ["speed", "radius", "center"],
|
|
1688
|
+
wander: ["speed", "angle", "meander", "coherence", "ease"],
|
|
1689
|
+
warp: ["speed", "center"],
|
|
1690
|
+
path: ["points", "duration", "curve", "closed", "scatter"]
|
|
1691
|
+
};
|
|
1692
|
+
var motionKnown = (t) => /* @__PURE__ */ new Set(["type", ...MOTION_FIELDS[t]]);
|
|
1693
|
+
var KNOWN_DRIFT = motionKnown("drift");
|
|
1694
|
+
var KNOWN_RISE = motionKnown("rise");
|
|
1695
|
+
var KNOWN_BOUNCE = motionKnown("bounce");
|
|
1696
|
+
var KNOWN_STATIC = motionKnown("static");
|
|
1697
|
+
var KNOWN_ORBIT = motionKnown("orbit");
|
|
1698
|
+
var KNOWN_WANDER = motionKnown("wander");
|
|
1699
|
+
var KNOWN_WARP = motionKnown("warp");
|
|
1700
|
+
var KNOWN_PATH = motionKnown("path");
|
|
1484
1701
|
var KNOWN_BG_SOLID = /* @__PURE__ */ new Set(["type", "color"]);
|
|
1485
1702
|
var KNOWN_BG_GRADIENT = /* @__PURE__ */ new Set(["type", "stops", "band", "drift"]);
|
|
1486
1703
|
var KNOWN_BG_FIELD = /* @__PURE__ */ new Set(["type", "scale", "octaves", "warp", "quantize", "bands", "drift", "seed"]);
|
|
1487
1704
|
var KNOWN_FIELD_DRIFT = /* @__PURE__ */ new Set(["period", "amount"]);
|
|
1488
|
-
var LAYER_PROPS_ON_SPRITE = /* @__PURE__ */ new Set(["blend", "trail", "alpha", "pulse", "spin", "grow", "region", "links", "flip", "wrap", "key", "emit", "clock", "life", "layout", "rotate"]);
|
|
1705
|
+
var LAYER_PROPS_ON_SPRITE = /* @__PURE__ */ new Set(["blend", "trail", "alpha", "pulse", "spin", "grow", "region", "links", "flip", "wrap", "key", "emit", "clock", "life", "layout", "rotate", "rock"]);
|
|
1489
1706
|
function unknownKeys(obj, known) {
|
|
1490
1707
|
return Object.keys(obj).filter((k) => !known.has(k));
|
|
1491
1708
|
}
|
|
@@ -1522,7 +1739,16 @@ function ignoredPropertyPaths(spec) {
|
|
|
1522
1739
|
if (!isObj(spec)) return [];
|
|
1523
1740
|
return (validateSpecCore(spec, false).warnings ?? []).filter((w) => w.code === "unknown-property" || w.code === "misplaced-property").map((w) => w.path.replace(/\[(\d+)\]/g, ".$1"));
|
|
1524
1741
|
}
|
|
1525
|
-
function validateSpecPaths(spec, paths) {
|
|
1742
|
+
function validateSpecPaths(spec, paths, keyed = false) {
|
|
1743
|
+
if (!keyed) return specPathsValid(spec, paths);
|
|
1744
|
+
keyedPathDepth++;
|
|
1745
|
+
try {
|
|
1746
|
+
return specPathsValid(spec, paths);
|
|
1747
|
+
} finally {
|
|
1748
|
+
keyedPathDepth--;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function specPathsValid(spec, paths) {
|
|
1526
1752
|
const layers = /* @__PURE__ */ new Set();
|
|
1527
1753
|
let background = false;
|
|
1528
1754
|
let finish = false;
|
|
@@ -1594,7 +1820,7 @@ function validateSpecCore(spec, checkApplied = true) {
|
|
|
1594
1820
|
err("ghosting", `must be a number 0..${LIMITS.maxGhosting}`);
|
|
1595
1821
|
}
|
|
1596
1822
|
for (const k of unknownKeys(spec, KNOWN_TOP)) {
|
|
1597
|
-
warn(k, "unknown-property", `unknown top-level property '${k}' \u2014 will be ignored`);
|
|
1823
|
+
warn(k, "unknown-property", unknownMessage("top", k, `unknown top-level property '${k}' \u2014 will be ignored`));
|
|
1598
1824
|
}
|
|
1599
1825
|
if (spec.background !== void 0) validateBackground(spec.background, err, warn);
|
|
1600
1826
|
if (spec.finish !== void 0) validateFinish(spec.finish, "finish", err, warn);
|
|
@@ -1666,6 +1892,14 @@ function validateGroups(groups, err, warn, spec) {
|
|
|
1666
1892
|
}
|
|
1667
1893
|
}
|
|
1668
1894
|
function validateTimeline(spec, errOuter, warn, checkApplied = true) {
|
|
1895
|
+
keyedPathDepth++;
|
|
1896
|
+
try {
|
|
1897
|
+
validateTimelineKeys(spec, errOuter, warn, checkApplied);
|
|
1898
|
+
} finally {
|
|
1899
|
+
keyedPathDepth--;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function validateTimelineKeys(spec, errOuter, warn, checkApplied) {
|
|
1669
1903
|
let failed = false;
|
|
1670
1904
|
const err = (p, m) => {
|
|
1671
1905
|
failed = true;
|
|
@@ -1688,13 +1922,13 @@ function validateTimeline(spec, errOuter, warn, checkApplied = true) {
|
|
|
1688
1922
|
tl.keys.forEach((key, i) => {
|
|
1689
1923
|
const p = `timeline.keys[${i}]`;
|
|
1690
1924
|
if (!isObj(key)) return err(p, "must be an object {t, path, value, ease?, dur?}");
|
|
1691
|
-
for (const k of unknownKeys(key, KNOWN_TIMELINE_KEY)) warn(`${p}.${k}`, "unknown-property", `unknown key property '${k}' \u2014 will be ignored`);
|
|
1925
|
+
for (const k of unknownKeys(key, KNOWN_TIMELINE_KEY)) warn(`${p}.${k}`, "unknown-property", unknownMessage("key", k, `unknown key property '${k}' \u2014 will be ignored`));
|
|
1692
1926
|
let ok = true;
|
|
1693
1927
|
if (!isNum(key.t) || key.t < 0) {
|
|
1694
1928
|
err(`${p}.t`, "must be a number of ms >= 0");
|
|
1695
1929
|
ok = false;
|
|
1696
1930
|
}
|
|
1697
|
-
if (key.ease !== void 0 &&
|
|
1931
|
+
if (key.ease !== void 0 && !GLIDE_EASES.has(key.ease)) err(`${p}.ease`, "must be 'step' | 'linear' | 'smooth' | 'in' | 'out' | 'spring'");
|
|
1698
1932
|
if (key.dur !== void 0 && (!isNum(key.dur) || key.dur < 0 || key.dur > LIMITS.maxTimelineKeyDur)) {
|
|
1699
1933
|
err(`${p}.dur`, `must be a number of ms, 0..${LIMITS.maxTimelineKeyDur}`);
|
|
1700
1934
|
}
|
|
@@ -1732,6 +1966,12 @@ function validateTimeline(spec, errOuter, warn, checkApplied = true) {
|
|
|
1732
1966
|
const o1 = key.value.origin ?? "viewport";
|
|
1733
1967
|
if (o0 !== o1) warn(`${p}.value`, "timeline-origin-steps", `this key changes the transform's origin (${String(o0)} \u2192 ${String(o1)}): the pivot switches on the key's first frame, so the layer jumps \u2014 keep one origin per layer`);
|
|
1734
1968
|
}
|
|
1969
|
+
if (cp.endsWith(".color")) {
|
|
1970
|
+
const owner = readSpecPath(base, cp.slice(0, -".color".length));
|
|
1971
|
+
if (isObj(owner) && owner.gradient !== void 0) {
|
|
1972
|
+
warn(`${p}.path`, "timeline-key-no-effect", `'${key.path}' is overridden by ${cp.includes(".parts.") ? "that part's" : "the sprite's"} gradient, so this key changes nothing on screen \u2014 key its gradient's stops (\`\u2026gradient.stops.N.color\`) instead`);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1735
1975
|
if (structuralSignature(applied) !== baseSig) {
|
|
1736
1976
|
warn(`${p}.path`, "timeline-structural-key", `'${key.path}' is structural (placement, count, motion, size\u2026): the scene rebuilds at this key and entities re-seed \u2014 a pop, not a glide. Animate paint (colour, opacity, transform, polygon points) instead`);
|
|
1737
1977
|
}
|
|
@@ -1875,7 +2115,7 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
1875
2115
|
} else if (k === "depth") {
|
|
1876
2116
|
warn(`${path}.depth`, "unknown-property", `'depth' is not a layer property \u2014 use motion type 'warp' for true perspective depth, or fake parallax with size/speed/alpha across layers`);
|
|
1877
2117
|
} else {
|
|
1878
|
-
warn(`${path}.${k}`, "unknown-property", `unknown layer property '${k}' \u2014 will be ignored`);
|
|
2118
|
+
warn(`${path}.${k}`, "unknown-property", unknownMessage("layer", k, `unknown layer property '${k}' \u2014 will be ignored`));
|
|
1879
2119
|
}
|
|
1880
2120
|
}
|
|
1881
2121
|
if (!isNum(layer.count) || !Number.isInteger(layer.count) || layer.count < 1) {
|
|
@@ -2038,14 +2278,17 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
2038
2278
|
if (lay.gap !== void 0) {
|
|
2039
2279
|
if (isNum(lay.gap)) {
|
|
2040
2280
|
if (!gapOk(lay.gap)) err(`${path}.layout.gap`, "must be > 0 (viewport units of min(w,h), or px)");
|
|
2041
|
-
} else if (
|
|
2281
|
+
} else if (isObj(lay.gap)) {
|
|
2042
2282
|
if (lay.gap.x !== void 0 && !gapOk(lay.gap.x)) err(`${path}.layout.gap.x`, "must be > 0");
|
|
2043
2283
|
if (lay.gap.y !== void 0 && !gapOk(lay.gap.y)) err(`${path}.layout.gap.y`, "must be > 0");
|
|
2044
2284
|
for (const k of unknownKeys(lay.gap, /* @__PURE__ */ new Set(["x", "y"]))) {
|
|
2045
2285
|
warn(`${path}.layout.gap.${k}`, "unknown-property", `unknown gap property '${k}' \u2014 will be ignored`);
|
|
2046
2286
|
}
|
|
2287
|
+
if (lay.type === "list" && lay.gap.x !== void 0) {
|
|
2288
|
+
warn(`${path}.layout.gap.x`, "no-effect", "a list is one column, so gap.x has no effect; gap.y is its row pitch (use a table for columns)");
|
|
2289
|
+
}
|
|
2047
2290
|
} else {
|
|
2048
|
-
err(`${path}.layout.gap`,
|
|
2291
|
+
err(`${path}.layout.gap`, "must be a number > 0 or { x?, y? }");
|
|
2049
2292
|
}
|
|
2050
2293
|
}
|
|
2051
2294
|
for (const k of unknownKeys(lay, new Set(lay.type === "list" ? ["type", "gap"] : ["type", "columns", "gap"]))) {
|
|
@@ -2096,6 +2339,26 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
2096
2339
|
if (layer.life.fade !== void 0 && (!isNum(layer.life.fade) || layer.life.fade < 0)) {
|
|
2097
2340
|
err(`${path}.life.fade`, "must be >= 0 (ms)");
|
|
2098
2341
|
}
|
|
2342
|
+
if (layer.life.stagger !== void 0 && (!isNum(layer.life.stagger) || layer.life.stagger < 0 || layer.life.stagger > LIMITS.maxLifeStagger)) {
|
|
2343
|
+
err(`${path}.life.stagger`, `must be 0..${LIMITS.maxLifeStagger} ms (between successive entities' entrances)`);
|
|
2344
|
+
}
|
|
2345
|
+
for (const k of unknownKeys(layer.life, /* @__PURE__ */ new Set(["enter", "exit", "fade", "stagger"]))) {
|
|
2346
|
+
warn(`${path}.life.${k}`, "unknown-property", `unknown life property '${k}' \u2014 will be ignored`);
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (layer.rock !== void 0) {
|
|
2351
|
+
if (!isObj(layer.rock)) err(`${path}.rock`, "must be an object { amp, period }");
|
|
2352
|
+
else {
|
|
2353
|
+
if (!isNum(layer.rock.amp) || layer.rock.amp <= 0 || layer.rock.amp > LIMITS.maxRockAmp) {
|
|
2354
|
+
err(`${path}.rock.amp`, `must be > 0 and <= ${LIMITS.maxRockAmp} (degrees either side of rest)`);
|
|
2355
|
+
}
|
|
2356
|
+
if (!isNum(layer.rock.period) || layer.rock.period < LIMITS.minRockPeriod) {
|
|
2357
|
+
err(`${path}.rock.period`, `must be >= ${LIMITS.minRockPeriod} ms (one swing there and back)`);
|
|
2358
|
+
}
|
|
2359
|
+
for (const k of unknownKeys(layer.rock, /* @__PURE__ */ new Set(["amp", "period"]))) {
|
|
2360
|
+
warn(`${path}.rock.${k}`, "unknown-property", `unknown rock property '${k}' \u2014 will be ignored`);
|
|
2361
|
+
}
|
|
2099
2362
|
}
|
|
2100
2363
|
}
|
|
2101
2364
|
if (layer.trail !== void 0) {
|
|
@@ -2157,6 +2420,9 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
2157
2420
|
if (isObj(layer.grow) && isNum(layer.grow.period) && layer.grow.period < floor) {
|
|
2158
2421
|
err(`${path}.grow.period`, `must be >= ${floor} ms when the layer has a clock (period / rate >= ${LIMITS.minClockedPeriod})`);
|
|
2159
2422
|
}
|
|
2423
|
+
if (isObj(layer.rock) && isNum(layer.rock.period) && layer.rock.period < floor) {
|
|
2424
|
+
err(`${path}.rock.period`, `must be >= ${floor} ms when the layer has a clock (period / rate >= ${LIMITS.minClockedPeriod})`);
|
|
2425
|
+
}
|
|
2160
2426
|
const sp = layer.sprite;
|
|
2161
2427
|
if (isObj(sp) && isObj(sp.cycle) && isNum(sp.cycle.period) && sp.cycle.period < floor) {
|
|
2162
2428
|
err(`${path}.sprite.cycle.period`, `must be >= ${floor} ms when the layer has a clock (period / rate >= ${LIMITS.minClockedPeriod})`);
|
|
@@ -2288,6 +2554,7 @@ function validateSprite(sprite, path, err, warn, spec) {
|
|
|
2288
2554
|
if (sprite.curve !== void 0 && sprite.curve !== "smooth" && sprite.curve !== "linear") err(`${path}.curve`, "must be 'smooth' | 'linear'");
|
|
2289
2555
|
if (sprite.taper !== void 0 && typeof sprite.taper !== "boolean") err(`${path}.taper`, "must be a boolean");
|
|
2290
2556
|
if (sprite.orient !== void 0 && typeof sprite.orient !== "boolean") err(`${path}.orient`, "must be a boolean");
|
|
2557
|
+
if (sprite.draw !== void 0) validateDraw(sprite.draw, `${path}.draw`, err, warn);
|
|
2291
2558
|
color(sprite.color, `${path}.color`, err);
|
|
2292
2559
|
validatePalette(sprite, path, err);
|
|
2293
2560
|
} else if (sprite.kind === "textBlock") {
|
|
@@ -2328,6 +2595,22 @@ function validateSprite(sprite, path, err, warn, spec) {
|
|
|
2328
2595
|
err(`${path}.opacity`, "must be a number between 0 and 1");
|
|
2329
2596
|
}
|
|
2330
2597
|
validateTextRole(sprite, path, err);
|
|
2598
|
+
if (sprite.number !== void 0) {
|
|
2599
|
+
const n = sprite.number;
|
|
2600
|
+
if (!isObj(n)) err(`${path}.number`, "must be { value, decimals?, prefix?, suffix?, grouping? }");
|
|
2601
|
+
else {
|
|
2602
|
+
if (!isNum(n.value)) err(`${path}.number.value`, "must be a finite number");
|
|
2603
|
+
if (n.decimals !== void 0 && (!isNum(n.decimals) || !Number.isInteger(n.decimals) || n.decimals < 0 || n.decimals > LIMITS.maxTextNumberDecimals)) {
|
|
2604
|
+
err(`${path}.number.decimals`, `must be a whole number 0..${LIMITS.maxTextNumberDecimals}`);
|
|
2605
|
+
}
|
|
2606
|
+
for (const k of ["prefix", "suffix"]) {
|
|
2607
|
+
const v = n[k];
|
|
2608
|
+
if (v !== void 0 && (!isStr(v) || v.length > LIMITS.maxTextAffix)) err(`${path}.number.${k}`, `must be a string of at most ${LIMITS.maxTextAffix} characters`);
|
|
2609
|
+
}
|
|
2610
|
+
if (n.grouping !== void 0 && typeof n.grouping !== "boolean") err(`${path}.number.grouping`, "must be a boolean");
|
|
2611
|
+
for (const k of unknownKeys(n, KNOWN_TEXT_NUMBER)) warn(`${path}.number.${k}`, "unknown-property", `unknown number property '${k}' \u2014 will be ignored`);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2331
2614
|
if (sprite.reveal !== void 0) {
|
|
2332
2615
|
const rv = sprite.reveal;
|
|
2333
2616
|
if (typeof rv !== "object" || rv === null || Array.isArray(rv)) {
|
|
@@ -2348,6 +2631,10 @@ function validateSprite(sprite, path, err, warn, spec) {
|
|
|
2348
2631
|
if (rv.speed !== void 0 && (!isNum(rv.speed) || rv.speed < 0 || rv.speed > LIMITS.maxRevealSpeed)) {
|
|
2349
2632
|
err(`${path}.reveal.speed`, `must be between 0 and ${LIMITS.maxRevealSpeed} (graphemes/sec)`);
|
|
2350
2633
|
}
|
|
2634
|
+
if (rv.rise !== void 0) {
|
|
2635
|
+
if (!isNum(rv.rise) || rv.rise < 0 || rv.rise > LIMITS.maxRevealRise) err(`${path}.reveal.rise`, `must be 0..${LIMITS.maxRevealRise} (em below the line a glyph rises from)`);
|
|
2636
|
+
else if (rv.mode !== "glyphFade") warn(`${path}.reveal.rise`, "unknown-property", "rise applies to mode 'glyphFade' only \u2014 will be ignored");
|
|
2637
|
+
}
|
|
2351
2638
|
if (rv.caret !== void 0 && typeof rv.caret !== "boolean") {
|
|
2352
2639
|
const caret = rv.caret;
|
|
2353
2640
|
if (typeof caret !== "object" || caret === null || Array.isArray(caret)) {
|
|
@@ -2416,6 +2703,12 @@ function validateMotion(motion, path, err, warn, spec) {
|
|
|
2416
2703
|
else if (v[0] < 0) err(p, "speed lower bound must be >= 0");
|
|
2417
2704
|
else if (v[1] > speedCap) err(p, `speed exceeds cap ${isViewport ? speedCap.toFixed(2) + " viewport-units/sec" : LIMITS.maxSpeed + " px/sec"}`);
|
|
2418
2705
|
};
|
|
2706
|
+
const wobbleCap = isViewport ? LIMITS.maxWobble : LIMITS.maxWobble * refVp;
|
|
2707
|
+
const wobbleCheck = (v, p, name, axis) => {
|
|
2708
|
+
if (!isNum(v) || Math.abs(v) <= wobbleCap) return;
|
|
2709
|
+
const px = isViewport ? ` (\u2248${Math.round(Math.abs(v) * refVp)} px at ${refVp} px)` : "";
|
|
2710
|
+
warn(p, "motion-amplitude", `${name} ${v} is a ${axis} swing of ${isViewport ? `${Math.abs(v)}\xD7 min(w, h)` : `${Math.abs(v)} px`}${px} each way, so entities leave the frame \u2014 ${name} is in ${isViewport ? "min(w, h) units, not px" : "px"}; typical ${isViewport ? "0.002\u20130.02" : "2\u201320 px"}, at most ${isViewport ? wobbleCap : Math.round(wobbleCap) + " px"} before this warning`);
|
|
2711
|
+
};
|
|
2419
2712
|
let knownSet;
|
|
2420
2713
|
if (motion.type === "drift") {
|
|
2421
2714
|
knownSet = KNOWN_DRIFT;
|
|
@@ -2423,6 +2716,7 @@ function validateMotion(motion, path, err, warn, spec) {
|
|
|
2423
2716
|
if (motion.angle !== void 0 && !isNum(motion.angle)) err(`${path}.angle`, "must be a number (degrees)");
|
|
2424
2717
|
if (motion.bidirectional !== void 0 && typeof motion.bidirectional !== "boolean") err(`${path}.bidirectional`, "must be a boolean");
|
|
2425
2718
|
if (motion.bob !== void 0 && !isNum(motion.bob)) err(`${path}.bob`, "must be a number");
|
|
2719
|
+
else wobbleCheck(motion.bob, `${path}.bob`, "bob", "vertical");
|
|
2426
2720
|
validateEase(motion.ease, `${path}.ease`, err, warn);
|
|
2427
2721
|
if (isRange(motion.speed) && motion.speed[1] < 1 && !isViewport) {
|
|
2428
2722
|
warn(`${path}.speed`, "near-zero-speed", `max speed is ${motion.speed[1]} px/sec \u2014 entities will appear frozen. Typical range: 10\u2013200 px/sec`);
|
|
@@ -2431,6 +2725,7 @@ function validateMotion(motion, path, err, warn, spec) {
|
|
|
2431
2725
|
knownSet = KNOWN_RISE;
|
|
2432
2726
|
speedOk(motion.speed, `${path}.speed`);
|
|
2433
2727
|
if (motion.sway !== void 0 && !isNum(motion.sway)) err(`${path}.sway`, "must be a number");
|
|
2728
|
+
else wobbleCheck(motion.sway, `${path}.sway`, "sway", "horizontal");
|
|
2434
2729
|
validateEase(motion.ease, `${path}.ease`, err, warn);
|
|
2435
2730
|
if (isRange(motion.speed) && motion.speed[1] < 1 && !isViewport) {
|
|
2436
2731
|
warn(`${path}.speed`, "near-zero-speed", `max speed is ${motion.speed[1]} px/sec \u2014 entities will appear frozen. Typical range: 5\u201380 px/sec`);
|
|
@@ -2530,20 +2825,23 @@ function validateMotion(motion, path, err, warn, spec) {
|
|
|
2530
2825
|
return;
|
|
2531
2826
|
}
|
|
2532
2827
|
for (const k of unknownKeys(motion, knownSet)) {
|
|
2533
|
-
warn(`${path}.${k}`, "unknown-property", `unknown motion property '${k}' \u2014 will be ignored`);
|
|
2828
|
+
warn(`${path}.${k}`, "unknown-property", unknownMessage("motion", k, `unknown motion property '${k}' \u2014 will be ignored`));
|
|
2534
2829
|
}
|
|
2535
2830
|
}
|
|
2536
2831
|
var isPair = (v, lim = 2) => Array.isArray(v) && v.length === 2 && isNum(v[0]) && isNum(v[1]) && Math.abs(v[0]) <= lim && Math.abs(v[1]) <= lim;
|
|
2537
2832
|
var MAX_PATH_PARTS = 256;
|
|
2538
2833
|
var pathVerdicts = /* @__PURE__ */ new Map();
|
|
2539
2834
|
var pathVerdictBytes = 0;
|
|
2835
|
+
var NO_AREA = "must enclose an area (at least 3 points), or set fill: false and a stroke for line art";
|
|
2836
|
+
var NO_LINE = "must draw at least one line (2+ points)";
|
|
2837
|
+
var keyedPathDepth = 0;
|
|
2540
2838
|
function pathGeometryProblem(d, rule, filled) {
|
|
2541
2839
|
const key = `${rule}|${filled ? 1 : 0}|${d}`;
|
|
2542
2840
|
const hit = pathVerdicts.get(key);
|
|
2543
2841
|
if (hit !== void 0) return hit;
|
|
2544
2842
|
const flat = flattenPath(d, rule);
|
|
2545
2843
|
const inBox = ([x, y]) => Math.abs(x) <= 1.0001 && Math.abs(y) <= 1.0001;
|
|
2546
|
-
const problem = flat.error ? `not a usable path: ${flat.error}` :
|
|
2844
|
+
const problem = flat.error ? `not a usable path: ${flat.error}` : flat.points.length > MAX_PATH_POINTS ? `flattens to ${flat.points.length} points; at most ${MAX_PATH_POINTS}` : !flat.points.every(inBox) || !flat.lines.every((l) => l.points.every(inBox)) ? "must stay inside the -1..1 unit box (curves included)" : filled && flat.points.length < 3 ? NO_AREA : !filled && flat.lines.length === 0 ? NO_LINE : null;
|
|
2547
2845
|
if (pathVerdicts.size >= 1024 || pathVerdictBytes + key.length > 2e6) {
|
|
2548
2846
|
pathVerdicts.clear();
|
|
2549
2847
|
pathVerdictBytes = 0;
|
|
@@ -2557,14 +2855,13 @@ function validatePathPiece(piece, path, err, warn) {
|
|
|
2557
2855
|
err(`${path}.path`, "must be an SVG path d string (1..65536 chars) in the -1..1 unit box");
|
|
2558
2856
|
} else {
|
|
2559
2857
|
const problem = pathGeometryProblem(piece.path, piece.fillRule ?? "auto", piece.fill !== false);
|
|
2560
|
-
if (problem) err(`${path}.path`, problem);
|
|
2858
|
+
if (problem && !(keyedPathDepth > 0 && (problem === NO_AREA || problem === NO_LINE))) err(`${path}.path`, problem);
|
|
2561
2859
|
}
|
|
2562
2860
|
if (piece.fill !== void 0 && typeof piece.fill !== "boolean") err(`${path}.fill`, "must be a boolean");
|
|
2563
2861
|
if (piece.fill === false && piece.stroke === void 0) err(`${path}.fill`, "fill: false draws nothing without a stroke");
|
|
2564
2862
|
if (piece.gradient !== void 0) validatePathGradient(piece.gradient, `${path}.gradient`, err);
|
|
2565
|
-
if (piece.stroke !== void 0) validatePathStroke(piece.stroke, `${path}.stroke`, err);
|
|
2863
|
+
if (piece.stroke !== void 0) validatePathStroke(piece.stroke, `${path}.stroke`, err, warn);
|
|
2566
2864
|
if (piece.fillRule !== void 0 && !["auto", "nonzero", "evenodd"].includes(piece.fillRule)) err(`${path}.fillRule`, "must be 'auto' | 'nonzero' | 'evenodd'");
|
|
2567
|
-
void warn;
|
|
2568
2865
|
}
|
|
2569
2866
|
function validatePathGradient(g, path, err) {
|
|
2570
2867
|
if (!isObj(g)) return err(path, "must be { type: 'linear', from, to, stops } or { type: 'radial', center, radius, stops }");
|
|
@@ -2589,13 +2886,38 @@ function validatePathGradient(g, path, err) {
|
|
|
2589
2886
|
if (st.opacity !== void 0 && (!isNum(st.opacity) || st.opacity < 0 || st.opacity > 1)) err(`${path}.stops[${i}].opacity`, "must be 0..1");
|
|
2590
2887
|
});
|
|
2591
2888
|
}
|
|
2592
|
-
function validatePathStroke(st, path, err) {
|
|
2593
|
-
if (!isObj(st)) return err(path, "must be { color, width, opacity?, join?, cap? }");
|
|
2889
|
+
function validatePathStroke(st, path, err, warn) {
|
|
2890
|
+
if (!isObj(st)) return err(path, "must be { color, width, opacity?, join?, cap?, draw? }");
|
|
2594
2891
|
color(st.color, `${path}.color`, err);
|
|
2595
2892
|
if (!isNum(st.width) || st.width <= 0 || st.width > 0.5) err(`${path}.width`, "must be > 0 and <= 0.5 (unit-box units: 0.01 is 1% of the drawing's half-size)");
|
|
2596
2893
|
if (st.opacity !== void 0 && (!isNum(st.opacity) || st.opacity < 0 || st.opacity > 1)) err(`${path}.opacity`, "must be 0..1");
|
|
2597
2894
|
if (st.join !== void 0 && !["round", "miter", "bevel"].includes(st.join)) err(`${path}.join`, "must be 'round' | 'miter' | 'bevel'");
|
|
2598
2895
|
if (st.cap !== void 0 && !["round", "butt", "square"].includes(st.cap)) err(`${path}.cap`, "must be 'round' | 'butt' | 'square'");
|
|
2896
|
+
if (st.draw !== void 0) validateDraw(st.draw, `${path}.draw`, err, warn);
|
|
2897
|
+
}
|
|
2898
|
+
function validateWipe(tr, path, err, warn) {
|
|
2899
|
+
if (tr.shape !== void 0 && tr.shape !== "circle" && tr.shape !== "line") err(`${path}.shape`, "must be 'circle' | 'line'");
|
|
2900
|
+
if (tr.origin !== void 0) {
|
|
2901
|
+
const o = tr.origin;
|
|
2902
|
+
if (!isObj(o) || !isNum(o.x) || !isNum(o.y) || o.x < 0 || o.x > 1 || o.y < 0 || o.y > 1) err(`${path}.origin`, "must be { x, y } with each 0..1 (where the iris opens)");
|
|
2903
|
+
else if (tr.shape === "line") err(`${path}.origin`, "applies to shape 'circle' only \u2014 a line wipe sweeps along `angle`");
|
|
2904
|
+
}
|
|
2905
|
+
if (tr.angle !== void 0) {
|
|
2906
|
+
if (!isNum(tr.angle) || Math.abs(tr.angle) > 360) err(`${path}.angle`, "must be degrees, -360..360 (0 = left to right, 90 = top to bottom)");
|
|
2907
|
+
else if (tr.shape !== "line") err(`${path}.angle`, "applies to shape 'line' only \u2014 an iris opens from `origin`");
|
|
2908
|
+
}
|
|
2909
|
+
if (tr.feather !== void 0 && (!isNum(tr.feather) || tr.feather < 0 || tr.feather > 1)) err(`${path}.feather`, "must be 0..1 (the edge width, as a fraction of the distance it travels)");
|
|
2910
|
+
for (const k of Object.keys(tr)) {
|
|
2911
|
+
if (!["type", "dur", "shape", "origin", "angle", "feather"].includes(k)) warn(`${path}.${k}`, "unknown-property", `unknown wipe property '${k}' \u2014 will be ignored`);
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
function validateDraw(d, path, err, warn) {
|
|
2915
|
+
if (!isObj(d)) return err(path, "must be { start?, end? } \u2014 fractions of the line, 0..1");
|
|
2916
|
+
for (const k of ["start", "end"]) {
|
|
2917
|
+
const v = d[k];
|
|
2918
|
+
if (v !== void 0 && (!isNum(v) || v < 0 || v > 1)) err(`${path}.${k}`, "must be 0..1 (a fraction of the line's length)");
|
|
2919
|
+
}
|
|
2920
|
+
for (const k of Object.keys(d)) if (k !== "start" && k !== "end") warn(`${path}.${k}`, "unknown-property", `unknown draw property '${k}' \u2014 draw takes start and end; will be ignored`);
|
|
2599
2921
|
}
|
|
2600
2922
|
function validateShapePoints(points, path, min, err) {
|
|
2601
2923
|
if (!Array.isArray(points) || points.length < min || points.length > LIMITS.maxShapePoints) {
|
|
@@ -2691,16 +3013,17 @@ function validateSequence(seq) {
|
|
|
2691
3013
|
if (s.transition !== void 0) {
|
|
2692
3014
|
if (!isObj(s.transition)) {
|
|
2693
3015
|
err(`${p}.transition`, "must be an object");
|
|
2694
|
-
} else if (s.transition.type === "morph" || s.transition.type === "fade") {
|
|
3016
|
+
} else if (s.transition.type === "morph" || s.transition.type === "fade" || s.transition.type === "wipe") {
|
|
2695
3017
|
if (!isNum(s.transition.dur) || s.transition.dur < LIMITS.minTransitionDur || s.transition.dur > LIMITS.maxTransitionDur) {
|
|
2696
3018
|
err(`${p}.transition.dur`, `must be a number between ${LIMITS.minTransitionDur} and ${LIMITS.maxTransitionDur}`);
|
|
2697
3019
|
}
|
|
3020
|
+
if (s.transition.type === "wipe") validateWipe(s.transition, `${p}.transition`, err, (wp, code, message) => void warnings.push({ path: wp, code, message }));
|
|
2698
3021
|
} else if (s.transition.type !== "cut") {
|
|
2699
|
-
err(`${p}.transition.type`, "must be 'cut', 'morph' or '
|
|
3022
|
+
err(`${p}.transition.type`, "must be 'cut', 'morph', 'fade' or 'wipe'");
|
|
2700
3023
|
}
|
|
2701
3024
|
if (isObj(s.transition) && s.transition.text !== void 0) {
|
|
2702
3025
|
if (s.transition.type !== "morph") {
|
|
2703
|
-
err(`${p}.transition.text`, "is a morph option (fade already
|
|
3026
|
+
err(`${p}.transition.text`, "is a morph option (fade and wipe already change whole frames; cut has no window)");
|
|
2704
3027
|
} else if (s.transition.text !== "step" && s.transition.text !== "crossfade" && s.transition.text !== "dip") {
|
|
2705
3028
|
err(`${p}.transition.text`, "must be 'step' | 'crossfade' | 'dip'");
|
|
2706
3029
|
}
|
|
@@ -3095,11 +3418,29 @@ function buildEntities(layer, rng, w, h, scale = 1, countScale = 1) {
|
|
|
3095
3418
|
out[out.length - 1].pulsePhase = clockPhase - (x0 * Math.cos(ang) + y0 * Math.sin(ang)) / wl * Math.PI * 2;
|
|
3096
3419
|
}
|
|
3097
3420
|
}
|
|
3421
|
+
if (layer.rock) {
|
|
3422
|
+
const amp = layer.rock.amp * Math.PI / 180;
|
|
3423
|
+
for (const ent of out) {
|
|
3424
|
+
const ph = rng.range(0, Math.PI * 2);
|
|
3425
|
+
ent.rock = { amp, period: layer.rock.period, phase: layer.clock ? clockPhase : ph };
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3098
3428
|
if (layer.emit) {
|
|
3099
3429
|
for (let i = 0; i < out.length; i++) out[i].emit = emitParams(layer.emit, i, out.length);
|
|
3100
3430
|
}
|
|
3101
3431
|
return out;
|
|
3102
3432
|
}
|
|
3433
|
+
function textBlockText(s) {
|
|
3434
|
+
const n = s.number;
|
|
3435
|
+
if (!n) return s.text;
|
|
3436
|
+
const d = Math.max(0, Math.min(LIMITS.maxTextNumberDecimals, Math.round(n.decimals ?? 0)));
|
|
3437
|
+
const v = Number.isFinite(n.value) ? n.value : 0;
|
|
3438
|
+
const fixed = Math.abs(v).toFixed(d);
|
|
3439
|
+
const [whole, frac] = fixed.split(".");
|
|
3440
|
+
const grouped = n.grouping === false ? whole : whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
3441
|
+
const neg = v < 0 && Number(fixed) !== 0;
|
|
3442
|
+
return `${n.prefix ?? ""}${neg ? "-" : ""}${grouped}${frac !== void 0 ? `.${frac}` : ""}${n.suffix ?? ""}`;
|
|
3443
|
+
}
|
|
3103
3444
|
function warpZ(e, t) {
|
|
3104
3445
|
const wp = e.warp;
|
|
3105
3446
|
return wrap(wp.z0 - wp.vz * (t / 1e3), WARP_NEAR, 1);
|
|
@@ -3154,12 +3495,14 @@ function sizeAt(e, t) {
|
|
|
3154
3495
|
return s > 0 ? s : 0.1;
|
|
3155
3496
|
}
|
|
3156
3497
|
function rotationAt(e, t) {
|
|
3157
|
-
|
|
3498
|
+
let base = e.rotate ?? 0;
|
|
3499
|
+
if (e.rock) base += e.rock.amp * Math.sin(clockTime(e, t) * 2 * Math.PI / e.rock.period + e.rock.phase);
|
|
3158
3500
|
if (!e.spinSpeed) return base;
|
|
3159
3501
|
return base + e.spinPhase + e.spinSpeed * (t / 1e3);
|
|
3160
3502
|
}
|
|
3161
3503
|
function lifeAlphaAt(life, t) {
|
|
3162
3504
|
if (!life) return 1;
|
|
3505
|
+
if (life.stagger) return life.enter !== void 0 && t < life.enter ? 0 : 1;
|
|
3163
3506
|
const fade = life.fade ?? 1e3;
|
|
3164
3507
|
let a = 1;
|
|
3165
3508
|
if (life.enter !== void 0) {
|
|
@@ -3171,16 +3514,20 @@ function lifeAlphaAt(life, t) {
|
|
|
3171
3514
|
}
|
|
3172
3515
|
return a;
|
|
3173
3516
|
}
|
|
3517
|
+
function lifeStaggerAt(life, t, i) {
|
|
3518
|
+
if (!life?.stagger) return 1;
|
|
3519
|
+
return lifeAlphaAt({ enter: life.enter, exit: life.exit, fade: life.fade }, t - i * life.stagger);
|
|
3520
|
+
}
|
|
3174
3521
|
function wrap(v, min, max) {
|
|
3175
|
-
const
|
|
3176
|
-
return ((v - min) %
|
|
3522
|
+
const range2 = max - min;
|
|
3523
|
+
return ((v - min) % range2 + range2) % range2 + min;
|
|
3177
3524
|
}
|
|
3178
3525
|
function reflect(v, min, max) {
|
|
3179
|
-
const
|
|
3180
|
-
if (
|
|
3181
|
-
const period = 2 *
|
|
3526
|
+
const range2 = max - min;
|
|
3527
|
+
if (range2 <= 0) return min;
|
|
3528
|
+
const period = 2 * range2;
|
|
3182
3529
|
const p = ((v - min) % period + period) % period;
|
|
3183
|
-
return min + (p <
|
|
3530
|
+
return min + (p < range2 ? p : period - p);
|
|
3184
3531
|
}
|
|
3185
3532
|
function catmullRom2(p0, p1, p2, p3, u) {
|
|
3186
3533
|
const u2 = u * u;
|
|
@@ -3394,6 +3741,26 @@ function textMetricsClassFor(font) {
|
|
|
3394
3741
|
return font && MONO_FAMILY_RE.test(font) ? "mono" : "proportional";
|
|
3395
3742
|
}
|
|
3396
3743
|
var MONO_ADVANCE_EM = 0.6;
|
|
3744
|
+
var FULL_WIDTH_EM = 1;
|
|
3745
|
+
function isCombiningKana(cp) {
|
|
3746
|
+
return cp === 12441 || cp === 12442;
|
|
3747
|
+
}
|
|
3748
|
+
function isFullWidth(cp) {
|
|
3749
|
+
if (isCombiningKana(cp)) return false;
|
|
3750
|
+
return cp >= 4352 && cp <= 4447 || // Hangul Jamo leading consonants
|
|
3751
|
+
cp >= 11904 && cp <= 12350 || // CJK radicals, Kangxi, CJK symbols & punctuation
|
|
3752
|
+
cp >= 12353 && cp <= 13311 || // kana, Bopomofo, Hangul compat Jamo, enclosed CJK, CJK compat
|
|
3753
|
+
cp >= 13312 && cp <= 19903 || // CJK Ext A
|
|
3754
|
+
cp >= 19968 && cp <= 40959 || // CJK Unified Ideographs
|
|
3755
|
+
cp >= 40960 && cp <= 42191 || // Yi
|
|
3756
|
+
cp >= 43360 && cp <= 43391 || // Hangul Jamo Ext A
|
|
3757
|
+
cp >= 44032 && cp <= 55203 || // Hangul syllables
|
|
3758
|
+
cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
|
|
3759
|
+
cp >= 65072 && cp <= 65103 || // CJK compatibility forms
|
|
3760
|
+
cp >= 65281 && cp <= 65376 || // fullwidth ASCII variants
|
|
3761
|
+
cp >= 65504 && cp <= 65510 || // fullwidth signs
|
|
3762
|
+
cp >= 131072 && cp <= 262141;
|
|
3763
|
+
}
|
|
3397
3764
|
function charWidthEm(ch, metrics = "proportional") {
|
|
3398
3765
|
if (metrics === "mono") return MONO_ADVANCE_EM;
|
|
3399
3766
|
if (ch === " " || ch === " ") return 0.3;
|
|
@@ -3402,9 +3769,25 @@ function charWidthEm(ch, metrics = "proportional") {
|
|
|
3402
3769
|
return 0.55;
|
|
3403
3770
|
}
|
|
3404
3771
|
function textWidthEm(str, metrics = "proportional") {
|
|
3405
|
-
if (metrics === "mono")
|
|
3772
|
+
if (metrics === "mono") {
|
|
3773
|
+
let w2 = 0;
|
|
3774
|
+
for (const c of graphemeClusters(str)) {
|
|
3775
|
+
const cp = c.codePointAt(0);
|
|
3776
|
+
if (!isCombiningKana(cp)) w2 += isFullWidth(cp) ? FULL_WIDTH_EM : MONO_ADVANCE_EM;
|
|
3777
|
+
}
|
|
3778
|
+
return w2;
|
|
3779
|
+
}
|
|
3406
3780
|
let w = 0;
|
|
3407
|
-
for (let i = 0; i < str.length; i++)
|
|
3781
|
+
for (let i = 0; i < str.length; i++) {
|
|
3782
|
+
const cp = str.codePointAt(i);
|
|
3783
|
+
if (isCombiningKana(cp)) continue;
|
|
3784
|
+
if (isFullWidth(cp)) {
|
|
3785
|
+
w += FULL_WIDTH_EM;
|
|
3786
|
+
if (cp > 65535) i++;
|
|
3787
|
+
continue;
|
|
3788
|
+
}
|
|
3789
|
+
w += charWidthEm(str[i], metrics);
|
|
3790
|
+
}
|
|
3408
3791
|
return w;
|
|
3409
3792
|
}
|
|
3410
3793
|
function breakTextBlock(text, maxWidthEm, metrics = "proportional") {
|
|
@@ -3986,7 +4369,7 @@ function manifestFor(spec) {
|
|
|
3986
4369
|
var clamp014 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
|
|
3987
4370
|
var EXPIRED = /* @__PURE__ */ Symbol("expired");
|
|
3988
4371
|
function overrideHeldAt(o, t) {
|
|
3989
|
-
const k = o.dur > 0 ?
|
|
4372
|
+
const k = o.dur > 0 ? easeGlide(o.ease, clamp014((t - o.t0) / o.dur)) : 1;
|
|
3990
4373
|
return k >= 1 ? o.to : lerpValue(o.from, o.to, k, false, o.leaf);
|
|
3991
4374
|
}
|
|
3992
4375
|
function overrideValueAt(o, t, timelineValue) {
|
|
@@ -3995,9 +4378,20 @@ function overrideValueAt(o, t, timelineValue) {
|
|
|
3995
4378
|
return EXPIRED;
|
|
3996
4379
|
}
|
|
3997
4380
|
var steerId = (d) => `${d.t}|${JSON.stringify(d.value)}`;
|
|
3998
|
-
function armOverride(authored, path, from, to, now, glide) {
|
|
4381
|
+
function armOverride(authored, path, from, to, now, glide, ease2) {
|
|
3999
4382
|
const next = nextKeyAfter(authored, path, now);
|
|
4000
|
-
|
|
4383
|
+
const o = { leaf: path.slice(path.lastIndexOf(".") + 1), from, to, t0: now, dur: glide, until: next ? next.at : Number.POSITIVE_INFINITY, outDur: next ? next.dur : 0 };
|
|
4384
|
+
if (ease2) o.ease = ease2;
|
|
4385
|
+
return o;
|
|
4386
|
+
}
|
|
4387
|
+
function outgoingCounter(from, now) {
|
|
4388
|
+
if (from.number && now.number) return { text: from.text, number: { ...now.number, prefix: from.number.prefix, suffix: from.number.suffix } };
|
|
4389
|
+
return { text: textBlockText(from), number: void 0 };
|
|
4390
|
+
}
|
|
4391
|
+
function steerEase(deltas) {
|
|
4392
|
+
let best;
|
|
4393
|
+
for (const d of deltas) if (!best || (d.dur ?? DEFAULT_STEER_DUR) > (best.dur ?? DEFAULT_STEER_DUR)) best = d;
|
|
4394
|
+
return best?.ease !== void 0 && STEER_EASES.has(best.ease) ? best.ease : void 0;
|
|
4001
4395
|
}
|
|
4002
4396
|
function applyOverridesAt(resolved, overrides, t, expired) {
|
|
4003
4397
|
if (overrides.size === 0) return resolved;
|
|
@@ -4027,7 +4421,7 @@ function overridesStayValid(authored, overrides, now) {
|
|
|
4027
4421
|
const state = JSON.stringify(groups.map((g) => readSpecPath(r, g))) + "|" + [...held.keys()].join(",");
|
|
4028
4422
|
if (seen.has(state)) continue;
|
|
4029
4423
|
seen.add(state);
|
|
4030
|
-
if (!validateSpecPaths(r, paths)) return false;
|
|
4424
|
+
if (!validateSpecPaths(r, paths, true)) return false;
|
|
4031
4425
|
}
|
|
4032
4426
|
return true;
|
|
4033
4427
|
}
|
|
@@ -4070,6 +4464,34 @@ function softFill(g, color2, r) {
|
|
|
4070
4464
|
grad.addColorStop(1, hexToRgba(color2, 0));
|
|
4071
4465
|
return grad;
|
|
4072
4466
|
}
|
|
4467
|
+
function wipeMask(g, w, k, cw, ch) {
|
|
4468
|
+
const f = Math.max(0, Math.min(1, w.feather ?? 0.08));
|
|
4469
|
+
if ((w.shape ?? "circle") === "line") {
|
|
4470
|
+
const a = (w.angle ?? 0) * Math.PI / 180;
|
|
4471
|
+
const ux = Math.cos(a);
|
|
4472
|
+
const uy = Math.sin(a);
|
|
4473
|
+
const proj = [0, cw * ux, ch * uy, cw * ux + ch * uy];
|
|
4474
|
+
const p0 = Math.min(...proj);
|
|
4475
|
+
const span = Math.max(...proj) - p0;
|
|
4476
|
+
const fw2 = Math.max(1, f * span);
|
|
4477
|
+
const lead = p0 + k * (span + fw2);
|
|
4478
|
+
const trail = lead - fw2;
|
|
4479
|
+
const g22 = g.createLinearGradient(ux * trail, uy * trail, ux * lead, uy * lead);
|
|
4480
|
+
g22.addColorStop(0, "rgba(0,0,0,1)");
|
|
4481
|
+
g22.addColorStop(1, "rgba(0,0,0,0)");
|
|
4482
|
+
return g22;
|
|
4483
|
+
}
|
|
4484
|
+
const ox = Math.max(0, Math.min(1, w.origin?.x ?? 0.5)) * cw;
|
|
4485
|
+
const oy = Math.max(0, Math.min(1, w.origin?.y ?? 0.5)) * ch;
|
|
4486
|
+
const reach = Math.max(Math.hypot(ox, oy), Math.hypot(cw - ox, oy), Math.hypot(ox, ch - oy), Math.hypot(cw - ox, ch - oy));
|
|
4487
|
+
const fw = Math.max(1, f * reach);
|
|
4488
|
+
const outer = Math.max(1e-3, k * (reach + fw));
|
|
4489
|
+
const inner = Math.max(0, outer - fw);
|
|
4490
|
+
const g2 = g.createRadialGradient(ox, oy, inner, ox, oy, Math.max(outer, inner + 1e-3));
|
|
4491
|
+
g2.addColorStop(0, "rgba(0,0,0,1)");
|
|
4492
|
+
g2.addColorStop(1, "rgba(0,0,0,0)");
|
|
4493
|
+
return g2;
|
|
4494
|
+
}
|
|
4073
4495
|
var SpecInstance = class {
|
|
4074
4496
|
constructor(spec, ctx, opts = {}) {
|
|
4075
4497
|
this.spec = spec;
|
|
@@ -4560,21 +4982,27 @@ var SpecInstance = class {
|
|
|
4560
4982
|
}
|
|
4561
4983
|
}
|
|
4562
4984
|
const st = part.stroke;
|
|
4563
|
-
|
|
4985
|
+
const lines = st ? strokeLines(part) : part.flat.lines;
|
|
4986
|
+
if (st && lines.length) {
|
|
4564
4987
|
g.globalAlpha = a0 * part.opacity * (st.opacity ?? 1);
|
|
4565
4988
|
g.strokeStyle = st.color;
|
|
4566
4989
|
g.lineJoin = st.join ?? "round";
|
|
4567
4990
|
g.lineCap = st.cap ?? "round";
|
|
4568
4991
|
if (fr.uniform) {
|
|
4569
4992
|
g.lineWidth = st.width * r / fr.sx;
|
|
4570
|
-
if (
|
|
4571
|
-
|
|
4572
|
-
else {
|
|
4573
|
-
traceLines(g, part.flat.lines);
|
|
4993
|
+
if (lines !== part.flat.lines) {
|
|
4994
|
+
traceLines(g, lines);
|
|
4574
4995
|
g.stroke();
|
|
4996
|
+
} else {
|
|
4997
|
+
if (cache.stroke === void 0) cache.stroke = typeof Path2D === "undefined" ? null : ringPath2D(part.flat.lines);
|
|
4998
|
+
if (cache.stroke) g.stroke(cache.stroke);
|
|
4999
|
+
else {
|
|
5000
|
+
traceLines(g, part.flat.lines);
|
|
5001
|
+
g.stroke();
|
|
5002
|
+
}
|
|
4575
5003
|
}
|
|
4576
5004
|
} else {
|
|
4577
|
-
const scaled =
|
|
5005
|
+
const scaled = lines.map((l) => ({ closed: l.closed, points: l.points.map(([x, y]) => [x * fr.sx, y * fr.sy]) }));
|
|
4578
5006
|
g.save();
|
|
4579
5007
|
g.scale(1 / fr.sx, 1 / fr.sy);
|
|
4580
5008
|
g.lineWidth = st.width * r;
|
|
@@ -4999,7 +5427,8 @@ var SpecInstance = class {
|
|
|
4999
5427
|
}
|
|
5000
5428
|
if (sprite.kind === "stroke") {
|
|
5001
5429
|
const resolvedColor = sprite.colors?.[e.colorIndex] ?? sprite.color;
|
|
5002
|
-
const pts =
|
|
5430
|
+
const pts = strokeDrawnSamples(sprite, sz / 2);
|
|
5431
|
+
if (pts.length < 2) return;
|
|
5003
5432
|
const lw = strokeWidthPx(sprite, unitScale);
|
|
5004
5433
|
ctx.save();
|
|
5005
5434
|
ctx.translate(p.x, p.y);
|
|
@@ -5010,7 +5439,7 @@ var SpecInstance = class {
|
|
|
5010
5439
|
ctx.lineCap = "round";
|
|
5011
5440
|
ctx.lineJoin = "round";
|
|
5012
5441
|
if (sprite.taper) {
|
|
5013
|
-
const outline = strokeOutline(pts, lw);
|
|
5442
|
+
const outline = strokeOutline(pts, lw, true, strokeDrawnAt(sprite, sz / 2, pts));
|
|
5014
5443
|
ctx.fillStyle = resolvedColor;
|
|
5015
5444
|
ctx.beginPath();
|
|
5016
5445
|
ctx.moveTo(outline[0].x, outline[0].y);
|
|
@@ -5057,7 +5486,7 @@ var SpecInstance = class {
|
|
|
5057
5486
|
const lh = (sprite.lineHeight ?? 1.4) * fsPx;
|
|
5058
5487
|
const maxWPx = sprite.maxWidth * unitScale2;
|
|
5059
5488
|
const maxWEm = maxWPx / fsPx;
|
|
5060
|
-
const lines = breakTextBlock(sprite
|
|
5489
|
+
const lines = breakTextBlock(textBlockText(sprite), maxWEm, textMetricsClassFor(sprite.font));
|
|
5061
5490
|
const align = sprite.align ?? "left";
|
|
5062
5491
|
const reveal = sprite.reveal;
|
|
5063
5492
|
const rs = reveal ? revealState(lines, reveal, t) : null;
|
|
@@ -5079,6 +5508,7 @@ var SpecInstance = class {
|
|
|
5079
5508
|
if (rs?.glyphAlphas) {
|
|
5080
5509
|
ctx.textAlign = "left";
|
|
5081
5510
|
const baseAlpha = ctx.globalAlpha;
|
|
5511
|
+
const riseEm = Math.max(0, Math.min(LIMITS.maxRevealRise, reveal.rise ?? 0));
|
|
5082
5512
|
for (let li = 0; li < lines.length; li++) {
|
|
5083
5513
|
const alphas = rs.glyphAlphas[li] ?? [];
|
|
5084
5514
|
if (alphas.length > 0 && alphas[0] <= 0) break;
|
|
@@ -5097,7 +5527,7 @@ var SpecInstance = class {
|
|
|
5097
5527
|
const a = alphas[gi] ?? 0;
|
|
5098
5528
|
if (a <= 0) break;
|
|
5099
5529
|
ctx.globalAlpha = baseAlpha * a;
|
|
5100
|
-
ctx.fillText(clusters[gi], x0 + ctx.measureText(prefix).width, li * lh);
|
|
5530
|
+
ctx.fillText(clusters[gi], x0 + ctx.measureText(prefix).width, li * lh + riseEm * fsPx * (1 - a));
|
|
5101
5531
|
prefix += clusters[gi];
|
|
5102
5532
|
}
|
|
5103
5533
|
}
|
|
@@ -5152,7 +5582,7 @@ var SpecInstance = class {
|
|
|
5152
5582
|
const tr = this.transition;
|
|
5153
5583
|
if (!tr) return;
|
|
5154
5584
|
const k = tr.dur <= 0 ? 1 : (t - tr.startT) / tr.dur;
|
|
5155
|
-
this.effSpec = k >= 1 ? tr.to : lerpSpec(tr.from, tr.to,
|
|
5585
|
+
this.effSpec = k >= 1 ? tr.to : lerpSpec(tr.from, tr.to, easeGlide(tr.ease, k));
|
|
5156
5586
|
if (k >= 1) this.transition = null;
|
|
5157
5587
|
const sig = structuralSignature(this.effSpec);
|
|
5158
5588
|
if (sig !== this.lastStructural) this.rebuild();
|
|
@@ -5181,6 +5611,8 @@ var SpecInstance = class {
|
|
|
5181
5611
|
startT: this.lastT,
|
|
5182
5612
|
dur
|
|
5183
5613
|
};
|
|
5614
|
+
const ease2 = steerEase(deltas);
|
|
5615
|
+
if (ease2) this.transition.ease = ease2;
|
|
5184
5616
|
if (this.paused) {
|
|
5185
5617
|
this.transition = null;
|
|
5186
5618
|
this.effSpec = target;
|
|
@@ -5226,7 +5658,7 @@ var SpecInstance = class {
|
|
|
5226
5658
|
});
|
|
5227
5659
|
}
|
|
5228
5660
|
drawLinks(built, t, lifeA, parentE) {
|
|
5229
|
-
const { links } = built.layer;
|
|
5661
|
+
const { links, life } = built.layer;
|
|
5230
5662
|
if (!links) return;
|
|
5231
5663
|
const { ctx } = this;
|
|
5232
5664
|
const motionWraps = ["drift", "rise", "wander"].includes(built.layer.motion.type);
|
|
@@ -5251,6 +5683,7 @@ var SpecInstance = class {
|
|
|
5251
5683
|
} else resolvedColor = "#e6e8ef";
|
|
5252
5684
|
}
|
|
5253
5685
|
let a = links.alpha ?? alphaAt(ei, t);
|
|
5686
|
+
if (life?.stagger) a *= Math.min(lifeStaggerAt(life, t, i), lifeStaggerAt(life, t, j));
|
|
5254
5687
|
if (links.falloff && Number.isFinite(maxDistPx)) a *= Math.max(0, 1 - dist / maxDistPx);
|
|
5255
5688
|
ctx.globalAlpha = a * lifeA;
|
|
5256
5689
|
ctx.strokeStyle = resolvedColor;
|
|
@@ -5313,15 +5746,19 @@ var SpecInstance = class {
|
|
|
5313
5746
|
ctx.globalCompositeOperation = built.layer.blend ?? "source-over";
|
|
5314
5747
|
this.drawLinks(built, t, lifeA, parentE);
|
|
5315
5748
|
const override = this.paintOverrides?.get(li) ?? null;
|
|
5316
|
-
|
|
5317
|
-
|
|
5749
|
+
const life = built.layer.life;
|
|
5750
|
+
for (let ei = 0; ei < built.entities.length; ei++) {
|
|
5751
|
+
const e = built.entities[ei];
|
|
5752
|
+
const entA = life?.stagger ? lifeA * lifeStaggerAt(life, t, ei) : lifeA;
|
|
5753
|
+
if (entA <= 0) continue;
|
|
5754
|
+
this.drawTrail(built, e, t, entA, parentE);
|
|
5318
5755
|
if (override === null) {
|
|
5319
|
-
this.drawEntity(built, e, t,
|
|
5756
|
+
this.drawEntity(built, e, t, entA, parentE);
|
|
5320
5757
|
continue;
|
|
5321
5758
|
}
|
|
5322
5759
|
const [aOut, aIn] = override.mode === "dip" ? [Math.max(0, 1 - 2 * override.k), Math.max(0, 2 * override.k - 1)] : [1 - override.k, override.k];
|
|
5323
|
-
if (aOut > 0) this.drawEntity(built, e, t,
|
|
5324
|
-
if (aIn > 0) this.drawEntity(built, e, t,
|
|
5760
|
+
if (aOut > 0) this.drawEntity(built, e, t, entA, parentE, { sprite: override.outgoing, alpha: aOut });
|
|
5761
|
+
if (aIn > 0) this.drawEntity(built, e, t, entA, parentE, { alpha: aIn });
|
|
5325
5762
|
}
|
|
5326
5763
|
if (tf || gtf) ctx.restore();
|
|
5327
5764
|
}
|
|
@@ -5374,10 +5811,12 @@ var SpecInstance = class {
|
|
|
5374
5811
|
if (group?.opacity !== void 0) lifeA *= clamp014(group.opacity);
|
|
5375
5812
|
let sig = `${pathPaintSig(built.layer.sprite)}:${lifeA.toFixed(4)}:${JSON.stringify(built.layer.transform ?? null)}:${JSON.stringify(group?.transform ?? null)}`;
|
|
5376
5813
|
const parentE = this.parentEntityFor(built);
|
|
5377
|
-
|
|
5814
|
+
const life = built.layer.life;
|
|
5815
|
+
built.entities.forEach((e, ei) => {
|
|
5378
5816
|
const p = this.entityPos(e, t, parentE);
|
|
5379
|
-
|
|
5380
|
-
|
|
5817
|
+
const a = life?.stagger ? alphaAt(e, t) * lifeStaggerAt(life, t, ei) : alphaAt(e, t);
|
|
5818
|
+
sig += `|${p.x.toFixed(2)},${p.y.toFixed(2)},${sizeAt(e, t).toFixed(3)},${rotationAt(e, t).toFixed(5)},${a.toFixed(4)},${e.colorIndex}`;
|
|
5819
|
+
});
|
|
5381
5820
|
return sig;
|
|
5382
5821
|
}
|
|
5383
5822
|
plateSig(a, b) {
|
|
@@ -5529,11 +5968,12 @@ var SpecInstance = class {
|
|
|
5529
5968
|
}
|
|
5530
5969
|
const next = new Map(this.overrides);
|
|
5531
5970
|
const armed = [];
|
|
5971
|
+
const batchEase = steerEase([...latest.values()]);
|
|
5532
5972
|
for (const [path, d] of latest) {
|
|
5533
5973
|
const id = steerId(d);
|
|
5534
5974
|
if (this.seenSteers.get(path) === id) continue;
|
|
5535
5975
|
armed.push([path, id]);
|
|
5536
|
-
next.set(path, armOverride(this.authored, path, glide > 0 ? readSpecPath(shown, path) : d.value, d.value, now, glide));
|
|
5976
|
+
next.set(path, armOverride(this.authored, path, glide > 0 ? readSpecPath(shown, path) : d.value, d.value, now, glide, batchEase));
|
|
5537
5977
|
}
|
|
5538
5978
|
if (armed.length === 0) return true;
|
|
5539
5979
|
if (!overridesStayValid(this.authored, next, now)) return false;
|
|
@@ -5903,16 +6343,80 @@ var SequenceInstance = class {
|
|
|
5903
6343
|
if (!textStringsDiffer(specA, specB, i)) continue;
|
|
5904
6344
|
const from = specA.layers[i].sprite;
|
|
5905
6345
|
const now = lerped.layers[i].sprite;
|
|
5906
|
-
const outgoing = from.kind === "text" && now.kind === "text" ? { ...now, strings: from.strings } : from.kind === "textBlock" && now.kind === "textBlock" ? { ...now,
|
|
6346
|
+
const outgoing = from.kind === "text" && now.kind === "text" ? { ...now, strings: from.strings } : from.kind === "textBlock" && now.kind === "textBlock" ? { ...now, ...outgoingCounter(from, now) } : now;
|
|
5907
6347
|
if (outgoing === now) continue;
|
|
5908
6348
|
(map ??= /* @__PURE__ */ new Map()).set(i, mode === "dip" ? { outgoing, k, mode } : { outgoing, k });
|
|
5909
6349
|
}
|
|
5910
6350
|
return map;
|
|
5911
6351
|
}
|
|
5912
|
-
/** Length of the fade out of segment `from`, or 0 (
|
|
6352
|
+
/** Length of the fade (or wipe) out of segment `from`, or 0 (none, or a tier that plays it as cut). */
|
|
5913
6353
|
fadeDur(from) {
|
|
5914
6354
|
const tr = this.seq.segments[from]?.transition;
|
|
5915
|
-
return this.fadeEnabled && tr?.type === "fade" ? tr.dur : 0;
|
|
6355
|
+
return this.fadeEnabled && (tr?.type === "fade" || tr?.type === "wipe") ? tr.dur : 0;
|
|
6356
|
+
}
|
|
6357
|
+
/** The wipe out of segment `from`, when that is its transition. */
|
|
6358
|
+
wipeOf(from) {
|
|
6359
|
+
const tr = this.seq.segments[from]?.transition;
|
|
6360
|
+
return tr?.type === "wipe" ? tr : null;
|
|
6361
|
+
}
|
|
6362
|
+
wipeScratch = null;
|
|
6363
|
+
wipeScratchIn = null;
|
|
6364
|
+
/** `canvas` masked to one side of the wipe edge at progress `k`, on a scratch canvas of its own. */
|
|
6365
|
+
wipeSide(slot, canvas, wipe, k, side) {
|
|
6366
|
+
const cw = Math.max(1, canvas.width);
|
|
6367
|
+
const ch = Math.max(1, canvas.height);
|
|
6368
|
+
const scratch = this[slot] ??= createSceneCanvas(cw, ch);
|
|
6369
|
+
if (scratch.width !== cw) scratch.width = cw;
|
|
6370
|
+
if (scratch.height !== ch) scratch.height = ch;
|
|
6371
|
+
const g = scratch.getContext("2d");
|
|
6372
|
+
if (!g) return null;
|
|
6373
|
+
g.save();
|
|
6374
|
+
g.setTransform(1, 0, 0, 1, 0, 0);
|
|
6375
|
+
g.globalCompositeOperation = "copy";
|
|
6376
|
+
g.drawImage(canvas, 0, 0);
|
|
6377
|
+
g.globalCompositeOperation = side === "out" ? "destination-out" : "destination-in";
|
|
6378
|
+
g.fillStyle = wipeMask(g, wipe, k, cw, ch);
|
|
6379
|
+
g.fillRect(0, 0, cw, ch);
|
|
6380
|
+
g.restore();
|
|
6381
|
+
return scratch;
|
|
6382
|
+
}
|
|
6383
|
+
/**
|
|
6384
|
+
* Composite one side of a transition out of segment `from` at eased
|
|
6385
|
+
* progress `k`. A fade draws the outgoing at 1 − k (and, over a bed, the
|
|
6386
|
+
* incoming at k). A wipe draws each side through the moving edge instead:
|
|
6387
|
+
* the outgoing everywhere the edge has not yet passed, the incoming where
|
|
6388
|
+
* it has — the canvas is masked on a scratch copy (`destination-out` /
|
|
6389
|
+
* `destination-in` with the edge's gradient), then drawn. `other`: the
|
|
6390
|
+
* opposite side's canvas, when it is drawn too (a wipe over a bed) — the
|
|
6391
|
+
* two masked sides are summed (`lighter`) on the scratch first, so a soft
|
|
6392
|
+
* edge keeps matching marks opaque instead of letting the bed show through.
|
|
6393
|
+
*/
|
|
6394
|
+
compositeTransition(canvas, k, from, side, other) {
|
|
6395
|
+
const wipe = this.wipeOf(from);
|
|
6396
|
+
if (!wipe) {
|
|
6397
|
+
if (other) this.composite(other, side === "out" ? k : 1 - k);
|
|
6398
|
+
return this.composite(canvas, side === "out" ? 1 - k : k);
|
|
6399
|
+
}
|
|
6400
|
+
const main = this.childCtx.surface?.getContext("2d");
|
|
6401
|
+
if (!main) return;
|
|
6402
|
+
const scratch = this.wipeSide("wipeScratch", canvas, wipe, k, side);
|
|
6403
|
+
if (!scratch) return this.composite(canvas, side === "out" ? 1 - k : k);
|
|
6404
|
+
if (other) {
|
|
6405
|
+
const rest = this.wipeSide("wipeScratchIn", other, wipe, k, side === "out" ? "in" : "out");
|
|
6406
|
+
const g = scratch.getContext("2d");
|
|
6407
|
+
if (rest && g) {
|
|
6408
|
+
g.save();
|
|
6409
|
+
g.setTransform(1, 0, 0, 1, 0, 0);
|
|
6410
|
+
g.globalCompositeOperation = "lighter";
|
|
6411
|
+
g.drawImage(rest, 0, 0);
|
|
6412
|
+
g.restore();
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
main.save();
|
|
6416
|
+
main.globalAlpha = 1;
|
|
6417
|
+
main.globalCompositeOperation = "source-over";
|
|
6418
|
+
main.drawImage(scratch, 0, 0, this.childCtx.width, this.childCtx.height);
|
|
6419
|
+
main.restore();
|
|
5916
6420
|
}
|
|
5917
6421
|
/** Sum of every timed segment — the lap length under `loop: true`. */
|
|
5918
6422
|
timedTotal() {
|
|
@@ -6178,15 +6682,14 @@ var SequenceInstance = class {
|
|
|
6178
6682
|
if (!this.fadingIn) this.fadingIn = this.offscreenChild(index, useMorphSeed ? chainRoot : index);
|
|
6179
6683
|
this.renderOffscreen(this.fadingIn, localT, effSeed);
|
|
6180
6684
|
const out = this.renderOutgoing(fadeFrom, localT, seed);
|
|
6181
|
-
this.
|
|
6182
|
-
this.composite(out.canvas, 1 - k);
|
|
6685
|
+
this.compositeTransition(out.canvas, k, fadeFrom, "out", this.fadingIn.canvas);
|
|
6183
6686
|
return;
|
|
6184
6687
|
}
|
|
6185
6688
|
const child = useMorphSeed ? this.ensureChild(index, this.childScene(chainRoot)) : this.ensureChild(index);
|
|
6186
6689
|
child.renderFrame(localT, effSeed);
|
|
6187
6690
|
if (fadeActive) {
|
|
6188
6691
|
const out = this.renderOutgoing(fadeFrom, localT, seed);
|
|
6189
|
-
this.
|
|
6692
|
+
this.compositeTransition(out.canvas, easeSmooth(localT / this.fadeDur(fadeFrom)), fadeFrom, "out");
|
|
6190
6693
|
}
|
|
6191
6694
|
}
|
|
6192
6695
|
}
|
|
@@ -6836,7 +7339,9 @@ function textFontPx(s, size, fontScale) {
|
|
|
6836
7339
|
function textBox(s, e, p, fontScale, unitScale) {
|
|
6837
7340
|
const fh = textFontPx(s, e.size, fontScale);
|
|
6838
7341
|
const str = s.strings[e.spriteIndex] ?? s.strings[0] ?? "";
|
|
6839
|
-
const
|
|
7342
|
+
const head = Array.from(str).slice(0, 48);
|
|
7343
|
+
const wide = head.reduce((n, c) => n + (isFullWidth(c.codePointAt(0)) ? 1 : 0), 0);
|
|
7344
|
+
const est = Math.max(fh, fh * (0.62 * head.length + 0.38 * wide));
|
|
6840
7345
|
const fw = s.maxWidth ? Math.min(est, s.maxWidth * unitScale) : est;
|
|
6841
7346
|
const align = s.align ?? "center";
|
|
6842
7347
|
const baseline = s.baseline ?? "middle";
|
|
@@ -6853,7 +7358,7 @@ function textBlockBox(s, _e, p, w, h) {
|
|
|
6853
7358
|
const lh = (s.lineHeight ?? 1.4) * fsPx;
|
|
6854
7359
|
const maxWPx = s.maxWidth * unitScale;
|
|
6855
7360
|
const maxWEm = maxWPx / fsPx;
|
|
6856
|
-
const lines = breakTextBlock(s
|
|
7361
|
+
const lines = breakTextBlock(textBlockText(s), maxWEm, textMetricsClassFor(s.font));
|
|
6857
7362
|
const totalH = lines.length * lh;
|
|
6858
7363
|
const maxLineW = lines.reduce((m, l) => Math.max(m, l.widthEm), 0) * fsPx;
|
|
6859
7364
|
const align = s.align ?? "left";
|
|
@@ -6870,7 +7375,7 @@ function textBlockRevealFraction(s, w, h, t) {
|
|
|
6870
7375
|
if (!s.reveal) return 1;
|
|
6871
7376
|
const unitScale = Math.min(w, h);
|
|
6872
7377
|
const maxWEm = s.maxWidth * unitScale / (s.fontSize * unitScale);
|
|
6873
|
-
const lines = breakTextBlock(s
|
|
7378
|
+
const lines = breakTextBlock(textBlockText(s), maxWEm, textMetricsClassFor(s.font));
|
|
6874
7379
|
const rs = revealState(lines, s.reveal, t);
|
|
6875
7380
|
if (rs.glyphAlphas) {
|
|
6876
7381
|
let sum = 0;
|
|
@@ -7113,7 +7618,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
7113
7618
|
if (!part.stroke) return [];
|
|
7114
7619
|
const hw = part.stroke.width * radius / 2;
|
|
7115
7620
|
const out = [];
|
|
7116
|
-
for (const l of part
|
|
7621
|
+
for (const l of strokeLines(part)) {
|
|
7117
7622
|
const m = l.points.map(([u, v]) => mapUnit(u, v));
|
|
7118
7623
|
const n = l.closed ? m.length : m.length - 1;
|
|
7119
7624
|
for (let i = 0; i < n; i++) {
|
|
@@ -7207,9 +7712,12 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
7207
7712
|
for (const { layer, entities } of scene.layers) {
|
|
7208
7713
|
const lifeA = layer.opacity === void 0 && layer.group === void 0 ? lifeAlphaAt(layer.life, tPass) : lifeAlphaAt(layer.life, tPass) * paintOpacity(spec, layer);
|
|
7209
7714
|
if (lifeA <= 0) continue;
|
|
7210
|
-
for (
|
|
7211
|
-
|
|
7212
|
-
const
|
|
7715
|
+
for (let ei = 0; ei < entities.length; ei++) {
|
|
7716
|
+
const e = entities[ei];
|
|
7717
|
+
const entA = layer.life?.stagger ? lifeA * lifeStaggerAt(layer.life, tPass, ei) : lifeA;
|
|
7718
|
+
if (entA <= 0) continue;
|
|
7719
|
+
splatTrail(layer, e, tPass, entA, alphaScale, entities.length);
|
|
7720
|
+
const a = Math.min(1, alphaAt(e, tPass) * entA) * alphaScale;
|
|
7213
7721
|
if (a <= 4e-3) continue;
|
|
7214
7722
|
const lum = spriteLuma(layer, e);
|
|
7215
7723
|
const p = posOf(scene, e, tPass);
|
|
@@ -7220,7 +7728,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
7220
7728
|
continue;
|
|
7221
7729
|
}
|
|
7222
7730
|
if (s.kind === "stroke") {
|
|
7223
|
-
const pts =
|
|
7731
|
+
const pts = strokeDrawnSamples(s, sz / 2);
|
|
7224
7732
|
let angle = rotationAt(e, tPass) + paintRotation(spec, layer);
|
|
7225
7733
|
if (s.orient) angle += headingAt(e, tPass, w, h) ?? 0;
|
|
7226
7734
|
const ca = Math.cos(angle);
|
|
@@ -7236,6 +7744,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
7236
7744
|
compose(r * cols + c, lum, a * Math.min(1, lw * taper / cellH), layer.blend);
|
|
7237
7745
|
};
|
|
7238
7746
|
const step = Math.min(cellW, cellH) / 2;
|
|
7747
|
+
if (pts.length < 2) continue;
|
|
7239
7748
|
stampAt(pts[0], 0);
|
|
7240
7749
|
for (let i = 1; i < pts.length; i++) {
|
|
7241
7750
|
const q0 = pts[i - 1];
|
|
@@ -7331,13 +7840,15 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
7331
7840
|
for (const edge of edges) {
|
|
7332
7841
|
const pi = positions[edge.i];
|
|
7333
7842
|
const pj = positions[edge.j];
|
|
7843
|
+
const env = layer.life?.stagger ? Math.min(lifeStaggerAt(layer.life, tPass, edge.i), lifeStaggerAt(layer.life, tPass, edge.j)) : 1;
|
|
7844
|
+
if (env <= 0) continue;
|
|
7334
7845
|
const steps = Math.max(1, Math.ceil(edge.dist / Math.min(cellW, cellH)));
|
|
7335
7846
|
for (let i = 0; i <= steps; i++) {
|
|
7336
7847
|
const k = i / steps;
|
|
7337
7848
|
const c = Math.floor((pi.x + (pj.x - pi.x) * k) / cellW);
|
|
7338
7849
|
const r = Math.floor((pi.y + (pj.y - pi.y) * k) / cellH);
|
|
7339
7850
|
if (c < 0 || c >= cols || r < 0 || r >= rows) continue;
|
|
7340
|
-
compose(r * cols + c, lum, wgt, layer.blend);
|
|
7851
|
+
compose(r * cols + c, lum, wgt * env, layer.blend);
|
|
7341
7852
|
}
|
|
7342
7853
|
}
|
|
7343
7854
|
}
|
|
@@ -7395,6 +7906,57 @@ function finishGrid(cols, rows, cells, bgRow, bgCells) {
|
|
|
7395
7906
|
colProfile
|
|
7396
7907
|
};
|
|
7397
7908
|
}
|
|
7909
|
+
function layerCellFootprints(spec, opts = {}) {
|
|
7910
|
+
const scene = buildScene(spec, opts);
|
|
7911
|
+
const cols = Math.max(8, Math.min(200, opts.cols ?? 80));
|
|
7912
|
+
const rows = Math.max(8, Math.min(200, opts.rows ?? 48));
|
|
7913
|
+
const { w, h, scale } = scene;
|
|
7914
|
+
const cellW = w / cols;
|
|
7915
|
+
const cellH = h / rows;
|
|
7916
|
+
const fontScale = fontScaleFor(spec, w, h);
|
|
7917
|
+
return scene.layers.map(({ layer, entities }) => {
|
|
7918
|
+
if (layer.motion.type !== "static" || layer.emit || layer.links || layer.trail) return null;
|
|
7919
|
+
const s = layer.sprite;
|
|
7920
|
+
const k = layer.transform !== void 0 || layer.group !== void 0 ? paintSize(spec, layer) : 1;
|
|
7921
|
+
const cells = /* @__PURE__ */ new Set();
|
|
7922
|
+
for (const e of entities) {
|
|
7923
|
+
if (e.orbitParent || e.cyclePeriod > 0) return null;
|
|
7924
|
+
const p = posOf(scene, e, 0);
|
|
7925
|
+
const grow = 1 + Math.abs(e.growAmp);
|
|
7926
|
+
const sz = e.size * grow * k;
|
|
7927
|
+
const per = e.size > 0 ? sz / e.size : 1;
|
|
7928
|
+
let reach;
|
|
7929
|
+
if (s.kind === "circle" || s.kind === "ring") reach = sz / 2;
|
|
7930
|
+
else if (s.kind === "polygon" && isPathPolygon(s)) {
|
|
7931
|
+
const fr = pathFrame(s, sz / 2, w, h);
|
|
7932
|
+
reach = 0;
|
|
7933
|
+
for (const part of pathParts(s)) {
|
|
7934
|
+
const { cx, cy, hw, hh } = part.box;
|
|
7935
|
+
const dx = Math.max(Math.abs(cx - hw - fr.ox), Math.abs(cx + hw - fr.ox)) * fr.sx;
|
|
7936
|
+
const dy = Math.max(Math.abs(cy - hh - fr.oy), Math.abs(cy + hh - fr.oy)) * fr.sy;
|
|
7937
|
+
reach = Math.max(reach, Math.hypot(dx, dy) + (part.stroke?.width ?? 0) * sz / 4);
|
|
7938
|
+
}
|
|
7939
|
+
} else if (s.kind === "polygon") reach = polygonPoints(s, sz / 2).reduce((m, q) => Math.max(m, Math.hypot(q.x, q.y)), sz / 2);
|
|
7940
|
+
else if (s.kind === "rect") reach = Math.hypot(sz, (e.size2 ?? e.size) * per) / 2;
|
|
7941
|
+
else if (s.kind === "bar") {
|
|
7942
|
+
const b = barBox(s.direction ?? "right", sz * barFraction(s, e.barIndex ?? 0), e.size2 !== void 0 ? e.size2 * per : sz * 0.2);
|
|
7943
|
+
reach = Math.hypot(Math.abs(b.cx) + b.halfX, Math.abs(b.cy) + b.halfY);
|
|
7944
|
+
} else if (s.kind === "streak") reach = sz + (s.width ?? (scale === 1 ? 2 : 2e-3)) * scale;
|
|
7945
|
+
else if (s.kind === "stroke") reach = strokeSamples(s, sz / 2).reduce((m, q) => Math.max(m, Math.hypot(q.x, q.y)), 0) + strokeWidthPx(s, scale);
|
|
7946
|
+
else if (s.kind === "text" || s.kind === "textBlock") {
|
|
7947
|
+
const b = s.kind === "text" ? textBox(s, e, p, fontScale, scale) : textBlockBox(s, e, p, w, h);
|
|
7948
|
+
reach = Math.hypot(Math.abs(b.cx - p.x) + b.halfX, Math.abs(b.cy - p.y) + b.halfY) * grow * Math.max(1, k);
|
|
7949
|
+
} else reach = sz;
|
|
7950
|
+
if (!Number.isFinite(reach) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return null;
|
|
7951
|
+
const c0 = Math.max(0, Math.floor((p.x - reach - 1) / cellW));
|
|
7952
|
+
const c1 = Math.min(cols - 1, Math.floor((p.x + reach + 1) / cellW));
|
|
7953
|
+
const r0 = Math.max(0, Math.floor((p.y - reach - 1) / cellH));
|
|
7954
|
+
const r1 = Math.min(rows - 1, Math.floor((p.y + reach + 1) / cellH));
|
|
7955
|
+
for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) cells.add(r * cols + c);
|
|
7956
|
+
}
|
|
7957
|
+
return [...cells].sort((a, b) => a - b);
|
|
7958
|
+
});
|
|
7959
|
+
}
|
|
7398
7960
|
var DITHER = [
|
|
7399
7961
|
[0.5 / 8, 4.5 / 8],
|
|
7400
7962
|
[2.5 / 8, 6.5 / 8],
|
|
@@ -7457,7 +8019,7 @@ function textSprites(spec, opts = {}) {
|
|
|
7457
8019
|
const s2 = layer.sprite;
|
|
7458
8020
|
const unitScale = Math.min(scene.w, scene.h);
|
|
7459
8021
|
const sizePx2 = s2.fontSize * unitScale;
|
|
7460
|
-
const info = { layerIndex, key: layer.key, strings: [s2
|
|
8022
|
+
const info = { layerIndex, key: layer.key, strings: [textBlockText(s2)], count: entities.length, sizePx: Math.round(sizePx2) };
|
|
7461
8023
|
if (s2.reveal) info.revealed = Math.round(textBlockRevealFraction(s2, scene.w, scene.h, t) * 100) / 100;
|
|
7462
8024
|
out.push(info);
|
|
7463
8025
|
return;
|
|
@@ -7551,8 +8113,9 @@ function rawDominance(spec, opts = {}) {
|
|
|
7551
8113
|
const areaScale = painted ? paintSize(spec, layer) ** 2 : 1;
|
|
7552
8114
|
let area = 0;
|
|
7553
8115
|
let lumAcc = 0;
|
|
7554
|
-
for (
|
|
7555
|
-
const
|
|
8116
|
+
for (let ei = 0; ei < entities.length; ei++) {
|
|
8117
|
+
const e = entities[ei];
|
|
8118
|
+
const a = alphaAt(e, t) * lifeA * lifeStaggerAt(layer.life, t, ei);
|
|
7556
8119
|
const sz = sizeAt(e, t);
|
|
7557
8120
|
const s = layer.sprite;
|
|
7558
8121
|
let entArea;
|
|
@@ -7573,7 +8136,7 @@ function rawDominance(spec, opts = {}) {
|
|
|
7573
8136
|
entArea = sz * barFraction(s, e.barIndex ?? 0) * th;
|
|
7574
8137
|
} else if (s.kind === "stroke") {
|
|
7575
8138
|
const meanTaper = s.taper ? 0.64 : 1;
|
|
7576
|
-
entArea = pathLength(
|
|
8139
|
+
entArea = pathLength(strokeDrawnSamples(s, sz / 2)) * strokeWidthPx(s, scale) * meanTaper * LINE_SALIENCE;
|
|
7577
8140
|
} else if (s.kind === "text") {
|
|
7578
8141
|
const box = textBox(s, e, { x: 0, y: 0 }, fontScaleFor(spec, w, h), scale);
|
|
7579
8142
|
entArea = box.halfX * 2 * box.halfY * 2 * 0.55;
|
|
@@ -7600,7 +8163,10 @@ function rawDominance(spec, opts = {}) {
|
|
|
7600
8163
|
const la = (layer.links.alpha ?? 0.6) * lifeA;
|
|
7601
8164
|
const defaultWidth = scale === 1 ? 1 : 1 / LIMITS.referenceViewport;
|
|
7602
8165
|
const lw = (layer.links.width ?? defaultWidth) * scale;
|
|
7603
|
-
for (const edge of edges)
|
|
8166
|
+
for (const edge of edges) {
|
|
8167
|
+
const env = layer.life?.stagger ? Math.min(lifeStaggerAt(layer.life, t, edge.i), lifeStaggerAt(layer.life, t, edge.j)) : 1;
|
|
8168
|
+
area += edge.dist * lw * la * LINE_SALIENCE * env;
|
|
8169
|
+
}
|
|
7604
8170
|
}
|
|
7605
8171
|
const meanLuma = entities.length ? lumAcc / entities.length : 0;
|
|
7606
8172
|
const contrast = Math.abs(meanLuma - bgMean) + 0.1;
|
|
@@ -7707,10 +8273,11 @@ function sceneColour(spec, opts = {}) {
|
|
|
7707
8273
|
wb += c.b * weight;
|
|
7708
8274
|
wsum += weight;
|
|
7709
8275
|
};
|
|
7710
|
-
for (
|
|
8276
|
+
for (let ei = 0; ei < entities.length; ei++) {
|
|
8277
|
+
const e = entities[ei];
|
|
7711
8278
|
const sz = sizeAt(e, t);
|
|
7712
8279
|
const s = layer.sprite;
|
|
7713
|
-
const a = alphaAt(e, t) * lifeA * onScreenFraction(scene, s, e, sz, k, t, paintRotation(spec, layer));
|
|
8280
|
+
const a = alphaAt(e, t) * lifeA * lifeStaggerAt(layer.life, t, ei) * onScreenFraction(scene, s, e, sz, k, t, paintRotation(spec, layer));
|
|
7714
8281
|
if (a <= 0) continue;
|
|
7715
8282
|
if (s.kind === "polygon" && isPathPolygon(s)) {
|
|
7716
8283
|
const fr = pathFrame(s, sz / 2, scene.w, scene.h);
|
|
@@ -7922,25 +8489,89 @@ function perceiveSequenceFrame(seq, T, opts = {}) {
|
|
|
7922
8489
|
|
|
7923
8490
|
// src/flash-timeline.ts
|
|
7924
8491
|
import { analyzeFlashes, srgbToLinear as srgbToLinear2 } from "@idle-screens/validator";
|
|
7925
|
-
var LUMA_PATH = /(^|\.)(opacity|alpha|color|colors|stops|bands|gradient|stroke|fill|background)(\.|$)|(^|\.)(sprite|parts?)$|^layers\.\d+$/;
|
|
8492
|
+
var LUMA_PATH = /(^|\.)(opacity|alpha|color|colors|stops|bands|gradient|stroke|fill|background|draw)(\.|$)|(^|\.)(sprite|parts?)$|^layers\.\d+$/;
|
|
7926
8493
|
var MIN_BOUNDARIES = 7;
|
|
7927
8494
|
var MAX_SAMPLES = 64;
|
|
7928
8495
|
var MAX_TOTAL_SAMPLES = 512;
|
|
7929
8496
|
var WINDOW = 1e3;
|
|
7930
|
-
|
|
8497
|
+
var COLS = 16;
|
|
8498
|
+
var ROWS = 9;
|
|
8499
|
+
var AREA_THRESHOLD = 0.25;
|
|
8500
|
+
function lumaTracks(spec) {
|
|
7931
8501
|
const tl = spec.timeline;
|
|
7932
|
-
if (!tl) return {
|
|
8502
|
+
if (!tl) return { tracks: [], lap: 0 };
|
|
7933
8503
|
const lap = tl.loop && tl.duration ? tl.duration : 0;
|
|
7934
|
-
const
|
|
8504
|
+
const tracks = [];
|
|
7935
8505
|
for (const [path, keys] of timelineTracks(spec)) {
|
|
7936
8506
|
if (!LUMA_PATH.test(path)) continue;
|
|
8507
|
+
const bounds = [];
|
|
7937
8508
|
for (const k of keys) {
|
|
7938
8509
|
const dur = k.dur ?? DEFAULT_KEY_DUR;
|
|
7939
8510
|
bounds.push(k.t);
|
|
7940
8511
|
if (dur > 0) bounds.push(k.t + dur);
|
|
7941
8512
|
}
|
|
8513
|
+
tracks.push({ path, times: lap ? [...bounds, ...bounds.map((b) => b + lap)] : bounds });
|
|
8514
|
+
}
|
|
8515
|
+
return { tracks, lap };
|
|
8516
|
+
}
|
|
8517
|
+
function lumaKeyTimes(spec) {
|
|
8518
|
+
const { tracks, lap } = lumaTracks(spec);
|
|
8519
|
+
return { times: [...new Set(tracks.flatMap((tr) => tr.times))].sort((a, b) => a - b), lap };
|
|
8520
|
+
}
|
|
8521
|
+
var PAINT_LEAF = /(^|\.)(opacity|alpha|color|fill)(\.\d+)?$|(^|\.)gradient(\.|$)|(^|\.)draw\.(start|end)$/;
|
|
8522
|
+
function pathCells(spec) {
|
|
8523
|
+
const layers = spec.layers ?? [];
|
|
8524
|
+
const volatile = /* @__PURE__ */ new Set();
|
|
8525
|
+
const members = (g) => layers.flatMap((l, i) => l.group === g ? [i] : []);
|
|
8526
|
+
const reach = /* @__PURE__ */ new Map();
|
|
8527
|
+
for (const path of timelineTracks(spec).keys()) {
|
|
8528
|
+
const [root, id, ...rest] = path.split(".");
|
|
8529
|
+
const leaf = rest.join(".");
|
|
8530
|
+
let to;
|
|
8531
|
+
if (root === "layers" && /^\d+$/.test(id ?? "")) {
|
|
8532
|
+
const n = Number(id);
|
|
8533
|
+
to = [n];
|
|
8534
|
+
if (!PAINT_LEAF.test(leaf)) {
|
|
8535
|
+
if (rest[0] === "transform") volatile.add(n);
|
|
8536
|
+
else for (let i = n; i < layers.length; i++) volatile.add(i);
|
|
8537
|
+
}
|
|
8538
|
+
} else if (root === "groups" && id !== void 0) {
|
|
8539
|
+
to = members(id);
|
|
8540
|
+
if (leaf !== "opacity") for (const i of to) volatile.add(i);
|
|
8541
|
+
} else if (root === "background") to = "all";
|
|
8542
|
+
else return null;
|
|
8543
|
+
if (LUMA_PATH.test(path)) reach.set(path, to);
|
|
8544
|
+
}
|
|
8545
|
+
if (reach.size === 0) return /* @__PURE__ */ new Map();
|
|
8546
|
+
const footprints = layerCellFootprints(withoutTimeline(spec), { cols: COLS, rows: ROWS });
|
|
8547
|
+
const out = /* @__PURE__ */ new Map();
|
|
8548
|
+
for (const [path, to] of reach) {
|
|
8549
|
+
if (to === "all" || to.some((i) => volatile.has(i) || !footprints[i])) out.set(path, null);
|
|
8550
|
+
else out.set(path, [...new Set(to.flatMap((i) => footprints[i]))]);
|
|
8551
|
+
}
|
|
8552
|
+
return out;
|
|
8553
|
+
}
|
|
8554
|
+
function cellsThatCanFlash(tracks, cellsOf, from, to) {
|
|
8555
|
+
const perCell = Array.from({ length: COLS * ROWS }, () => []);
|
|
8556
|
+
for (const tr of tracks) {
|
|
8557
|
+
const ts = tr.times.filter((t) => t >= from && t <= to);
|
|
8558
|
+
if (ts.length === 0) continue;
|
|
8559
|
+
const cells = cellsOf.get(tr.path);
|
|
8560
|
+
if (cells === null || cells === void 0) for (const list of perCell) list.push(...ts);
|
|
8561
|
+
else for (const c of cells) perCell[c].push(...ts);
|
|
8562
|
+
}
|
|
8563
|
+
let n = 0;
|
|
8564
|
+
for (const list of perCell) {
|
|
8565
|
+
if (list.length < MIN_BOUNDARIES) continue;
|
|
8566
|
+
const ts = [...new Set(list)].sort((a, b) => a - b);
|
|
8567
|
+
for (let i = 0; i + MIN_BOUNDARIES - 1 < ts.length; i++) {
|
|
8568
|
+
if (ts[i + MIN_BOUNDARIES - 1] - ts[i] <= WINDOW + 2) {
|
|
8569
|
+
n++;
|
|
8570
|
+
break;
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
7942
8573
|
}
|
|
7943
|
-
return
|
|
8574
|
+
return n;
|
|
7944
8575
|
}
|
|
7945
8576
|
function flashSampleTimes(boundaries) {
|
|
7946
8577
|
const ts = /* @__PURE__ */ new Set();
|
|
@@ -7955,7 +8586,8 @@ function flashSampleTimes(boundaries) {
|
|
|
7955
8586
|
function timelineFlashReport(spec) {
|
|
7956
8587
|
const pass = { fails: false, flashesPerSecond: 0, flashingArea: 0, at: null };
|
|
7957
8588
|
if (!spec.timeline) return pass;
|
|
7958
|
-
const {
|
|
8589
|
+
const { tracks, lap } = lumaTracks(spec);
|
|
8590
|
+
const times = [...new Set(tracks.flatMap((tr) => tr.times))].sort((a, b) => a - b);
|
|
7959
8591
|
const dense = [];
|
|
7960
8592
|
for (let i = 0, e = 0; i < times.length; i++) {
|
|
7961
8593
|
while (e < times.length && times[e] < times[i] + WINDOW) e++;
|
|
@@ -7970,12 +8602,14 @@ function timelineFlashReport(spec) {
|
|
|
7970
8602
|
dense.sort((a, b) => b.n - a.n);
|
|
7971
8603
|
let budget = MAX_TOTAL_SAMPLES;
|
|
7972
8604
|
let worst = pass;
|
|
8605
|
+
const cellsOf = pathCells(spec);
|
|
7973
8606
|
for (const w of dense) {
|
|
8607
|
+
if (cellsOf && cellsThatCanFlash(tracks, cellsOf, w.from, w.to) < Math.ceil(AREA_THRESHOLD * COLS * ROWS)) continue;
|
|
7974
8608
|
const sampleTimes = flashSampleTimes(times.filter((t) => t >= w.from && t <= w.to));
|
|
7975
8609
|
for (let from = 0; from < sampleTimes.length - 1; ) {
|
|
7976
8610
|
const chunk = sampleTimes.slice(from, from + MAX_SAMPLES);
|
|
7977
8611
|
if (chunk.length < 2) break;
|
|
7978
|
-
if (chunk.length > budget) return { ...worst, fails: true, at: lap ? w.from % lap : w.from, incomplete: true };
|
|
8612
|
+
if (chunk.length > budget) return { ...worst, fails: true, at: lap ? w.from % lap : w.from, incomplete: true, boundariesPerSecond: w.n };
|
|
7979
8613
|
budget -= chunk.length;
|
|
7980
8614
|
const samples = chunk.map((t) => ({ t, lum: luminanceGrid(spec, { t, cols: 16, rows: 9 }).cells.flat().map((v) => srgbToLinear2(v * 255)) }));
|
|
7981
8615
|
const r = analyzeFlashes(samples);
|
|
@@ -7998,6 +8632,102 @@ function timelineFlashReport(spec) {
|
|
|
7998
8632
|
return worst;
|
|
7999
8633
|
}
|
|
8000
8634
|
|
|
8635
|
+
// src/period.ts
|
|
8636
|
+
var MAX_SCENE_PERIOD = 36e5;
|
|
8637
|
+
var PERIOD_LEAF = /(^|\.)(period|rate|every|duration|spin|speed|blink|drift)$/;
|
|
8638
|
+
var gcd = (a, b) => {
|
|
8639
|
+
while (b) [a, b] = [b, a % b];
|
|
8640
|
+
return a;
|
|
8641
|
+
};
|
|
8642
|
+
function toRatio(ms) {
|
|
8643
|
+
for (let q = 1; q <= 1e3; q++) {
|
|
8644
|
+
const p = ms * q;
|
|
8645
|
+
if (Math.abs(p - Math.round(p)) < 1e-6) {
|
|
8646
|
+
const n = Math.round(p);
|
|
8647
|
+
const g = gcd(n, q);
|
|
8648
|
+
return [n / g, q / g];
|
|
8649
|
+
}
|
|
8650
|
+
}
|
|
8651
|
+
return null;
|
|
8652
|
+
}
|
|
8653
|
+
var range = (r) => r === void 0 ? null : typeof r === "number" ? r : r[0] === r[1] ? r[0] : NaN;
|
|
8654
|
+
function scenePeriod(spec) {
|
|
8655
|
+
const parts = [];
|
|
8656
|
+
const reasons = [];
|
|
8657
|
+
const add = (label, ms) => {
|
|
8658
|
+
if (!(ms > 0) || !Number.isFinite(ms)) return;
|
|
8659
|
+
const ratio = toRatio(ms);
|
|
8660
|
+
if (!ratio) reasons.push(`${label} repeats every ${Number(ms.toFixed(3))} ms, which has no exact ratio to combine`);
|
|
8661
|
+
else parts.push([label, ratio[0], ratio[1]]);
|
|
8662
|
+
};
|
|
8663
|
+
const tl = spec.timeline;
|
|
8664
|
+
if (tl && tl.keys.length > 0) {
|
|
8665
|
+
const keyed = tl.keys.filter((k) => PERIOD_LEAF.test(String(k.path)));
|
|
8666
|
+
if (keyed.length > 0) reasons.push(`timeline keys animate ${[...new Set(keyed.map((k) => String(k.path)))].join(", ")}, which sets the scene's own period`);
|
|
8667
|
+
if (tl.loop && (tl.duration ?? 0) > 0) add("timeline", tl.duration);
|
|
8668
|
+
else reasons.push("timeline does not loop (it plays once, then holds)");
|
|
8669
|
+
}
|
|
8670
|
+
const bg = spec.background;
|
|
8671
|
+
if (bg && bg.type !== "solid" && bg.drift) {
|
|
8672
|
+
const { period } = bg.drift;
|
|
8673
|
+
const field = bg.type === "field" && bg.drift.amount !== 0;
|
|
8674
|
+
const r = field ? toRatio(period) : null;
|
|
8675
|
+
add("background drift", r ? r[0] / gcd(r[0], FIELD_BUCKET_MS) * FIELD_BUCKET_MS : period);
|
|
8676
|
+
}
|
|
8677
|
+
if (spec.finish?.animate && ((spec.finish.grain ?? 0) > 0 || (spec.finish.dither ?? 0) > 0)) reasons.push("finish.animate steps the grain without repeating");
|
|
8678
|
+
spec.layers.forEach((layer, i) => {
|
|
8679
|
+
const at = `layers[${i}]${layer.key ? ` (${layer.key})` : ""}`;
|
|
8680
|
+
const rate = layer.clock?.rate ?? 1;
|
|
8681
|
+
const life = layer.life;
|
|
8682
|
+
if (life && (life.exit !== void 0 || life.enter !== void 0 && ((life.enter ?? 0) > 0 || (life.fade ?? 1e3) > 0))) reasons.push(`${at} life builds in or out once`);
|
|
8683
|
+
if (layer.pulse) add(`${at} pulse`, layer.pulse.period / rate);
|
|
8684
|
+
if (layer.grow) add(`${at} grow`, layer.grow.period / rate);
|
|
8685
|
+
if (layer.rock) add(`${at} rock`, layer.rock.period / rate);
|
|
8686
|
+
if (layer.emit) add(`${at} emit`, layer.emit.every);
|
|
8687
|
+
const spin = range(layer.spin);
|
|
8688
|
+
if (spin !== null) {
|
|
8689
|
+
if (Number.isNaN(spin)) reasons.push(`${at} spin range gives each entity its own speed`);
|
|
8690
|
+
else if (spin !== 0) add(`${at} spin`, 36e4 / Math.abs(spin));
|
|
8691
|
+
}
|
|
8692
|
+
const sp = layer.sprite;
|
|
8693
|
+
if ((sp.kind === "emoji" || sp.kind === "text") && sp.cycle && spriteVariants(sp) > 1) add(`${at} cycle`, sp.cycle.period * spriteVariants(sp) / rate);
|
|
8694
|
+
if (sp.kind === "textBlock" && sp.reveal) {
|
|
8695
|
+
if ((sp.reveal.speed ?? 0) > 0) reasons.push(`${at} reveal types itself once`);
|
|
8696
|
+
if (sp.reveal.caret) {
|
|
8697
|
+
const hz = Math.min(3, sp.reveal.caret === true ? 1.2 : sp.reveal.caret.blink ?? 1.2);
|
|
8698
|
+
if (hz > 0) add(`${at} caret`, 1e3 / hz);
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
const m = layer.motion;
|
|
8702
|
+
if (m.type === "path") add(`${at} path`, m.duration);
|
|
8703
|
+
else if (m.type === "orbit") {
|
|
8704
|
+
const s = range(m.speed);
|
|
8705
|
+
if (s === null || Number.isNaN(s)) reasons.push(`${at} orbit speed range gives each entity its own lap`);
|
|
8706
|
+
else if (s !== 0) add(`${at} orbit`, 36e4 / Math.abs(s));
|
|
8707
|
+
} else if (m.type !== "static") {
|
|
8708
|
+
if (m.type === "drift" && m.ease?.type === "settle" && !layer.emit) reasons.push(`${at} drift settles once`);
|
|
8709
|
+
else reasons.push(`${at} ${m.type} motion travels at seeded per-entity speeds`);
|
|
8710
|
+
}
|
|
8711
|
+
});
|
|
8712
|
+
const labelled = parts.map(([l, n, d]) => `${l}: ${Number((n / d).toFixed(3))} ms`);
|
|
8713
|
+
if (reasons.length > 0) return { period: null, parts: labelled, reasons };
|
|
8714
|
+
if (parts.length === 0) return { period: 0, parts: labelled, reasons };
|
|
8715
|
+
let num = 1;
|
|
8716
|
+
let den = 0;
|
|
8717
|
+
for (const [, n, d] of parts) {
|
|
8718
|
+
num = num / gcd(num, n) * n;
|
|
8719
|
+
den = gcd(den, d);
|
|
8720
|
+
if (num / den > MAX_SCENE_PERIOD) {
|
|
8721
|
+
return { period: null, parts: labelled, reasons: [`its parts share no cycle shorter than ${MAX_SCENE_PERIOD / 6e4} min`] };
|
|
8722
|
+
}
|
|
8723
|
+
}
|
|
8724
|
+
if (num % den !== 0) return { period: null, parts: labelled, reasons: [`its parts repeat together every ${Number((num / den).toFixed(3))} ms, not a whole number of milliseconds`] };
|
|
8725
|
+
return { period: num / den, parts: labelled, reasons };
|
|
8726
|
+
}
|
|
8727
|
+
function periodWithoutTimeline(spec) {
|
|
8728
|
+
return scenePeriod(withoutTimeline(spec));
|
|
8729
|
+
}
|
|
8730
|
+
|
|
8001
8731
|
// src/advise.ts
|
|
8002
8732
|
var LOW_CONTRAST_FLOOR = 0.05;
|
|
8003
8733
|
var SPARSE_DECLARED_MAX_COVERAGE = 0.02;
|
|
@@ -8006,9 +8736,9 @@ var READ_SAFE_AREA = 0.05;
|
|
|
8006
8736
|
var ADDITIVE_INK_CEILING = 1;
|
|
8007
8737
|
var CALM_MAX_SPIN = 90;
|
|
8008
8738
|
var NEAR_FLASH_MAX_BOUNDARIES = 10;
|
|
8009
|
-
function edgeAnchored(
|
|
8010
|
-
if (!
|
|
8011
|
-
const [a, b] =
|
|
8739
|
+
function edgeAnchored(range2) {
|
|
8740
|
+
if (!range2) return false;
|
|
8741
|
+
const [a, b] = range2;
|
|
8012
8742
|
return (a <= 0.02 || b >= 0.98) && b - a <= 0.6;
|
|
8013
8743
|
}
|
|
8014
8744
|
function adviseSpec(spec, viewport = { width: 1920, height: 1080 }, opts = {}) {
|
|
@@ -8137,7 +8867,7 @@ function adviseSpec(spec, viewport = { width: 1920, height: 1080 }, opts = {}) {
|
|
|
8137
8867
|
const fsPx = layer.sprite.fontSize * scale;
|
|
8138
8868
|
const lh = (layer.sprite.lineHeight ?? 1.4) * fsPx;
|
|
8139
8869
|
const maxWPx = layer.sprite.maxWidth * scale;
|
|
8140
|
-
const lines = breakTextBlock(layer.sprite
|
|
8870
|
+
const lines = breakTextBlock(textBlockText(layer.sprite), maxWPx / fsPx, textMetricsClassFor(layer.sprite.font));
|
|
8141
8871
|
pixArea = maxWPx * lines.length * lh * 0.55 * (layer.sprite.opacity ?? 1);
|
|
8142
8872
|
} else if (layer.sprite.kind === "text") {
|
|
8143
8873
|
const b = textBoxAt(layer.sprite, e, positionAt(e, 0, w, h), spec, w, h);
|
|
@@ -8236,6 +8966,20 @@ function adviseSpec(spec, viewport = { width: 1920, height: 1080 }, opts = {}) {
|
|
|
8236
8966
|
message: `the timeline flickers ${flicker.flashesPerSecond} times a second over ${Math.round(flicker.area * 100)}% of the frame around ${Math.round(flicker.at)} ms \u2014 at or near WCAG 2.3.1's limit of 3; space opposing luminance changes \u2265 500 ms apart, or lower the contrast between the two states`
|
|
8237
8967
|
});
|
|
8238
8968
|
}
|
|
8969
|
+
if (authored.timeline?.loop && (authored.timeline.duration ?? 0) > 0) {
|
|
8970
|
+
const lap = Math.round(authored.timeline.duration);
|
|
8971
|
+
const rest = periodWithoutTimeline(authored);
|
|
8972
|
+
if (rest.period !== null && rest.period > 0 && lap % rest.period !== 0) {
|
|
8973
|
+
let a = lap;
|
|
8974
|
+
let b = rest.period;
|
|
8975
|
+
while (b) [a, b] = [b, a % b];
|
|
8976
|
+
warnings.push({
|
|
8977
|
+
path: "timeline.duration",
|
|
8978
|
+
code: "loop-seam",
|
|
8979
|
+
message: `the timeline loops every ${lap} ms but the rest of the scene repeats every ${rest.period} ms (${rest.parts.join(", ")}), so the whole scene only repeats every ${lap / a * rest.period} ms \u2014 make the lap a multiple of ${rest.period} ms for one lap to loop`
|
|
8980
|
+
});
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8239
8983
|
for (let li = 0; li < spec.layers.length; li++) {
|
|
8240
8984
|
if (hidden?.[li]) continue;
|
|
8241
8985
|
const layer = spec.layers[li];
|
|
@@ -8312,7 +9056,7 @@ function adviseSpec(spec, viewport = { width: 1920, height: 1080 }, opts = {}) {
|
|
|
8312
9056
|
const raw = rot ? { ...unturned, ...mapBox(turn, unturned) } : unturned;
|
|
8313
9057
|
const baseAlpha = Math.max(0, Math.min(1, e.alpha - e.pulseAmp)) * (s.kind === "textBlock" ? s.opacity ?? 1 : 1);
|
|
8314
9058
|
const alpha = layer.opacity === void 0 && layer.group === void 0 ? baseAlpha : baseAlpha * paintOpacity(spec, layer);
|
|
8315
|
-
const str = s.kind === "textBlock" ? s
|
|
9059
|
+
const str = s.kind === "textBlock" ? textBlockText(s) : s.strings[e.spriteIndex] ?? s.strings[0] ?? "";
|
|
8316
9060
|
textBoxes.push({ li, label, alpha, str, ...box, rawW: raw.x1 - raw.x0, rawH: raw.y1 - raw.y0 });
|
|
8317
9061
|
}
|
|
8318
9062
|
}
|
|
@@ -8638,7 +9382,7 @@ function textBlockBoxAt(s, p, w, h) {
|
|
|
8638
9382
|
const fsPx = s.fontSize * unit;
|
|
8639
9383
|
const lh = (s.lineHeight ?? 1.4) * fsPx;
|
|
8640
9384
|
const maxWPx = s.maxWidth * unit;
|
|
8641
|
-
const lines = breakTextBlock(s
|
|
9385
|
+
const lines = breakTextBlock(textBlockText(s), maxWPx / fsPx, textMetricsClassFor(s.font));
|
|
8642
9386
|
const maxLineW = lines.reduce((mx, l) => Math.max(mx, l.widthEm), 0) * fsPx;
|
|
8643
9387
|
const totalH = lines.length * lh;
|
|
8644
9388
|
const align = s.align ?? "left";
|
|
@@ -8679,13 +9423,15 @@ function adviseSequence(seq, viewport = { width: 1920, height: 1080 }) {
|
|
|
8679
9423
|
}
|
|
8680
9424
|
}
|
|
8681
9425
|
const fadeBound = seq.loop ? seq.segments.length : seq.segments.length - 1;
|
|
8682
|
-
const
|
|
9426
|
+
const live = (s) => s.transition?.type === "fade" || s.transition?.type === "wipe";
|
|
9427
|
+
const fades = seq.segments.slice(0, fadeBound).filter(live).length;
|
|
8683
9428
|
if (fades > 0) {
|
|
8684
|
-
const first = seq.segments.slice(0, fadeBound).findIndex(
|
|
9429
|
+
const first = seq.segments.slice(0, fadeBound).findIndex(live);
|
|
9430
|
+
const wipes = seq.segments.slice(0, fadeBound).some((s) => s.transition?.type === "wipe");
|
|
8685
9431
|
warnings.push({
|
|
8686
9432
|
path: `segments[${first}].transition`,
|
|
8687
9433
|
code: "fade-degrades-on-low-tier",
|
|
8688
|
-
message: `informational: ${fades} fade transition${fades === 1 ? "" : "s"} declared \u2014 viewers on the 'basic' or 'minimal' capability tier render fade as cut (two live segments for dur is over that budget); the show still plays`
|
|
9434
|
+
message: wipes ? `informational: ${fades} fade/wipe transition${fades === 1 ? "" : "s"} declared \u2014 viewers on the 'basic' or 'minimal' capability tier render them as cuts (two live segments for dur is over that budget); the show still plays` : `informational: ${fades} fade transition${fades === 1 ? "" : "s"} declared \u2014 viewers on the 'basic' or 'minimal' capability tier render fade as cut (two live segments for dur is over that budget); the show still plays`
|
|
8689
9435
|
});
|
|
8690
9436
|
}
|
|
8691
9437
|
for (let i = 0; i < seq.segments.length - 1; i++) {
|
|
@@ -8693,7 +9439,7 @@ function adviseSequence(seq, viewport = { width: 1920, height: 1080 }) {
|
|
|
8693
9439
|
const lumaA = backgroundLuma(resolveTimelineAt(seq.segments[i].scene, seq.segments[i].duration ?? 0));
|
|
8694
9440
|
const lumaB = backgroundLuma(resolveTimelineAt(seq.segments[i + 1].scene, 0));
|
|
8695
9441
|
const delta = Math.abs(lumaA - lumaB);
|
|
8696
|
-
if (delta > 0.5 && tr?.type !== "fade") {
|
|
9442
|
+
if (delta > 0.5 && tr?.type !== "fade" && tr?.type !== "wipe") {
|
|
8697
9443
|
warnings.push({
|
|
8698
9444
|
path: `segments[${i}]`,
|
|
8699
9445
|
code: "boundary-luminance-jump",
|
|
@@ -11190,6 +11936,74 @@ var TYPED_CUE_SPEC = {
|
|
|
11190
11936
|
]
|
|
11191
11937
|
};
|
|
11192
11938
|
|
|
11939
|
+
// src/examples/ink-tide.ts
|
|
11940
|
+
var INK_TIDE_SPEC = {
|
|
11941
|
+
schemaVersion: 1,
|
|
11942
|
+
id: "ink-tide",
|
|
11943
|
+
label: "Ink Tide",
|
|
11944
|
+
seed: 31,
|
|
11945
|
+
motionIntensity: "calm",
|
|
11946
|
+
background: { type: "gradient", stops: [{ at: 0, color: "#f1e9d8" }, { at: 1, color: "#e2d6bd" }] },
|
|
11947
|
+
layers: [
|
|
11948
|
+
{
|
|
11949
|
+
key: "ink",
|
|
11950
|
+
count: 1,
|
|
11951
|
+
position: { x: 0.5, y: 0.48 },
|
|
11952
|
+
motion: { type: "static" },
|
|
11953
|
+
sprite: {
|
|
11954
|
+
kind: "polygon",
|
|
11955
|
+
radius: [0.4, 0.4],
|
|
11956
|
+
color: "#2b2622",
|
|
11957
|
+
fill: false,
|
|
11958
|
+
// One pen run: the ridge, the shoreline, then the moon.
|
|
11959
|
+
path: "M -1 0.32 L -0.64 -0.12 L -0.44 0.06 L -0.08 -0.46 L 0.2 -0.1 L 0.4 -0.26 L 1 0.32 M -0.96 0.5 C -0.5 0.4 -0.2 0.58 0.1 0.5 C 0.4 0.42 0.7 0.58 0.96 0.5 M 0.62 -0.66 A 0.12 0.12 0 1 1 0.619 -0.66",
|
|
11960
|
+
stroke: { color: "#2b2622", width: 0.01, cap: "round", join: "round", draw: { start: 0, end: 1 } }
|
|
11961
|
+
}
|
|
11962
|
+
},
|
|
11963
|
+
{
|
|
11964
|
+
key: "boat",
|
|
11965
|
+
count: 1,
|
|
11966
|
+
position: { x: 0.5, y: 0.5, dx: 0.12, dy: 0.17 },
|
|
11967
|
+
motion: { type: "static" },
|
|
11968
|
+
sprite: { kind: "polygon", radius: [0.035, 0.035], color: "#2b2622", points: [[-1, -0.15], [1, -0.15], [0.62, 0.3], [-0.62, 0.3]] },
|
|
11969
|
+
rock: { amp: 6, period: 6e3 },
|
|
11970
|
+
clock: { phase: 0 }
|
|
11971
|
+
},
|
|
11972
|
+
{
|
|
11973
|
+
key: "caption",
|
|
11974
|
+
count: 1,
|
|
11975
|
+
position: { x: 0.5, y: 0.86 },
|
|
11976
|
+
motion: { type: "static" },
|
|
11977
|
+
sprite: {
|
|
11978
|
+
kind: "textBlock",
|
|
11979
|
+
text: "the tide goes out and leaves the sky behind",
|
|
11980
|
+
maxWidth: 1.2,
|
|
11981
|
+
fontSize: 0.032,
|
|
11982
|
+
anchor: "center",
|
|
11983
|
+
align: "center",
|
|
11984
|
+
color: "#5a4f45",
|
|
11985
|
+
font: "italic Georgia, 'Times New Roman', serif",
|
|
11986
|
+
opacity: 1,
|
|
11987
|
+
reveal: { progress: 1, mode: "glyphFade", fade: 0.3, rise: 0.4 }
|
|
11988
|
+
}
|
|
11989
|
+
}
|
|
11990
|
+
],
|
|
11991
|
+
timeline: {
|
|
11992
|
+
loop: true,
|
|
11993
|
+
duration: 3e4,
|
|
11994
|
+
keys: [
|
|
11995
|
+
{ t: 0, path: "ink.sprite.stroke.draw.end", value: 1, dur: 12e3, ease: "out" },
|
|
11996
|
+
{ t: 9e3, path: "caption.sprite.reveal.progress", value: 1, dur: 4e3, ease: "out" },
|
|
11997
|
+
{ t: 2e4, path: "ink.sprite.stroke.draw.start", value: 1, dur: 7e3, ease: "in" },
|
|
11998
|
+
{ t: 21e3, path: "caption.sprite.opacity", value: 0, dur: 3e3 },
|
|
11999
|
+
{ t: 29e3, path: "ink.sprite.stroke.draw.end", value: 0, dur: 0 },
|
|
12000
|
+
{ t: 29e3, path: "ink.sprite.stroke.draw.start", value: 0, dur: 0 },
|
|
12001
|
+
{ t: 29200, path: "caption.sprite.reveal.progress", value: 0, dur: 0 },
|
|
12002
|
+
{ t: 29400, path: "caption.sprite.opacity", value: 1, dur: 0 }
|
|
12003
|
+
]
|
|
12004
|
+
}
|
|
12005
|
+
};
|
|
12006
|
+
|
|
11193
12007
|
// src/examples/index.ts
|
|
11194
12008
|
var SCHEMA_EXAMPLES = [
|
|
11195
12009
|
{ id: "aquarium", label: "Aquarium", spec: AQUARIUM_SPEC },
|
|
@@ -11219,7 +12033,8 @@ var SCHEMA_EXAMPLES = [
|
|
|
11219
12033
|
{ id: "phase-duet", label: "Phase Duet", spec: PHASE_DUET_SPEC },
|
|
11220
12034
|
{ id: "web-work", label: "Web Work", spec: WEB_WORK_SPEC },
|
|
11221
12035
|
{ id: "shard-fall", label: "Shard Fall", spec: SHARD_FALL_SPEC },
|
|
11222
|
-
{ id: "murmur", label: "Murmur", spec: MURMUR_SPEC }
|
|
12036
|
+
{ id: "murmur", label: "Murmur", spec: MURMUR_SPEC },
|
|
12037
|
+
{ id: "ink-tide", label: "Ink Tide", spec: INK_TIDE_SPEC }
|
|
11223
12038
|
];
|
|
11224
12039
|
var EXAMPLE_SEQUENCES = [
|
|
11225
12040
|
{ id: "three-movements", label: "Three Movements", sequence: THREE_MOVEMENTS_SEQUENCE }
|
|
@@ -11255,6 +12070,8 @@ export {
|
|
|
11255
12070
|
LOBBY_TALK_SPEC,
|
|
11256
12071
|
MATRIX_RAIN_SPEC,
|
|
11257
12072
|
MAX_PATH_POINTS,
|
|
12073
|
+
MAX_SCENE_PERIOD,
|
|
12074
|
+
MOTION_FIELDS,
|
|
11258
12075
|
MURMUR_SPEC,
|
|
11259
12076
|
NOSTALGHIA_CANDLE_SPEC,
|
|
11260
12077
|
ORRERY_SPEC,
|
|
@@ -11271,6 +12088,7 @@ export {
|
|
|
11271
12088
|
SIGNAL_BOARD_SPEC,
|
|
11272
12089
|
SNOWFALL_SPEC,
|
|
11273
12090
|
SPARSE_NIGHT_SPEC,
|
|
12091
|
+
STEER_EASES,
|
|
11274
12092
|
THERMAL_FIELD_SPEC,
|
|
11275
12093
|
THREE_MOVEMENTS_SEQUENCE,
|
|
11276
12094
|
TYPED_CUE_SPEC,
|
|
@@ -11302,6 +12120,8 @@ export {
|
|
|
11302
12120
|
describeScene,
|
|
11303
12121
|
diffScenes,
|
|
11304
12122
|
dominanceRanking,
|
|
12123
|
+
drawSpan,
|
|
12124
|
+
easeGlide,
|
|
11305
12125
|
easeSmooth,
|
|
11306
12126
|
emitEnvelope,
|
|
11307
12127
|
emitWindow,
|
|
@@ -11320,13 +12140,17 @@ export {
|
|
|
11320
12140
|
headingAt,
|
|
11321
12141
|
hexToRgb255,
|
|
11322
12142
|
ignoredPropertyPaths,
|
|
12143
|
+
isCombiningKana,
|
|
12144
|
+
isFullWidth,
|
|
11323
12145
|
isPathPolygon,
|
|
11324
12146
|
latticeHash,
|
|
12147
|
+
layerCellFootprints,
|
|
11325
12148
|
layerCohesion,
|
|
11326
12149
|
lerpPathD,
|
|
11327
12150
|
lerpSpec,
|
|
11328
12151
|
lerpValue,
|
|
11329
12152
|
lifeAlphaAt,
|
|
12153
|
+
lifeStaggerAt,
|
|
11330
12154
|
linkEdges,
|
|
11331
12155
|
linkMaxDistPx,
|
|
11332
12156
|
linkPairs,
|
|
@@ -11341,6 +12165,7 @@ export {
|
|
|
11341
12165
|
pathParts,
|
|
11342
12166
|
perceiveScene,
|
|
11343
12167
|
perceiveSequenceFrame,
|
|
12168
|
+
periodWithoutTimeline,
|
|
11344
12169
|
pickScreenOp,
|
|
11345
12170
|
positionAt,
|
|
11346
12171
|
presentWithFinish,
|
|
@@ -11354,6 +12179,7 @@ export {
|
|
|
11354
12179
|
rgb255Luma,
|
|
11355
12180
|
rotationAt,
|
|
11356
12181
|
sceneColour,
|
|
12182
|
+
scenePeriod,
|
|
11357
12183
|
seamCauseOf,
|
|
11358
12184
|
seamsWorthWarning,
|
|
11359
12185
|
segmentRenderSeed,
|
|
@@ -11365,8 +12191,11 @@ export {
|
|
|
11365
12191
|
spriteIndexAt,
|
|
11366
12192
|
spriteVariants,
|
|
11367
12193
|
steerablePaths,
|
|
12194
|
+
strokeDrawnSamples,
|
|
12195
|
+
strokeLines,
|
|
11368
12196
|
structuralSignature,
|
|
11369
12197
|
textBlockAnchorOffset,
|
|
12198
|
+
textBlockText,
|
|
11370
12199
|
textMetricsClassFor,
|
|
11371
12200
|
textSprites,
|
|
11372
12201
|
textStringsDiffer,
|
|
@@ -11377,9 +12206,12 @@ export {
|
|
|
11377
12206
|
timelineSampleTimes,
|
|
11378
12207
|
timelineSampleTimesAfter,
|
|
11379
12208
|
timelineTracks,
|
|
12209
|
+
trimLines,
|
|
12210
|
+
trimPolyline,
|
|
11380
12211
|
validateSequence,
|
|
11381
12212
|
validateSpec,
|
|
11382
12213
|
validateSpecPaths,
|
|
12214
|
+
wipeMask,
|
|
11383
12215
|
withoutTimeline
|
|
11384
12216
|
};
|
|
11385
12217
|
//# sourceMappingURL=index.js.map
|