reframe-video 0.6.34 → 0.6.39
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/.claude-plugin/plugin.json +1 -1
- package/dist/assemble.js +138 -0
- package/dist/bin.js +178 -31
- package/dist/browserEntry.js +62 -3
- package/dist/cli.js +93 -14
- package/dist/compile-api.js +20 -0
- package/dist/compile.js +20 -0
- package/dist/diff.js +81 -3
- package/dist/frame.js +81 -3
- package/dist/index.js +1528 -1107
- package/dist/labels.js +80 -2
- package/dist/lint.js +1060 -0
- package/dist/manifest.js +1065 -0
- package/dist/types/compose.d.ts +6 -0
- package/dist/types/devicePreset.d.ts +36 -3
- package/dist/types/dsl.d.ts +4 -3
- package/dist/types/index.d.ts +4 -2
- package/dist/types/ir.d.ts +13 -5
- package/dist/types/manifest.d.ts +92 -0
- package/dist/types/titles.d.ts +71 -0
- package/dist/verifyOverlay.js +1152 -0
- package/guides/edsl-guide.md +58 -0
- package/guides/regen-contract.md +14 -0
- package/package.json +1 -1
- package/skills/reframe/SKILL.md +26 -0
package/dist/index.js
CHANGED
|
@@ -255,11 +255,68 @@ function compileScene(ir) {
|
|
|
255
255
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
256
256
|
const natural = durationOf(grouping, 0);
|
|
257
257
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
258
|
-
const
|
|
258
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
259
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
259
260
|
return beatStart + k * natural;
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
};
|
|
264
|
+
let labelClock;
|
|
265
|
+
const anyAnchor = (tl) => tl.kind === "beat" && typeof tl.at === "string" || "children" in tl && tl.children.some(anyAnchor);
|
|
266
|
+
if (ir.timeline && anyAnchor(ir.timeline)) {
|
|
267
|
+
const clock = /* @__PURE__ */ new Map();
|
|
268
|
+
const clockWalk = (tl, start) => {
|
|
269
|
+
let end = start;
|
|
270
|
+
switch (tl.kind) {
|
|
271
|
+
case "seq": {
|
|
272
|
+
let t = start;
|
|
273
|
+
for (const c of orderBeats(tl.children)) t = clockWalk(c, t);
|
|
274
|
+
end = t;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case "par": {
|
|
278
|
+
for (const c of tl.children) end = Math.max(end, clockWalk(c, start));
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case "stagger": {
|
|
282
|
+
tl.children.forEach((c, i) => {
|
|
283
|
+
end = Math.max(end, clockWalk(c, start + i * tl.interval));
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
case "wait":
|
|
288
|
+
end = start + tl.duration;
|
|
289
|
+
break;
|
|
290
|
+
case "tween":
|
|
291
|
+
end = start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
292
|
+
break;
|
|
293
|
+
case "motionPath":
|
|
294
|
+
end = start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
295
|
+
break;
|
|
296
|
+
case "to": {
|
|
297
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
298
|
+
const si = tl.stagger ?? 0;
|
|
299
|
+
const targets = nodeOrder.filter((id) => id in override && (tl.filter === void 0 || tl.filter.includes(id)));
|
|
300
|
+
end = start + (tl.duration ?? DEFAULT_TO_DURATION) + Math.max(0, targets.length - 1) * si;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case "beat": {
|
|
304
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
305
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
306
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
307
|
+
const at = typeof tl.at === "number" ? tl.at : void 0;
|
|
308
|
+
const beatStart = at ?? start + (tl.gap ?? 0);
|
|
309
|
+
end = clockWalk(inner, beatStart);
|
|
310
|
+
clock.set(tl.name, { t0: beatStart, t1: end });
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if ("label" in tl && tl.label !== void 0) clock.set(tl.label, { t0: start, t1: end });
|
|
315
|
+
return end;
|
|
316
|
+
};
|
|
317
|
+
clockWalk(ir.timeline, 0);
|
|
318
|
+
labelClock = clock;
|
|
319
|
+
}
|
|
263
320
|
const walk = (tl, start) => {
|
|
264
321
|
const end = walkInner(tl, start);
|
|
265
322
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -276,7 +333,8 @@ function compileScene(ir) {
|
|
|
276
333
|
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
277
334
|
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
278
335
|
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
279
|
-
const
|
|
336
|
+
const anchored = typeof tl.at === "string" ? labelClock?.get(tl.at)?.t0 : tl.at;
|
|
337
|
+
const beatStart = anchored !== void 0 ? anchored + (typeof tl.at === "string" ? tl.gap ?? 0 : 0) : start + (tl.gap ?? 0);
|
|
280
338
|
const end = walk(inner, beatStart);
|
|
281
339
|
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
282
340
|
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
@@ -596,6 +654,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
596
654
|
function validateScene(ir) {
|
|
597
655
|
const problems = [];
|
|
598
656
|
const nodeById = /* @__PURE__ */ new Map();
|
|
657
|
+
const startAnchors = [];
|
|
599
658
|
const checkPaint = (where, value) => {
|
|
600
659
|
if (typeof value !== "object" || value === null) return;
|
|
601
660
|
const g = value;
|
|
@@ -628,6 +687,7 @@ function validateScene(ir) {
|
|
|
628
687
|
if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
|
|
629
688
|
if (typeof props.blend === "string" && !BLEND_MODES.has(props.blend)) problems.push(`node "${node.id}": unknown blend "${props.blend}" \u2014 use ${[...BLEND_MODES].join(", ")}`);
|
|
630
689
|
if (typeof props.fit === "string" && !IMAGE_FITS.has(props.fit)) problems.push(`node "${node.id}": unknown fit "${props.fit}" \u2014 use ${[...IMAGE_FITS].join(", ")}`);
|
|
690
|
+
if (node.type === "video" && typeof node.props.start === "string") startAnchors.push({ id: node.id, at: node.props.start });
|
|
631
691
|
if (node.type === "group") {
|
|
632
692
|
const clip = node.props.clip;
|
|
633
693
|
if (clip) {
|
|
@@ -688,6 +748,7 @@ function validateScene(ir) {
|
|
|
688
748
|
);
|
|
689
749
|
}
|
|
690
750
|
const labels = /* @__PURE__ */ new Set();
|
|
751
|
+
const beatAnchors = [];
|
|
691
752
|
const checkEase = (path2, ease) => {
|
|
692
753
|
if (ease === void 0) return;
|
|
693
754
|
if (typeof ease === "string") {
|
|
@@ -779,6 +840,7 @@ function validateScene(ir) {
|
|
|
779
840
|
);
|
|
780
841
|
}
|
|
781
842
|
labels.add(tl.name);
|
|
843
|
+
if (typeof tl.at === "string") beatAnchors.push({ name: tl.name, at: tl.at, path: path2 });
|
|
782
844
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
783
845
|
problems.push(`${path2}: beat "${tl.name}" duration must be > 0`);
|
|
784
846
|
}
|
|
@@ -797,6 +859,22 @@ function validateScene(ir) {
|
|
|
797
859
|
}
|
|
798
860
|
};
|
|
799
861
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
862
|
+
for (const a of beatAnchors) {
|
|
863
|
+
if (a.at === a.name) {
|
|
864
|
+
problems.push(`${a.path}: beat "${a.name}" at: "${a.at}" cannot anchor to itself`);
|
|
865
|
+
} else if (!labels.has(a.at)) {
|
|
866
|
+
problems.push(
|
|
867
|
+
`${a.path}: beat "${a.name}" at: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
for (const a of startAnchors) {
|
|
872
|
+
if (!labels.has(a.at)) {
|
|
873
|
+
problems.push(
|
|
874
|
+
`video "${a.id}" start: "${a.at}" \u2014 unknown timeline label \u2014 known labels: ${[...labels].join(", ") || "(none)"}`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
800
878
|
for (const [i, b] of (ir.behaviors ?? []).entries()) {
|
|
801
879
|
checkProps(`behaviors[${i}]`, b.target, { [b.prop]: 0 });
|
|
802
880
|
}
|
|
@@ -1000,6 +1078,13 @@ function compileComposition(comp) {
|
|
|
1000
1078
|
|
|
1001
1079
|
// ../core/src/compose.ts
|
|
1002
1080
|
var SCENE_PATCHABLE = ["background", "duration", "fps"];
|
|
1081
|
+
var TIMELINE_PATCHABLE = {
|
|
1082
|
+
to: ["duration", "ease", "stagger"],
|
|
1083
|
+
tween: ["duration", "ease"],
|
|
1084
|
+
wait: ["duration"],
|
|
1085
|
+
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
1086
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
1087
|
+
};
|
|
1003
1088
|
function composeScene(base, ...overlays) {
|
|
1004
1089
|
const ir = structuredClone(base);
|
|
1005
1090
|
const report = { applied: [], orphans: [], warnings: [] };
|
|
@@ -1134,13 +1219,6 @@ function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
|
1134
1219
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
1135
1220
|
};
|
|
1136
1221
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
1137
|
-
const PATCHABLE = {
|
|
1138
|
-
to: ["duration", "ease", "stagger"],
|
|
1139
|
-
tween: ["duration", "ease"],
|
|
1140
|
-
wait: ["duration"],
|
|
1141
|
-
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
1142
|
-
beat: ["at", "gap", "scale", "duration", "order"]
|
|
1143
|
-
};
|
|
1144
1222
|
let timingPatched = false;
|
|
1145
1223
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
1146
1224
|
const step = byLabel.get(label);
|
|
@@ -1151,7 +1229,7 @@ function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
|
1151
1229
|
);
|
|
1152
1230
|
continue;
|
|
1153
1231
|
}
|
|
1154
|
-
const allowed =
|
|
1232
|
+
const allowed = TIMELINE_PATCHABLE[step.kind] ?? [];
|
|
1155
1233
|
for (const [key2, value] of Object.entries(patch)) {
|
|
1156
1234
|
if (value === void 0) continue;
|
|
1157
1235
|
if (!allowed.includes(key2)) {
|
|
@@ -1231,6 +1309,134 @@ function formatComposeReport(report) {
|
|
|
1231
1309
|
return lines.join("\n");
|
|
1232
1310
|
}
|
|
1233
1311
|
|
|
1312
|
+
// ../core/src/manifest.ts
|
|
1313
|
+
function animatedPropsOf(compiled, id) {
|
|
1314
|
+
const props = /* @__PURE__ */ new Set();
|
|
1315
|
+
const prefix = `${id}.`;
|
|
1316
|
+
for (const key2 of compiled.segments.keys()) {
|
|
1317
|
+
if (key2.startsWith(prefix)) props.add(key2.slice(prefix.length));
|
|
1318
|
+
}
|
|
1319
|
+
const drivers = compiled.motionPaths.get(id);
|
|
1320
|
+
if (drivers && drivers.length > 0) {
|
|
1321
|
+
props.add("x");
|
|
1322
|
+
props.add("y");
|
|
1323
|
+
if (drivers.some((d) => d.autoRotate)) props.add("rotation");
|
|
1324
|
+
}
|
|
1325
|
+
return [...props].sort();
|
|
1326
|
+
}
|
|
1327
|
+
function indexTimeline(tl) {
|
|
1328
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
1329
|
+
const walk = (t) => {
|
|
1330
|
+
if ("label" in t && t.label !== void 0) byLabel.set(t.label, t);
|
|
1331
|
+
if (t.kind === "beat") byLabel.set(t.name, t);
|
|
1332
|
+
if ("children" in t) t.children.forEach(walk);
|
|
1333
|
+
};
|
|
1334
|
+
if (tl) walk(tl);
|
|
1335
|
+
return byLabel;
|
|
1336
|
+
}
|
|
1337
|
+
function walkMotion(tl, visit) {
|
|
1338
|
+
const walk = (t) => {
|
|
1339
|
+
if (t.kind === "tween" || t.kind === "motionPath") {
|
|
1340
|
+
visit(t.kind, "label" in t && t.label !== void 0, t.target);
|
|
1341
|
+
} else if (t.kind === "to") {
|
|
1342
|
+
visit("to", t.label !== void 0, t.state);
|
|
1343
|
+
}
|
|
1344
|
+
if ("children" in t) t.children.forEach(walk);
|
|
1345
|
+
};
|
|
1346
|
+
if (tl) walk(tl);
|
|
1347
|
+
}
|
|
1348
|
+
function sceneManifest(compiled) {
|
|
1349
|
+
const ir = compiled.ir;
|
|
1350
|
+
const statesByNode = /* @__PURE__ */ new Map();
|
|
1351
|
+
for (const [name, override] of Object.entries(ir.states ?? {})) {
|
|
1352
|
+
for (const id of Object.keys(override)) {
|
|
1353
|
+
const list = statesByNode.get(id) ?? [];
|
|
1354
|
+
list.push(name);
|
|
1355
|
+
statesByNode.set(id, list);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const nodes = [];
|
|
1359
|
+
const walkNodes = (list, parent) => {
|
|
1360
|
+
for (const node of list) {
|
|
1361
|
+
nodes.push({
|
|
1362
|
+
id: node.id,
|
|
1363
|
+
type: node.type,
|
|
1364
|
+
...parent !== void 0 ? { parent } : {},
|
|
1365
|
+
address: `nodes.${node.id}`,
|
|
1366
|
+
editableProps: [...PROPS_BY_TYPE[node.type]],
|
|
1367
|
+
animatedProps: animatedPropsOf(compiled, node.id),
|
|
1368
|
+
inStates: statesByNode.get(node.id) ?? []
|
|
1369
|
+
});
|
|
1370
|
+
if (node.type === "group") walkNodes(node.children, node.id);
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
walkNodes(ir.nodes, void 0);
|
|
1374
|
+
const states = Object.entries(ir.states ?? {}).map(([name, override]) => ({
|
|
1375
|
+
name,
|
|
1376
|
+
address: `states.${name}`,
|
|
1377
|
+
touches: Object.entries(override).map(([id, props]) => ({ id, props: Object.keys(props) }))
|
|
1378
|
+
}));
|
|
1379
|
+
const byLabel = indexTimeline(ir.timeline);
|
|
1380
|
+
const timeline = [];
|
|
1381
|
+
const beats = [];
|
|
1382
|
+
for (const [label, span] of compiled.labelTimes) {
|
|
1383
|
+
const step = byLabel.get(label);
|
|
1384
|
+
const kind = step?.kind ?? "seq";
|
|
1385
|
+
if (compiled.beatTimes.has(label) && step?.kind === "beat") {
|
|
1386
|
+
beats.push({ name: label, t0: span.t0, t1: span.t1, ownsNodes: step.nodes ?? [], address: `timeline.${label}` });
|
|
1387
|
+
} else {
|
|
1388
|
+
timeline.push({ label, kind, t0: span.t0, t1: span.t1, patchable: TIMELINE_PATCHABLE[kind] ?? [], address: `timeline.${label}` });
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
timeline.sort((a, b) => a.t0 - b.t0 || a.label.localeCompare(b.label));
|
|
1392
|
+
beats.sort((a, b) => a.t0 - b.t0 || a.name.localeCompare(b.name));
|
|
1393
|
+
const behaviors = (ir.behaviors ?? []).map((b) => ({
|
|
1394
|
+
target: b.target,
|
|
1395
|
+
prop: b.prop,
|
|
1396
|
+
kind: b.behavior.name,
|
|
1397
|
+
address: `behaviors.${b.target}.${b.prop}`
|
|
1398
|
+
}));
|
|
1399
|
+
let motionTotal = 0;
|
|
1400
|
+
let motionLabeled = 0;
|
|
1401
|
+
walkMotion(ir.timeline, (_kind, labeled) => {
|
|
1402
|
+
motionTotal++;
|
|
1403
|
+
if (labeled) motionLabeled++;
|
|
1404
|
+
});
|
|
1405
|
+
return {
|
|
1406
|
+
scene: {
|
|
1407
|
+
id: ir.id,
|
|
1408
|
+
duration: compiled.duration,
|
|
1409
|
+
fps: ir.fps ?? 30,
|
|
1410
|
+
size: ir.size,
|
|
1411
|
+
...ir.background !== void 0 ? { background: ir.background } : {}
|
|
1412
|
+
},
|
|
1413
|
+
nodes,
|
|
1414
|
+
states,
|
|
1415
|
+
timeline,
|
|
1416
|
+
beats,
|
|
1417
|
+
behaviors,
|
|
1418
|
+
summary: {
|
|
1419
|
+
nodeCount: nodes.length,
|
|
1420
|
+
labeledSteps: timeline.length + beats.length,
|
|
1421
|
+
unlabeledMotionSteps: motionTotal - motionLabeled,
|
|
1422
|
+
motionAddressableRatio: motionTotal === 0 ? 1 : motionLabeled / motionTotal
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function lintScene(compiled) {
|
|
1427
|
+
const findings = [];
|
|
1428
|
+
walkMotion(compiled.ir.timeline, (kind, labeled, target) => {
|
|
1429
|
+
if (labeled) return;
|
|
1430
|
+
const what = kind === "to" ? `to("${target}")` : `${kind} on "${target}"`;
|
|
1431
|
+
findings.push({
|
|
1432
|
+
rule: "unlabeled-motion",
|
|
1433
|
+
severity: "warn",
|
|
1434
|
+
message: `${what} has no label \u2014 its timing can't be retimed or redirected by an overlay, and a base regeneration can silently drop it. Add a stable label.`
|
|
1435
|
+
});
|
|
1436
|
+
});
|
|
1437
|
+
return findings;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1234
1440
|
// ../core/src/camera.ts
|
|
1235
1441
|
var CAMERA_ID = "camera";
|
|
1236
1442
|
var CAMERA_PROPS2 = ["x", "y", "zoom", "rotation", "perspective"];
|
|
@@ -1654,7 +1860,8 @@ function evaluate(compiled, t) {
|
|
|
1654
1860
|
const height = num2(id, "height", node.props.height);
|
|
1655
1861
|
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
1656
1862
|
const fps = compiled.ir.fps ?? 30;
|
|
1657
|
-
const
|
|
1863
|
+
const startRaw = node.props.start;
|
|
1864
|
+
const start = typeof startRaw === "string" ? compiled.labelTimes.get(startRaw)?.t0 ?? 0 : startRaw ?? 0;
|
|
1658
1865
|
const rate = node.props.rate ?? 1;
|
|
1659
1866
|
const clipStart = node.props.clipStart ?? 0;
|
|
1660
1867
|
const srcT = clipStart + Math.max(0, t - start) * rate;
|
|
@@ -1923,13 +2130,10 @@ function photoMontage(images, opts = {}) {
|
|
|
1923
2130
|
const cy = H / 2;
|
|
1924
2131
|
const nodes = [];
|
|
1925
2132
|
const shots = [];
|
|
1926
|
-
let clock = 0;
|
|
1927
2133
|
slides.forEach((slide, i) => {
|
|
1928
2134
|
const nid = `${id}-${i}`;
|
|
1929
2135
|
const slideHold = Math.max(0.5, slide.hold ?? hold);
|
|
1930
2136
|
const transition = Math.min(opts.transition ?? 0.6, slideHold * 0.9);
|
|
1931
|
-
const shotStart = clock;
|
|
1932
|
-
clock += slideHold;
|
|
1933
2137
|
const kind = slide.ken ?? ["in", "out", "pan"][Math.floor(rand2() * 3)] ?? "in";
|
|
1934
2138
|
const angle = rand2() * Math.PI * 2;
|
|
1935
2139
|
const panFrac = 0.4 + rand2() * 0.35;
|
|
@@ -1955,7 +2159,7 @@ function photoMontage(images, opts = {}) {
|
|
|
1955
2159
|
}
|
|
1956
2160
|
const box = { id: nid, src: slide.src, x: xA, y: yA, width: W, height: H, anchor: "center", fit: "cover", scale: kA, opacity: i === 0 ? 1 : 0 };
|
|
1957
2161
|
nodes.push(
|
|
1958
|
-
isVideoSrc(slide.src) ? video({ ...box, start:
|
|
2162
|
+
isVideoSrc(slide.src) ? video({ ...box, start: `shot-${i}`, volume: slide.volume ?? 0 }) : image(box)
|
|
1959
2163
|
);
|
|
1960
2164
|
const ken = tween(
|
|
1961
2165
|
nid,
|
|
@@ -2009,903 +2213,6 @@ function photoMontage(images, opts = {}) {
|
|
|
2009
2213
|
}
|
|
2010
2214
|
var videoMontage = photoMontage;
|
|
2011
2215
|
|
|
2012
|
-
// ../core/src/presets.ts
|
|
2013
|
-
var PRESET_NAMES = [
|
|
2014
|
-
"draw-bloom",
|
|
2015
|
-
"punch-in",
|
|
2016
|
-
"rise-settle",
|
|
2017
|
-
"slide-bank",
|
|
2018
|
-
"reveal-orbit",
|
|
2019
|
-
"spin-forge"
|
|
2020
|
-
];
|
|
2021
|
-
function makeRng2(seed) {
|
|
2022
|
-
let a = seed >>> 0 || 2654435769;
|
|
2023
|
-
return () => {
|
|
2024
|
-
a = a + 1831565813 | 0;
|
|
2025
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2026
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2027
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2028
|
-
};
|
|
2029
|
-
}
|
|
2030
|
-
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
2031
|
-
var SET = 1 / 120;
|
|
2032
|
-
function ctx(o) {
|
|
2033
|
-
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
2034
|
-
return {
|
|
2035
|
-
e: clamp01(o.energy ?? 0.5),
|
|
2036
|
-
sp: Math.max(0.25, o.speed ?? 1),
|
|
2037
|
-
it: clamp01(o.intensity ?? 0.5),
|
|
2038
|
-
from: o.from,
|
|
2039
|
-
rand: rand2,
|
|
2040
|
-
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
2041
|
-
g: o.target.group,
|
|
2042
|
-
cx: o.target.center[0],
|
|
2043
|
-
cy: o.target.center[1],
|
|
2044
|
-
s: o.target.baseScale,
|
|
2045
|
-
fills: o.target.fills,
|
|
2046
|
-
inks: o.target.inks
|
|
2047
|
-
};
|
|
2048
|
-
}
|
|
2049
|
-
var dur = (base, sp) => base / sp;
|
|
2050
|
-
function settleEase(e) {
|
|
2051
|
-
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
2052
|
-
}
|
|
2053
|
-
function fromVec(from, dist) {
|
|
2054
|
-
switch (from) {
|
|
2055
|
-
case "left":
|
|
2056
|
-
return [-dist, 0];
|
|
2057
|
-
case "right":
|
|
2058
|
-
return [dist, 0];
|
|
2059
|
-
case "top":
|
|
2060
|
-
return [0, -dist];
|
|
2061
|
-
default:
|
|
2062
|
-
return [0, dist];
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
2066
|
-
return stagger(
|
|
2067
|
-
gap / c.sp,
|
|
2068
|
-
...c.fills.map(
|
|
2069
|
-
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
2070
|
-
)
|
|
2071
|
-
);
|
|
2072
|
-
}
|
|
2073
|
-
function drawInks(c) {
|
|
2074
|
-
return stagger(
|
|
2075
|
-
0.15 / c.sp,
|
|
2076
|
-
...c.inks.map(
|
|
2077
|
-
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
2078
|
-
)
|
|
2079
|
-
);
|
|
2080
|
-
}
|
|
2081
|
-
function motionPreset(name, opts) {
|
|
2082
|
-
const c = ctx(opts);
|
|
2083
|
-
switch (name) {
|
|
2084
|
-
case "draw-bloom":
|
|
2085
|
-
return beat("draw-bloom", {}, [
|
|
2086
|
-
drawInks(c),
|
|
2087
|
-
fadeFills(c, 0.45),
|
|
2088
|
-
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
2089
|
-
]);
|
|
2090
|
-
case "punch-in": {
|
|
2091
|
-
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
2092
|
-
return beat("punch-in", {}, [
|
|
2093
|
-
par(
|
|
2094
|
-
fadeFills(c, 0.25),
|
|
2095
|
-
seq(
|
|
2096
|
-
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
2097
|
-
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
2098
|
-
)
|
|
2099
|
-
)
|
|
2100
|
-
]);
|
|
2101
|
-
}
|
|
2102
|
-
case "rise-settle": {
|
|
2103
|
-
const es = 0.65 + c.rand() * 0.7;
|
|
2104
|
-
const dist = (220 + 260 * c.it) * es;
|
|
2105
|
-
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
2106
|
-
const jx = c.jit(110);
|
|
2107
|
-
return beat("rise-settle", {}, [
|
|
2108
|
-
par(
|
|
2109
|
-
motionPath(
|
|
2110
|
-
c.g,
|
|
2111
|
-
[
|
|
2112
|
-
[c.cx + dx + jx, c.cy + dy],
|
|
2113
|
-
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
2114
|
-
[c.cx, c.cy]
|
|
2115
|
-
],
|
|
2116
|
-
{ duration: dur(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
2117
|
-
),
|
|
2118
|
-
fadeFills(c, 0.4)
|
|
2119
|
-
)
|
|
2120
|
-
]);
|
|
2121
|
-
}
|
|
2122
|
-
case "slide-bank": {
|
|
2123
|
-
const es = 0.65 + c.rand() * 0.7;
|
|
2124
|
-
const dist = (420 + 240 * c.it) * es;
|
|
2125
|
-
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
2126
|
-
const arc = c.jit(140);
|
|
2127
|
-
const midx = c.jit(120);
|
|
2128
|
-
const move = dur(1.2, c.sp);
|
|
2129
|
-
return beat("slide-bank", {}, [
|
|
2130
|
-
par(
|
|
2131
|
-
motionPath(
|
|
2132
|
-
c.g,
|
|
2133
|
-
[
|
|
2134
|
-
[c.cx + dx, c.cy + dy],
|
|
2135
|
-
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
2136
|
-
[c.cx, c.cy]
|
|
2137
|
-
],
|
|
2138
|
-
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
2139
|
-
),
|
|
2140
|
-
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
2141
|
-
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
2142
|
-
fadeFills(c, 0.4)
|
|
2143
|
-
)
|
|
2144
|
-
]);
|
|
2145
|
-
}
|
|
2146
|
-
case "reveal-orbit": {
|
|
2147
|
-
const es = 0.65 + c.rand() * 0.7;
|
|
2148
|
-
const orbit = (180 + 160 * c.it) * es;
|
|
2149
|
-
const jx = c.jit(0.4);
|
|
2150
|
-
const jy = c.jit(0.4);
|
|
2151
|
-
return beat("reveal-orbit", {}, [
|
|
2152
|
-
drawInks(c),
|
|
2153
|
-
fadeFills(c, 0.45),
|
|
2154
|
-
par(
|
|
2155
|
-
motionPath(
|
|
2156
|
-
c.g,
|
|
2157
|
-
[
|
|
2158
|
-
[c.cx, c.cy],
|
|
2159
|
-
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
2160
|
-
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
2161
|
-
[c.cx, c.cy]
|
|
2162
|
-
],
|
|
2163
|
-
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
2164
|
-
),
|
|
2165
|
-
seq(
|
|
2166
|
-
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
2167
|
-
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
2168
|
-
)
|
|
2169
|
-
)
|
|
2170
|
-
]);
|
|
2171
|
-
}
|
|
2172
|
-
case "spin-forge": {
|
|
2173
|
-
const turns = 1 + Math.round(c.it);
|
|
2174
|
-
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
2175
|
-
const startRot = dir * 360 * turns;
|
|
2176
|
-
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
2177
|
-
return beat("spin-forge", {}, [
|
|
2178
|
-
par(
|
|
2179
|
-
seq(
|
|
2180
|
-
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
2181
|
-
// establish (invisible)
|
|
2182
|
-
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
2183
|
-
tween(c.g, { scale: c.s }, { duration: dur(0.3, c.sp), ease: "easeInOutQuad" })
|
|
2184
|
-
),
|
|
2185
|
-
seq(wait(SET), fadeFills(c, 0.3))
|
|
2186
|
-
)
|
|
2187
|
-
]);
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
// ../core/src/devicePreset.ts
|
|
2193
|
-
var DEVICE_PRESET_NAMES = ["phone", "tablet", "laptop", "browser", "watch", "monitor", "tv", "foldable", "terminal", "car"];
|
|
2194
|
-
var DARK = { body: "#15161C", bodyStroke: "#2A2D38", screen: "#0E0F15", detail: "#3A3D48", chrome: "#1B1D24", chromeText: "#9AA0AD" };
|
|
2195
|
-
var LIGHT = { body: "#E7E9EE", bodyStroke: "#C3C7D1", screen: "#FFFFFF", detail: "#AEB3C0", chrome: "#F2F3F6", chromeText: "#5B606C" };
|
|
2196
|
-
var SCREENS = {
|
|
2197
|
-
phone: { width: 352, height: 736, radius: 38 },
|
|
2198
|
-
tablet: { width: 544, height: 764, radius: 18 },
|
|
2199
|
-
laptop: { width: 840, height: 520, radius: 8, cy: -150 },
|
|
2200
|
-
browser: { width: 984, height: 568, radius: 6, cy: 24 },
|
|
2201
|
-
watch: { width: 184, height: 224, radius: 44 },
|
|
2202
|
-
monitor: { width: 1056, height: 600, radius: 6 },
|
|
2203
|
-
tv: { width: 1280, height: 720, radius: 8, cy: -24 },
|
|
2204
|
-
foldable: { width: 760, height: 560, radius: 20 },
|
|
2205
|
-
terminal: { width: 900, height: 560, radius: 6, cy: 18 },
|
|
2206
|
-
car: { width: 1e3, height: 520, radius: 24 }
|
|
2207
|
-
};
|
|
2208
|
-
var BOUNDS = {
|
|
2209
|
-
phone: { width: 392, height: 812 },
|
|
2210
|
-
tablet: { width: 600, height: 820 },
|
|
2211
|
-
laptop: { width: 1100, height: 650 },
|
|
2212
|
-
browser: { width: 1e3, height: 660 },
|
|
2213
|
-
watch: { width: 220, height: 300 },
|
|
2214
|
-
monitor: { width: 1120, height: 860 },
|
|
2215
|
-
tv: { width: 1340, height: 920 },
|
|
2216
|
-
foldable: { width: 800, height: 600 },
|
|
2217
|
-
terminal: { width: 916, height: 636 },
|
|
2218
|
-
car: { width: 1060, height: 600 }
|
|
2219
|
-
};
|
|
2220
|
-
var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
|
|
2221
|
-
function screenDims(name, o) {
|
|
2222
|
-
const d = SCREENS[name];
|
|
2223
|
-
const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
|
|
2224
|
-
return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
|
|
2225
|
-
}
|
|
2226
|
-
function deviceScreen(name, opts = {}) {
|
|
2227
|
-
const d = screenDims(name, opts);
|
|
2228
|
-
return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
|
|
2229
|
-
}
|
|
2230
|
-
function deviceScreenCenter(name, opts = {}) {
|
|
2231
|
-
const d = screenDims(name, opts);
|
|
2232
|
-
return { x: d.cx, y: d.cy };
|
|
2233
|
-
}
|
|
2234
|
-
function deviceBounds(name, opts = {}) {
|
|
2235
|
-
const b = BOUNDS[name];
|
|
2236
|
-
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
2237
|
-
}
|
|
2238
|
-
function deviceScreenPoint(name, opts, local) {
|
|
2239
|
-
const c = deviceScreenCenter(name, opts);
|
|
2240
|
-
const s = opts.scale ?? 1;
|
|
2241
|
-
return [(opts.x ?? 0) + s * (c.x + local[0]), (opts.y ?? 0) + s * (c.y + local[1])];
|
|
2242
|
-
}
|
|
2243
|
-
function screenGroup(id, p, o, cx, cy, dims, content) {
|
|
2244
|
-
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 } }, [
|
|
2245
|
-
rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
|
|
2246
|
-
group({ id: `${id}-content`, x: 0, y: 0 }, content)
|
|
2247
|
-
]);
|
|
2248
|
-
}
|
|
2249
|
-
function buildDevice(name, id, p, o, content) {
|
|
2250
|
-
const dims = screenDims(name, o);
|
|
2251
|
-
const sw = dims.width;
|
|
2252
|
-
const sh = dims.height;
|
|
2253
|
-
const screen = () => screenGroup(id, p, o, dims.cx, dims.cy, dims, content);
|
|
2254
|
-
switch (name) {
|
|
2255
|
-
case "phone":
|
|
2256
|
-
case "tablet": {
|
|
2257
|
-
const bezel = name === "phone" ? 20 : 28;
|
|
2258
|
-
const bodyW = sw + bezel * 2;
|
|
2259
|
-
const bodyH = sh + bezel * 2;
|
|
2260
|
-
const bodyR = name === "phone" ? 54 : 34;
|
|
2261
|
-
const land = isLandscape(name, o);
|
|
2262
|
-
const nodes = [
|
|
2263
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: bodyR }),
|
|
2264
|
-
screen()
|
|
2265
|
-
];
|
|
2266
|
-
if (name === "phone") {
|
|
2267
|
-
nodes.push(
|
|
2268
|
-
land ? rect({ id: `${id}-notch`, x: -sw / 2 + 16, y: 0, anchor: "center", width: 30, height: 96, fill: "#000000", radius: 15 }) : rect({ id: `${id}-notch`, x: 0, y: -sh / 2 + 16, anchor: "center", width: 96, height: 30, fill: "#000000", radius: 15 }),
|
|
2269
|
-
land ? rect({ id: `${id}-home`, x: sw / 2 - 4, y: 0, anchor: "center", width: 5, height: 120, fill: p.detail, radius: 3 }) : rect({ id: `${id}-home`, x: 0, y: sh / 2 - 18, anchor: "center", width: 120, height: 5, fill: p.detail, radius: 3 })
|
|
2270
|
-
);
|
|
2271
|
-
if (!land) {
|
|
2272
|
-
nodes.push(
|
|
2273
|
-
rect({ id: `${id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
|
|
2274
|
-
rect({ id: `${id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
|
|
2275
|
-
rect({ id: `${id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
|
|
2276
|
-
);
|
|
2277
|
-
}
|
|
2278
|
-
} else {
|
|
2279
|
-
nodes.push(
|
|
2280
|
-
rect({ id: `${id}-camera`, x: land ? -sw / 2 - 14 : 0, y: land ? 0 : -sh / 2 - 14, anchor: "center", width: 8, height: 8, fill: p.detail, radius: 4 }),
|
|
2281
|
-
rect({ id: `${id}-pwr`, x: land ? -bodyW * 0.18 : bodyW * 0.18, y: land ? -bodyH / 2 : -bodyH / 2, anchor: "center", width: 60, height: 4, fill: p.detail, radius: 2 })
|
|
2282
|
-
);
|
|
2283
|
-
}
|
|
2284
|
-
return nodes;
|
|
2285
|
-
}
|
|
2286
|
-
case "laptop": {
|
|
2287
|
-
const lidTop = dims.cy - (sh + 40) / 2;
|
|
2288
|
-
const keyRows = [0, 1, 2, 3].map(
|
|
2289
|
-
(r) => rect({ id: `${id}-keys${r}`, x: 0, y: 150 + r * 11, anchor: "center", width: 640 + r * 50, height: 6, fill: p.chrome, radius: 3 })
|
|
2290
|
-
);
|
|
2291
|
-
return [
|
|
2292
|
-
path({ id: `${id}-base`, x: 0, y: 0, d: "M -450 140 L 450 140 L 520 196 L -520 196 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
2293
|
-
rect({ id: `${id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
2294
|
-
rect({ id: `${id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
2295
|
-
...keyRows,
|
|
2296
|
-
rect({ id: `${id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
|
|
2297
|
-
rect({ id: `${id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
|
|
2298
|
-
screen(),
|
|
2299
|
-
ellipse({ id: `${id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
2300
|
-
rect({ id: `${id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: p.bodyStroke, strokeWidth: 2, radius: 18 })
|
|
2301
|
-
];
|
|
2302
|
-
}
|
|
2303
|
-
case "browser": {
|
|
2304
|
-
const winW = sw + 16;
|
|
2305
|
-
const winH = sh + 92;
|
|
2306
|
-
const barY = -winH / 2 + 24;
|
|
2307
|
-
return [
|
|
2308
|
-
rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 14 }),
|
|
2309
|
-
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
|
|
2310
|
-
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
|
|
2311
|
-
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
|
|
2312
|
-
// an active tab tucked under the lights
|
|
2313
|
-
rect({ id: `${id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: o.screen ?? p.screen, radius: 8 }),
|
|
2314
|
-
text({ id: `${id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
2315
|
-
rect({ id: `${id}-urlpill`, x: 96, y: barY, anchor: "center", width: 700, height: 26, fill: o.screen ?? p.screen, stroke: p.bodyStroke, strokeWidth: 1, radius: 13 }),
|
|
2316
|
-
rect({ id: `${id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
|
|
2317
|
-
text({ id: `${id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
|
|
2318
|
-
screen()
|
|
2319
|
-
];
|
|
2320
|
-
}
|
|
2321
|
-
case "watch": {
|
|
2322
|
-
const bw = sw + 36;
|
|
2323
|
-
const bh = sh + 36;
|
|
2324
|
-
return [
|
|
2325
|
-
// straps (drawn behind the body) flaring out top & bottom
|
|
2326
|
-
path({ id: `${id}-bandtop`, x: 0, y: -bh / 2 + 4, d: "M -78 0 L 78 0 L 64 -86 L -64 -86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
2327
|
-
path({ id: `${id}-bandbot`, x: 0, y: bh / 2 - 4, d: "M -78 0 L 78 0 L 64 86 L -64 86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
2328
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bw, height: bh, fill: p.body, stroke: p.bodyStroke, strokeWidth: 3, radius: 60 }),
|
|
2329
|
-
screen(),
|
|
2330
|
-
rect({ id: `${id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
|
|
2331
|
-
rect({ id: `${id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
|
|
2332
|
-
];
|
|
2333
|
-
}
|
|
2334
|
-
case "monitor": {
|
|
2335
|
-
const panelW = sw + 44;
|
|
2336
|
-
const panelH = sh + 60;
|
|
2337
|
-
return [
|
|
2338
|
-
rect({ id: `${id}-panel`, x: 0, y: 0, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 16 }),
|
|
2339
|
-
screen(),
|
|
2340
|
-
ellipse({ id: `${id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
|
|
2341
|
-
rect({ id: `${id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
|
|
2342
|
-
path({ id: `${id}-stand`, x: 0, y: panelH / 2 + 60, d: "M -160 50 L 160 50 L 220 80 L -220 80 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
2343
|
-
];
|
|
2344
|
-
}
|
|
2345
|
-
case "tv": {
|
|
2346
|
-
const panelW = sw + 44;
|
|
2347
|
-
const panelH = sh + 48;
|
|
2348
|
-
const panelBottom = dims.cy + panelH / 2;
|
|
2349
|
-
return [
|
|
2350
|
-
rect({ id: `${id}-panel`, x: 0, y: dims.cy, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 12 }),
|
|
2351
|
-
screen(),
|
|
2352
|
-
ellipse({ id: `${id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
2353
|
-
rect({ id: `${id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
|
|
2354
|
-
path({ id: `${id}-stand`, x: 0, y: panelBottom + 96, d: "M -210 0 L 210 0 L 270 34 L -270 34 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
2355
|
-
];
|
|
2356
|
-
}
|
|
2357
|
-
case "foldable": {
|
|
2358
|
-
const bodyW = sw + 40;
|
|
2359
|
-
const bodyH = sh + 40;
|
|
2360
|
-
return [
|
|
2361
|
-
rect({ id: `${id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
2362
|
-
rect({ id: `${id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
2363
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 28 }),
|
|
2364
|
-
screen(),
|
|
2365
|
-
rect({ id: `${id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
|
|
2366
|
-
ellipse({ id: `${id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
|
|
2367
|
-
ellipse({ id: `${id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
|
|
2368
|
-
];
|
|
2369
|
-
}
|
|
2370
|
-
case "terminal": {
|
|
2371
|
-
const winW = sw + 16;
|
|
2372
|
-
const winH = sh + 76;
|
|
2373
|
-
return [
|
|
2374
|
-
rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 12 }),
|
|
2375
|
-
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 28, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FF5F57" }),
|
|
2376
|
-
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 50, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FEBC2E" }),
|
|
2377
|
-
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 72, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#28C840" }),
|
|
2378
|
-
rect({ id: `${id}-tab`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", width: 130, height: 24, fill: o.screen ?? p.screen, radius: 6 }),
|
|
2379
|
-
text({ id: `${id}-title`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", content: urlText(o.url ?? "zsh"), fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
2380
|
-
screen()
|
|
2381
|
-
];
|
|
2382
|
-
}
|
|
2383
|
-
case "car": {
|
|
2384
|
-
const bodyW = sw + 60;
|
|
2385
|
-
const bodyH = sh + 60;
|
|
2386
|
-
return [
|
|
2387
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 40 }),
|
|
2388
|
-
ellipse({ id: `${id}-knob`, x: -bodyW / 2 + 18, y: 0, anchor: "center", width: 22, height: 22, fill: p.body, stroke: p.detail, strokeWidth: 3 }),
|
|
2389
|
-
screen(),
|
|
2390
|
-
ellipse({ id: `${id}-btn1`, x: -44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
2391
|
-
ellipse({ id: `${id}-btn2`, x: 0, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
2392
|
-
ellipse({ id: `${id}-btn3`, x: 44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail })
|
|
2393
|
-
];
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
}
|
|
2397
|
-
var urlText = (url) => {
|
|
2398
|
-
const u = url ?? "reframe.video";
|
|
2399
|
-
return u.length > 70 ? `${u.slice(0, 67)}\u2026` : u;
|
|
2400
|
-
};
|
|
2401
|
-
function devicePreset(name, opts = {}) {
|
|
2402
|
-
const id = opts.id ?? "device";
|
|
2403
|
-
const p = opts.color === "light" ? LIGHT : DARK;
|
|
2404
|
-
const children = buildDevice(name, id, p, opts, opts.content ?? []);
|
|
2405
|
-
return group(
|
|
2406
|
-
{
|
|
2407
|
-
id,
|
|
2408
|
-
x: opts.x ?? 0,
|
|
2409
|
-
y: opts.y ?? 0,
|
|
2410
|
-
...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
|
|
2411
|
-
...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
|
|
2412
|
-
},
|
|
2413
|
-
children
|
|
2414
|
-
);
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
// ../core/src/cursor.ts
|
|
2418
|
-
var ARROW_D = "M0 0 L0 30 L8 23 L12.6 33 L17 31 L12.4 21.4 L21 21.4 Z";
|
|
2419
|
-
function cursor(opts = {}) {
|
|
2420
|
-
const id = opts.id ?? "cursor";
|
|
2421
|
-
const style = opts.style ?? "arrow";
|
|
2422
|
-
const fill = opts.fill ?? "#FFFFFF";
|
|
2423
|
-
const accent = opts.accent ?? "#FF5A1F";
|
|
2424
|
-
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" })];
|
|
2425
|
-
return group(
|
|
2426
|
-
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
2427
|
-
[
|
|
2428
|
-
// ripple ring (behind the pointer), emanates from the hotspot on click
|
|
2429
|
-
ellipse({ id: `${id}-ripple`, x: 0, y: 0, width: 30, height: 30, fill: "none", stroke: accent, strokeWidth: 3, opacity: 0, scale: 0, anchor: "center" }),
|
|
2430
|
-
// the pointer art lives in its own group so a click "tap" can scale it
|
|
2431
|
-
// independently of the cursor's resting scale
|
|
2432
|
-
group({ id: `${id}-art`, x: 0, y: 0 }, art)
|
|
2433
|
-
]
|
|
2434
|
-
);
|
|
2435
|
-
}
|
|
2436
|
-
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
2437
|
-
function cursorTo(id, from, to2, opts = {}) {
|
|
2438
|
-
const dx = to2[0] - from[0], dy = to2[1] - from[1];
|
|
2439
|
-
const dist = Math.hypot(dx, dy) || 1;
|
|
2440
|
-
const arc = opts.arc ?? 0.12;
|
|
2441
|
-
const mid = [(from[0] + to2[0]) / 2 + -dy / dist * arc * dist, (from[1] + to2[1]) / 2 + dx / dist * arc * dist];
|
|
2442
|
-
const duration = opts.duration ?? clamp(dist / 1400, 0.4, 0.9);
|
|
2443
|
-
return motionPath(id, [from, mid, to2], { duration, ease: opts.ease ?? "easeInOutCubic", curviness: 1, ...opts.label && { label: opts.label } });
|
|
2444
|
-
}
|
|
2445
|
-
function cursorPath(id, points, opts = {}) {
|
|
2446
|
-
return motionPath(id, points, {
|
|
2447
|
-
duration: opts.duration ?? clamp(points.length * 0.5, 0.5, 4),
|
|
2448
|
-
ease: opts.ease ?? "easeInOutCubic",
|
|
2449
|
-
curviness: opts.curviness ?? 1,
|
|
2450
|
-
...opts.label && { label: opts.label }
|
|
2451
|
-
});
|
|
2452
|
-
}
|
|
2453
|
-
function clickBody(id, o) {
|
|
2454
|
-
const sp = Math.max(0.25, o.speed ?? 1);
|
|
2455
|
-
const d = (b) => b / sp;
|
|
2456
|
-
const out = [
|
|
2457
|
-
// the pointer taps
|
|
2458
|
-
seq(tween(`${id}-art`, { scale: 0.82 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(`${id}-art`, { scale: 1 }, { duration: d(0.1), ease: "easeOutBack" }))
|
|
2459
|
-
];
|
|
2460
|
-
if (o.ripple !== false) {
|
|
2461
|
-
out.push(seq(
|
|
2462
|
-
tween(`${id}-ripple`, { scale: 0.2, opacity: 0.55 }, { duration: 1e-3 }),
|
|
2463
|
-
par(
|
|
2464
|
-
tween(`${id}-ripple`, { scale: 5 }, { duration: d(0.5), ease: "easeOutCubic" }),
|
|
2465
|
-
tween(`${id}-ripple`, { opacity: 0 }, { duration: d(0.5), ease: "easeOutQuad" })
|
|
2466
|
-
)
|
|
2467
|
-
));
|
|
2468
|
-
}
|
|
2469
|
-
if (o.press) {
|
|
2470
|
-
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" })));
|
|
2471
|
-
}
|
|
2472
|
-
return out;
|
|
2473
|
-
}
|
|
2474
|
-
function cursorClick(id, opts = {}) {
|
|
2475
|
-
return beat(opts.label ?? "cursor-click", {}, [par(...clickBody(id, opts))]);
|
|
2476
|
-
}
|
|
2477
|
-
function cursorDouble(id, opts = {}) {
|
|
2478
|
-
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
2479
|
-
return beat(opts.label ?? "cursor-double", {}, [
|
|
2480
|
-
seq(par(...clickBody(id, { ...opts, ripple: false })), wait(0.12 / sp), par(...clickBody(id, opts)))
|
|
2481
|
-
]);
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
|
-
// ../core/src/rig.ts
|
|
2485
|
-
var DEFAULT_LINE = "#FFE3D2";
|
|
2486
|
-
var DEFAULT_FILL = "#0E1424";
|
|
2487
|
-
var LINE_W = 5;
|
|
2488
|
-
var GLOW_W = 16;
|
|
2489
|
-
var K = 0.5523;
|
|
2490
|
-
var n = (v) => Number(v.toFixed(2));
|
|
2491
|
-
function capsulePath(hw, len) {
|
|
2492
|
-
const yT = hw;
|
|
2493
|
-
const yB = Math.max(hw, len - hw);
|
|
2494
|
-
const k = hw * K;
|
|
2495
|
-
return `M ${n(-hw)} ${n(yT)} C ${n(-hw)} ${n(yT - k)} ${n(-k)} ${n(yT - hw)} 0 ${n(yT - hw)} C ${n(k)} ${n(yT - hw)} ${n(hw)} ${n(yT - k)} ${n(hw)} ${n(yT)} L ${n(hw)} ${n(yB)} C ${n(hw)} ${n(yB + k)} ${n(k)} ${n(yB + hw)} 0 ${n(yB + hw)} C ${n(-k)} ${n(yB + hw)} ${n(-hw)} ${n(yB + k)} ${n(-hw)} ${n(yB)} Z`;
|
|
2496
|
-
}
|
|
2497
|
-
function ovalPath(a, b, cx = 0, cy = 0) {
|
|
2498
|
-
const ka = n(a * K), kb = n(b * K);
|
|
2499
|
-
const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
|
|
2500
|
-
return `M ${X} ${t} C ${n(cx + ka)} ${t} ${n(cx + A)} ${n(cy - kb)} ${n(cx + A)} ${c} C ${n(cx + A)} ${n(cy + kb)} ${n(cx + ka)} ${bo} ${X} ${bo} C ${n(cx - ka)} ${bo} ${n(cx + L)} ${n(cy + kb)} ${n(cx + L)} ${c} C ${n(cx + L)} ${n(cy - kb)} ${n(cx - ka)} ${t} ${X} ${t} Z`;
|
|
2501
|
-
}
|
|
2502
|
-
function boneShape(jointId, bone, o) {
|
|
2503
|
-
if (bone.shape) return bone.shape;
|
|
2504
|
-
const len = bone.length ?? 0;
|
|
2505
|
-
if (len <= 0) return [];
|
|
2506
|
-
const d = capsulePath((bone.width ?? 20) / 2, len);
|
|
2507
|
-
const nodes = [];
|
|
2508
|
-
if (o.glow) nodes.push(path({ id: `${jointId}-glow`, d, x: 0, y: 0, fill: "none", stroke: o.glow, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
2509
|
-
nodes.push(path({ id: `${jointId}-shape`, d, x: 0, y: 0, fill: o.fill, stroke: o.color, strokeWidth: LINE_W }));
|
|
2510
|
-
return nodes;
|
|
2511
|
-
}
|
|
2512
|
-
function buildBone(bone, id, o) {
|
|
2513
|
-
const jointId = `${id}-${bone.name}`;
|
|
2514
|
-
return group(
|
|
2515
|
-
{ id: jointId, x: bone.at[0], y: bone.at[1], rotation: bone.rotation ?? 0 },
|
|
2516
|
-
[...boneShape(jointId, bone, o), ...(bone.children ?? []).map((c) => buildBone(c, id, o))]
|
|
2517
|
-
);
|
|
2518
|
-
}
|
|
2519
|
-
function rig(root, opts = {}) {
|
|
2520
|
-
const id = opts.id ?? "rig";
|
|
2521
|
-
const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
|
|
2522
|
-
return group(
|
|
2523
|
-
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
2524
|
-
[buildBone(root, id, o)]
|
|
2525
|
-
);
|
|
2526
|
-
}
|
|
2527
|
-
function rigPose(id, pose) {
|
|
2528
|
-
const out = {};
|
|
2529
|
-
for (const [name, deg] of Object.entries(pose)) out[`${id}-${name}`] = { rotation: deg };
|
|
2530
|
-
return out;
|
|
2531
|
-
}
|
|
2532
|
-
function poseTo(id, pose, opts = {}) {
|
|
2533
|
-
const tweens = Object.entries(pose).map(
|
|
2534
|
-
([name, deg]) => tween(`${id}-${name}`, { rotation: deg }, { duration: opts.duration ?? 0.5, ease: opts.ease ?? "easeInOutCubic" })
|
|
2535
|
-
);
|
|
2536
|
-
return opts.stagger ? stagger(opts.stagger, ...tweens) : par(...tweens);
|
|
2537
|
-
}
|
|
2538
|
-
function ikReach(upper, lower, dx, dy, flip = false) {
|
|
2539
|
-
const D = Math.hypot(dx, dy);
|
|
2540
|
-
const cos2 = Math.max(-1, Math.min(1, (D * D - upper * upper - lower * lower) / (2 * upper * lower)));
|
|
2541
|
-
const theta2 = (flip ? -1 : 1) * Math.acos(cos2);
|
|
2542
|
-
const vx = -lower * Math.sin(theta2);
|
|
2543
|
-
const vy = upper + lower * Math.cos(theta2);
|
|
2544
|
-
const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
|
|
2545
|
-
const deg = (r) => r * 180 / Math.PI;
|
|
2546
|
-
return [deg(theta1), deg(theta2)];
|
|
2547
|
-
}
|
|
2548
|
-
function humanoid(opts = {}) {
|
|
2549
|
-
const line2 = opts.color ?? DEFAULT_LINE;
|
|
2550
|
-
const fill = opts.fill ?? DEFAULT_FILL;
|
|
2551
|
-
const glow2 = opts.glow;
|
|
2552
|
-
const blob = (jid, a, b, cy) => {
|
|
2553
|
-
const d = ovalPath(a, b, 0, cy);
|
|
2554
|
-
const nodes = [];
|
|
2555
|
-
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
2556
|
-
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
2557
|
-
return nodes;
|
|
2558
|
-
};
|
|
2559
|
-
const id = opts.id ?? "rig";
|
|
2560
|
-
const root = {
|
|
2561
|
-
name: "chest",
|
|
2562
|
-
at: [0, 0],
|
|
2563
|
-
shape: blob(`${id}-chest`, 44, 62, 22),
|
|
2564
|
-
children: [
|
|
2565
|
-
{ name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
|
|
2566
|
-
{ name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
|
|
2567
|
-
{ name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
|
|
2568
|
-
] },
|
|
2569
|
-
{ name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
|
|
2570
|
-
{ name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
|
|
2571
|
-
] },
|
|
2572
|
-
{ name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
|
|
2573
|
-
{ name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
|
|
2574
|
-
] },
|
|
2575
|
-
{ name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
|
|
2576
|
-
{ name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
|
|
2577
|
-
] }
|
|
2578
|
-
]
|
|
2579
|
-
};
|
|
2580
|
-
return rig(root, opts);
|
|
2581
|
-
}
|
|
2582
|
-
|
|
2583
|
-
// ../core/src/characterPreset.ts
|
|
2584
|
-
var CHARACTER_PRESET_NAMES = ["walk", "run", "jump", "dance", "wave", "cheer"];
|
|
2585
|
-
var THIGH = 76;
|
|
2586
|
-
var SHIN = 72;
|
|
2587
|
-
var clamp012 = (x) => Math.max(0, Math.min(1, x));
|
|
2588
|
-
function makeRng3(seed) {
|
|
2589
|
-
let a = seed >>> 0 || 2654435769;
|
|
2590
|
-
return () => {
|
|
2591
|
-
a = a + 1831565813 | 0;
|
|
2592
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2593
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2594
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2595
|
-
};
|
|
2596
|
-
}
|
|
2597
|
-
var dur2 = (base, sp) => base / sp;
|
|
2598
|
-
function ctx2(o) {
|
|
2599
|
-
const rand2 = makeRng3((o.seed ?? 0) + 1);
|
|
2600
|
-
return {
|
|
2601
|
-
g: o.target,
|
|
2602
|
-
label: o.label,
|
|
2603
|
-
e: clamp012(o.energy ?? 0.5),
|
|
2604
|
-
sp: Math.max(0.25, o.speed ?? 1),
|
|
2605
|
-
cycles: Math.max(1, Math.round(o.cycles ?? 4)),
|
|
2606
|
-
facing: o.facing ?? 1,
|
|
2607
|
-
at: o.at ?? [0, 0],
|
|
2608
|
-
travel: o.travel,
|
|
2609
|
-
rand: rand2,
|
|
2610
|
-
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
2611
|
-
};
|
|
2612
|
-
}
|
|
2613
|
-
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
2614
|
-
function footPos(p, stride, lift) {
|
|
2615
|
-
p = (p % 1 + 1) % 1;
|
|
2616
|
-
if (p < 0.5) {
|
|
2617
|
-
const u2 = p / 0.5;
|
|
2618
|
-
return [stride * (1 - 2 * u2), 138];
|
|
2619
|
-
}
|
|
2620
|
-
const u = (p - 0.5) / 0.5;
|
|
2621
|
-
return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
|
|
2622
|
-
}
|
|
2623
|
-
function gaitPose(ph, stride, lift, armSwing, facing) {
|
|
2624
|
-
const fl = footPos(ph, stride, lift);
|
|
2625
|
-
const fr = footPos(ph + 0.5, stride, lift);
|
|
2626
|
-
const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
|
|
2627
|
-
const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
|
|
2628
|
-
const swing = Math.cos(2 * Math.PI * ph);
|
|
2629
|
-
return {
|
|
2630
|
-
legUpperL: round(hipL),
|
|
2631
|
-
legLowerL: round(kneeL),
|
|
2632
|
-
legUpperR: round(hipR),
|
|
2633
|
-
legLowerR: round(kneeR),
|
|
2634
|
-
armUpperR: round(-10 - armSwing * swing),
|
|
2635
|
-
armLowerR: -16,
|
|
2636
|
-
armUpperL: round(10 + armSwing * swing),
|
|
2637
|
-
armLowerL: 16
|
|
2638
|
-
};
|
|
2639
|
-
}
|
|
2640
|
-
function gait(c, run) {
|
|
2641
|
-
const stride = (run ? 34 : 24) + (run ? 40 : 30) * c.e + c.jit(3);
|
|
2642
|
-
const lift = run ? 40 : 26;
|
|
2643
|
-
const armSwing = (run ? 26 : 16) + 20 * c.e;
|
|
2644
|
-
const halfDur = (run ? 0.26 : 0.42) + c.jit(0.02);
|
|
2645
|
-
const lean = run ? c.facing * -6 : 0;
|
|
2646
|
-
const steps = c.cycles * 2;
|
|
2647
|
-
const d = dur2(halfDur, c.sp);
|
|
2648
|
-
const intro = dur2(0.16, c.sp);
|
|
2649
|
-
const keys = [];
|
|
2650
|
-
for (let k = 0; k <= steps; k++) {
|
|
2651
|
-
const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
|
|
2652
|
-
keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
|
|
2653
|
-
}
|
|
2654
|
-
const total = intro + steps * d;
|
|
2655
|
-
const travel = c.travel ?? stride * 2;
|
|
2656
|
-
const children = [seq(...keys)];
|
|
2657
|
-
if (travel !== 0) {
|
|
2658
|
-
children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
|
|
2659
|
-
}
|
|
2660
|
-
return beat(run ? "run" : "walk", {}, [par(...children)]);
|
|
2661
|
-
}
|
|
2662
|
-
function jumpBeat(c) {
|
|
2663
|
-
const h = 120 + 150 * c.e;
|
|
2664
|
-
const [y0] = [c.at[1]];
|
|
2665
|
-
const CROUCH = { legUpperL: 18, legLowerL: 54, legUpperR: -18, legLowerR: 54, armUpperL: 28, armUpperR: -28 };
|
|
2666
|
-
const LAUNCH = { legUpperL: 0, legLowerL: 0, legUpperR: 0, legLowerR: 0, armUpperL: 150, armUpperR: -150 };
|
|
2667
|
-
const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
|
|
2668
|
-
const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
|
|
2669
|
-
const j = c.jit(0.03);
|
|
2670
|
-
return beat("jump", {}, [
|
|
2671
|
-
seq(
|
|
2672
|
-
par(poseTo(c.g, CROUCH, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" }), tween(c.g, { y: y0 + 26 }, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" })),
|
|
2673
|
-
par(poseTo(c.g, LAUNCH, { duration: dur2(0.22 + j, c.sp), ease: "easeOutCubic" }), tween(c.g, { y: y0 - h }, { duration: dur2(0.36, c.sp), ease: "easeOutCubic", label: "launch" })),
|
|
2674
|
-
poseTo(c.g, TUCK, { duration: dur2(0.22, c.sp) }),
|
|
2675
|
-
par(poseTo(c.g, CROUCH, { duration: dur2(0.28, c.sp), ease: "easeInQuad" }), tween(c.g, { y: y0 + 18 }, { duration: dur2(0.3, c.sp), ease: "easeInCubic", label: "land" })),
|
|
2676
|
-
par(poseTo(c.g, REST, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }), tween(c.g, { y: y0 }, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }))
|
|
2677
|
-
)
|
|
2678
|
-
]);
|
|
2679
|
-
}
|
|
2680
|
-
function danceBeat(c) {
|
|
2681
|
-
const y0 = c.at[1];
|
|
2682
|
-
const sway = 8 + 6 * c.e;
|
|
2683
|
-
const armUp = 130 + 30 * c.e;
|
|
2684
|
-
const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
|
|
2685
|
-
const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
|
|
2686
|
-
const d = dur2(0.34, c.sp);
|
|
2687
|
-
const keys = [];
|
|
2688
|
-
for (let k = 0; k < c.cycles * 2; k++) {
|
|
2689
|
-
const pose = k % 2 === 0 ? A : B;
|
|
2690
|
-
keys.push(par(
|
|
2691
|
-
poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
|
|
2692
|
-
tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
|
|
2693
|
-
));
|
|
2694
|
-
}
|
|
2695
|
-
keys.push(tween(c.g, { y: y0 }, { duration: d }));
|
|
2696
|
-
return beat("dance", {}, [seq(...keys)]);
|
|
2697
|
-
}
|
|
2698
|
-
function waveBeat(c) {
|
|
2699
|
-
const n3 = 3 + Math.round(c.rand() * 2);
|
|
2700
|
-
const amp = 16 + 10 * c.e;
|
|
2701
|
-
const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur2(0.4, c.sp), ease: "easeOutBack" })];
|
|
2702
|
-
for (let k = 0; k < n3; k++) {
|
|
2703
|
-
steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur2(0.22, c.sp), ease: "easeInOutQuad" }));
|
|
2704
|
-
}
|
|
2705
|
-
steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur2(0.4, c.sp), ease: "easeInOutCubic" }));
|
|
2706
|
-
return beat("wave", {}, [seq(...steps)]);
|
|
2707
|
-
}
|
|
2708
|
-
function cheerBeat(c) {
|
|
2709
|
-
const y0 = c.at[1];
|
|
2710
|
-
const UP = { armUpperL: 152, armLowerL: 8, armUpperR: -152, armLowerR: -8 };
|
|
2711
|
-
const d = dur2(0.3, c.sp);
|
|
2712
|
-
const keys = [poseTo(c.g, UP, { duration: dur2(0.35, c.sp), ease: "easeOutBack" })];
|
|
2713
|
-
for (let k = 0; k < c.cycles; k++) {
|
|
2714
|
-
keys.push(par(tween(c.g, { y: y0 - 28 }, { duration: d, ease: "easeOutQuad" }), poseTo(c.g, { armUpperL: 160, armUpperR: -160 }, { duration: d })));
|
|
2715
|
-
keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
|
|
2716
|
-
}
|
|
2717
|
-
return beat("cheer", {}, [seq(...keys)]);
|
|
2718
|
-
}
|
|
2719
|
-
function characterPreset(name, opts) {
|
|
2720
|
-
const c = ctx2(opts);
|
|
2721
|
-
let tl;
|
|
2722
|
-
switch (name) {
|
|
2723
|
-
case "walk":
|
|
2724
|
-
tl = gait(c, false);
|
|
2725
|
-
break;
|
|
2726
|
-
case "run":
|
|
2727
|
-
tl = gait(c, true);
|
|
2728
|
-
break;
|
|
2729
|
-
case "jump":
|
|
2730
|
-
tl = jumpBeat(c);
|
|
2731
|
-
break;
|
|
2732
|
-
case "dance":
|
|
2733
|
-
tl = danceBeat(c);
|
|
2734
|
-
break;
|
|
2735
|
-
case "wave":
|
|
2736
|
-
tl = waveBeat(c);
|
|
2737
|
-
break;
|
|
2738
|
-
case "cheer":
|
|
2739
|
-
tl = cheerBeat(c);
|
|
2740
|
-
break;
|
|
2741
|
-
default: {
|
|
2742
|
-
const _exhaustive = name;
|
|
2743
|
-
throw new Error(`unknown characterPreset "${_exhaustive}"`);
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
// ../core/src/figure.ts
|
|
2750
|
-
var K2 = 0.5523;
|
|
2751
|
-
var n2 = (v) => Number(v.toFixed(2));
|
|
2752
|
-
function limb(a, b, y0, y1) {
|
|
2753
|
-
const ka = n2(a * K2), kb = n2(b * K2);
|
|
2754
|
-
return `M ${-a} ${y0} C ${-a} ${n2(y0 - ka)} ${-ka} ${n2(y0 - a)} 0 ${n2(y0 - a)} C ${ka} ${n2(y0 - a)} ${a} ${n2(y0 - ka)} ${a} ${y0} L ${b} ${y1} C ${b} ${n2(y1 + kb)} ${kb} ${n2(y1 + b)} 0 ${n2(y1 + b)} C ${-kb} ${n2(y1 + b)} ${-b} ${n2(y1 + kb)} ${-b} ${y1} Z`;
|
|
2755
|
-
}
|
|
2756
|
-
function rrect(a, b, y0, y1, r) {
|
|
2757
|
-
return `M ${n2(-a + r)} ${y0} L ${n2(a - r)} ${y0} Q ${a} ${y0} ${a} ${n2(y0 + r)} L ${b} ${n2(y1 - r)} Q ${b} ${y1} ${n2(b - r)} ${y1} L ${n2(-b + r)} ${y1} Q ${-b} ${y1} ${-b} ${n2(y1 - r)} L ${-a} ${n2(y0 + r)} Q ${-a} ${y0} ${n2(-a + r)} ${y0} Z`;
|
|
2758
|
-
}
|
|
2759
|
-
function darken(hex, f) {
|
|
2760
|
-
const h = hex.replace("#", "");
|
|
2761
|
-
const v = parseInt(h.length === 3 ? [...h].map((c) => c + c).join("") : h, 16);
|
|
2762
|
-
const ch = (s) => Math.max(0, Math.min(255, Math.round((v >> s & 255) * (1 - f))));
|
|
2763
|
-
const hx = (x) => x.toString(16).padStart(2, "0");
|
|
2764
|
-
return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
|
|
2765
|
-
}
|
|
2766
|
-
var DEF = {
|
|
2767
|
-
clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
|
|
2768
|
-
cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
|
|
2769
|
-
};
|
|
2770
|
-
function resolvePal(style, p = {}) {
|
|
2771
|
-
const d = DEF[style];
|
|
2772
|
-
const accent = p.accent ?? d.accent;
|
|
2773
|
-
const top = p.top ?? (style === "clean" ? accent : d.top);
|
|
2774
|
-
const skin = p.skin ?? d.skin;
|
|
2775
|
-
const hair = p.hair ?? d.hair;
|
|
2776
|
-
const pants = p.pants ?? d.pants;
|
|
2777
|
-
const shoe = p.shoe ?? d.shoe;
|
|
2778
|
-
return {
|
|
2779
|
-
skin,
|
|
2780
|
-
skinSh: darken(skin, 0.12),
|
|
2781
|
-
hair,
|
|
2782
|
-
hairSh: darken(hair, 0.14),
|
|
2783
|
-
top,
|
|
2784
|
-
topSh: darken(top, 0.12),
|
|
2785
|
-
pants,
|
|
2786
|
-
pantsSh: darken(pants, 0.14),
|
|
2787
|
-
shoe,
|
|
2788
|
-
shoeSh: darken(shoe, 0.22),
|
|
2789
|
-
eye: "#2B313F",
|
|
2790
|
-
cheek: "#FF9E7E",
|
|
2791
|
-
white: "#FFFFFF",
|
|
2792
|
-
mouth: "#8A4233"
|
|
2793
|
-
};
|
|
2794
|
-
}
|
|
2795
|
-
var fp = (id, d, fill, stroke, sw = 0, opacity = 1) => path({ id, d, x: 0, y: 0, fill, opacity, ...stroke && sw > 0 ? { stroke, strokeWidth: sw } : {} });
|
|
2796
|
-
function cleanParts(p, face) {
|
|
2797
|
-
const HC = -42;
|
|
2798
|
-
return {
|
|
2799
|
-
upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
|
|
2800
|
-
forearm: (j) => [
|
|
2801
|
-
fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
|
|
2802
|
-
fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
|
|
2803
|
-
fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
|
|
2804
|
-
],
|
|
2805
|
-
thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
|
|
2806
|
-
shin: (j) => [
|
|
2807
|
-
fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
|
|
2808
|
-
fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
|
|
2809
|
-
fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
|
|
2810
|
-
],
|
|
2811
|
-
torso: (j) => [
|
|
2812
|
-
fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
|
|
2813
|
-
fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
|
|
2814
|
-
fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
|
|
2815
|
-
],
|
|
2816
|
-
head: (j) => [
|
|
2817
|
-
fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
|
|
2818
|
-
fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
|
|
2819
|
-
fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
|
|
2820
|
-
fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
|
|
2821
|
-
fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
|
|
2822
|
-
...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
|
|
2823
|
-
]
|
|
2824
|
-
};
|
|
2825
|
-
}
|
|
2826
|
-
var CUTE_HAIR = "M -64 -54 C -78 -96 -50 -126 0 -126 C 50 -126 78 -96 64 -54 C 60 -34 48 -28 41 -33 C 35 -54 23 -60 9 -60 C 3 -60 -3 -60 -9 -60 C -23 -60 -35 -54 -41 -33 C -48 -28 -60 -34 -64 -54 Z";
|
|
2827
|
-
function cuteParts(p, face) {
|
|
2828
|
-
const HC = -50;
|
|
2829
|
-
return {
|
|
2830
|
-
upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
|
|
2831
|
-
forearm: (j) => [
|
|
2832
|
-
fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
|
|
2833
|
-
fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
|
|
2834
|
-
fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
|
|
2835
|
-
],
|
|
2836
|
-
thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
|
|
2837
|
-
shin: (j) => [
|
|
2838
|
-
fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
|
|
2839
|
-
fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
|
|
2840
|
-
fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
|
|
2841
|
-
],
|
|
2842
|
-
torso: (j) => [
|
|
2843
|
-
fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
|
|
2844
|
-
fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
|
|
2845
|
-
fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
|
|
2846
|
-
],
|
|
2847
|
-
head: (j) => [
|
|
2848
|
-
fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
|
|
2849
|
-
fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
|
|
2850
|
-
fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2851
|
-
fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2852
|
-
fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
|
|
2853
|
-
...face ? [
|
|
2854
|
-
fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
|
|
2855
|
-
fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
|
|
2856
|
-
fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
|
|
2857
|
-
fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
|
|
2858
|
-
path({ id: `${j}-mouth`, d: "M -15 0 Q 0 15 15 0", x: 0, y: HC + 28, fill: "none", stroke: p.mouth, strokeWidth: 5 })
|
|
2859
|
-
] : []
|
|
2860
|
-
]
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
|
-
function buildSkeleton(id, S) {
|
|
2864
|
-
const arm = (side, x, r1, r2) => ({
|
|
2865
|
-
name: `armUpper${side}`,
|
|
2866
|
-
at: [x, -14],
|
|
2867
|
-
length: 60,
|
|
2868
|
-
width: 0,
|
|
2869
|
-
rotation: r1,
|
|
2870
|
-
shape: S.upperArm(`${id}-armUpper${side}`),
|
|
2871
|
-
children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
|
|
2872
|
-
});
|
|
2873
|
-
const leg = (side, x, r1, r2) => ({
|
|
2874
|
-
name: `legUpper${side}`,
|
|
2875
|
-
at: [x, 76],
|
|
2876
|
-
length: 76,
|
|
2877
|
-
width: 0,
|
|
2878
|
-
rotation: r1,
|
|
2879
|
-
shape: S.thigh(`${id}-legUpper${side}`),
|
|
2880
|
-
children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
|
|
2881
|
-
});
|
|
2882
|
-
return {
|
|
2883
|
-
name: "chest",
|
|
2884
|
-
at: [0, 0],
|
|
2885
|
-
shape: S.torso(`${id}-chest`),
|
|
2886
|
-
children: [
|
|
2887
|
-
{ name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
|
|
2888
|
-
arm("L", -40, 8, 6),
|
|
2889
|
-
arm("R", 40, -8, -6),
|
|
2890
|
-
leg("L", -20, 3, -2),
|
|
2891
|
-
leg("R", 20, -3, 2)
|
|
2892
|
-
]
|
|
2893
|
-
};
|
|
2894
|
-
}
|
|
2895
|
-
function figure(opts = {}) {
|
|
2896
|
-
const style = opts.style ?? "clean";
|
|
2897
|
-
const pal = resolvePal(style, opts.palette);
|
|
2898
|
-
const face = opts.face ?? true;
|
|
2899
|
-
const id = opts.id ?? "figure";
|
|
2900
|
-
const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
|
|
2901
|
-
const rigOpts = { id };
|
|
2902
|
-
if (opts.x !== void 0) rigOpts.x = opts.x;
|
|
2903
|
-
if (opts.y !== void 0) rigOpts.y = opts.y;
|
|
2904
|
-
if (opts.scale !== void 0) rigOpts.scale = opts.scale;
|
|
2905
|
-
if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
|
|
2906
|
-
return rig(buildSkeleton(id, parts), rigOpts);
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
2216
|
// ../core/src/textMetrics.ts
|
|
2910
2217
|
var INTER_ADVANCE = {
|
|
2911
2218
|
"400": {
|
|
@@ -3200,212 +2507,1320 @@ var INTER_ADVANCE = {
|
|
|
3200
2507
|
"~": 68.55
|
|
3201
2508
|
}
|
|
3202
2509
|
};
|
|
3203
|
-
var INTER_FALLBACK = {
|
|
3204
|
-
"400": 56.16,
|
|
3205
|
-
"700": 58.74,
|
|
3206
|
-
"800": 59.79
|
|
2510
|
+
var INTER_FALLBACK = {
|
|
2511
|
+
"400": 56.16,
|
|
2512
|
+
"700": 58.74,
|
|
2513
|
+
"800": 59.79
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
// ../core/src/textFx.ts
|
|
2517
|
+
var clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
2518
|
+
var fract = (v) => v - Math.floor(v);
|
|
2519
|
+
var rand = (i, salt) => fract(Math.sin(i * 127.1 + salt * 311.7) * 43758.5453);
|
|
2520
|
+
var dur = (base, sp) => base / sp;
|
|
2521
|
+
var SCRAMBLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@";
|
|
2522
|
+
var advance = (ch, weight, fontSize) => (INTER_ADVANCE[weight]?.[ch] ?? INTER_FALLBACK[weight]) * (fontSize / 100);
|
|
2523
|
+
function splitText(textStr, opts) {
|
|
2524
|
+
const { id, x, y, fontSize } = opts;
|
|
2525
|
+
const weight = opts.fontWeight ?? 800;
|
|
2526
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
2527
|
+
const ls = opts.letterSpacing ?? 0;
|
|
2528
|
+
const align = opts.align ?? "center";
|
|
2529
|
+
const unit = opts.unit ?? "glyph";
|
|
2530
|
+
const opacity = opts.opacity ?? 0;
|
|
2531
|
+
const chars = [...textStr];
|
|
2532
|
+
let total = 0;
|
|
2533
|
+
chars.forEach((ch, i) => {
|
|
2534
|
+
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
2535
|
+
});
|
|
2536
|
+
let cursor2 = align === "center" ? x - total / 2 : x;
|
|
2537
|
+
const glyphs = [];
|
|
2538
|
+
const nodes = [];
|
|
2539
|
+
const mk = (ch, cx, adv, lsProp) => {
|
|
2540
|
+
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
2541
|
+
glyphs.push(g);
|
|
2542
|
+
nodes.push(
|
|
2543
|
+
text({
|
|
2544
|
+
id: g.id,
|
|
2545
|
+
x: cx,
|
|
2546
|
+
y,
|
|
2547
|
+
content: ch,
|
|
2548
|
+
fontFamily: "Inter",
|
|
2549
|
+
fontSize,
|
|
2550
|
+
fontWeight: weight,
|
|
2551
|
+
fill,
|
|
2552
|
+
anchor: "center",
|
|
2553
|
+
opacity,
|
|
2554
|
+
...lsProp ? { letterSpacing: lsProp } : {}
|
|
2555
|
+
})
|
|
2556
|
+
);
|
|
2557
|
+
};
|
|
2558
|
+
if (unit === "word") {
|
|
2559
|
+
let i = 0;
|
|
2560
|
+
while (i < chars.length) {
|
|
2561
|
+
if (chars[i] === " ") {
|
|
2562
|
+
cursor2 += advance(" ", weight, fontSize) + ls;
|
|
2563
|
+
i++;
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
let word = "";
|
|
2567
|
+
let w = 0;
|
|
2568
|
+
const startCursor = cursor2;
|
|
2569
|
+
while (i < chars.length && chars[i] !== " ") {
|
|
2570
|
+
const a = advance(chars[i], weight, fontSize);
|
|
2571
|
+
word += chars[i];
|
|
2572
|
+
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
2573
|
+
i++;
|
|
2574
|
+
}
|
|
2575
|
+
mk(word, startCursor + w / 2, w, ls);
|
|
2576
|
+
cursor2 = startCursor + w + ls;
|
|
2577
|
+
}
|
|
2578
|
+
} else {
|
|
2579
|
+
chars.forEach((ch) => {
|
|
2580
|
+
const a = advance(ch, weight, fontSize);
|
|
2581
|
+
if (ch !== " ") mk(ch, cursor2 + a / 2, a);
|
|
2582
|
+
cursor2 += a + ls;
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
return { nodes, glyphs, ids: glyphs.map((g) => g.id), width: total, x, y, fontSize };
|
|
2586
|
+
}
|
|
2587
|
+
var ctx = (o) => ({
|
|
2588
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
2589
|
+
e: clamp01(o.energy ?? 0.5),
|
|
2590
|
+
seed: o.seed ?? 0,
|
|
2591
|
+
fs: 0,
|
|
2592
|
+
stag: o.stagger
|
|
2593
|
+
});
|
|
2594
|
+
var IN_STAGGER = { typewriter: 0.065, cascade: 0.04, rise: 0.03, bounce: 0.045, assemble: 0.05, decode: 0.05 };
|
|
2595
|
+
function glyphIn(name, g, c) {
|
|
2596
|
+
const set = (props) => tween(g.id, props, { duration: 1e-3 });
|
|
2597
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2598
|
+
switch (name) {
|
|
2599
|
+
case "typewriter":
|
|
2600
|
+
return tween(g.id, { opacity: 1 }, { duration: dur(0.04, c.sp), ease: "linear" });
|
|
2601
|
+
case "cascade":
|
|
2602
|
+
return seq(
|
|
2603
|
+
set({ y: g.y + 56, opacity: 0 }),
|
|
2604
|
+
par(
|
|
2605
|
+
tween(g.id, { opacity: 1 }, { duration: dur(0.22, c.sp), ease: "easeOutQuad" }),
|
|
2606
|
+
tween(g.id, { y: g.y }, { duration: dur(0.34, c.sp), ease: "easeOutCubic" })
|
|
2607
|
+
)
|
|
2608
|
+
);
|
|
2609
|
+
case "rise":
|
|
2610
|
+
return seq(
|
|
2611
|
+
set({ y: g.y + 36, opacity: 0 }),
|
|
2612
|
+
par(
|
|
2613
|
+
tween(g.id, { opacity: 1 }, { duration: dur(0.3, c.sp), ease: "easeOutQuad" }),
|
|
2614
|
+
tween(g.id, { y: g.y }, { duration: dur(0.4, c.sp), ease: "easeOutQuad" })
|
|
2615
|
+
)
|
|
2616
|
+
);
|
|
2617
|
+
case "bounce":
|
|
2618
|
+
return seq(
|
|
2619
|
+
set({ y: g.y - 80 * (0.6 + c.e), opacity: 0, scale: 0.7 }),
|
|
2620
|
+
par(
|
|
2621
|
+
tween(g.id, { opacity: 1 }, { duration: dur(0.2, c.sp), ease: "easeOutQuad" }),
|
|
2622
|
+
tween(g.id, { y: g.y, scale: 1 }, { duration: dur(0.7, c.sp), ease: "easeOutBounce" })
|
|
2623
|
+
)
|
|
2624
|
+
);
|
|
2625
|
+
case "assemble":
|
|
2626
|
+
return seq(
|
|
2627
|
+
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 }),
|
|
2628
|
+
par(
|
|
2629
|
+
tween(g.id, { opacity: 1 }, { duration: dur(0.4, c.sp), ease: "easeOutQuad" }),
|
|
2630
|
+
tween(g.id, { x: g.x, y: g.y, rotation: 0, scale: 1 }, { duration: dur(0.8, c.sp), ease: "easeOutExpo" })
|
|
2631
|
+
)
|
|
2632
|
+
);
|
|
2633
|
+
case "decode": {
|
|
2634
|
+
const steps = 4 + Math.floor(rs(7) * 3);
|
|
2635
|
+
const flicker = [set({ opacity: 1 })];
|
|
2636
|
+
for (let k = 0; k < steps; k++) {
|
|
2637
|
+
flicker.push(tween(g.id, { content: SCRAMBLE[Math.floor(rand(g.i, 20 + k + c.seed) * SCRAMBLE.length)] }, { duration: dur(0.05, c.sp), ease: "linear" }));
|
|
2638
|
+
}
|
|
2639
|
+
flicker.push(tween(g.id, { content: g.ch }, { duration: dur(0.05, c.sp), ease: "linear" }));
|
|
2640
|
+
return seq(...flicker);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
function textIn(name, block, opts = {}) {
|
|
2645
|
+
const c = { ...ctx(opts), fs: block.fontSize };
|
|
2646
|
+
const interval = (c.stag ?? IN_STAGGER[name]) / c.sp;
|
|
2647
|
+
return beat(opts.label ?? `text-in-${name}`, {}, [stagger(interval, ...block.glyphs.map((g) => glyphIn(name, g, c)))]);
|
|
2648
|
+
}
|
|
2649
|
+
function textLoop(name, block, opts = {}) {
|
|
2650
|
+
const win = { ...opts.from !== void 0 && { from: opts.from }, ...opts.until !== void 0 && { until: opts.until }, ...opts.ramp !== void 0 && { ramp: opts.ramp } };
|
|
2651
|
+
const f = opts.frequency ?? (name === "wave" ? 0.9 : name === "shimmer" ? 1.4 : 0.7);
|
|
2652
|
+
const ps = opts.phaseStep ?? 0.55;
|
|
2653
|
+
return block.glyphs.map((g, i) => {
|
|
2654
|
+
switch (name) {
|
|
2655
|
+
case "wave":
|
|
2656
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 9, frequency: f, phase: i * ps }, win);
|
|
2657
|
+
case "shimmer":
|
|
2658
|
+
return oscillate(g.id, "opacity", { amplitude: opts.amplitude ?? 0.25, frequency: f, phase: i * ps }, win);
|
|
2659
|
+
case "wobble":
|
|
2660
|
+
return oscillate(g.id, "rotation", { amplitude: opts.amplitude ?? 6, frequency: f, phase: i * ps }, win);
|
|
2661
|
+
case "float":
|
|
2662
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 5, frequency: f, phase: i * ps }, win);
|
|
2663
|
+
}
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
var OUT_STAGGER = { shatter: 0.02, fly: 0.012, dissolve: 0, fall: 0.02, collapse: 0.02 };
|
|
2667
|
+
function glyphOut(name, g, c, block, dir) {
|
|
2668
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
2669
|
+
switch (name) {
|
|
2670
|
+
case "shatter":
|
|
2671
|
+
return par(
|
|
2672
|
+
tween(g.id, { x: g.x + (rs(21) - 0.5) * 1100 * (0.6 + c.e), y: g.y + (rs(22) - 0.5) * 760 }, { duration: dur(0.7, c.sp), ease: "easeInCubic" }),
|
|
2673
|
+
tween(g.id, { rotation: (rs(23) - 0.5) * 300, opacity: 0 }, { duration: dur(0.7, c.sp), ease: "easeInQuad" })
|
|
2674
|
+
);
|
|
2675
|
+
case "fly":
|
|
2676
|
+
return par(
|
|
2677
|
+
tween(g.id, { x: g.x + dir[0] * 1200, y: g.y + dir[1] * 1200 }, { duration: dur(0.6, c.sp), ease: "easeInCubic" }),
|
|
2678
|
+
tween(g.id, { opacity: 0 }, { duration: dur(0.5, c.sp), ease: "easeInQuad" })
|
|
2679
|
+
);
|
|
2680
|
+
case "dissolve":
|
|
2681
|
+
return seq(wait(rs(31) * 0.5), par(
|
|
2682
|
+
tween(g.id, { opacity: 0 }, { duration: dur(0.4, c.sp), ease: "easeInQuad" }),
|
|
2683
|
+
tween(g.id, { scale: 1.4 }, { duration: dur(0.4, c.sp), ease: "easeOutQuad" })
|
|
2684
|
+
));
|
|
2685
|
+
case "fall":
|
|
2686
|
+
return par(
|
|
2687
|
+
tween(g.id, { y: g.y + 700 + rs(41) * 200 }, { duration: dur(0.8, c.sp), ease: "easeInQuad" }),
|
|
2688
|
+
tween(g.id, { rotation: (rs(42) - 0.5) * 120, opacity: 0 }, { duration: dur(0.8, c.sp), ease: "easeInQuad" })
|
|
2689
|
+
);
|
|
2690
|
+
case "collapse":
|
|
2691
|
+
return par(
|
|
2692
|
+
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur(0.5, c.sp), ease: "easeInBack" }),
|
|
2693
|
+
tween(g.id, { opacity: 0 }, { duration: dur(0.5, c.sp), ease: "easeInQuad" })
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
function textOut(name, block, opts = {}) {
|
|
2698
|
+
const c = { ...ctx(opts), fs: block.fontSize };
|
|
2699
|
+
const dir = opts.dir ?? [0, -1];
|
|
2700
|
+
const steps = block.glyphs.map((g) => glyphOut(name, g, c, block, dir));
|
|
2701
|
+
const interval = (c.stag ?? OUT_STAGGER[name]) / c.sp;
|
|
2702
|
+
const body = interval > 0 ? stagger(interval, ...steps) : par(...steps);
|
|
2703
|
+
return beat(opts.label ?? `text-out-${name}`, {}, [body]);
|
|
2704
|
+
}
|
|
2705
|
+
function textTypeCues(block, opts) {
|
|
2706
|
+
const interval = opts.interval ?? 0.065;
|
|
2707
|
+
const gain = opts.gain ?? 0.4;
|
|
2708
|
+
const off = opts.offset ?? 0;
|
|
2709
|
+
const KEYS = ["001", "004", "007", "010", "014"];
|
|
2710
|
+
return block.glyphs.map((g, i) => ({
|
|
2711
|
+
at: opts.at,
|
|
2712
|
+
offset: off + i * interval,
|
|
2713
|
+
file: `keypress-${KEYS[i % KEYS.length]}.wav`,
|
|
2714
|
+
gain: gain + 0.2 * rand(i, 31)
|
|
2715
|
+
}));
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// ../core/src/titles.ts
|
|
2719
|
+
function title(opts) {
|
|
2720
|
+
const id = opts.id ?? "title";
|
|
2721
|
+
const block = splitText(opts.text, {
|
|
2722
|
+
id,
|
|
2723
|
+
x: opts.x ?? 960,
|
|
2724
|
+
y: opts.y ?? 540,
|
|
2725
|
+
fontSize: opts.fontSize ?? 96,
|
|
2726
|
+
...opts.fontWeight !== void 0 && { fontWeight: opts.fontWeight },
|
|
2727
|
+
...opts.fill !== void 0 && { fill: opts.fill },
|
|
2728
|
+
...opts.letterSpacing !== void 0 && { letterSpacing: opts.letterSpacing }
|
|
2729
|
+
});
|
|
2730
|
+
const fx = {
|
|
2731
|
+
...opts.speed !== void 0 && { speed: opts.speed },
|
|
2732
|
+
...opts.seed !== void 0 && { seed: opts.seed }
|
|
2733
|
+
};
|
|
2734
|
+
const entrance = textIn(opts.entrance ?? "cascade", block, { ...fx, label: `${id}-in` });
|
|
2735
|
+
if (!opts.exit) return { nodes: block.nodes, timeline: entrance, block };
|
|
2736
|
+
const timeline = seq(
|
|
2737
|
+
entrance,
|
|
2738
|
+
wait(Math.max(0, opts.hold ?? 2)),
|
|
2739
|
+
textOut(opts.exit, block, { ...fx, label: `${id}-out` })
|
|
2740
|
+
);
|
|
2741
|
+
return { nodes: block.nodes, timeline, block };
|
|
2742
|
+
}
|
|
2743
|
+
function lowerThird(opts) {
|
|
2744
|
+
const id = opts.id ?? "lt";
|
|
2745
|
+
const x = opts.x ?? 120;
|
|
2746
|
+
const y = opts.y ?? 860;
|
|
2747
|
+
const fs = opts.fontSize ?? 48;
|
|
2748
|
+
const roleFs = Math.round(fs * 0.58);
|
|
2749
|
+
const barH = opts.role ? fs + roleFs + 24 : fs + 16;
|
|
2750
|
+
const restX = 28;
|
|
2751
|
+
const startX = restX - 8;
|
|
2752
|
+
const children = [
|
|
2753
|
+
rect({ id: `${id}-bar`, x: 0, y: 0, width: 6, height: barH, anchor: "top-left", fill: opts.accent ?? "#FF4D00", scaleY: 0 }),
|
|
2754
|
+
text({ id: `${id}-name`, x: startX, y: 6, anchor: "top-left", content: opts.name, fontFamily: "Inter", fontSize: fs, fontWeight: 700, fill: opts.fill ?? "#FFFFFF", opacity: 0 })
|
|
2755
|
+
];
|
|
2756
|
+
if (opts.role !== void 0) {
|
|
2757
|
+
children.push(
|
|
2758
|
+
text({ id: `${id}-role`, x: startX, y: 6 + fs + 8, anchor: "top-left", content: opts.role, fontFamily: "Inter", fontSize: roleFs, fill: opts.subFill ?? "#C9C9C9", opacity: 0 })
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
const nodes = [group({ id, x, y }, children)];
|
|
2762
|
+
const entrance = beat(`${id}-in`, {}, [
|
|
2763
|
+
par(
|
|
2764
|
+
tween(`${id}-bar`, { scaleY: 1 }, { duration: 0.5, ease: "easeOutCubic" }),
|
|
2765
|
+
seq(wait(0.08), tween(`${id}-name`, { opacity: 1, x: restX }, { duration: 0.45, ease: "easeOutCubic" })),
|
|
2766
|
+
...opts.role !== void 0 ? [seq(wait(0.16), tween(`${id}-role`, { opacity: 1, x: restX }, { duration: 0.45, ease: "easeOutCubic" }))] : []
|
|
2767
|
+
)
|
|
2768
|
+
]);
|
|
2769
|
+
const exit = beat(`${id}-out`, {}, [
|
|
2770
|
+
par(
|
|
2771
|
+
tween(`${id}-bar`, { scaleY: 0 }, { duration: 0.35, ease: "easeInCubic" }),
|
|
2772
|
+
tween(`${id}-name`, { opacity: 0 }, { duration: 0.3, ease: "easeInCubic" }),
|
|
2773
|
+
...opts.role !== void 0 ? [tween(`${id}-role`, { opacity: 0 }, { duration: 0.3, ease: "easeInCubic" })] : []
|
|
2774
|
+
)
|
|
2775
|
+
]);
|
|
2776
|
+
return { nodes, timeline: seq(entrance, wait(Math.max(0, opts.hold ?? 3)), exit) };
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// ../core/src/presets.ts
|
|
2780
|
+
var PRESET_NAMES = [
|
|
2781
|
+
"draw-bloom",
|
|
2782
|
+
"punch-in",
|
|
2783
|
+
"rise-settle",
|
|
2784
|
+
"slide-bank",
|
|
2785
|
+
"reveal-orbit",
|
|
2786
|
+
"spin-forge"
|
|
2787
|
+
];
|
|
2788
|
+
function makeRng2(seed) {
|
|
2789
|
+
let a = seed >>> 0 || 2654435769;
|
|
2790
|
+
return () => {
|
|
2791
|
+
a = a + 1831565813 | 0;
|
|
2792
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2793
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2794
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
var clamp012 = (x) => Math.max(0, Math.min(1, x));
|
|
2798
|
+
var SET = 1 / 120;
|
|
2799
|
+
function ctx2(o) {
|
|
2800
|
+
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
2801
|
+
return {
|
|
2802
|
+
e: clamp012(o.energy ?? 0.5),
|
|
2803
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
2804
|
+
it: clamp012(o.intensity ?? 0.5),
|
|
2805
|
+
from: o.from,
|
|
2806
|
+
rand: rand2,
|
|
2807
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
2808
|
+
g: o.target.group,
|
|
2809
|
+
cx: o.target.center[0],
|
|
2810
|
+
cy: o.target.center[1],
|
|
2811
|
+
s: o.target.baseScale,
|
|
2812
|
+
fills: o.target.fills,
|
|
2813
|
+
inks: o.target.inks
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
var dur2 = (base, sp) => base / sp;
|
|
2817
|
+
function settleEase(e) {
|
|
2818
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
2819
|
+
}
|
|
2820
|
+
function fromVec(from, dist) {
|
|
2821
|
+
switch (from) {
|
|
2822
|
+
case "left":
|
|
2823
|
+
return [-dist, 0];
|
|
2824
|
+
case "right":
|
|
2825
|
+
return [dist, 0];
|
|
2826
|
+
case "top":
|
|
2827
|
+
return [0, -dist];
|
|
2828
|
+
default:
|
|
2829
|
+
return [0, dist];
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
2833
|
+
return stagger(
|
|
2834
|
+
gap / c.sp,
|
|
2835
|
+
...c.fills.map(
|
|
2836
|
+
(id, i) => tween(id, { opacity: 1 }, { duration: dur2(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
2837
|
+
)
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
function drawInks(c) {
|
|
2841
|
+
return stagger(
|
|
2842
|
+
0.15 / c.sp,
|
|
2843
|
+
...c.inks.map(
|
|
2844
|
+
(id, i) => tween(id, { progress: 1 }, { duration: dur2(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
2845
|
+
)
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
function motionPreset(name, opts) {
|
|
2849
|
+
const c = ctx2(opts);
|
|
2850
|
+
switch (name) {
|
|
2851
|
+
case "draw-bloom":
|
|
2852
|
+
return beat("draw-bloom", {}, [
|
|
2853
|
+
drawInks(c),
|
|
2854
|
+
fadeFills(c, 0.45),
|
|
2855
|
+
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur2(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
2856
|
+
]);
|
|
2857
|
+
case "punch-in": {
|
|
2858
|
+
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
2859
|
+
return beat("punch-in", {}, [
|
|
2860
|
+
par(
|
|
2861
|
+
fadeFills(c, 0.25),
|
|
2862
|
+
seq(
|
|
2863
|
+
tween(c.g, { scale: peak }, { duration: dur2(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
2864
|
+
tween(c.g, { scale: c.s }, { duration: dur2(0.5, c.sp), ease: settleEase(c.e) })
|
|
2865
|
+
)
|
|
2866
|
+
)
|
|
2867
|
+
]);
|
|
2868
|
+
}
|
|
2869
|
+
case "rise-settle": {
|
|
2870
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2871
|
+
const dist = (220 + 260 * c.it) * es;
|
|
2872
|
+
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
2873
|
+
const jx = c.jit(110);
|
|
2874
|
+
return beat("rise-settle", {}, [
|
|
2875
|
+
par(
|
|
2876
|
+
motionPath(
|
|
2877
|
+
c.g,
|
|
2878
|
+
[
|
|
2879
|
+
[c.cx + dx + jx, c.cy + dy],
|
|
2880
|
+
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
2881
|
+
[c.cx, c.cy]
|
|
2882
|
+
],
|
|
2883
|
+
{ duration: dur2(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
2884
|
+
),
|
|
2885
|
+
fadeFills(c, 0.4)
|
|
2886
|
+
)
|
|
2887
|
+
]);
|
|
2888
|
+
}
|
|
2889
|
+
case "slide-bank": {
|
|
2890
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2891
|
+
const dist = (420 + 240 * c.it) * es;
|
|
2892
|
+
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
2893
|
+
const arc = c.jit(140);
|
|
2894
|
+
const midx = c.jit(120);
|
|
2895
|
+
const move = dur2(1.2, c.sp);
|
|
2896
|
+
return beat("slide-bank", {}, [
|
|
2897
|
+
par(
|
|
2898
|
+
motionPath(
|
|
2899
|
+
c.g,
|
|
2900
|
+
[
|
|
2901
|
+
[c.cx + dx, c.cy + dy],
|
|
2902
|
+
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
2903
|
+
[c.cx, c.cy]
|
|
2904
|
+
],
|
|
2905
|
+
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
2906
|
+
),
|
|
2907
|
+
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
2908
|
+
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur2(0.5, c.sp), ease: "easeOutCubic" })),
|
|
2909
|
+
fadeFills(c, 0.4)
|
|
2910
|
+
)
|
|
2911
|
+
]);
|
|
2912
|
+
}
|
|
2913
|
+
case "reveal-orbit": {
|
|
2914
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2915
|
+
const orbit = (180 + 160 * c.it) * es;
|
|
2916
|
+
const jx = c.jit(0.4);
|
|
2917
|
+
const jy = c.jit(0.4);
|
|
2918
|
+
return beat("reveal-orbit", {}, [
|
|
2919
|
+
drawInks(c),
|
|
2920
|
+
fadeFills(c, 0.45),
|
|
2921
|
+
par(
|
|
2922
|
+
motionPath(
|
|
2923
|
+
c.g,
|
|
2924
|
+
[
|
|
2925
|
+
[c.cx, c.cy],
|
|
2926
|
+
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
2927
|
+
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
2928
|
+
[c.cx, c.cy]
|
|
2929
|
+
],
|
|
2930
|
+
{ duration: dur2(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
2931
|
+
),
|
|
2932
|
+
seq(
|
|
2933
|
+
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur2(0.85, c.sp), ease: "easeOutBack" }),
|
|
2934
|
+
tween(c.g, { scale: c.s }, { duration: dur2(0.85, c.sp), ease: "easeInOutQuad" })
|
|
2935
|
+
)
|
|
2936
|
+
)
|
|
2937
|
+
]);
|
|
2938
|
+
}
|
|
2939
|
+
case "spin-forge": {
|
|
2940
|
+
const turns = 1 + Math.round(c.it);
|
|
2941
|
+
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
2942
|
+
const startRot = dir * 360 * turns;
|
|
2943
|
+
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
2944
|
+
return beat("spin-forge", {}, [
|
|
2945
|
+
par(
|
|
2946
|
+
seq(
|
|
2947
|
+
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
2948
|
+
// establish (invisible)
|
|
2949
|
+
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur2(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
2950
|
+
tween(c.g, { scale: c.s }, { duration: dur2(0.3, c.sp), ease: "easeInOutQuad" })
|
|
2951
|
+
),
|
|
2952
|
+
seq(wait(SET), fadeFills(c, 0.3))
|
|
2953
|
+
)
|
|
2954
|
+
]);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
// ../core/src/devicePreset.ts
|
|
2960
|
+
var DEVICE_PRESET_NAMES = ["phone", "tablet", "laptop", "browser", "watch", "monitor", "tv", "foldable", "terminal", "car"];
|
|
2961
|
+
var DARK = {
|
|
2962
|
+
body: "#15161C",
|
|
2963
|
+
bodyStroke: "#2A2D38",
|
|
2964
|
+
bodyGrad: ["#272A35", "#0E0F15"],
|
|
2965
|
+
screen: "#0E0F15",
|
|
2966
|
+
detail: "#3A3D48",
|
|
2967
|
+
chrome: "#1B1D24",
|
|
2968
|
+
chromeGrad: ["#23262F", "#141620"],
|
|
2969
|
+
chromeText: "#9AA0AD",
|
|
2970
|
+
ambient: "#9FB4FF"
|
|
2971
|
+
};
|
|
2972
|
+
var LIGHT = {
|
|
2973
|
+
body: "#E7E9EE",
|
|
2974
|
+
bodyStroke: "#C3C7D1",
|
|
2975
|
+
bodyGrad: ["#FAFBFD", "#D4D8E1"],
|
|
2976
|
+
screen: "#FFFFFF",
|
|
2977
|
+
detail: "#AEB3C0",
|
|
2978
|
+
chrome: "#F2F3F6",
|
|
2979
|
+
chromeGrad: ["#FCFCFE", "#E2E5EC"],
|
|
2980
|
+
chromeText: "#5B606C",
|
|
2981
|
+
ambient: "#7C9BFF"
|
|
2982
|
+
};
|
|
2983
|
+
var NEON_ACCENTS = ["#00E5FF", "#FF3DCB", "#7C5CFF", "#3DFF88", "#FF7A00"];
|
|
2984
|
+
var SCREENS = {
|
|
2985
|
+
phone: { width: 352, height: 736, radius: 38 },
|
|
2986
|
+
tablet: { width: 544, height: 764, radius: 18 },
|
|
2987
|
+
laptop: { width: 840, height: 520, radius: 8, cy: -150 },
|
|
2988
|
+
browser: { width: 984, height: 568, radius: 6, cy: 24 },
|
|
2989
|
+
watch: { width: 184, height: 224, radius: 44 },
|
|
2990
|
+
monitor: { width: 1056, height: 600, radius: 6 },
|
|
2991
|
+
tv: { width: 1280, height: 720, radius: 8, cy: -24 },
|
|
2992
|
+
foldable: { width: 760, height: 560, radius: 20 },
|
|
2993
|
+
terminal: { width: 900, height: 560, radius: 6, cy: 18 },
|
|
2994
|
+
car: { width: 1e3, height: 520, radius: 24 }
|
|
2995
|
+
};
|
|
2996
|
+
var BOUNDS = {
|
|
2997
|
+
phone: { width: 392, height: 812 },
|
|
2998
|
+
tablet: { width: 600, height: 820 },
|
|
2999
|
+
laptop: { width: 1100, height: 650 },
|
|
3000
|
+
browser: { width: 1e3, height: 660 },
|
|
3001
|
+
watch: { width: 220, height: 300 },
|
|
3002
|
+
monitor: { width: 1120, height: 860 },
|
|
3003
|
+
tv: { width: 1340, height: 920 },
|
|
3004
|
+
foldable: { width: 800, height: 600 },
|
|
3005
|
+
terminal: { width: 916, height: 636 },
|
|
3006
|
+
car: { width: 1060, height: 600 }
|
|
3007
|
+
};
|
|
3008
|
+
var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
|
|
3009
|
+
function screenDims(name, o) {
|
|
3010
|
+
const d = SCREENS[name];
|
|
3011
|
+
const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
|
|
3012
|
+
return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
|
|
3013
|
+
}
|
|
3014
|
+
function deviceScreen(name, opts = {}) {
|
|
3015
|
+
const d = screenDims(name, opts);
|
|
3016
|
+
return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
|
|
3017
|
+
}
|
|
3018
|
+
function deviceScreenCenter(name, opts = {}) {
|
|
3019
|
+
const d = screenDims(name, opts);
|
|
3020
|
+
return { x: d.cx, y: d.cy };
|
|
3021
|
+
}
|
|
3022
|
+
function deviceBounds(name, opts = {}) {
|
|
3023
|
+
const b = BOUNDS[name];
|
|
3024
|
+
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
3025
|
+
}
|
|
3026
|
+
function deviceScreenPoint(name, opts, local) {
|
|
3027
|
+
const c = deviceScreenCenter(name, opts);
|
|
3028
|
+
const s = opts.scale ?? 1;
|
|
3029
|
+
return [(opts.x ?? 0) + s * (c.x + local[0]), (opts.y ?? 0) + s * (c.y + local[1])];
|
|
3030
|
+
}
|
|
3031
|
+
function makeRng3(seed) {
|
|
3032
|
+
let a = seed >>> 0 || 2654435769;
|
|
3033
|
+
return () => {
|
|
3034
|
+
a = a + 1831565813 | 0;
|
|
3035
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
3036
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
3037
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
function hashId(id) {
|
|
3041
|
+
let h = 2166136261;
|
|
3042
|
+
for (let i = 0; i < id.length; i++) {
|
|
3043
|
+
h ^= id.charCodeAt(i);
|
|
3044
|
+
h = Math.imul(h, 16777619);
|
|
3045
|
+
}
|
|
3046
|
+
return h >>> 0;
|
|
3047
|
+
}
|
|
3048
|
+
function makeCtx(opts) {
|
|
3049
|
+
const id = opts.id ?? "device";
|
|
3050
|
+
const palette = opts.color === "light" ? LIGHT : DARK;
|
|
3051
|
+
const material = opts.material ?? "premium";
|
|
3052
|
+
const style = opts.style ?? "glass";
|
|
3053
|
+
const seed = (opts.seed ?? hashId(id)) >>> 0;
|
|
3054
|
+
const rng = makeRng3(seed + 1);
|
|
3055
|
+
const accent = NEON_ACCENTS[Math.floor(rng() * NEON_ACCENTS.length)] ?? NEON_ACCENTS[0];
|
|
3056
|
+
return {
|
|
3057
|
+
id,
|
|
3058
|
+
palette,
|
|
3059
|
+
material,
|
|
3060
|
+
style,
|
|
3061
|
+
accent,
|
|
3062
|
+
s: opts.scale ?? 1,
|
|
3063
|
+
rng,
|
|
3064
|
+
jit: (amp) => (rng() - 0.5) * 2 * amp,
|
|
3065
|
+
o: opts
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
var isPremium = (c) => c.material === "premium";
|
|
3069
|
+
var isNeon = (c) => c.material === "premium" && c.style === "neon";
|
|
3070
|
+
function premiumFill(c, solid, grad) {
|
|
3071
|
+
if (!isPremium(c) || c.style === "neon") return solid;
|
|
3072
|
+
return linearGradient(grad, { angle: 125 + c.jit(12) });
|
|
3073
|
+
}
|
|
3074
|
+
var bodyStroke = (c, fallback) => isNeon(c) ? c.accent : fallback;
|
|
3075
|
+
function bodyFx(c) {
|
|
3076
|
+
if (!isPremium(c)) return {};
|
|
3077
|
+
return isNeon(c) ? glow(c.accent, (24 + c.jit(5)) * c.s) : dropShadow("rgba(0,0,0,0.5)", 46 * c.s, 0, 24 * c.s);
|
|
3078
|
+
}
|
|
3079
|
+
function slab(c, suffix, x, y, w, h, r, grad, solid, strokeC) {
|
|
3080
|
+
return rect({
|
|
3081
|
+
id: `${c.id}-${suffix}`,
|
|
3082
|
+
x,
|
|
3083
|
+
y,
|
|
3084
|
+
anchor: "center",
|
|
3085
|
+
width: w,
|
|
3086
|
+
height: h,
|
|
3087
|
+
radius: r,
|
|
3088
|
+
fill: premiumFill(c, solid, grad),
|
|
3089
|
+
stroke: bodyStroke(c, strokeC),
|
|
3090
|
+
strokeWidth: isNeon(c) ? 2.5 : 2,
|
|
3091
|
+
...bodyFx(c)
|
|
3092
|
+
});
|
|
3093
|
+
}
|
|
3094
|
+
function screenStack(c, cx, cy, dims, content) {
|
|
3095
|
+
const children = [
|
|
3096
|
+
rect({ id: `${c.id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: c.o.screen ?? c.palette.screen })
|
|
3097
|
+
];
|
|
3098
|
+
if (isPremium(c)) {
|
|
3099
|
+
const tint = isNeon(c) ? c.accent : c.palette.ambient;
|
|
3100
|
+
children.push(
|
|
3101
|
+
rect({
|
|
3102
|
+
id: `${c.id}-ambient`,
|
|
3103
|
+
x: 0,
|
|
3104
|
+
y: 0,
|
|
3105
|
+
anchor: "center",
|
|
3106
|
+
width: dims.width,
|
|
3107
|
+
height: dims.height,
|
|
3108
|
+
fill: radialGradient([`${tint}33`, `${tint}00`], { cy: 0.32, r: 0.72 }),
|
|
3109
|
+
opacity: isNeon(c) ? 0.55 : 0.32,
|
|
3110
|
+
blend: "screen"
|
|
3111
|
+
})
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
children.push(group({ id: `${c.id}-content`, x: 0, y: 0 }, content));
|
|
3115
|
+
if (isPremium(c) && c.style === "glass") {
|
|
3116
|
+
const ang = 22 + c.jit(9);
|
|
3117
|
+
children.push(
|
|
3118
|
+
rect({
|
|
3119
|
+
id: `${c.id}-glare`,
|
|
3120
|
+
x: -dims.width * 0.18,
|
|
3121
|
+
y: 0,
|
|
3122
|
+
anchor: "center",
|
|
3123
|
+
width: dims.width * 0.46,
|
|
3124
|
+
height: dims.height * 1.8,
|
|
3125
|
+
fill: "#FFFFFF",
|
|
3126
|
+
opacity: 0.05 + Math.abs(c.jit(0.025)),
|
|
3127
|
+
rotation: ang,
|
|
3128
|
+
blend: "screen"
|
|
3129
|
+
})
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
return group({ id: `${c.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 } }, children);
|
|
3133
|
+
}
|
|
3134
|
+
function phoneNotch(c, style, sh) {
|
|
3135
|
+
const yTop = -sh / 2 + 16;
|
|
3136
|
+
switch (style) {
|
|
3137
|
+
case "none":
|
|
3138
|
+
return null;
|
|
3139
|
+
case "punch":
|
|
3140
|
+
return rect({ id: `${c.id}-notch`, x: 0, y: yTop + 2, anchor: "center", width: 18, height: 18, fill: "#000000", radius: 9 });
|
|
3141
|
+
case "notch":
|
|
3142
|
+
return rect({ id: `${c.id}-notch`, x: 0, y: -sh / 2 + 11, anchor: "center", width: 150, height: 26, fill: "#000000", radius: 13 });
|
|
3143
|
+
case "island":
|
|
3144
|
+
default:
|
|
3145
|
+
return rect({ id: `${c.id}-notch`, x: 0, y: yTop, anchor: "center", width: 96, height: 30, fill: "#000000", radius: 15 });
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
var urlText = (url) => {
|
|
3149
|
+
const u = url ?? "reframe.video";
|
|
3150
|
+
return u.length > 70 ? `${u.slice(0, 67)}\u2026` : u;
|
|
3151
|
+
};
|
|
3152
|
+
var CHASSIS = {
|
|
3153
|
+
phone: (c, dims, content) => phoneOrTablet("phone", c, dims, content),
|
|
3154
|
+
tablet: (c, dims, content) => phoneOrTablet("tablet", c, dims, content),
|
|
3155
|
+
laptop: (c, dims, content) => {
|
|
3156
|
+
const p = c.palette;
|
|
3157
|
+
const sw = dims.width, sh = dims.height;
|
|
3158
|
+
const lidTop = dims.cy - (sh + 40) / 2;
|
|
3159
|
+
const keyRows = [0, 1, 2, 3].map(
|
|
3160
|
+
(r) => rect({ id: `${c.id}-keys${r}`, x: 0, y: 150 + r * 11, anchor: "center", width: 640 + r * 50, height: 6, fill: p.chrome, radius: 3 })
|
|
3161
|
+
);
|
|
3162
|
+
return [
|
|
3163
|
+
path({ id: `${c.id}-base`, x: 0, y: 0, d: "M -450 140 L 450 140 L 520 196 L -520 196 Z", fill: premiumFill(c, p.body, p.bodyGrad), stroke: bodyStroke(c, p.bodyStroke), strokeWidth: isNeon(c) ? 2.5 : 2, ...bodyFx(c) }),
|
|
3164
|
+
rect({ id: `${c.id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
3165
|
+
rect({ id: `${c.id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
3166
|
+
...keyRows,
|
|
3167
|
+
rect({ id: `${c.id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
|
|
3168
|
+
rect({ id: `${c.id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
|
|
3169
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3170
|
+
ellipse({ id: `${c.id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
3171
|
+
rect({ id: `${c.id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: bodyStroke(c, p.bodyStroke), strokeWidth: 2, radius: 18 })
|
|
3172
|
+
];
|
|
3173
|
+
},
|
|
3174
|
+
browser: (c, dims, content) => {
|
|
3175
|
+
const p = c.palette;
|
|
3176
|
+
const sw = dims.width, sh = dims.height;
|
|
3177
|
+
const winW = sw + 16;
|
|
3178
|
+
const winH = sh + 92;
|
|
3179
|
+
const barY = -winH / 2 + 24;
|
|
3180
|
+
return [
|
|
3181
|
+
rect({ id: `${c.id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: premiumFill(c, p.chrome, p.chromeGrad), stroke: bodyStroke(c, p.bodyStroke), strokeWidth: 1.5, radius: 14, ...bodyFx(c) }),
|
|
3182
|
+
ellipse({ id: `${c.id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
|
|
3183
|
+
ellipse({ id: `${c.id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
|
|
3184
|
+
ellipse({ id: `${c.id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
|
|
3185
|
+
// an active tab tucked under the lights
|
|
3186
|
+
rect({ id: `${c.id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: c.o.screen ?? p.screen, radius: 8 }),
|
|
3187
|
+
text({ id: `${c.id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
3188
|
+
rect({ id: `${c.id}-urlpill`, x: 96, y: barY, anchor: "center", width: 700, height: 26, fill: c.o.screen ?? p.screen, stroke: p.bodyStroke, strokeWidth: 1, radius: 13 }),
|
|
3189
|
+
rect({ id: `${c.id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
|
|
3190
|
+
text({ id: `${c.id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(c.o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
|
|
3191
|
+
screenStack(c, dims.cx, dims.cy, dims, content)
|
|
3192
|
+
];
|
|
3193
|
+
},
|
|
3194
|
+
watch: (c, dims, content) => {
|
|
3195
|
+
const p = c.palette;
|
|
3196
|
+
const sw = dims.width, sh = dims.height;
|
|
3197
|
+
const bw = sw + 36;
|
|
3198
|
+
const bh = sh + 36;
|
|
3199
|
+
return [
|
|
3200
|
+
// straps (drawn behind the body) flaring out top & bottom
|
|
3201
|
+
path({ id: `${c.id}-bandtop`, x: 0, y: -bh / 2 + 4, d: "M -78 0 L 78 0 L 64 -86 L -64 -86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
3202
|
+
path({ id: `${c.id}-bandbot`, x: 0, y: bh / 2 - 4, d: "M -78 0 L 78 0 L 64 86 L -64 86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
3203
|
+
slab(c, "body", 0, 0, bw, bh, 60, p.bodyGrad, p.body, p.bodyStroke),
|
|
3204
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3205
|
+
rect({ id: `${c.id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
|
|
3206
|
+
rect({ id: `${c.id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
|
|
3207
|
+
];
|
|
3208
|
+
},
|
|
3209
|
+
monitor: (c, dims, content) => {
|
|
3210
|
+
const p = c.palette;
|
|
3211
|
+
const sw = dims.width, sh = dims.height;
|
|
3212
|
+
const panelW = sw + 44;
|
|
3213
|
+
const panelH = sh + 60;
|
|
3214
|
+
return [
|
|
3215
|
+
slab(c, "panel", 0, 0, panelW, panelH, 16, p.bodyGrad, p.body, p.bodyStroke),
|
|
3216
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3217
|
+
ellipse({ id: `${c.id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
|
|
3218
|
+
rect({ id: `${c.id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
|
|
3219
|
+
path({ id: `${c.id}-stand`, x: 0, y: panelH / 2 + 60, d: "M -160 50 L 160 50 L 220 80 L -220 80 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
3220
|
+
];
|
|
3221
|
+
},
|
|
3222
|
+
tv: (c, dims, content) => {
|
|
3223
|
+
const p = c.palette;
|
|
3224
|
+
const sw = dims.width, sh = dims.height;
|
|
3225
|
+
const panelW = sw + 44;
|
|
3226
|
+
const panelH = sh + 48;
|
|
3227
|
+
const panelBottom = dims.cy + panelH / 2;
|
|
3228
|
+
return [
|
|
3229
|
+
slab(c, "panel", 0, dims.cy, panelW, panelH, 12, p.bodyGrad, p.body, p.bodyStroke),
|
|
3230
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3231
|
+
ellipse({ id: `${c.id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
3232
|
+
rect({ id: `${c.id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
|
|
3233
|
+
path({ id: `${c.id}-stand`, x: 0, y: panelBottom + 96, d: "M -210 0 L 210 0 L 270 34 L -270 34 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
3234
|
+
];
|
|
3235
|
+
},
|
|
3236
|
+
foldable: (c, dims, content) => {
|
|
3237
|
+
const p = c.palette;
|
|
3238
|
+
const sw = dims.width, sh = dims.height;
|
|
3239
|
+
const bodyW = sw + 40;
|
|
3240
|
+
const bodyH = sh + 40;
|
|
3241
|
+
return [
|
|
3242
|
+
rect({ id: `${c.id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
3243
|
+
rect({ id: `${c.id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
3244
|
+
slab(c, "body", 0, 0, bodyW, bodyH, 28, p.bodyGrad, p.body, p.bodyStroke),
|
|
3245
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3246
|
+
rect({ id: `${c.id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
|
|
3247
|
+
ellipse({ id: `${c.id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
|
|
3248
|
+
ellipse({ id: `${c.id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
|
|
3249
|
+
];
|
|
3250
|
+
},
|
|
3251
|
+
terminal: (c, dims, content) => {
|
|
3252
|
+
const p = c.palette;
|
|
3253
|
+
const sw = dims.width, sh = dims.height;
|
|
3254
|
+
const winW = sw + 16;
|
|
3255
|
+
const winH = sh + 76;
|
|
3256
|
+
return [
|
|
3257
|
+
rect({ id: `${c.id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: premiumFill(c, p.chrome, p.chromeGrad), stroke: bodyStroke(c, p.bodyStroke), strokeWidth: 1.5, radius: 12, ...bodyFx(c) }),
|
|
3258
|
+
ellipse({ id: `${c.id}-dot1`, x: -winW / 2 + 28, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FF5F57" }),
|
|
3259
|
+
ellipse({ id: `${c.id}-dot2`, x: -winW / 2 + 50, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FEBC2E" }),
|
|
3260
|
+
ellipse({ id: `${c.id}-dot3`, x: -winW / 2 + 72, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#28C840" }),
|
|
3261
|
+
rect({ id: `${c.id}-tab`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", width: 130, height: 24, fill: c.o.screen ?? p.screen, radius: 6 }),
|
|
3262
|
+
text({ id: `${c.id}-title`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", content: urlText(c.o.url ?? "zsh"), fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
3263
|
+
screenStack(c, dims.cx, dims.cy, dims, content)
|
|
3264
|
+
];
|
|
3265
|
+
},
|
|
3266
|
+
car: (c, dims, content) => {
|
|
3267
|
+
const p = c.palette;
|
|
3268
|
+
const sw = dims.width, sh = dims.height;
|
|
3269
|
+
const bodyW = sw + 60;
|
|
3270
|
+
const bodyH = sh + 60;
|
|
3271
|
+
return [
|
|
3272
|
+
slab(c, "body", 0, 0, bodyW, bodyH, 40, p.bodyGrad, p.body, p.bodyStroke),
|
|
3273
|
+
ellipse({ id: `${c.id}-knob`, x: -bodyW / 2 + 18, y: 0, anchor: "center", width: 22, height: 22, fill: p.body, stroke: p.detail, strokeWidth: 3 }),
|
|
3274
|
+
screenStack(c, dims.cx, dims.cy, dims, content),
|
|
3275
|
+
ellipse({ id: `${c.id}-btn1`, x: -44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
3276
|
+
ellipse({ id: `${c.id}-btn2`, x: 0, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
3277
|
+
ellipse({ id: `${c.id}-btn3`, x: 44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail })
|
|
3278
|
+
];
|
|
3279
|
+
}
|
|
3207
3280
|
};
|
|
3281
|
+
function phoneOrTablet(name, c, dims, content) {
|
|
3282
|
+
const p = c.palette;
|
|
3283
|
+
const sw = dims.width, sh = dims.height;
|
|
3284
|
+
const bezel = name === "phone" ? 20 : 28;
|
|
3285
|
+
const bodyW = sw + bezel * 2;
|
|
3286
|
+
const bodyH = sh + bezel * 2;
|
|
3287
|
+
const bodyR = name === "phone" ? 54 : 34;
|
|
3288
|
+
const land = isLandscape(name, c.o);
|
|
3289
|
+
const nodes = [
|
|
3290
|
+
slab(c, "body", 0, 0, bodyW, bodyH, bodyR, p.bodyGrad, p.body, p.bodyStroke),
|
|
3291
|
+
screenStack(c, dims.cx, dims.cy, dims, content)
|
|
3292
|
+
];
|
|
3293
|
+
if (name === "phone") {
|
|
3294
|
+
if (land) {
|
|
3295
|
+
nodes.push(
|
|
3296
|
+
rect({ id: `${c.id}-notch`, x: -sw / 2 + 16, y: 0, anchor: "center", width: 30, height: 96, fill: "#000000", radius: 15 }),
|
|
3297
|
+
rect({ id: `${c.id}-home`, x: sw / 2 - 4, y: 0, anchor: "center", width: 5, height: 120, fill: p.detail, radius: 3 })
|
|
3298
|
+
);
|
|
3299
|
+
} else {
|
|
3300
|
+
const notch = phoneNotch(c, c.o.notch ?? "island", sh);
|
|
3301
|
+
if (notch) nodes.push(notch);
|
|
3302
|
+
nodes.push(
|
|
3303
|
+
rect({ id: `${c.id}-home`, x: 0, y: sh / 2 - 18, anchor: "center", width: 120, height: 5, fill: p.detail, radius: 3 }),
|
|
3304
|
+
// side hardware: power on the right, volume rocker on the left
|
|
3305
|
+
rect({ id: `${c.id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
|
|
3306
|
+
rect({ id: `${c.id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
|
|
3307
|
+
rect({ id: `${c.id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
} else {
|
|
3311
|
+
nodes.push(
|
|
3312
|
+
rect({ id: `${c.id}-camera`, x: land ? -sw / 2 - 14 : 0, y: land ? 0 : -sh / 2 - 14, anchor: "center", width: 8, height: 8, fill: p.detail, radius: 4 }),
|
|
3313
|
+
rect({ id: `${c.id}-pwr`, x: land ? -bodyW * 0.18 : bodyW * 0.18, y: land ? -bodyH / 2 : -bodyH / 2, anchor: "center", width: 60, height: 4, fill: p.detail, radius: 2 })
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
return nodes;
|
|
3317
|
+
}
|
|
3318
|
+
function devicePreset(name, opts = {}) {
|
|
3319
|
+
const c = makeCtx(opts);
|
|
3320
|
+
const dims = screenDims(name, opts);
|
|
3321
|
+
const children = CHASSIS[name](c, dims, opts.content ?? []);
|
|
3322
|
+
return group(
|
|
3323
|
+
{
|
|
3324
|
+
id: c.id,
|
|
3325
|
+
x: opts.x ?? 0,
|
|
3326
|
+
y: opts.y ?? 0,
|
|
3327
|
+
...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
|
|
3328
|
+
...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
|
|
3329
|
+
},
|
|
3330
|
+
children
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// ../core/src/cursor.ts
|
|
3335
|
+
var ARROW_D = "M0 0 L0 30 L8 23 L12.6 33 L17 31 L12.4 21.4 L21 21.4 Z";
|
|
3336
|
+
function cursor(opts = {}) {
|
|
3337
|
+
const id = opts.id ?? "cursor";
|
|
3338
|
+
const style = opts.style ?? "arrow";
|
|
3339
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
3340
|
+
const accent = opts.accent ?? "#FF5A1F";
|
|
3341
|
+
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" })];
|
|
3342
|
+
return group(
|
|
3343
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
3344
|
+
[
|
|
3345
|
+
// ripple ring (behind the pointer), emanates from the hotspot on click
|
|
3346
|
+
ellipse({ id: `${id}-ripple`, x: 0, y: 0, width: 30, height: 30, fill: "none", stroke: accent, strokeWidth: 3, opacity: 0, scale: 0, anchor: "center" }),
|
|
3347
|
+
// the pointer art lives in its own group so a click "tap" can scale it
|
|
3348
|
+
// independently of the cursor's resting scale
|
|
3349
|
+
group({ id: `${id}-art`, x: 0, y: 0 }, art)
|
|
3350
|
+
]
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
3354
|
+
function cursorTo(id, from, to2, opts = {}) {
|
|
3355
|
+
const dx = to2[0] - from[0], dy = to2[1] - from[1];
|
|
3356
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
3357
|
+
const arc = opts.arc ?? 0.12;
|
|
3358
|
+
const mid = [(from[0] + to2[0]) / 2 + -dy / dist * arc * dist, (from[1] + to2[1]) / 2 + dx / dist * arc * dist];
|
|
3359
|
+
const duration = opts.duration ?? clamp(dist / 1400, 0.4, 0.9);
|
|
3360
|
+
return motionPath(id, [from, mid, to2], { duration, ease: opts.ease ?? "easeInOutCubic", curviness: 1, ...opts.label && { label: opts.label } });
|
|
3361
|
+
}
|
|
3362
|
+
function cursorPath(id, points, opts = {}) {
|
|
3363
|
+
return motionPath(id, points, {
|
|
3364
|
+
duration: opts.duration ?? clamp(points.length * 0.5, 0.5, 4),
|
|
3365
|
+
ease: opts.ease ?? "easeInOutCubic",
|
|
3366
|
+
curviness: opts.curviness ?? 1,
|
|
3367
|
+
...opts.label && { label: opts.label }
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
function clickBody(id, o) {
|
|
3371
|
+
const sp = Math.max(0.25, o.speed ?? 1);
|
|
3372
|
+
const d = (b) => b / sp;
|
|
3373
|
+
const out = [
|
|
3374
|
+
// the pointer taps
|
|
3375
|
+
seq(tween(`${id}-art`, { scale: 0.82 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(`${id}-art`, { scale: 1 }, { duration: d(0.1), ease: "easeOutBack" }))
|
|
3376
|
+
];
|
|
3377
|
+
if (o.ripple !== false) {
|
|
3378
|
+
out.push(seq(
|
|
3379
|
+
tween(`${id}-ripple`, { scale: 0.2, opacity: 0.55 }, { duration: 1e-3 }),
|
|
3380
|
+
par(
|
|
3381
|
+
tween(`${id}-ripple`, { scale: 5 }, { duration: d(0.5), ease: "easeOutCubic" }),
|
|
3382
|
+
tween(`${id}-ripple`, { opacity: 0 }, { duration: d(0.5), ease: "easeOutQuad" })
|
|
3383
|
+
)
|
|
3384
|
+
));
|
|
3385
|
+
}
|
|
3386
|
+
if (o.press) {
|
|
3387
|
+
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" })));
|
|
3388
|
+
}
|
|
3389
|
+
return out;
|
|
3390
|
+
}
|
|
3391
|
+
function cursorClick(id, opts = {}) {
|
|
3392
|
+
return beat(opts.label ?? "cursor-click", {}, [par(...clickBody(id, opts))]);
|
|
3393
|
+
}
|
|
3394
|
+
function cursorDouble(id, opts = {}) {
|
|
3395
|
+
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
3396
|
+
return beat(opts.label ?? "cursor-double", {}, [
|
|
3397
|
+
seq(par(...clickBody(id, { ...opts, ripple: false })), wait(0.12 / sp), par(...clickBody(id, opts)))
|
|
3398
|
+
]);
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// ../core/src/rig.ts
|
|
3402
|
+
var DEFAULT_LINE = "#FFE3D2";
|
|
3403
|
+
var DEFAULT_FILL = "#0E1424";
|
|
3404
|
+
var LINE_W = 5;
|
|
3405
|
+
var GLOW_W = 16;
|
|
3406
|
+
var K = 0.5523;
|
|
3407
|
+
var n = (v) => Number(v.toFixed(2));
|
|
3408
|
+
function capsulePath(hw, len) {
|
|
3409
|
+
const yT = hw;
|
|
3410
|
+
const yB = Math.max(hw, len - hw);
|
|
3411
|
+
const k = hw * K;
|
|
3412
|
+
return `M ${n(-hw)} ${n(yT)} C ${n(-hw)} ${n(yT - k)} ${n(-k)} ${n(yT - hw)} 0 ${n(yT - hw)} C ${n(k)} ${n(yT - hw)} ${n(hw)} ${n(yT - k)} ${n(hw)} ${n(yT)} L ${n(hw)} ${n(yB)} C ${n(hw)} ${n(yB + k)} ${n(k)} ${n(yB + hw)} 0 ${n(yB + hw)} C ${n(-k)} ${n(yB + hw)} ${n(-hw)} ${n(yB + k)} ${n(-hw)} ${n(yB)} Z`;
|
|
3413
|
+
}
|
|
3414
|
+
function ovalPath(a, b, cx = 0, cy = 0) {
|
|
3415
|
+
const ka = n(a * K), kb = n(b * K);
|
|
3416
|
+
const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
|
|
3417
|
+
return `M ${X} ${t} C ${n(cx + ka)} ${t} ${n(cx + A)} ${n(cy - kb)} ${n(cx + A)} ${c} C ${n(cx + A)} ${n(cy + kb)} ${n(cx + ka)} ${bo} ${X} ${bo} C ${n(cx - ka)} ${bo} ${n(cx + L)} ${n(cy + kb)} ${n(cx + L)} ${c} C ${n(cx + L)} ${n(cy - kb)} ${n(cx - ka)} ${t} ${X} ${t} Z`;
|
|
3418
|
+
}
|
|
3419
|
+
function boneShape(jointId, bone, o) {
|
|
3420
|
+
if (bone.shape) return bone.shape;
|
|
3421
|
+
const len = bone.length ?? 0;
|
|
3422
|
+
if (len <= 0) return [];
|
|
3423
|
+
const d = capsulePath((bone.width ?? 20) / 2, len);
|
|
3424
|
+
const nodes = [];
|
|
3425
|
+
if (o.glow) nodes.push(path({ id: `${jointId}-glow`, d, x: 0, y: 0, fill: "none", stroke: o.glow, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
3426
|
+
nodes.push(path({ id: `${jointId}-shape`, d, x: 0, y: 0, fill: o.fill, stroke: o.color, strokeWidth: LINE_W }));
|
|
3427
|
+
return nodes;
|
|
3428
|
+
}
|
|
3429
|
+
function buildBone(bone, id, o) {
|
|
3430
|
+
const jointId = `${id}-${bone.name}`;
|
|
3431
|
+
return group(
|
|
3432
|
+
{ id: jointId, x: bone.at[0], y: bone.at[1], rotation: bone.rotation ?? 0 },
|
|
3433
|
+
[...boneShape(jointId, bone, o), ...(bone.children ?? []).map((c) => buildBone(c, id, o))]
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
function rig(root, opts = {}) {
|
|
3437
|
+
const id = opts.id ?? "rig";
|
|
3438
|
+
const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
|
|
3439
|
+
return group(
|
|
3440
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
3441
|
+
[buildBone(root, id, o)]
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
function rigPose(id, pose) {
|
|
3445
|
+
const out = {};
|
|
3446
|
+
for (const [name, deg] of Object.entries(pose)) out[`${id}-${name}`] = { rotation: deg };
|
|
3447
|
+
return out;
|
|
3448
|
+
}
|
|
3449
|
+
function poseTo(id, pose, opts = {}) {
|
|
3450
|
+
const tweens = Object.entries(pose).map(
|
|
3451
|
+
([name, deg]) => tween(`${id}-${name}`, { rotation: deg }, { duration: opts.duration ?? 0.5, ease: opts.ease ?? "easeInOutCubic" })
|
|
3452
|
+
);
|
|
3453
|
+
return opts.stagger ? stagger(opts.stagger, ...tweens) : par(...tweens);
|
|
3454
|
+
}
|
|
3455
|
+
function ikReach(upper, lower, dx, dy, flip = false) {
|
|
3456
|
+
const D = Math.hypot(dx, dy);
|
|
3457
|
+
const cos2 = Math.max(-1, Math.min(1, (D * D - upper * upper - lower * lower) / (2 * upper * lower)));
|
|
3458
|
+
const theta2 = (flip ? -1 : 1) * Math.acos(cos2);
|
|
3459
|
+
const vx = -lower * Math.sin(theta2);
|
|
3460
|
+
const vy = upper + lower * Math.cos(theta2);
|
|
3461
|
+
const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
|
|
3462
|
+
const deg = (r) => r * 180 / Math.PI;
|
|
3463
|
+
return [deg(theta1), deg(theta2)];
|
|
3464
|
+
}
|
|
3465
|
+
function humanoid(opts = {}) {
|
|
3466
|
+
const line2 = opts.color ?? DEFAULT_LINE;
|
|
3467
|
+
const fill = opts.fill ?? DEFAULT_FILL;
|
|
3468
|
+
const glow2 = opts.glow;
|
|
3469
|
+
const blob = (jid, a, b, cy) => {
|
|
3470
|
+
const d = ovalPath(a, b, 0, cy);
|
|
3471
|
+
const nodes = [];
|
|
3472
|
+
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
3473
|
+
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
3474
|
+
return nodes;
|
|
3475
|
+
};
|
|
3476
|
+
const id = opts.id ?? "rig";
|
|
3477
|
+
const root = {
|
|
3478
|
+
name: "chest",
|
|
3479
|
+
at: [0, 0],
|
|
3480
|
+
shape: blob(`${id}-chest`, 44, 62, 22),
|
|
3481
|
+
children: [
|
|
3482
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
|
|
3483
|
+
{ name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
|
|
3484
|
+
{ name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
|
|
3485
|
+
] },
|
|
3486
|
+
{ name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
|
|
3487
|
+
{ name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
|
|
3488
|
+
] },
|
|
3489
|
+
{ name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
|
|
3490
|
+
{ name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
|
|
3491
|
+
] },
|
|
3492
|
+
{ name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
|
|
3493
|
+
{ name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
|
|
3494
|
+
] }
|
|
3495
|
+
]
|
|
3496
|
+
};
|
|
3497
|
+
return rig(root, opts);
|
|
3498
|
+
}
|
|
3208
3499
|
|
|
3209
|
-
// ../core/src/
|
|
3210
|
-
var
|
|
3211
|
-
var
|
|
3212
|
-
var
|
|
3500
|
+
// ../core/src/characterPreset.ts
|
|
3501
|
+
var CHARACTER_PRESET_NAMES = ["walk", "run", "jump", "dance", "wave", "cheer"];
|
|
3502
|
+
var THIGH = 76;
|
|
3503
|
+
var SHIN = 72;
|
|
3504
|
+
var clamp013 = (x) => Math.max(0, Math.min(1, x));
|
|
3505
|
+
function makeRng4(seed) {
|
|
3506
|
+
let a = seed >>> 0 || 2654435769;
|
|
3507
|
+
return () => {
|
|
3508
|
+
a = a + 1831565813 | 0;
|
|
3509
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
3510
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
3511
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
3512
|
+
};
|
|
3513
|
+
}
|
|
3213
3514
|
var dur3 = (base, sp) => base / sp;
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
3228
|
-
});
|
|
3229
|
-
let cursor2 = align === "center" ? x - total / 2 : x;
|
|
3230
|
-
const glyphs = [];
|
|
3231
|
-
const nodes = [];
|
|
3232
|
-
const mk = (ch, cx, adv, lsProp) => {
|
|
3233
|
-
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
3234
|
-
glyphs.push(g);
|
|
3235
|
-
nodes.push(
|
|
3236
|
-
text({
|
|
3237
|
-
id: g.id,
|
|
3238
|
-
x: cx,
|
|
3239
|
-
y,
|
|
3240
|
-
content: ch,
|
|
3241
|
-
fontFamily: "Inter",
|
|
3242
|
-
fontSize,
|
|
3243
|
-
fontWeight: weight,
|
|
3244
|
-
fill,
|
|
3245
|
-
anchor: "center",
|
|
3246
|
-
opacity,
|
|
3247
|
-
...lsProp ? { letterSpacing: lsProp } : {}
|
|
3248
|
-
})
|
|
3249
|
-
);
|
|
3515
|
+
function ctx3(o) {
|
|
3516
|
+
const rand2 = makeRng4((o.seed ?? 0) + 1);
|
|
3517
|
+
return {
|
|
3518
|
+
g: o.target,
|
|
3519
|
+
label: o.label,
|
|
3520
|
+
e: clamp013(o.energy ?? 0.5),
|
|
3521
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
3522
|
+
cycles: Math.max(1, Math.round(o.cycles ?? 4)),
|
|
3523
|
+
facing: o.facing ?? 1,
|
|
3524
|
+
at: o.at ?? [0, 0],
|
|
3525
|
+
travel: o.travel,
|
|
3526
|
+
rand: rand2,
|
|
3527
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
3250
3528
|
};
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
}
|
|
3259
|
-
let word = "";
|
|
3260
|
-
let w = 0;
|
|
3261
|
-
const startCursor = cursor2;
|
|
3262
|
-
while (i < chars.length && chars[i] !== " ") {
|
|
3263
|
-
const a = advance(chars[i], weight, fontSize);
|
|
3264
|
-
word += chars[i];
|
|
3265
|
-
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
3266
|
-
i++;
|
|
3267
|
-
}
|
|
3268
|
-
mk(word, startCursor + w / 2, w, ls);
|
|
3269
|
-
cursor2 = startCursor + w + ls;
|
|
3270
|
-
}
|
|
3271
|
-
} else {
|
|
3272
|
-
chars.forEach((ch) => {
|
|
3273
|
-
const a = advance(ch, weight, fontSize);
|
|
3274
|
-
if (ch !== " ") mk(ch, cursor2 + a / 2, a);
|
|
3275
|
-
cursor2 += a + ls;
|
|
3276
|
-
});
|
|
3529
|
+
}
|
|
3530
|
+
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
3531
|
+
function footPos(p, stride, lift) {
|
|
3532
|
+
p = (p % 1 + 1) % 1;
|
|
3533
|
+
if (p < 0.5) {
|
|
3534
|
+
const u2 = p / 0.5;
|
|
3535
|
+
return [stride * (1 - 2 * u2), 138];
|
|
3277
3536
|
}
|
|
3278
|
-
|
|
3537
|
+
const u = (p - 0.5) / 0.5;
|
|
3538
|
+
return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
|
|
3279
3539
|
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
}
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3540
|
+
function gaitPose(ph, stride, lift, armSwing, facing) {
|
|
3541
|
+
const fl = footPos(ph, stride, lift);
|
|
3542
|
+
const fr = footPos(ph + 0.5, stride, lift);
|
|
3543
|
+
const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
|
|
3544
|
+
const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
|
|
3545
|
+
const swing = Math.cos(2 * Math.PI * ph);
|
|
3546
|
+
return {
|
|
3547
|
+
legUpperL: round(hipL),
|
|
3548
|
+
legLowerL: round(kneeL),
|
|
3549
|
+
legUpperR: round(hipR),
|
|
3550
|
+
legLowerR: round(kneeR),
|
|
3551
|
+
armUpperR: round(-10 - armSwing * swing),
|
|
3552
|
+
armLowerR: -16,
|
|
3553
|
+
armUpperL: round(10 + armSwing * swing),
|
|
3554
|
+
armLowerL: 16
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3557
|
+
function gait(c, run) {
|
|
3558
|
+
const stride = (run ? 34 : 24) + (run ? 40 : 30) * c.e + c.jit(3);
|
|
3559
|
+
const lift = run ? 40 : 26;
|
|
3560
|
+
const armSwing = (run ? 26 : 16) + 20 * c.e;
|
|
3561
|
+
const halfDur = (run ? 0.26 : 0.42) + c.jit(0.02);
|
|
3562
|
+
const lean = run ? c.facing * -6 : 0;
|
|
3563
|
+
const steps = c.cycles * 2;
|
|
3564
|
+
const d = dur3(halfDur, c.sp);
|
|
3565
|
+
const intro = dur3(0.16, c.sp);
|
|
3566
|
+
const keys = [];
|
|
3567
|
+
for (let k = 0; k <= steps; k++) {
|
|
3568
|
+
const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
|
|
3569
|
+
keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
|
|
3570
|
+
}
|
|
3571
|
+
const total = intro + steps * d;
|
|
3572
|
+
const travel = c.travel ?? stride * 2;
|
|
3573
|
+
const children = [seq(...keys)];
|
|
3574
|
+
if (travel !== 0) {
|
|
3575
|
+
children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
|
|
3576
|
+
}
|
|
3577
|
+
return beat(run ? "run" : "walk", {}, [par(...children)]);
|
|
3578
|
+
}
|
|
3579
|
+
function jumpBeat(c) {
|
|
3580
|
+
const h = 120 + 150 * c.e;
|
|
3581
|
+
const [y0] = [c.at[1]];
|
|
3582
|
+
const CROUCH = { legUpperL: 18, legLowerL: 54, legUpperR: -18, legLowerR: 54, armUpperL: 28, armUpperR: -28 };
|
|
3583
|
+
const LAUNCH = { legUpperL: 0, legLowerL: 0, legUpperR: 0, legLowerR: 0, armUpperL: 150, armUpperR: -150 };
|
|
3584
|
+
const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
|
|
3585
|
+
const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
|
|
3586
|
+
const j = c.jit(0.03);
|
|
3587
|
+
return beat("jump", {}, [
|
|
3588
|
+
seq(
|
|
3589
|
+
par(poseTo(c.g, CROUCH, { duration: dur3(0.24, c.sp), ease: "easeOutQuad" }), tween(c.g, { y: y0 + 26 }, { duration: dur3(0.24, c.sp), ease: "easeOutQuad" })),
|
|
3590
|
+
par(poseTo(c.g, LAUNCH, { duration: dur3(0.22 + j, c.sp), ease: "easeOutCubic" }), tween(c.g, { y: y0 - h }, { duration: dur3(0.36, c.sp), ease: "easeOutCubic", label: "launch" })),
|
|
3591
|
+
poseTo(c.g, TUCK, { duration: dur3(0.22, c.sp) }),
|
|
3592
|
+
par(poseTo(c.g, CROUCH, { duration: dur3(0.28, c.sp), ease: "easeInQuad" }), tween(c.g, { y: y0 + 18 }, { duration: dur3(0.3, c.sp), ease: "easeInCubic", label: "land" })),
|
|
3593
|
+
par(poseTo(c.g, REST, { duration: dur3(0.45, c.sp), ease: "easeOutBack" }), tween(c.g, { y: y0 }, { duration: dur3(0.45, c.sp), ease: "easeOutBack" }))
|
|
3594
|
+
)
|
|
3595
|
+
]);
|
|
3596
|
+
}
|
|
3597
|
+
function danceBeat(c) {
|
|
3598
|
+
const y0 = c.at[1];
|
|
3599
|
+
const sway = 8 + 6 * c.e;
|
|
3600
|
+
const armUp = 130 + 30 * c.e;
|
|
3601
|
+
const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
|
|
3602
|
+
const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
|
|
3603
|
+
const d = dur3(0.34, c.sp);
|
|
3604
|
+
const keys = [];
|
|
3605
|
+
for (let k = 0; k < c.cycles * 2; k++) {
|
|
3606
|
+
const pose = k % 2 === 0 ? A : B;
|
|
3607
|
+
keys.push(par(
|
|
3608
|
+
poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
|
|
3609
|
+
tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
|
|
3610
|
+
));
|
|
3335
3611
|
}
|
|
3612
|
+
keys.push(tween(c.g, { y: y0 }, { duration: d }));
|
|
3613
|
+
return beat("dance", {}, [seq(...keys)]);
|
|
3336
3614
|
}
|
|
3337
|
-
function
|
|
3338
|
-
const
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3615
|
+
function waveBeat(c) {
|
|
3616
|
+
const n3 = 3 + Math.round(c.rand() * 2);
|
|
3617
|
+
const amp = 16 + 10 * c.e;
|
|
3618
|
+
const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur3(0.4, c.sp), ease: "easeOutBack" })];
|
|
3619
|
+
for (let k = 0; k < n3; k++) {
|
|
3620
|
+
steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur3(0.22, c.sp), ease: "easeInOutQuad" }));
|
|
3621
|
+
}
|
|
3622
|
+
steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur3(0.4, c.sp), ease: "easeInOutCubic" }));
|
|
3623
|
+
return beat("wave", {}, [seq(...steps)]);
|
|
3341
3624
|
}
|
|
3342
|
-
function
|
|
3343
|
-
const
|
|
3344
|
-
const
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
case "wobble":
|
|
3353
|
-
return oscillate(g.id, "rotation", { amplitude: opts.amplitude ?? 6, frequency: f, phase: i * ps }, win);
|
|
3354
|
-
case "float":
|
|
3355
|
-
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 5, frequency: f, phase: i * ps }, win);
|
|
3356
|
-
}
|
|
3357
|
-
});
|
|
3625
|
+
function cheerBeat(c) {
|
|
3626
|
+
const y0 = c.at[1];
|
|
3627
|
+
const UP = { armUpperL: 152, armLowerL: 8, armUpperR: -152, armLowerR: -8 };
|
|
3628
|
+
const d = dur3(0.3, c.sp);
|
|
3629
|
+
const keys = [poseTo(c.g, UP, { duration: dur3(0.35, c.sp), ease: "easeOutBack" })];
|
|
3630
|
+
for (let k = 0; k < c.cycles; k++) {
|
|
3631
|
+
keys.push(par(tween(c.g, { y: y0 - 28 }, { duration: d, ease: "easeOutQuad" }), poseTo(c.g, { armUpperL: 160, armUpperR: -160 }, { duration: d })));
|
|
3632
|
+
keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
|
|
3633
|
+
}
|
|
3634
|
+
return beat("cheer", {}, [seq(...keys)]);
|
|
3358
3635
|
}
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3636
|
+
function characterPreset(name, opts) {
|
|
3637
|
+
const c = ctx3(opts);
|
|
3638
|
+
let tl;
|
|
3362
3639
|
switch (name) {
|
|
3363
|
-
case "
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
);
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
case "
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur3(0.5, c.sp), ease: "easeInBack" }),
|
|
3386
|
-
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
3387
|
-
);
|
|
3640
|
+
case "walk":
|
|
3641
|
+
tl = gait(c, false);
|
|
3642
|
+
break;
|
|
3643
|
+
case "run":
|
|
3644
|
+
tl = gait(c, true);
|
|
3645
|
+
break;
|
|
3646
|
+
case "jump":
|
|
3647
|
+
tl = jumpBeat(c);
|
|
3648
|
+
break;
|
|
3649
|
+
case "dance":
|
|
3650
|
+
tl = danceBeat(c);
|
|
3651
|
+
break;
|
|
3652
|
+
case "wave":
|
|
3653
|
+
tl = waveBeat(c);
|
|
3654
|
+
break;
|
|
3655
|
+
case "cheer":
|
|
3656
|
+
tl = cheerBeat(c);
|
|
3657
|
+
break;
|
|
3658
|
+
default: {
|
|
3659
|
+
const _exhaustive = name;
|
|
3660
|
+
throw new Error(`unknown characterPreset "${_exhaustive}"`);
|
|
3661
|
+
}
|
|
3388
3662
|
}
|
|
3663
|
+
return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
|
|
3389
3664
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
const
|
|
3396
|
-
return
|
|
3665
|
+
|
|
3666
|
+
// ../core/src/figure.ts
|
|
3667
|
+
var K2 = 0.5523;
|
|
3668
|
+
var n2 = (v) => Number(v.toFixed(2));
|
|
3669
|
+
function limb(a, b, y0, y1) {
|
|
3670
|
+
const ka = n2(a * K2), kb = n2(b * K2);
|
|
3671
|
+
return `M ${-a} ${y0} C ${-a} ${n2(y0 - ka)} ${-ka} ${n2(y0 - a)} 0 ${n2(y0 - a)} C ${ka} ${n2(y0 - a)} ${a} ${n2(y0 - ka)} ${a} ${y0} L ${b} ${y1} C ${b} ${n2(y1 + kb)} ${kb} ${n2(y1 + b)} 0 ${n2(y1 + b)} C ${-kb} ${n2(y1 + b)} ${-b} ${n2(y1 + kb)} ${-b} ${y1} Z`;
|
|
3397
3672
|
}
|
|
3398
|
-
function
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3673
|
+
function rrect(a, b, y0, y1, r) {
|
|
3674
|
+
return `M ${n2(-a + r)} ${y0} L ${n2(a - r)} ${y0} Q ${a} ${y0} ${a} ${n2(y0 + r)} L ${b} ${n2(y1 - r)} Q ${b} ${y1} ${n2(b - r)} ${y1} L ${n2(-b + r)} ${y1} Q ${-b} ${y1} ${-b} ${n2(y1 - r)} L ${-a} ${n2(y0 + r)} Q ${-a} ${y0} ${n2(-a + r)} ${y0} Z`;
|
|
3675
|
+
}
|
|
3676
|
+
function darken(hex, f) {
|
|
3677
|
+
const h = hex.replace("#", "");
|
|
3678
|
+
const v = parseInt(h.length === 3 ? [...h].map((c) => c + c).join("") : h, 16);
|
|
3679
|
+
const ch = (s) => Math.max(0, Math.min(255, Math.round((v >> s & 255) * (1 - f))));
|
|
3680
|
+
const hx = (x) => x.toString(16).padStart(2, "0");
|
|
3681
|
+
return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
|
|
3682
|
+
}
|
|
3683
|
+
var DEF = {
|
|
3684
|
+
clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
|
|
3685
|
+
cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
|
|
3686
|
+
};
|
|
3687
|
+
function resolvePal(style, p = {}) {
|
|
3688
|
+
const d = DEF[style];
|
|
3689
|
+
const accent = p.accent ?? d.accent;
|
|
3690
|
+
const top = p.top ?? (style === "clean" ? accent : d.top);
|
|
3691
|
+
const skin = p.skin ?? d.skin;
|
|
3692
|
+
const hair = p.hair ?? d.hair;
|
|
3693
|
+
const pants = p.pants ?? d.pants;
|
|
3694
|
+
const shoe = p.shoe ?? d.shoe;
|
|
3695
|
+
return {
|
|
3696
|
+
skin,
|
|
3697
|
+
skinSh: darken(skin, 0.12),
|
|
3698
|
+
hair,
|
|
3699
|
+
hairSh: darken(hair, 0.14),
|
|
3700
|
+
top,
|
|
3701
|
+
topSh: darken(top, 0.12),
|
|
3702
|
+
pants,
|
|
3703
|
+
pantsSh: darken(pants, 0.14),
|
|
3704
|
+
shoe,
|
|
3705
|
+
shoeSh: darken(shoe, 0.22),
|
|
3706
|
+
eye: "#2B313F",
|
|
3707
|
+
cheek: "#FF9E7E",
|
|
3708
|
+
white: "#FFFFFF",
|
|
3709
|
+
mouth: "#8A4233"
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
var fp = (id, d, fill, stroke, sw = 0, opacity = 1) => path({ id, d, x: 0, y: 0, fill, opacity, ...stroke && sw > 0 ? { stroke, strokeWidth: sw } : {} });
|
|
3713
|
+
function cleanParts(p, face) {
|
|
3714
|
+
const HC = -42;
|
|
3715
|
+
return {
|
|
3716
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
|
|
3717
|
+
forearm: (j) => [
|
|
3718
|
+
fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
|
|
3719
|
+
fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
|
|
3720
|
+
fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
|
|
3721
|
+
],
|
|
3722
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
|
|
3723
|
+
shin: (j) => [
|
|
3724
|
+
fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
|
|
3725
|
+
fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
|
|
3726
|
+
fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
|
|
3727
|
+
],
|
|
3728
|
+
torso: (j) => [
|
|
3729
|
+
fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
|
|
3730
|
+
fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
|
|
3731
|
+
fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
|
|
3732
|
+
],
|
|
3733
|
+
head: (j) => [
|
|
3734
|
+
fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
|
|
3735
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
|
|
3736
|
+
fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
|
|
3737
|
+
fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
|
|
3738
|
+
fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
|
|
3739
|
+
...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
|
|
3740
|
+
]
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
var CUTE_HAIR = "M -64 -54 C -78 -96 -50 -126 0 -126 C 50 -126 78 -96 64 -54 C 60 -34 48 -28 41 -33 C 35 -54 23 -60 9 -60 C 3 -60 -3 -60 -9 -60 C -23 -60 -35 -54 -41 -33 C -48 -28 -60 -34 -64 -54 Z";
|
|
3744
|
+
function cuteParts(p, face) {
|
|
3745
|
+
const HC = -50;
|
|
3746
|
+
return {
|
|
3747
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
|
|
3748
|
+
forearm: (j) => [
|
|
3749
|
+
fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
|
|
3750
|
+
fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
|
|
3751
|
+
fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
|
|
3752
|
+
],
|
|
3753
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
|
|
3754
|
+
shin: (j) => [
|
|
3755
|
+
fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
|
|
3756
|
+
fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
|
|
3757
|
+
fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
|
|
3758
|
+
],
|
|
3759
|
+
torso: (j) => [
|
|
3760
|
+
fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
|
|
3761
|
+
fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
|
|
3762
|
+
fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
|
|
3763
|
+
],
|
|
3764
|
+
head: (j) => [
|
|
3765
|
+
fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
|
|
3766
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
|
|
3767
|
+
fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
3768
|
+
fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
3769
|
+
fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
|
|
3770
|
+
...face ? [
|
|
3771
|
+
fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
|
|
3772
|
+
fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
|
|
3773
|
+
fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
|
|
3774
|
+
fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
|
|
3775
|
+
path({ id: `${j}-mouth`, d: "M -15 0 Q 0 15 15 0", x: 0, y: HC + 28, fill: "none", stroke: p.mouth, strokeWidth: 5 })
|
|
3776
|
+
] : []
|
|
3777
|
+
]
|
|
3778
|
+
};
|
|
3779
|
+
}
|
|
3780
|
+
function buildSkeleton(id, S) {
|
|
3781
|
+
const arm = (side, x, r1, r2) => ({
|
|
3782
|
+
name: `armUpper${side}`,
|
|
3783
|
+
at: [x, -14],
|
|
3784
|
+
length: 60,
|
|
3785
|
+
width: 0,
|
|
3786
|
+
rotation: r1,
|
|
3787
|
+
shape: S.upperArm(`${id}-armUpper${side}`),
|
|
3788
|
+
children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
|
|
3789
|
+
});
|
|
3790
|
+
const leg = (side, x, r1, r2) => ({
|
|
3791
|
+
name: `legUpper${side}`,
|
|
3792
|
+
at: [x, 76],
|
|
3793
|
+
length: 76,
|
|
3794
|
+
width: 0,
|
|
3795
|
+
rotation: r1,
|
|
3796
|
+
shape: S.thigh(`${id}-legUpper${side}`),
|
|
3797
|
+
children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
|
|
3798
|
+
});
|
|
3799
|
+
return {
|
|
3800
|
+
name: "chest",
|
|
3801
|
+
at: [0, 0],
|
|
3802
|
+
shape: S.torso(`${id}-chest`),
|
|
3803
|
+
children: [
|
|
3804
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
|
|
3805
|
+
arm("L", -40, 8, 6),
|
|
3806
|
+
arm("R", 40, -8, -6),
|
|
3807
|
+
leg("L", -20, 3, -2),
|
|
3808
|
+
leg("R", 20, -3, 2)
|
|
3809
|
+
]
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
function figure(opts = {}) {
|
|
3813
|
+
const style = opts.style ?? "clean";
|
|
3814
|
+
const pal = resolvePal(style, opts.palette);
|
|
3815
|
+
const face = opts.face ?? true;
|
|
3816
|
+
const id = opts.id ?? "figure";
|
|
3817
|
+
const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
|
|
3818
|
+
const rigOpts = { id };
|
|
3819
|
+
if (opts.x !== void 0) rigOpts.x = opts.x;
|
|
3820
|
+
if (opts.y !== void 0) rigOpts.y = opts.y;
|
|
3821
|
+
if (opts.scale !== void 0) rigOpts.scale = opts.scale;
|
|
3822
|
+
if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
|
|
3823
|
+
return rig(buildSkeleton(id, parts), rigOpts);
|
|
3409
3824
|
}
|
|
3410
3825
|
|
|
3411
3826
|
// ../core/src/motionOps.ts
|
|
@@ -3536,13 +3951,14 @@ var SFX_DURATION = {
|
|
|
3536
3951
|
camera: 0.18
|
|
3537
3952
|
};
|
|
3538
3953
|
var FILE_CUE_DURATION = 0.4;
|
|
3539
|
-
function collectClipAudio(ir, duration, warnings) {
|
|
3954
|
+
function collectClipAudio(ir, duration, labelTimes, warnings) {
|
|
3540
3955
|
const out = [];
|
|
3541
3956
|
const walk = (nodes) => {
|
|
3542
3957
|
for (const node of nodes) {
|
|
3543
3958
|
if (node.type === "video") {
|
|
3544
3959
|
const gain = node.props.volume ?? 1;
|
|
3545
|
-
const
|
|
3960
|
+
const startRaw = node.props.start;
|
|
3961
|
+
const start = typeof startRaw === "string" ? labelTimes.get(startRaw)?.t0 ?? 0 : startRaw ?? 0;
|
|
3546
3962
|
if (gain <= 0) continue;
|
|
3547
3963
|
if (start >= duration) {
|
|
3548
3964
|
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
@@ -3560,7 +3976,7 @@ function resolveAudioPlan(compiled) {
|
|
|
3560
3976
|
const audio = compiled.ir.audio;
|
|
3561
3977
|
const warnings = [];
|
|
3562
3978
|
const duration = compiled.duration;
|
|
3563
|
-
const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
|
|
3979
|
+
const clipAudio = collectClipAudio(compiled.ir, duration, compiled.labelTimes, warnings);
|
|
3564
3980
|
const autoCues = audio?.autoFoley ? autoFoley(compiled, audio.autoFoley === true ? {} : audio.autoFoley) : [];
|
|
3565
3981
|
const manualCues = [...audio?.cues ?? [], ...autoCues];
|
|
3566
3982
|
if (!audio || !audio.bgm && manualCues.length === 0) {
|
|
@@ -3799,6 +4215,7 @@ export {
|
|
|
3799
4215
|
SFX_DURATION,
|
|
3800
4216
|
SFX_NAMES,
|
|
3801
4217
|
SceneValidationError,
|
|
4218
|
+
TIMELINE_PATCHABLE,
|
|
3802
4219
|
autoFoley,
|
|
3803
4220
|
beat,
|
|
3804
4221
|
cameraFit,
|
|
@@ -3839,6 +4256,8 @@ export {
|
|
|
3839
4256
|
lerpValue,
|
|
3840
4257
|
line,
|
|
3841
4258
|
linearGradient,
|
|
4259
|
+
lintScene,
|
|
4260
|
+
lowerThird,
|
|
3842
4261
|
motionOp,
|
|
3843
4262
|
motionOpLabel,
|
|
3844
4263
|
motionPath,
|
|
@@ -3863,6 +4282,7 @@ export {
|
|
|
3863
4282
|
sampleBehavior,
|
|
3864
4283
|
sampleProp,
|
|
3865
4284
|
scene,
|
|
4285
|
+
sceneManifest,
|
|
3866
4286
|
seq,
|
|
3867
4287
|
sketchToTimeline,
|
|
3868
4288
|
splitText,
|
|
@@ -3872,6 +4292,7 @@ export {
|
|
|
3872
4292
|
textLoop,
|
|
3873
4293
|
textOut,
|
|
3874
4294
|
textTypeCues,
|
|
4295
|
+
title,
|
|
3875
4296
|
to,
|
|
3876
4297
|
tween,
|
|
3877
4298
|
validateComposition,
|