reframe-video 0.3.0 → 0.5.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/dist/bin.js +26 -0
- package/dist/index.js +600 -15
- package/dist/types/cursor.d.ts +57 -0
- package/dist/types/devicePreset.d.ts +4 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/textFx.d.ts +91 -0
- package/dist/types/textMetrics.d.ts +3 -0
- package/guides/edsl-guide.md +59 -0
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1044,6 +1044,14 @@ var init_devicePreset = __esm({
|
|
|
1044
1044
|
}
|
|
1045
1045
|
});
|
|
1046
1046
|
|
|
1047
|
+
// ../core/src/cursor.ts
|
|
1048
|
+
var init_cursor = __esm({
|
|
1049
|
+
"../core/src/cursor.ts"() {
|
|
1050
|
+
"use strict";
|
|
1051
|
+
init_dsl();
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1047
1055
|
// ../core/src/rig.ts
|
|
1048
1056
|
var init_rig = __esm({
|
|
1049
1057
|
"../core/src/rig.ts"() {
|
|
@@ -1070,6 +1078,22 @@ var init_figure = __esm({
|
|
|
1070
1078
|
}
|
|
1071
1079
|
});
|
|
1072
1080
|
|
|
1081
|
+
// ../core/src/textMetrics.ts
|
|
1082
|
+
var init_textMetrics = __esm({
|
|
1083
|
+
"../core/src/textMetrics.ts"() {
|
|
1084
|
+
"use strict";
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
// ../core/src/textFx.ts
|
|
1089
|
+
var init_textFx = __esm({
|
|
1090
|
+
"../core/src/textFx.ts"() {
|
|
1091
|
+
"use strict";
|
|
1092
|
+
init_dsl();
|
|
1093
|
+
init_textMetrics();
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1073
1097
|
// ../core/src/motionOps.ts
|
|
1074
1098
|
var init_motionOps = __esm({
|
|
1075
1099
|
"../core/src/motionOps.ts"() {
|
|
@@ -1288,9 +1312,11 @@ var init_src = __esm({
|
|
|
1288
1312
|
init_path();
|
|
1289
1313
|
init_presets();
|
|
1290
1314
|
init_devicePreset();
|
|
1315
|
+
init_cursor();
|
|
1291
1316
|
init_rig();
|
|
1292
1317
|
init_characterPreset();
|
|
1293
1318
|
init_figure();
|
|
1319
|
+
init_textFx();
|
|
1294
1320
|
init_motionOps();
|
|
1295
1321
|
init_audio();
|
|
1296
1322
|
init_evaluate();
|
package/dist/index.js
CHANGED
|
@@ -884,14 +884,14 @@ function makeRng(seed) {
|
|
|
884
884
|
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
885
885
|
var SET = 1 / 120;
|
|
886
886
|
function ctx(o) {
|
|
887
|
-
const
|
|
887
|
+
const rand2 = makeRng((o.seed ?? 0) + 1);
|
|
888
888
|
return {
|
|
889
889
|
e: clamp01(o.energy ?? 0.5),
|
|
890
890
|
sp: Math.max(0.25, o.speed ?? 1),
|
|
891
891
|
it: clamp01(o.intensity ?? 0.5),
|
|
892
892
|
from: o.from,
|
|
893
|
-
rand,
|
|
894
|
-
jit: (amp) => (
|
|
893
|
+
rand: rand2,
|
|
894
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
895
895
|
g: o.target.group,
|
|
896
896
|
cx: o.target.center[0],
|
|
897
897
|
cy: o.target.center[1],
|
|
@@ -1089,6 +1089,11 @@ function deviceBounds(name, opts = {}) {
|
|
|
1089
1089
|
const b = BOUNDS[name];
|
|
1090
1090
|
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
1091
1091
|
}
|
|
1092
|
+
function deviceScreenPoint(name, opts, local) {
|
|
1093
|
+
const c = deviceScreenCenter(name, opts);
|
|
1094
|
+
const s = opts.scale ?? 1;
|
|
1095
|
+
return [(opts.x ?? 0) + s * (c.x + local[0]), (opts.y ?? 0) + s * (c.y + local[1])];
|
|
1096
|
+
}
|
|
1092
1097
|
function screenGroup(id, p, o, cx, cy, dims, content) {
|
|
1093
1098
|
return group({ id: `${id}-screen`, x: cx, y: cy, clip: { kind: "rect", x: -dims.width / 2, y: -dims.height / 2, width: dims.width, height: dims.height, radius: dims.radius } }, [
|
|
1094
1099
|
rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
|
|
@@ -1263,6 +1268,73 @@ function devicePreset(name, opts = {}) {
|
|
|
1263
1268
|
);
|
|
1264
1269
|
}
|
|
1265
1270
|
|
|
1271
|
+
// ../core/src/cursor.ts
|
|
1272
|
+
var ARROW_D = "M0 0 L0 30 L8 23 L12.6 33 L17 31 L12.4 21.4 L21 21.4 Z";
|
|
1273
|
+
function cursor(opts = {}) {
|
|
1274
|
+
const id = opts.id ?? "cursor";
|
|
1275
|
+
const style = opts.style ?? "arrow";
|
|
1276
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
1277
|
+
const accent = opts.accent ?? "#FF5A1F";
|
|
1278
|
+
const art = style === "arrow" ? [path({ id: `${id}-arrow`, d: ARROW_D, x: 0, y: 0, fill, stroke: "#15171E", strokeWidth: 2 })] : style === "dot" ? [ellipse({ id: `${id}-dot`, x: 0, y: 0, width: 18, height: 18, fill: accent, anchor: "center" })] : [ellipse({ id: `${id}-ring`, x: 0, y: 0, width: 22, height: 22, fill: "none", stroke: accent, strokeWidth: 3, anchor: "center" })];
|
|
1279
|
+
return group(
|
|
1280
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
1281
|
+
[
|
|
1282
|
+
// ripple ring (behind the pointer), emanates from the hotspot on click
|
|
1283
|
+
ellipse({ id: `${id}-ripple`, x: 0, y: 0, width: 30, height: 30, fill: "none", stroke: accent, strokeWidth: 3, opacity: 0, scale: 0, anchor: "center" }),
|
|
1284
|
+
// the pointer art lives in its own group so a click "tap" can scale it
|
|
1285
|
+
// independently of the cursor's resting scale
|
|
1286
|
+
group({ id: `${id}-art`, x: 0, y: 0 }, art)
|
|
1287
|
+
]
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
1291
|
+
function cursorTo(id, from, to2, opts = {}) {
|
|
1292
|
+
const dx = to2[0] - from[0], dy = to2[1] - from[1];
|
|
1293
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
1294
|
+
const arc = opts.arc ?? 0.12;
|
|
1295
|
+
const mid = [(from[0] + to2[0]) / 2 + -dy / dist * arc * dist, (from[1] + to2[1]) / 2 + dx / dist * arc * dist];
|
|
1296
|
+
const duration = opts.duration ?? clamp(dist / 1400, 0.4, 0.9);
|
|
1297
|
+
return motionPath(id, [from, mid, to2], { duration, ease: opts.ease ?? "easeInOutCubic", curviness: 1, ...opts.label && { label: opts.label } });
|
|
1298
|
+
}
|
|
1299
|
+
function cursorPath(id, points, opts = {}) {
|
|
1300
|
+
return motionPath(id, points, {
|
|
1301
|
+
duration: opts.duration ?? clamp(points.length * 0.5, 0.5, 4),
|
|
1302
|
+
ease: opts.ease ?? "easeInOutCubic",
|
|
1303
|
+
curviness: opts.curviness ?? 1,
|
|
1304
|
+
...opts.label && { label: opts.label }
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
function clickBody(id, o) {
|
|
1308
|
+
const sp = Math.max(0.25, o.speed ?? 1);
|
|
1309
|
+
const d = (b) => b / sp;
|
|
1310
|
+
const out = [
|
|
1311
|
+
// the pointer taps
|
|
1312
|
+
seq(tween(`${id}-art`, { scale: 0.82 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(`${id}-art`, { scale: 1 }, { duration: d(0.1), ease: "easeOutBack" }))
|
|
1313
|
+
];
|
|
1314
|
+
if (o.ripple !== false) {
|
|
1315
|
+
out.push(seq(
|
|
1316
|
+
tween(`${id}-ripple`, { scale: 0.2, opacity: 0.55 }, { duration: 1e-3 }),
|
|
1317
|
+
par(
|
|
1318
|
+
tween(`${id}-ripple`, { scale: 5 }, { duration: d(0.5), ease: "easeOutCubic" }),
|
|
1319
|
+
tween(`${id}-ripple`, { opacity: 0 }, { duration: d(0.5), ease: "easeOutQuad" })
|
|
1320
|
+
)
|
|
1321
|
+
));
|
|
1322
|
+
}
|
|
1323
|
+
if (o.press) {
|
|
1324
|
+
out.push(seq(tween(o.press, { scale: 0.94 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(o.press, { scale: 1 }, { duration: d(0.14), ease: "easeOutBack" })));
|
|
1325
|
+
}
|
|
1326
|
+
return out;
|
|
1327
|
+
}
|
|
1328
|
+
function cursorClick(id, opts = {}) {
|
|
1329
|
+
return beat(opts.label ?? "cursor-click", {}, [par(...clickBody(id, opts))]);
|
|
1330
|
+
}
|
|
1331
|
+
function cursorDouble(id, opts = {}) {
|
|
1332
|
+
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
1333
|
+
return beat(opts.label ?? "cursor-double", {}, [
|
|
1334
|
+
seq(par(...clickBody(id, { ...opts, ripple: false })), wait(0.12 / sp), par(...clickBody(id, opts)))
|
|
1335
|
+
]);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1266
1338
|
// ../core/src/rig.ts
|
|
1267
1339
|
var DEFAULT_LINE = "#FFE3D2";
|
|
1268
1340
|
var DEFAULT_FILL = "#0E1424";
|
|
@@ -1378,7 +1450,7 @@ function makeRng2(seed) {
|
|
|
1378
1450
|
}
|
|
1379
1451
|
var dur2 = (base, sp) => base / sp;
|
|
1380
1452
|
function ctx2(o) {
|
|
1381
|
-
const
|
|
1453
|
+
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
1382
1454
|
return {
|
|
1383
1455
|
g: o.target,
|
|
1384
1456
|
label: o.label,
|
|
@@ -1388,8 +1460,8 @@ function ctx2(o) {
|
|
|
1388
1460
|
facing: o.facing ?? 1,
|
|
1389
1461
|
at: o.at ?? [0, 0],
|
|
1390
1462
|
travel: o.travel,
|
|
1391
|
-
rand,
|
|
1392
|
-
jit: (amp) => (
|
|
1463
|
+
rand: rand2,
|
|
1464
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
1393
1465
|
};
|
|
1394
1466
|
}
|
|
1395
1467
|
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
@@ -1688,9 +1760,511 @@ function figure(opts = {}) {
|
|
|
1688
1760
|
return rig(buildSkeleton(id, parts), rigOpts);
|
|
1689
1761
|
}
|
|
1690
1762
|
|
|
1763
|
+
// ../core/src/textMetrics.ts
|
|
1764
|
+
var INTER_ADVANCE = {
|
|
1765
|
+
"400": {
|
|
1766
|
+
"0": 63.09,
|
|
1767
|
+
"1": 40.67,
|
|
1768
|
+
"2": 60.99,
|
|
1769
|
+
"3": 61.77,
|
|
1770
|
+
"4": 64.6,
|
|
1771
|
+
"5": 59.33,
|
|
1772
|
+
"6": 62.01,
|
|
1773
|
+
"7": 56.59,
|
|
1774
|
+
"8": 61.87,
|
|
1775
|
+
"9": 62.01,
|
|
1776
|
+
" ": 28.13,
|
|
1777
|
+
"!": 28.76,
|
|
1778
|
+
'"': 46.58,
|
|
1779
|
+
"#": 63.33,
|
|
1780
|
+
"$": 64.16,
|
|
1781
|
+
"%": 98.19,
|
|
1782
|
+
"&": 64.4,
|
|
1783
|
+
"'": 29.98,
|
|
1784
|
+
"(": 36.47,
|
|
1785
|
+
")": 36.47,
|
|
1786
|
+
"*": 50.1,
|
|
1787
|
+
"+": 66.16,
|
|
1788
|
+
",": 28.81,
|
|
1789
|
+
"-": 46,
|
|
1790
|
+
".": 28.81,
|
|
1791
|
+
"/": 36.04,
|
|
1792
|
+
":": 28.81,
|
|
1793
|
+
";": 30.18,
|
|
1794
|
+
"<": 66.16,
|
|
1795
|
+
"=": 66.16,
|
|
1796
|
+
">": 66.16,
|
|
1797
|
+
"?": 51.12,
|
|
1798
|
+
"@": 96.58,
|
|
1799
|
+
"A": 68.99,
|
|
1800
|
+
"B": 65.43,
|
|
1801
|
+
"C": 73.05,
|
|
1802
|
+
"D": 72.17,
|
|
1803
|
+
"E": 60.11,
|
|
1804
|
+
"F": 59.03,
|
|
1805
|
+
"G": 74.61,
|
|
1806
|
+
"H": 74.32,
|
|
1807
|
+
"I": 26.86,
|
|
1808
|
+
"J": 57.08,
|
|
1809
|
+
"K": 67.19,
|
|
1810
|
+
"L": 56.54,
|
|
1811
|
+
"M": 90.33,
|
|
1812
|
+
"N": 75.34,
|
|
1813
|
+
"O": 76.46,
|
|
1814
|
+
"P": 63.87,
|
|
1815
|
+
"Q": 76.46,
|
|
1816
|
+
"R": 64.36,
|
|
1817
|
+
"S": 64.16,
|
|
1818
|
+
"T": 64.55,
|
|
1819
|
+
"U": 74.41,
|
|
1820
|
+
"V": 68.99,
|
|
1821
|
+
"W": 98.54,
|
|
1822
|
+
"X": 68.21,
|
|
1823
|
+
"Y": 67.87,
|
|
1824
|
+
"Z": 62.89,
|
|
1825
|
+
"[": 36.47,
|
|
1826
|
+
"\\": 36.04,
|
|
1827
|
+
"]": 36.47,
|
|
1828
|
+
"^": 47.12,
|
|
1829
|
+
"_": 45.61,
|
|
1830
|
+
"`": 32.28,
|
|
1831
|
+
"a": 56.15,
|
|
1832
|
+
"b": 61.23,
|
|
1833
|
+
"c": 57.13,
|
|
1834
|
+
"d": 61.23,
|
|
1835
|
+
"e": 58.3,
|
|
1836
|
+
"f": 37.01,
|
|
1837
|
+
"g": 61.33,
|
|
1838
|
+
"h": 59.13,
|
|
1839
|
+
"i": 24.22,
|
|
1840
|
+
"j": 24.22,
|
|
1841
|
+
"k": 54.88,
|
|
1842
|
+
"l": 24.22,
|
|
1843
|
+
"m": 87.6,
|
|
1844
|
+
"n": 59.08,
|
|
1845
|
+
"o": 59.96,
|
|
1846
|
+
"p": 61.23,
|
|
1847
|
+
"q": 61.23,
|
|
1848
|
+
"r": 37.65,
|
|
1849
|
+
"s": 52.78,
|
|
1850
|
+
"t": 32.71,
|
|
1851
|
+
"u": 59.13,
|
|
1852
|
+
"v": 56.2,
|
|
1853
|
+
"w": 81.84,
|
|
1854
|
+
"x": 54.59,
|
|
1855
|
+
"y": 56.2,
|
|
1856
|
+
"z": 55.22,
|
|
1857
|
+
"{": 42.63,
|
|
1858
|
+
"|": 33.25,
|
|
1859
|
+
"}": 42.63,
|
|
1860
|
+
"~": 66.16
|
|
1861
|
+
},
|
|
1862
|
+
"700": {
|
|
1863
|
+
"0": 67.43,
|
|
1864
|
+
"1": 43.12,
|
|
1865
|
+
"2": 62.94,
|
|
1866
|
+
"3": 64.55,
|
|
1867
|
+
"4": 67.63,
|
|
1868
|
+
"5": 62.21,
|
|
1869
|
+
"6": 64.94,
|
|
1870
|
+
"7": 58.15,
|
|
1871
|
+
"8": 65.09,
|
|
1872
|
+
"9": 64.94,
|
|
1873
|
+
" ": 23.68,
|
|
1874
|
+
"!": 33.79,
|
|
1875
|
+
'"': 55.13,
|
|
1876
|
+
"#": 64.89,
|
|
1877
|
+
"$": 65.48,
|
|
1878
|
+
"%": 101.56,
|
|
1879
|
+
"&": 67.19,
|
|
1880
|
+
"'": 33.89,
|
|
1881
|
+
"(": 37.7,
|
|
1882
|
+
")": 37.7,
|
|
1883
|
+
"*": 55.91,
|
|
1884
|
+
"+": 67.87,
|
|
1885
|
+
",": 33.4,
|
|
1886
|
+
"-": 46.78,
|
|
1887
|
+
".": 33.4,
|
|
1888
|
+
"/": 38.82,
|
|
1889
|
+
":": 33.4,
|
|
1890
|
+
";": 34.28,
|
|
1891
|
+
"<": 67.87,
|
|
1892
|
+
"=": 67.87,
|
|
1893
|
+
">": 67.87,
|
|
1894
|
+
"?": 55.96,
|
|
1895
|
+
"@": 101.61,
|
|
1896
|
+
"A": 74.66,
|
|
1897
|
+
"B": 66.16,
|
|
1898
|
+
"C": 73.97,
|
|
1899
|
+
"D": 72.22,
|
|
1900
|
+
"E": 60.74,
|
|
1901
|
+
"F": 58.69,
|
|
1902
|
+
"G": 75.05,
|
|
1903
|
+
"H": 74.71,
|
|
1904
|
+
"I": 28.08,
|
|
1905
|
+
"J": 58.45,
|
|
1906
|
+
"K": 71.92,
|
|
1907
|
+
"L": 56.54,
|
|
1908
|
+
"M": 93.16,
|
|
1909
|
+
"N": 76.22,
|
|
1910
|
+
"O": 77.05,
|
|
1911
|
+
"P": 64.79,
|
|
1912
|
+
"Q": 77.69,
|
|
1913
|
+
"R": 65.67,
|
|
1914
|
+
"S": 65.48,
|
|
1915
|
+
"T": 66.75,
|
|
1916
|
+
"U": 73.19,
|
|
1917
|
+
"V": 74.66,
|
|
1918
|
+
"W": 103.76,
|
|
1919
|
+
"X": 73.83,
|
|
1920
|
+
"Y": 73.1,
|
|
1921
|
+
"Z": 66.41,
|
|
1922
|
+
"[": 37.7,
|
|
1923
|
+
"\\": 38.82,
|
|
1924
|
+
"]": 37.7,
|
|
1925
|
+
"^": 48.68,
|
|
1926
|
+
"_": 47.61,
|
|
1927
|
+
"`": 36.52,
|
|
1928
|
+
"a": 58.06,
|
|
1929
|
+
"b": 63.04,
|
|
1930
|
+
"c": 58.84,
|
|
1931
|
+
"d": 63.04,
|
|
1932
|
+
"e": 59.57,
|
|
1933
|
+
"f": 39.79,
|
|
1934
|
+
"g": 63.18,
|
|
1935
|
+
"h": 62.26,
|
|
1936
|
+
"i": 27.1,
|
|
1937
|
+
"j": 27.1,
|
|
1938
|
+
"k": 58.01,
|
|
1939
|
+
"l": 27.1,
|
|
1940
|
+
"m": 91.26,
|
|
1941
|
+
"n": 62.26,
|
|
1942
|
+
"o": 61.33,
|
|
1943
|
+
"p": 63.04,
|
|
1944
|
+
"q": 63.04,
|
|
1945
|
+
"r": 40.72,
|
|
1946
|
+
"s": 56.01,
|
|
1947
|
+
"t": 36.62,
|
|
1948
|
+
"u": 62.26,
|
|
1949
|
+
"v": 59.96,
|
|
1950
|
+
"w": 85.01,
|
|
1951
|
+
"x": 58.01,
|
|
1952
|
+
"y": 60.21,
|
|
1953
|
+
"z": 57.28,
|
|
1954
|
+
"{": 46.88,
|
|
1955
|
+
"|": 37.16,
|
|
1956
|
+
"}": 46.88,
|
|
1957
|
+
"~": 67.87
|
|
1958
|
+
},
|
|
1959
|
+
"800": {
|
|
1960
|
+
"0": 69.19,
|
|
1961
|
+
"1": 44.14,
|
|
1962
|
+
"2": 63.77,
|
|
1963
|
+
"3": 65.67,
|
|
1964
|
+
"4": 68.85,
|
|
1965
|
+
"5": 63.38,
|
|
1966
|
+
"6": 66.16,
|
|
1967
|
+
"7": 58.79,
|
|
1968
|
+
"8": 66.41,
|
|
1969
|
+
"9": 66.16,
|
|
1970
|
+
" ": 21.88,
|
|
1971
|
+
"!": 35.84,
|
|
1972
|
+
'"': 58.64,
|
|
1973
|
+
"#": 65.53,
|
|
1974
|
+
"$": 66.02,
|
|
1975
|
+
"%": 102.93,
|
|
1976
|
+
"&": 68.31,
|
|
1977
|
+
"'": 35.45,
|
|
1978
|
+
"(": 38.23,
|
|
1979
|
+
")": 38.23,
|
|
1980
|
+
"*": 58.25,
|
|
1981
|
+
"+": 68.55,
|
|
1982
|
+
",": 35.25,
|
|
1983
|
+
"-": 47.12,
|
|
1984
|
+
".": 35.25,
|
|
1985
|
+
"/": 39.99,
|
|
1986
|
+
":": 35.25,
|
|
1987
|
+
";": 35.99,
|
|
1988
|
+
"<": 68.55,
|
|
1989
|
+
"=": 68.55,
|
|
1990
|
+
">": 68.55,
|
|
1991
|
+
"?": 57.91,
|
|
1992
|
+
"@": 103.61,
|
|
1993
|
+
"A": 76.95,
|
|
1994
|
+
"B": 66.46,
|
|
1995
|
+
"C": 74.37,
|
|
1996
|
+
"D": 72.27,
|
|
1997
|
+
"E": 60.99,
|
|
1998
|
+
"F": 58.54,
|
|
1999
|
+
"G": 75.24,
|
|
2000
|
+
"H": 74.85,
|
|
2001
|
+
"I": 28.56,
|
|
2002
|
+
"J": 58.98,
|
|
2003
|
+
"K": 73.83,
|
|
2004
|
+
"L": 56.54,
|
|
2005
|
+
"M": 94.34,
|
|
2006
|
+
"N": 76.56,
|
|
2007
|
+
"O": 77.29,
|
|
2008
|
+
"P": 65.19,
|
|
2009
|
+
"Q": 78.17,
|
|
2010
|
+
"R": 66.21,
|
|
2011
|
+
"S": 66.02,
|
|
2012
|
+
"T": 67.68,
|
|
2013
|
+
"U": 72.71,
|
|
2014
|
+
"V": 76.95,
|
|
2015
|
+
"W": 105.86,
|
|
2016
|
+
"X": 76.12,
|
|
2017
|
+
"Y": 75.2,
|
|
2018
|
+
"Z": 67.87,
|
|
2019
|
+
"[": 38.23,
|
|
2020
|
+
"\\": 39.99,
|
|
2021
|
+
"]": 38.23,
|
|
2022
|
+
"^": 49.32,
|
|
2023
|
+
"_": 48.44,
|
|
2024
|
+
"`": 38.23,
|
|
2025
|
+
"a": 58.84,
|
|
2026
|
+
"b": 63.77,
|
|
2027
|
+
"c": 59.52,
|
|
2028
|
+
"d": 63.77,
|
|
2029
|
+
"e": 60.06,
|
|
2030
|
+
"f": 40.97,
|
|
2031
|
+
"g": 63.92,
|
|
2032
|
+
"h": 63.53,
|
|
2033
|
+
"i": 28.32,
|
|
2034
|
+
"j": 28.32,
|
|
2035
|
+
"k": 59.28,
|
|
2036
|
+
"l": 28.32,
|
|
2037
|
+
"m": 92.72,
|
|
2038
|
+
"n": 63.53,
|
|
2039
|
+
"o": 61.91,
|
|
2040
|
+
"p": 63.77,
|
|
2041
|
+
"q": 63.77,
|
|
2042
|
+
"r": 41.99,
|
|
2043
|
+
"s": 57.32,
|
|
2044
|
+
"t": 38.18,
|
|
2045
|
+
"u": 63.53,
|
|
2046
|
+
"v": 61.52,
|
|
2047
|
+
"w": 86.28,
|
|
2048
|
+
"x": 59.42,
|
|
2049
|
+
"y": 61.82,
|
|
2050
|
+
"z": 58.11,
|
|
2051
|
+
"{": 48.63,
|
|
2052
|
+
"|": 38.77,
|
|
2053
|
+
"}": 48.63,
|
|
2054
|
+
"~": 68.55
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
var INTER_FALLBACK = {
|
|
2058
|
+
"400": 56.16,
|
|
2059
|
+
"700": 58.74,
|
|
2060
|
+
"800": 59.79
|
|
2061
|
+
};
|
|
2062
|
+
|
|
2063
|
+
// ../core/src/textFx.ts
|
|
2064
|
+
var clamp013 = (v) => Math.max(0, Math.min(1, v));
|
|
2065
|
+
var fract = (v) => v - Math.floor(v);
|
|
2066
|
+
var rand = (i, salt) => fract(Math.sin(i * 127.1 + salt * 311.7) * 43758.5453);
|
|
2067
|
+
var dur3 = (base, sp) => base / sp;
|
|
2068
|
+
var SCRAMBLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@";
|
|
2069
|
+
var advance = (ch, weight, fontSize) => (INTER_ADVANCE[weight]?.[ch] ?? INTER_FALLBACK[weight]) * (fontSize / 100);
|
|
2070
|
+
function splitText(textStr, opts) {
|
|
2071
|
+
const { id, x, y, fontSize } = opts;
|
|
2072
|
+
const weight = opts.fontWeight ?? 800;
|
|
2073
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
2074
|
+
const ls = opts.letterSpacing ?? 0;
|
|
2075
|
+
const align = opts.align ?? "center";
|
|
2076
|
+
const unit = opts.unit ?? "glyph";
|
|
2077
|
+
const opacity = opts.opacity ?? 0;
|
|
2078
|
+
const chars = [...textStr];
|
|
2079
|
+
let total = 0;
|
|
2080
|
+
chars.forEach((ch, i) => {
|
|
2081
|
+
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
2082
|
+
});
|
|
2083
|
+
let cursor2 = align === "center" ? x - total / 2 : x;
|
|
2084
|
+
const glyphs = [];
|
|
2085
|
+
const nodes = [];
|
|
2086
|
+
const mk = (ch, cx, adv, lsProp) => {
|
|
2087
|
+
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
2088
|
+
glyphs.push(g);
|
|
2089
|
+
nodes.push(
|
|
2090
|
+
text({
|
|
2091
|
+
id: g.id,
|
|
2092
|
+
x: cx,
|
|
2093
|
+
y,
|
|
2094
|
+
content: ch,
|
|
2095
|
+
fontFamily: "Inter",
|
|
2096
|
+
fontSize,
|
|
2097
|
+
fontWeight: weight,
|
|
2098
|
+
fill,
|
|
2099
|
+
anchor: "center",
|
|
2100
|
+
opacity,
|
|
2101
|
+
...lsProp ? { letterSpacing: lsProp } : {}
|
|
2102
|
+
})
|
|
2103
|
+
);
|
|
2104
|
+
};
|
|
2105
|
+
if (unit === "word") {
|
|
2106
|
+
let i = 0;
|
|
2107
|
+
while (i < chars.length) {
|
|
2108
|
+
if (chars[i] === " ") {
|
|
2109
|
+
cursor2 += advance(" ", weight, fontSize) + ls;
|
|
2110
|
+
i++;
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
let word = "";
|
|
2114
|
+
let w = 0;
|
|
2115
|
+
const startCursor = cursor2;
|
|
2116
|
+
while (i < chars.length && chars[i] !== " ") {
|
|
2117
|
+
const a = advance(chars[i], weight, fontSize);
|
|
2118
|
+
word += chars[i];
|
|
2119
|
+
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
2120
|
+
i++;
|
|
2121
|
+
}
|
|
2122
|
+
mk(word, startCursor + w / 2, w, ls);
|
|
2123
|
+
cursor2 = startCursor + w + ls;
|
|
2124
|
+
}
|
|
2125
|
+
} else {
|
|
2126
|
+
chars.forEach((ch) => {
|
|
2127
|
+
const a = advance(ch, weight, fontSize);
|
|
2128
|
+
if (ch !== " ") mk(ch, cursor2 + a / 2, a);
|
|
2129
|
+
cursor2 += a + ls;
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
return { nodes, glyphs, ids: glyphs.map((g) => g.id), width: total, x, y, fontSize };
|
|
2133
|
+
}
|
|
2134
|
+
var ctx3 = (o) => ({
|
|
2135
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
2136
|
+
e: clamp013(o.energy ?? 0.5),
|
|
2137
|
+
seed: o.seed ?? 0,
|
|
2138
|
+
fs: 0,
|
|
2139
|
+
stag: o.stagger
|
|
2140
|
+
});
|
|
2141
|
+
var IN_STAGGER = { typewriter: 0.065, cascade: 0.04, rise: 0.03, bounce: 0.045, assemble: 0.05, decode: 0.05 };
|
|
2142
|
+
function glyphIn(name, g, c) {
|
|
2143
|
+
const set = (props) => tween(g.id, props, { duration: 1e-3 });
|
|
2144
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2145
|
+
switch (name) {
|
|
2146
|
+
case "typewriter":
|
|
2147
|
+
return tween(g.id, { opacity: 1 }, { duration: dur3(0.04, c.sp), ease: "linear" });
|
|
2148
|
+
case "cascade":
|
|
2149
|
+
return seq(
|
|
2150
|
+
set({ y: g.y + 56, opacity: 0 }),
|
|
2151
|
+
par(
|
|
2152
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.22, c.sp), ease: "easeOutQuad" }),
|
|
2153
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.34, c.sp), ease: "easeOutCubic" })
|
|
2154
|
+
)
|
|
2155
|
+
);
|
|
2156
|
+
case "rise":
|
|
2157
|
+
return seq(
|
|
2158
|
+
set({ y: g.y + 36, opacity: 0 }),
|
|
2159
|
+
par(
|
|
2160
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.3, c.sp), ease: "easeOutQuad" }),
|
|
2161
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
2162
|
+
)
|
|
2163
|
+
);
|
|
2164
|
+
case "bounce":
|
|
2165
|
+
return seq(
|
|
2166
|
+
set({ y: g.y - 80 * (0.6 + c.e), opacity: 0, scale: 0.7 }),
|
|
2167
|
+
par(
|
|
2168
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.2, c.sp), ease: "easeOutQuad" }),
|
|
2169
|
+
tween(g.id, { y: g.y, scale: 1 }, { duration: dur3(0.7, c.sp), ease: "easeOutBounce" })
|
|
2170
|
+
)
|
|
2171
|
+
);
|
|
2172
|
+
case "assemble":
|
|
2173
|
+
return seq(
|
|
2174
|
+
set({ x: g.x + (rs(11) - 0.5) * 1e3 * (0.5 + c.e), y: g.y + (rs(12) - 0.5) * 640, rotation: (rs(13) - 0.5) * 200, scale: 0.4, opacity: 0 }),
|
|
2175
|
+
par(
|
|
2176
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" }),
|
|
2177
|
+
tween(g.id, { x: g.x, y: g.y, rotation: 0, scale: 1 }, { duration: dur3(0.8, c.sp), ease: "easeOutExpo" })
|
|
2178
|
+
)
|
|
2179
|
+
);
|
|
2180
|
+
case "decode": {
|
|
2181
|
+
const steps = 4 + Math.floor(rs(7) * 3);
|
|
2182
|
+
const flicker = [set({ opacity: 1 })];
|
|
2183
|
+
for (let k = 0; k < steps; k++) {
|
|
2184
|
+
flicker.push(tween(g.id, { content: SCRAMBLE[Math.floor(rand(g.i, 20 + k + c.seed) * SCRAMBLE.length)] }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
2185
|
+
}
|
|
2186
|
+
flicker.push(tween(g.id, { content: g.ch }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
2187
|
+
return seq(...flicker);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
function textIn(name, block, opts = {}) {
|
|
2192
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
2193
|
+
const interval = (c.stag ?? IN_STAGGER[name]) / c.sp;
|
|
2194
|
+
return beat(opts.label ?? `text-in-${name}`, {}, [stagger(interval, ...block.glyphs.map((g) => glyphIn(name, g, c)))]);
|
|
2195
|
+
}
|
|
2196
|
+
function textLoop(name, block, opts = {}) {
|
|
2197
|
+
const win = { ...opts.from !== void 0 && { from: opts.from }, ...opts.until !== void 0 && { until: opts.until }, ...opts.ramp !== void 0 && { ramp: opts.ramp } };
|
|
2198
|
+
const f = opts.frequency ?? (name === "wave" ? 0.9 : name === "shimmer" ? 1.4 : 0.7);
|
|
2199
|
+
const ps = opts.phaseStep ?? 0.55;
|
|
2200
|
+
return block.glyphs.map((g, i) => {
|
|
2201
|
+
switch (name) {
|
|
2202
|
+
case "wave":
|
|
2203
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 9, frequency: f, phase: i * ps }, win);
|
|
2204
|
+
case "shimmer":
|
|
2205
|
+
return oscillate(g.id, "opacity", { amplitude: opts.amplitude ?? 0.25, frequency: f, phase: i * ps }, win);
|
|
2206
|
+
case "wobble":
|
|
2207
|
+
return oscillate(g.id, "rotation", { amplitude: opts.amplitude ?? 6, frequency: f, phase: i * ps }, win);
|
|
2208
|
+
case "float":
|
|
2209
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 5, frequency: f, phase: i * ps }, win);
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
var OUT_STAGGER = { shatter: 0.02, fly: 0.012, dissolve: 0, fall: 0.02, collapse: 0.02 };
|
|
2214
|
+
function glyphOut(name, g, c, block, dir) {
|
|
2215
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2216
|
+
switch (name) {
|
|
2217
|
+
case "shatter":
|
|
2218
|
+
return par(
|
|
2219
|
+
tween(g.id, { x: g.x + (rs(21) - 0.5) * 1100 * (0.6 + c.e), y: g.y + (rs(22) - 0.5) * 760 }, { duration: dur3(0.7, c.sp), ease: "easeInCubic" }),
|
|
2220
|
+
tween(g.id, { rotation: (rs(23) - 0.5) * 300, opacity: 0 }, { duration: dur3(0.7, c.sp), ease: "easeInQuad" })
|
|
2221
|
+
);
|
|
2222
|
+
case "fly":
|
|
2223
|
+
return par(
|
|
2224
|
+
tween(g.id, { x: g.x + dir[0] * 1200, y: g.y + dir[1] * 1200 }, { duration: dur3(0.6, c.sp), ease: "easeInCubic" }),
|
|
2225
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2226
|
+
);
|
|
2227
|
+
case "dissolve":
|
|
2228
|
+
return seq(wait(rs(31) * 0.5), par(
|
|
2229
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.4, c.sp), ease: "easeInQuad" }),
|
|
2230
|
+
tween(g.id, { scale: 1.4 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
2231
|
+
));
|
|
2232
|
+
case "fall":
|
|
2233
|
+
return par(
|
|
2234
|
+
tween(g.id, { y: g.y + 700 + rs(41) * 200 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" }),
|
|
2235
|
+
tween(g.id, { rotation: (rs(42) - 0.5) * 120, opacity: 0 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" })
|
|
2236
|
+
);
|
|
2237
|
+
case "collapse":
|
|
2238
|
+
return par(
|
|
2239
|
+
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur3(0.5, c.sp), ease: "easeInBack" }),
|
|
2240
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
function textOut(name, block, opts = {}) {
|
|
2245
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
2246
|
+
const dir = opts.dir ?? [0, -1];
|
|
2247
|
+
const steps = block.glyphs.map((g) => glyphOut(name, g, c, block, dir));
|
|
2248
|
+
const interval = (c.stag ?? OUT_STAGGER[name]) / c.sp;
|
|
2249
|
+
const body = interval > 0 ? stagger(interval, ...steps) : par(...steps);
|
|
2250
|
+
return beat(opts.label ?? `text-out-${name}`, {}, [body]);
|
|
2251
|
+
}
|
|
2252
|
+
function textTypeCues(block, opts) {
|
|
2253
|
+
const interval = opts.interval ?? 0.065;
|
|
2254
|
+
const gain = opts.gain ?? 0.4;
|
|
2255
|
+
const off = opts.offset ?? 0;
|
|
2256
|
+
const KEYS = ["001", "004", "007", "010", "014"];
|
|
2257
|
+
return block.glyphs.map((g, i) => ({
|
|
2258
|
+
at: opts.at,
|
|
2259
|
+
offset: off + i * interval,
|
|
2260
|
+
file: `keypress-${KEYS[i % KEYS.length]}.wav`,
|
|
2261
|
+
gain: gain + 0.2 * rand(i, 31)
|
|
2262
|
+
}));
|
|
2263
|
+
}
|
|
2264
|
+
|
|
1691
2265
|
// ../core/src/motionOps.ts
|
|
1692
2266
|
var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
|
|
1693
|
-
var
|
|
2267
|
+
var clamp014 = (n3) => Math.max(0, Math.min(1, n3));
|
|
1694
2268
|
function settleEase2(e) {
|
|
1695
2269
|
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
1696
2270
|
}
|
|
@@ -1708,7 +2282,7 @@ function fromVec2(from, dist) {
|
|
|
1708
2282
|
}
|
|
1709
2283
|
var motionOpLabel = (name, target) => `op-${name}-${target}`;
|
|
1710
2284
|
function motionOp(name, target, opts = {}) {
|
|
1711
|
-
const e =
|
|
2285
|
+
const e = clamp014(opts.energy ?? 0.5);
|
|
1712
2286
|
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
1713
2287
|
const amt = opts.amount ?? 1;
|
|
1714
2288
|
const b = { scale: 1, x: 0, y: 0, rotation: 0, ...opts.base };
|
|
@@ -2411,29 +2985,29 @@ function sketchToTimeline(sketch, nodeIds) {
|
|
|
2411
2985
|
const steps = [];
|
|
2412
2986
|
events.forEach((ev, i) => {
|
|
2413
2987
|
const node = nodeIds[i % nodeIds.length];
|
|
2414
|
-
const
|
|
2988
|
+
const dur4 = Math.max(0.05, ev.t1 - ev.t0);
|
|
2415
2989
|
const ease = easeFor(ev.easing);
|
|
2416
2990
|
let motion;
|
|
2417
2991
|
switch (ev.kind) {
|
|
2418
2992
|
case "enter":
|
|
2419
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
2993
|
+
motion = tween(node, { opacity: 1 }, { duration: dur4, ease });
|
|
2420
2994
|
break;
|
|
2421
2995
|
case "exit":
|
|
2422
|
-
motion = tween(node, { opacity: 0 }, { duration:
|
|
2996
|
+
motion = tween(node, { opacity: 0 }, { duration: dur4, ease });
|
|
2423
2997
|
break;
|
|
2424
2998
|
case "emphasis": {
|
|
2425
2999
|
const peak = 1 + Math.max(0.08, Math.min(0.5, ev.magnitude));
|
|
2426
3000
|
motion = seq(
|
|
2427
|
-
tween(node, { scale: peak }, { duration:
|
|
2428
|
-
tween(node, { scale: 1 }, { duration:
|
|
3001
|
+
tween(node, { scale: peak }, { duration: dur4 / 2, ease: "easeOutCubic" }),
|
|
3002
|
+
tween(node, { scale: 1 }, { duration: dur4 / 2, ease: "easeInOutQuad" })
|
|
2429
3003
|
);
|
|
2430
3004
|
break;
|
|
2431
3005
|
}
|
|
2432
3006
|
case "scale":
|
|
2433
|
-
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration:
|
|
3007
|
+
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur4, ease });
|
|
2434
3008
|
break;
|
|
2435
3009
|
case "move":
|
|
2436
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
3010
|
+
motion = tween(node, { opacity: 1 }, { duration: dur4, ease });
|
|
2437
3011
|
break;
|
|
2438
3012
|
}
|
|
2439
3013
|
steps.push(ev.t0 > 0 ? seq(wait(ev.t0), motion) : motion);
|
|
@@ -2461,10 +3035,16 @@ export {
|
|
|
2461
3035
|
compileScene,
|
|
2462
3036
|
composeScene,
|
|
2463
3037
|
composition,
|
|
3038
|
+
cursor,
|
|
3039
|
+
cursorClick,
|
|
3040
|
+
cursorDouble,
|
|
3041
|
+
cursorPath,
|
|
3042
|
+
cursorTo,
|
|
2464
3043
|
deviceBounds,
|
|
2465
3044
|
devicePreset,
|
|
2466
3045
|
deviceScreen,
|
|
2467
3046
|
deviceScreenCenter,
|
|
3047
|
+
deviceScreenPoint,
|
|
2468
3048
|
ellipse,
|
|
2469
3049
|
evaluate,
|
|
2470
3050
|
figure,
|
|
@@ -2499,8 +3079,13 @@ export {
|
|
|
2499
3079
|
scene,
|
|
2500
3080
|
seq,
|
|
2501
3081
|
sketchToTimeline,
|
|
3082
|
+
splitText,
|
|
2502
3083
|
stagger,
|
|
2503
3084
|
text,
|
|
3085
|
+
textIn,
|
|
3086
|
+
textLoop,
|
|
3087
|
+
textOut,
|
|
3088
|
+
textTypeCues,
|
|
2504
3089
|
to,
|
|
2505
3090
|
tween,
|
|
2506
3091
|
validateComposition,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor / pointer motion — a vector mouse pointer that glides across the scene
|
|
3
|
+
* and clicks things (the UI-demo staple). `cursor()` returns a NodeIR (like
|
|
4
|
+
* `devicePreset`); `cursorTo` / `cursorPath` / `cursorClick` return TimelineIR
|
|
5
|
+
* (like `characterPreset`). The pointer's HOTSPOT is the group origin (0,0), so a
|
|
6
|
+
* move lands the tip exactly on a target. Pairs with `deviceScreenPoint` to click
|
|
7
|
+
* UI inside a `devicePreset` screen.
|
|
8
|
+
*
|
|
9
|
+
* nodes: [devicePreset("browser", { id: "d", x, y, scale, content }), cursor({ id: "cur" })]
|
|
10
|
+
* timeline: seq(cursorTo("cur", [start], deviceScreenPoint("browser", dOpts, [lx, ly])),
|
|
11
|
+
* cursorClick("cur", { press: "d-ui-cta" }))
|
|
12
|
+
*/
|
|
13
|
+
import type { Ease, NodeIR, TimelineIR } from "./ir.js";
|
|
14
|
+
export type CursorStyle = "arrow" | "dot" | "ring";
|
|
15
|
+
export interface CursorOpts {
|
|
16
|
+
id?: string;
|
|
17
|
+
x?: number;
|
|
18
|
+
y?: number;
|
|
19
|
+
scale?: number;
|
|
20
|
+
opacity?: number;
|
|
21
|
+
style?: CursorStyle;
|
|
22
|
+
/** Pointer body colour (default white for arrow). */
|
|
23
|
+
fill?: string;
|
|
24
|
+
/** Accent for dot/ring body and the click ripple. */
|
|
25
|
+
accent?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function cursor(opts?: CursorOpts): NodeIR;
|
|
28
|
+
export interface CursorToOpts {
|
|
29
|
+
duration?: number;
|
|
30
|
+
ease?: Ease;
|
|
31
|
+
/** perpendicular bow as a fraction of distance (default 0.12; 0 = straight). */
|
|
32
|
+
arc?: number;
|
|
33
|
+
label?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Glide the cursor from `from` to `to` along a gentle human arc. */
|
|
36
|
+
export declare function cursorTo(id: string, from: [number, number], to: [number, number], opts?: CursorToOpts): TimelineIR;
|
|
37
|
+
export interface CursorPathOpts {
|
|
38
|
+
duration?: number;
|
|
39
|
+
ease?: Ease;
|
|
40
|
+
curviness?: number;
|
|
41
|
+
label?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Move the cursor through a tour of waypoints (one smooth path). */
|
|
44
|
+
export declare function cursorPath(id: string, points: [number, number][], opts?: CursorPathOpts): TimelineIR;
|
|
45
|
+
export interface CursorClickOpts {
|
|
46
|
+
/** overall click duration scale (default 1). */
|
|
47
|
+
speed?: number;
|
|
48
|
+
/** node id to "press" (a quick scale dip) when the cursor clicks it. */
|
|
49
|
+
press?: string;
|
|
50
|
+
/** show the expanding ripple ring (default true). */
|
|
51
|
+
ripple?: boolean;
|
|
52
|
+
label?: string;
|
|
53
|
+
}
|
|
54
|
+
/** A click: the pointer taps, a ripple ring expands, and an optional target presses. */
|
|
55
|
+
export declare function cursorClick(id: string, opts?: CursorClickOpts): TimelineIR;
|
|
56
|
+
/** Two quick clicks. */
|
|
57
|
+
export declare function cursorDouble(id: string, opts?: CursorClickOpts): TimelineIR;
|
|
@@ -61,5 +61,9 @@ export declare function deviceBounds(name: DevicePresetName, opts?: DevicePreset
|
|
|
61
61
|
width: number;
|
|
62
62
|
height: number;
|
|
63
63
|
};
|
|
64
|
+
/** Map a SCREEN-LOCAL point (origin = screen centre, the coords `content` is
|
|
65
|
+
* authored in) to absolute SCENE coords, given the same `opts` passed to
|
|
66
|
+
* `devicePreset`. For aiming a `cursor` at on-screen UI. */
|
|
67
|
+
export declare function deviceScreenPoint(name: DevicePresetName, opts: DevicePresetOpts, local: [number, number]): [number, number];
|
|
64
68
|
/** Build a device-mockup frame (a group) with a clipped screen content slot. */
|
|
65
69
|
export declare function devicePreset(name: DevicePresetName, opts?: DevicePresetOpts): NodeIR;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,10 +6,12 @@ export { composeScene, formatComposeReport, type OverlayDoc, type ComposeReport,
|
|
|
6
6
|
export { compileScene, type CompiledScene, type PropertySegment, type LabelSpan, type MotionDriver } from "./compile.js";
|
|
7
7
|
export { pathPoint, pathTangentAngle, type Pt } from "./path.js";
|
|
8
8
|
export { motionPreset, PRESET_NAMES, type PresetName, type PresetRig, type PresetOpts } from "./presets.js";
|
|
9
|
-
export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, DEVICE_PRESET_NAMES, type DevicePresetName, type DevicePresetOpts } from "./devicePreset.js";
|
|
9
|
+
export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, deviceScreenPoint, DEVICE_PRESET_NAMES, type DevicePresetName, type DevicePresetOpts } from "./devicePreset.js";
|
|
10
|
+
export { cursor, cursorTo, cursorPath, cursorClick, cursorDouble, type CursorStyle, type CursorOpts, type CursorToOpts, type CursorPathOpts, type CursorClickOpts } from "./cursor.js";
|
|
10
11
|
export { rig, rigPose, poseTo, ikReach, humanoid, ovalPath, type Bone, type RigOpts, type Pose, type HumanoidOpts } from "./rig.js";
|
|
11
12
|
export { characterPreset, CHARACTER_PRESET_NAMES, type CharacterPresetName, type CharacterPresetOpts } from "./characterPreset.js";
|
|
12
13
|
export { figure, type FigureStyle, type FigureOpts, type FigurePalette } from "./figure.js";
|
|
14
|
+
export { splitText, textIn, textLoop, textOut, textTypeCues, type SplitOpts, type Glyph, type TextBlock, type FontWeight, type TextInName, type TextLoopName, type TextOutName, type TextLoopOpts, type TextOutOpts, type TypeCueOpts, } from "./textFx.js";
|
|
13
15
|
export { motionOp, motionOpLabel, MOTION_OPS, type MotionOpName, type MotionOpOpts, type MotionOpResult } from "./motionOps.js";
|
|
14
16
|
export { resolveAudioPlan, resolveCompositionAudioPlan, SFX_DURATION, type AudioPlan, type ResolvedCue, } from "./audio.js";
|
|
15
17
|
export { evaluate, sampleProp, nodeParentMatrix, type DisplayList, type DisplayOp, type Mat2D, type ClipRegion, type TextAlign, type TextBaseline, } from "./evaluate.js";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinetic text — a deterministic per-glyph text splitter plus a library of
|
|
3
|
+
* seeded effect generators (entrance / sustained / exit). The text analog of
|
|
4
|
+
* `motionPreset` / `characterPreset`: `splitText()` lays a phrase out as
|
|
5
|
+
* center-anchored `text` nodes (advances measured from the real font, so layout
|
|
6
|
+
* matches the render), then `textIn` / `textLoop` / `textOut` animate the glyphs.
|
|
7
|
+
*
|
|
8
|
+
* const T = splitText("MOTION IS DATA", { id: "t", x: 960, y: 470, fontSize: 130 });
|
|
9
|
+
* // nodes: [...T.nodes]
|
|
10
|
+
* // timeline: seq(textIn("typewriter", T), wait(2), textOut("shatter", T, { seed: 3 }))
|
|
11
|
+
* // behaviors: textLoop("wave", T, { from: 1.6, until: 3.6 })
|
|
12
|
+
*/
|
|
13
|
+
import { type BehaviorWindow } from "./dsl.js";
|
|
14
|
+
import type { AudioCueIR, BehaviorIR, NodeIR, TimelineIR } from "./ir.js";
|
|
15
|
+
export type FontWeight = 400 | 700 | 800;
|
|
16
|
+
export interface SplitOpts {
|
|
17
|
+
/** Id PREFIX → glyph ids `${id}-${i}`. */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Anchor point of the line. */
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
fontSize: number;
|
|
23
|
+
fontWeight?: FontWeight;
|
|
24
|
+
fill?: string;
|
|
25
|
+
/** Extra px between glyphs (tracking). */
|
|
26
|
+
letterSpacing?: number;
|
|
27
|
+
/** Horizontal alignment about `x` (default "center"). */
|
|
28
|
+
align?: "left" | "center";
|
|
29
|
+
/** Animate per glyph or per word (default "glyph"). */
|
|
30
|
+
unit?: "glyph" | "word";
|
|
31
|
+
/** Starting opacity of the nodes (default 0, for entrances). */
|
|
32
|
+
opacity?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface Glyph {
|
|
35
|
+
id: string;
|
|
36
|
+
/** The character (glyph unit) or word (word unit). */
|
|
37
|
+
ch: string;
|
|
38
|
+
/** Home centre (the laid-out resting position). */
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
/** This unit's advance width in px. */
|
|
42
|
+
advance: number;
|
|
43
|
+
/** Index in declaration order. */
|
|
44
|
+
i: number;
|
|
45
|
+
}
|
|
46
|
+
export interface TextBlock {
|
|
47
|
+
nodes: NodeIR[];
|
|
48
|
+
glyphs: Glyph[];
|
|
49
|
+
ids: string[];
|
|
50
|
+
/** Total laid-out width in px. */
|
|
51
|
+
width: number;
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
fontSize: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function splitText(textStr: string, opts: SplitOpts): TextBlock;
|
|
57
|
+
interface FxOpts {
|
|
58
|
+
speed?: number;
|
|
59
|
+
energy?: number;
|
|
60
|
+
seed?: number;
|
|
61
|
+
stagger?: number;
|
|
62
|
+
label?: string;
|
|
63
|
+
}
|
|
64
|
+
export type TextInName = "typewriter" | "cascade" | "rise" | "bounce" | "assemble" | "decode";
|
|
65
|
+
export declare function textIn(name: TextInName, block: TextBlock, opts?: FxOpts): TimelineIR;
|
|
66
|
+
export type TextLoopName = "wave" | "shimmer" | "wobble" | "float";
|
|
67
|
+
export interface TextLoopOpts extends BehaviorWindow {
|
|
68
|
+
amplitude?: number;
|
|
69
|
+
frequency?: number;
|
|
70
|
+
/** phase offset per glyph (the travelling-wave speed). */
|
|
71
|
+
phaseStep?: number;
|
|
72
|
+
}
|
|
73
|
+
export declare function textLoop(name: TextLoopName, block: TextBlock, opts?: TextLoopOpts): BehaviorIR[];
|
|
74
|
+
export type TextOutName = "shatter" | "fly" | "dissolve" | "fall" | "collapse";
|
|
75
|
+
export interface TextOutOpts extends FxOpts {
|
|
76
|
+
/** direction for "fly" (default up). */
|
|
77
|
+
dir?: [number, number];
|
|
78
|
+
}
|
|
79
|
+
export declare function textOut(name: TextOutName, block: TextBlock, opts?: TextOutOpts): TimelineIR;
|
|
80
|
+
export interface TypeCueOpts {
|
|
81
|
+
/** the timeline label the typewriter `textIn` starts at. */
|
|
82
|
+
at: string | number;
|
|
83
|
+
/** seconds between keystrokes (match the textIn stagger / speed). */
|
|
84
|
+
interval?: number;
|
|
85
|
+
gain?: number;
|
|
86
|
+
/** offset of the first key from `at`. */
|
|
87
|
+
offset?: number;
|
|
88
|
+
}
|
|
89
|
+
/** Per-glyph CC0 keypress for `textIn("typewriter", …)`. */
|
|
90
|
+
export declare function textTypeCues(block: TextBlock, opts: TypeCueOpts): AudioCueIR[];
|
|
91
|
+
export {};
|
package/guides/edsl-guide.md
CHANGED
|
@@ -167,6 +167,65 @@ no new renderer concept, so overlays/preview/determinism all apply.
|
|
|
167
167
|
set it when the same preset is used more than once in a scene). Legs use
|
|
168
168
|
`ikReach`, arms FK; pure keyframes, so add continuous idle yourself with `oscillate`.
|
|
169
169
|
|
|
170
|
+
## Kinetic text (split + effect presets)
|
|
171
|
+
|
|
172
|
+
reframe's `text` node renders a whole string as one node, so per-glyph effects
|
|
173
|
+
need the string split into per-character nodes. `splitText` does that once;
|
|
174
|
+
seeded effect generators animate the glyphs (the text analog of `motionPreset`).
|
|
175
|
+
|
|
176
|
+
- `splitText(text, { id, x, y, fontSize, fontWeight?, fill?, letterSpacing?,
|
|
177
|
+
align?, unit?, opacity? }) → TextBlock` — lays the phrase out as center-anchored
|
|
178
|
+
`text` nodes using **real Inter advance widths** (so layout matches the render).
|
|
179
|
+
Returns `{ nodes, glyphs, ids, width, ... }`; put `...block.nodes` in `nodes`.
|
|
180
|
+
Glyph ids are `${id}-${i}` (stable regen addresses). `unit: "word"` animates
|
|
181
|
+
whole words instead of letters; `opacity: 0` (default) starts hidden for entrances.
|
|
182
|
+
- `textIn(name, block, { speed?, energy?, seed?, stagger?, label? }) → TimelineIR`
|
|
183
|
+
(a `beat`) — entrance: `typewriter`, `cascade`, `rise`, `bounce`, `assemble`
|
|
184
|
+
(fly in from a seeded scatter), `decode` (scramble through random glyphs then lock).
|
|
185
|
+
- `textLoop(name, block, { from?, until?, ramp?, amplitude?, frequency?, phaseStep? })
|
|
186
|
+
→ BehaviorIR[]` — sustained: `wave` (standing sine), `shimmer`, `wobble`, `float`.
|
|
187
|
+
Spread it into `behaviors`.
|
|
188
|
+
- `textOut(name, block, { …, dir? }) → TimelineIR` — exit: `shatter` (random
|
|
189
|
+
direction + spin + fade), `fly` (directional), `dissolve`, `fall`, `collapse`.
|
|
190
|
+
- `textTypeCues(block, { at, interval?, gain? }) → AudioCueIR[]` — per-glyph CC0
|
|
191
|
+
keypress for a typewriter entrance; spread into `audio.cues`.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const T = splitText("MOTION IS DATA", { id: "t", x: 960, y: 470, fontSize: 130 });
|
|
195
|
+
// nodes: [...T.nodes]
|
|
196
|
+
// timeline: seq(textIn("cascade", T), wait(2), textOut("shatter", T, { seed: 3 }))
|
|
197
|
+
// behaviors: textLoop("wave", T, { from: 1.6, until: 3.6 })
|
|
198
|
+
```
|
|
199
|
+
Every effect is seeded (same `seed` → identical) and pure keyframes. To time a
|
|
200
|
+
`textLoop` window, add up the `textIn` beat length (≈ `(n-1)·stagger + glyphDur`).
|
|
201
|
+
|
|
202
|
+
## Cursor (UI demos)
|
|
203
|
+
|
|
204
|
+
A vector mouse pointer that glides across the scene and clicks things — for app
|
|
205
|
+
walkthroughs. `cursor()` returns a node; the moves/clicks return timeline steps.
|
|
206
|
+
The pointer's **hotspot is the group origin**, so a move lands the tip on a target.
|
|
207
|
+
|
|
208
|
+
- `cursor({ id, x, y, scale?, opacity?, style?, accent? }) → NodeIR` — styles
|
|
209
|
+
`arrow` (default), `dot`, `ring`. Draw it LAST so it sits on top. Carries a
|
|
210
|
+
hidden `${id}-ripple` ring for clicks.
|
|
211
|
+
- `cursorTo(id, from, to, { duration?, ease?, arc? }) → TimelineIR` — glide along
|
|
212
|
+
a gentle human arc (`arc` is the bow, default 0.12). Thread the position: start
|
|
213
|
+
= the node's `x/y`, each `to` becomes the next `from`.
|
|
214
|
+
- `cursorPath(id, points, opts)` — a multi-stop tour through waypoints.
|
|
215
|
+
- `cursorClick(id, { press?, ripple?, label? })` / `cursorDouble(...)` — the
|
|
216
|
+
pointer taps, a ripple ring expands, and the `press` node (a button) dips. Pass
|
|
217
|
+
a unique `label` when you click more than once in a scene.
|
|
218
|
+
- `deviceScreenPoint(name, deviceOpts, [lx, ly]) → [x, y]` — map a UI element's
|
|
219
|
+
screen-local coords (the coords `devicePreset` `content` is authored in) to
|
|
220
|
+
scene coords, so the cursor clicks on-screen UI precisely (account for the
|
|
221
|
+
device's `scale` at click time and any `slot` offset).
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
// nodes: devicePreset("browser", { id:"d", x, y, scale:0.88, content }), cursor({ id:"cur" })
|
|
225
|
+
const cta = deviceScreenPoint("browser", { x, y, scale: 0.88 }, [lx, ly]);
|
|
226
|
+
seq(cursorTo("cur", [sx, sy], cta), cursorClick("cur", { press: "browser-ui-cta" }))
|
|
227
|
+
```
|
|
228
|
+
|
|
170
229
|
## Audio (optional)
|
|
171
230
|
|
|
172
231
|
Label-anchored sound design — cues follow retiming and regeneration:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reframe-video",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"motion-graphics",
|