reframe-video 0.6.31 → 0.6.33
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/assets/sfx/LICENSE.md +5 -4
- package/dist/bin.js +359 -42
- package/dist/browserEntry.js +8 -8
- package/dist/cli.js +327 -17
- package/dist/compile-api.js +3 -3
- package/dist/compile.js +3 -3
- package/dist/diff.js +3 -3
- package/dist/frame.js +3 -3
- package/dist/index.js +2289 -2186
- package/dist/labels.js +3 -3
- package/dist/trace-cli.js +3 -3
- package/dist/types/autoFoley.d.ts +18 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ir.d.ts +25 -0
- package/guides/edsl-guide.md +13 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1259,6 +1259,35 @@ function cameraTo(props, opts = {}) {
|
|
|
1259
1259
|
return tween(CAMERA_ID, props, opts);
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
1262
|
+
// ../core/src/behaviors.ts
|
|
1263
|
+
function sampleBehavior(b, t) {
|
|
1264
|
+
switch (b.name) {
|
|
1265
|
+
case "oscillate": {
|
|
1266
|
+
const { amplitude, frequency, phase = 0 } = b.params;
|
|
1267
|
+
return amplitude * Math.sin(2 * Math.PI * frequency * t + phase);
|
|
1268
|
+
}
|
|
1269
|
+
case "wiggle": {
|
|
1270
|
+
const { amplitude, frequency, seed } = b.params;
|
|
1271
|
+
return amplitude * valueNoise(t * frequency, seed);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function valueNoise(x, seed) {
|
|
1276
|
+
const i = Math.floor(x);
|
|
1277
|
+
const f = x - i;
|
|
1278
|
+
const u = f * f * (3 - 2 * f);
|
|
1279
|
+
const a = hash01(i, seed) * 2 - 1;
|
|
1280
|
+
const b = hash01(i + 1, seed) * 2 - 1;
|
|
1281
|
+
return a + (b - a) * u;
|
|
1282
|
+
}
|
|
1283
|
+
function hash01(n3, seed) {
|
|
1284
|
+
let h = n3 * 374761393 + seed * 668265263 | 0;
|
|
1285
|
+
h = h ^ h >>> 13 | 0;
|
|
1286
|
+
h = Math.imul(h, 1274126177);
|
|
1287
|
+
h = (h ^ h >>> 16) >>> 0;
|
|
1288
|
+
return h / 4294967295;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1262
1291
|
// ../core/src/gradient.ts
|
|
1263
1292
|
function isGradient(p) {
|
|
1264
1293
|
return typeof p === "object" && p !== null && (p.kind === "linear" || p.kind === "radial" || p.kind === "conic");
|
|
@@ -1291,735 +1320,700 @@ function conicGradient(stops, opts = {}) {
|
|
|
1291
1320
|
};
|
|
1292
1321
|
}
|
|
1293
1322
|
|
|
1294
|
-
// ../core/src/
|
|
1295
|
-
|
|
1296
|
-
|
|
1323
|
+
// ../core/src/evaluate.ts
|
|
1324
|
+
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1325
|
+
function multiply(m, n3) {
|
|
1326
|
+
return [
|
|
1327
|
+
m[0] * n3[0] + m[2] * n3[1],
|
|
1328
|
+
m[1] * n3[0] + m[3] * n3[1],
|
|
1329
|
+
m[0] * n3[2] + m[2] * n3[3],
|
|
1330
|
+
m[1] * n3[2] + m[3] * n3[3],
|
|
1331
|
+
m[0] * n3[4] + m[2] * n3[5] + m[4],
|
|
1332
|
+
m[1] * n3[4] + m[3] * n3[5] + m[5]
|
|
1333
|
+
];
|
|
1297
1334
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1335
|
+
var DEG = Math.PI / 180;
|
|
1336
|
+
var z0 = (x) => x === 0 ? 0 : x;
|
|
1337
|
+
function projectDepth(m, z, vx, vy, d) {
|
|
1338
|
+
if (z === 0) return m;
|
|
1339
|
+
const p = d + z > 0 ? d / (d + z) : 1e-6;
|
|
1340
|
+
return [
|
|
1341
|
+
z0(m[0] * p),
|
|
1342
|
+
z0(m[1] * p),
|
|
1343
|
+
z0(m[2] * p),
|
|
1344
|
+
z0(m[3] * p),
|
|
1345
|
+
z0(vx + (m[4] - vx) * p),
|
|
1346
|
+
z0(vy + (m[5] - vy) * p)
|
|
1347
|
+
];
|
|
1300
1348
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
if (
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1349
|
+
function tiltSkew(m, rotXdeg, rotYdeg, hw, hh, d) {
|
|
1350
|
+
const ky = Math.sin(rotYdeg * DEG) * hw / d;
|
|
1351
|
+
const kx = Math.sin(rotXdeg * DEG) * hh / d;
|
|
1352
|
+
if (ky === 0 && kx === 0) return m;
|
|
1353
|
+
return multiply(m, [1, kx, ky, 1, 0, 0]);
|
|
1354
|
+
}
|
|
1355
|
+
function localMatrix(x, y, rotationDeg, scale, scaleX = 1, scaleY = 1, skewXDeg = 0, skewYDeg = 0) {
|
|
1356
|
+
const r = rotationDeg * Math.PI / 180;
|
|
1357
|
+
if (scaleX === 1 && scaleY === 1 && skewXDeg === 0 && skewYDeg === 0) {
|
|
1358
|
+
const cos = Math.cos(r) * scale;
|
|
1359
|
+
const sin = Math.sin(r) * scale;
|
|
1360
|
+
return [cos, sin, -sin, cos, x, y];
|
|
1311
1361
|
}
|
|
1312
|
-
const
|
|
1313
|
-
const
|
|
1314
|
-
const
|
|
1315
|
-
const
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1362
|
+
const c = Math.cos(r);
|
|
1363
|
+
const s = Math.sin(r);
|
|
1364
|
+
const tx = Math.tan(skewXDeg * Math.PI / 180);
|
|
1365
|
+
const ty = Math.tan(skewYDeg * Math.PI / 180);
|
|
1366
|
+
const R = [c, s, -s, c, 0, 0];
|
|
1367
|
+
const K3 = [1, ty, tx, 1, 0, 0];
|
|
1368
|
+
const S = [scale * scaleX, 0, 0, scale * scaleY, 0, 0];
|
|
1369
|
+
const m = multiply(R, multiply(K3, S));
|
|
1370
|
+
return [m[0], m[1], m[2], m[3], x, y];
|
|
1318
1371
|
}
|
|
1319
|
-
var
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1372
|
+
var ANCHOR_FACTORS = {
|
|
1373
|
+
"top-left": [0, 0],
|
|
1374
|
+
"top-center": [0.5, 0],
|
|
1375
|
+
"top-right": [1, 0],
|
|
1376
|
+
"center-left": [0, 0.5],
|
|
1377
|
+
center: [0.5, 0.5],
|
|
1378
|
+
"center-right": [1, 0.5],
|
|
1379
|
+
"bottom-left": [0, 1],
|
|
1380
|
+
"bottom-center": [0.5, 1],
|
|
1381
|
+
"bottom-right": [1, 1]
|
|
1382
|
+
};
|
|
1383
|
+
var TEXT_ALIGN = { 0: "left", 0.5: "center", 1: "right" };
|
|
1384
|
+
var TEXT_BASELINE = { 0: "top", 0.5: "middle", 1: "bottom" };
|
|
1385
|
+
function formatNumber(value, decimals, thousands) {
|
|
1386
|
+
const fixed = value.toFixed(decimals);
|
|
1387
|
+
if (!thousands) return fixed;
|
|
1388
|
+
const neg = fixed.startsWith("-");
|
|
1389
|
+
const body = neg ? fixed.slice(1) : fixed;
|
|
1390
|
+
const dot = body.indexOf(".");
|
|
1391
|
+
const intPart = dot === -1 ? body : body.slice(0, dot);
|
|
1392
|
+
const frac = dot === -1 ? "" : body.slice(dot);
|
|
1393
|
+
const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1394
|
+
return (neg ? "-" : "") + grouped + frac;
|
|
1332
1395
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
let
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
1343
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1344
|
-
};
|
|
1396
|
+
function behaviorEnvelope(b, t) {
|
|
1397
|
+
const from = b.from ?? Number.NEGATIVE_INFINITY;
|
|
1398
|
+
const until = b.until ?? Number.POSITIVE_INFINITY;
|
|
1399
|
+
if (t < from || t > until) return 0;
|
|
1400
|
+
const ramp = b.ramp ?? 0.2;
|
|
1401
|
+
let envelope = 1;
|
|
1402
|
+
if (Number.isFinite(from) && ramp > 0) envelope = Math.min(envelope, (t - from) / ramp);
|
|
1403
|
+
if (Number.isFinite(until) && ramp > 0) envelope = Math.min(envelope, (until - t) / ramp);
|
|
1404
|
+
return Math.max(0, Math.min(1, envelope));
|
|
1345
1405
|
}
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
const slides = images.map(norm);
|
|
1356
|
-
const cx = W / 2;
|
|
1357
|
-
const cy = H / 2;
|
|
1358
|
-
const nodes = [];
|
|
1359
|
-
const shots = [];
|
|
1360
|
-
let clock = 0;
|
|
1361
|
-
slides.forEach((slide, i) => {
|
|
1362
|
-
const nid = `${id}-${i}`;
|
|
1363
|
-
const slideHold = Math.max(0.5, slide.hold ?? hold);
|
|
1364
|
-
const transition = Math.min(opts.transition ?? 0.6, slideHold * 0.9);
|
|
1365
|
-
const shotStart = clock;
|
|
1366
|
-
clock += slideHold;
|
|
1367
|
-
const kind = slide.ken ?? ["in", "out", "pan"][Math.floor(rand2() * 3)] ?? "in";
|
|
1368
|
-
const angle = rand2() * Math.PI * 2;
|
|
1369
|
-
const panFrac = 0.4 + rand2() * 0.35;
|
|
1370
|
-
const dx = Math.cos(angle);
|
|
1371
|
-
const dy = Math.sin(angle);
|
|
1372
|
-
let kA, kB;
|
|
1373
|
-
let xA, xB, yA, yB;
|
|
1374
|
-
if (kind === "pan") {
|
|
1375
|
-
kA = kB = zoom;
|
|
1376
|
-
const sx = dx * (zoom - 1) * (W / 2) * panFrac;
|
|
1377
|
-
const sy = dy * (zoom - 1) * (H / 2) * panFrac;
|
|
1378
|
-
xA = cx - sx;
|
|
1379
|
-
xB = cx + sx;
|
|
1380
|
-
yA = cy - sy;
|
|
1381
|
-
yB = cy + sy;
|
|
1382
|
-
} else {
|
|
1383
|
-
kA = kind === "in" ? 1 : zoom;
|
|
1384
|
-
kB = kind === "in" ? zoom : 1;
|
|
1385
|
-
xA = cx + dx * (kA - 1) * (W / 2) * panFrac;
|
|
1386
|
-
xB = cx + dx * (kB - 1) * (W / 2) * panFrac;
|
|
1387
|
-
yA = cy + dy * (kA - 1) * (H / 2) * panFrac;
|
|
1388
|
-
yB = cy + dy * (kB - 1) * (H / 2) * panFrac;
|
|
1406
|
+
function sampleProp(compiled, t, target, prop, fallback) {
|
|
1407
|
+
let value = compiled.initialValues.get(`${target}.${prop}`) ?? fallback;
|
|
1408
|
+
let segStart = Number.NEGATIVE_INFINITY;
|
|
1409
|
+
const segs = compiled.segments.get(`${target}.${prop}`);
|
|
1410
|
+
if (segs) {
|
|
1411
|
+
let active;
|
|
1412
|
+
for (const seg of segs) {
|
|
1413
|
+
if (seg.t0 <= t) active = seg;
|
|
1414
|
+
else break;
|
|
1389
1415
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
);
|
|
1399
|
-
const shot = i === 0 ? par(ken) : par(
|
|
1400
|
-
ken,
|
|
1401
|
-
tween(`${id}-${i - 1}`, { opacity: 0 }, { duration: transition, ease: "linear", label: `cross-${i}` }),
|
|
1402
|
-
tween(nid, { opacity: 1 }, { duration: transition, ease: "linear" })
|
|
1403
|
-
);
|
|
1404
|
-
shots.push(shot);
|
|
1405
|
-
});
|
|
1406
|
-
if (grade) {
|
|
1407
|
-
nodes.push(
|
|
1408
|
-
rect({
|
|
1409
|
-
id: `${id}-vignette`,
|
|
1410
|
-
x: 0,
|
|
1411
|
-
y: 0,
|
|
1412
|
-
width: W,
|
|
1413
|
-
height: H,
|
|
1414
|
-
fill: radialGradient(
|
|
1415
|
-
[
|
|
1416
|
-
{ offset: 0.55, color: "#FFFFFF" },
|
|
1417
|
-
{ offset: 1, color: "#6E6E6E" }
|
|
1418
|
-
],
|
|
1419
|
-
{ cx: 0.5, cy: 0.5, r: 0.72 }
|
|
1420
|
-
),
|
|
1421
|
-
blend: "multiply"
|
|
1422
|
-
})
|
|
1423
|
-
);
|
|
1424
|
-
nodes.push(
|
|
1425
|
-
rect({
|
|
1426
|
-
id: `${id}-scrim`,
|
|
1427
|
-
x: 0,
|
|
1428
|
-
y: 0,
|
|
1429
|
-
width: W,
|
|
1430
|
-
height: H,
|
|
1431
|
-
fill: linearGradient(
|
|
1432
|
-
[
|
|
1433
|
-
{ offset: 0, color: "#00000000" },
|
|
1434
|
-
{ offset: 0.62, color: "#00000000" },
|
|
1435
|
-
{ offset: 1, color: "#000000B0" }
|
|
1436
|
-
],
|
|
1437
|
-
{ angle: 90 }
|
|
1438
|
-
)
|
|
1439
|
-
})
|
|
1440
|
-
);
|
|
1441
|
-
}
|
|
1442
|
-
return { nodes, timeline: beat("montage", { nodes: nodes.map((n3) => n3.id) }, [seq(...shots)]) };
|
|
1443
|
-
}
|
|
1444
|
-
var videoMontage = photoMontage;
|
|
1445
|
-
|
|
1446
|
-
// ../core/src/presets.ts
|
|
1447
|
-
var PRESET_NAMES = [
|
|
1448
|
-
"draw-bloom",
|
|
1449
|
-
"punch-in",
|
|
1450
|
-
"rise-settle",
|
|
1451
|
-
"slide-bank",
|
|
1452
|
-
"reveal-orbit",
|
|
1453
|
-
"spin-forge"
|
|
1454
|
-
];
|
|
1455
|
-
function makeRng2(seed) {
|
|
1456
|
-
let a = seed >>> 0 || 2654435769;
|
|
1457
|
-
return () => {
|
|
1458
|
-
a = a + 1831565813 | 0;
|
|
1459
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
1460
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
1461
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1462
|
-
};
|
|
1463
|
-
}
|
|
1464
|
-
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
1465
|
-
var SET = 1 / 120;
|
|
1466
|
-
function ctx(o) {
|
|
1467
|
-
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
1468
|
-
return {
|
|
1469
|
-
e: clamp01(o.energy ?? 0.5),
|
|
1470
|
-
sp: Math.max(0.25, o.speed ?? 1),
|
|
1471
|
-
it: clamp01(o.intensity ?? 0.5),
|
|
1472
|
-
from: o.from,
|
|
1473
|
-
rand: rand2,
|
|
1474
|
-
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
1475
|
-
g: o.target.group,
|
|
1476
|
-
cx: o.target.center[0],
|
|
1477
|
-
cy: o.target.center[1],
|
|
1478
|
-
s: o.target.baseScale,
|
|
1479
|
-
fills: o.target.fills,
|
|
1480
|
-
inks: o.target.inks
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
var dur = (base, sp) => base / sp;
|
|
1484
|
-
function settleEase(e) {
|
|
1485
|
-
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
1486
|
-
}
|
|
1487
|
-
function fromVec(from, dist) {
|
|
1488
|
-
switch (from) {
|
|
1489
|
-
case "left":
|
|
1490
|
-
return [-dist, 0];
|
|
1491
|
-
case "right":
|
|
1492
|
-
return [dist, 0];
|
|
1493
|
-
case "top":
|
|
1494
|
-
return [0, -dist];
|
|
1495
|
-
default:
|
|
1496
|
-
return [0, dist];
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
1500
|
-
return stagger(
|
|
1501
|
-
gap / c.sp,
|
|
1502
|
-
...c.fills.map(
|
|
1503
|
-
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
1504
|
-
)
|
|
1505
|
-
);
|
|
1506
|
-
}
|
|
1507
|
-
function drawInks(c) {
|
|
1508
|
-
return stagger(
|
|
1509
|
-
0.15 / c.sp,
|
|
1510
|
-
...c.inks.map(
|
|
1511
|
-
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
1512
|
-
)
|
|
1513
|
-
);
|
|
1514
|
-
}
|
|
1515
|
-
function motionPreset(name, opts) {
|
|
1516
|
-
const c = ctx(opts);
|
|
1517
|
-
switch (name) {
|
|
1518
|
-
case "draw-bloom":
|
|
1519
|
-
return beat("draw-bloom", {}, [
|
|
1520
|
-
drawInks(c),
|
|
1521
|
-
fadeFills(c, 0.45),
|
|
1522
|
-
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
1523
|
-
]);
|
|
1524
|
-
case "punch-in": {
|
|
1525
|
-
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
1526
|
-
return beat("punch-in", {}, [
|
|
1527
|
-
par(
|
|
1528
|
-
fadeFills(c, 0.25),
|
|
1529
|
-
seq(
|
|
1530
|
-
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
1531
|
-
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
1532
|
-
)
|
|
1533
|
-
)
|
|
1534
|
-
]);
|
|
1416
|
+
if (active) {
|
|
1417
|
+
segStart = active.t0;
|
|
1418
|
+
if (t >= active.t1) {
|
|
1419
|
+
value = active.to;
|
|
1420
|
+
} else {
|
|
1421
|
+
const u = resolveEase(active.ease)((t - active.t0) / (active.t1 - active.t0));
|
|
1422
|
+
value = lerpValue(active.from, active.to, u);
|
|
1423
|
+
}
|
|
1535
1424
|
}
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
fadeFills(c, 0.4)
|
|
1553
|
-
)
|
|
1554
|
-
]);
|
|
1425
|
+
}
|
|
1426
|
+
if (prop === "x" || prop === "y" || prop === "rotation") {
|
|
1427
|
+
const drivers = compiled.motionPaths.get(target);
|
|
1428
|
+
if (drivers) {
|
|
1429
|
+
let active;
|
|
1430
|
+
for (const d of drivers) {
|
|
1431
|
+
if (d.t0 <= t) active = d;
|
|
1432
|
+
else break;
|
|
1433
|
+
}
|
|
1434
|
+
if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
|
|
1435
|
+
const span = active.t1 - active.t0;
|
|
1436
|
+
const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
|
|
1437
|
+
if (prop === "x") value = pathPoint(active.points, active.closed, u, active.curviness)[0];
|
|
1438
|
+
else if (prop === "y") value = pathPoint(active.points, active.closed, u, active.curviness)[1];
|
|
1439
|
+
else value = pathTangentAngle(active.points, active.closed, u, active.curviness) + active.rotateOffset;
|
|
1440
|
+
}
|
|
1555
1441
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
const midx = c.jit(120);
|
|
1562
|
-
const move = dur(1.2, c.sp);
|
|
1563
|
-
return beat("slide-bank", {}, [
|
|
1564
|
-
par(
|
|
1565
|
-
motionPath(
|
|
1566
|
-
c.g,
|
|
1567
|
-
[
|
|
1568
|
-
[c.cx + dx, c.cy + dy],
|
|
1569
|
-
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
1570
|
-
[c.cx, c.cy]
|
|
1571
|
-
],
|
|
1572
|
-
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
1573
|
-
),
|
|
1574
|
-
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
1575
|
-
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
1576
|
-
fadeFills(c, 0.4)
|
|
1577
|
-
)
|
|
1578
|
-
]);
|
|
1442
|
+
}
|
|
1443
|
+
for (const b of compiled.ir.behaviors ?? []) {
|
|
1444
|
+
if (b.target === target && b.prop === prop && typeof value === "number") {
|
|
1445
|
+
const envelope = behaviorEnvelope(b, t);
|
|
1446
|
+
if (envelope > 0) value = value + envelope * sampleBehavior(b.behavior, t);
|
|
1579
1447
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
1594
|
-
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
1595
|
-
[c.cx, c.cy]
|
|
1596
|
-
],
|
|
1597
|
-
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
1598
|
-
),
|
|
1599
|
-
seq(
|
|
1600
|
-
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
1601
|
-
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
1602
|
-
)
|
|
1603
|
-
)
|
|
1604
|
-
]);
|
|
1448
|
+
}
|
|
1449
|
+
return value;
|
|
1450
|
+
}
|
|
1451
|
+
function nodeParentMatrix(compiled, id, t) {
|
|
1452
|
+
const num2 = (target, prop, fallback) => {
|
|
1453
|
+
const v = sampleProp(compiled, t, target, prop, fallback);
|
|
1454
|
+
return typeof v === "number" ? v : fallback;
|
|
1455
|
+
};
|
|
1456
|
+
let result = null;
|
|
1457
|
+
const walk = (node, parent) => {
|
|
1458
|
+
if (node.id === id) {
|
|
1459
|
+
result = parent;
|
|
1460
|
+
return true;
|
|
1605
1461
|
}
|
|
1606
|
-
|
|
1607
|
-
const
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
),
|
|
1619
|
-
seq(wait(SET), fadeFills(c, 0.3))
|
|
1462
|
+
if (node.type === "group") {
|
|
1463
|
+
const m = multiply(
|
|
1464
|
+
parent,
|
|
1465
|
+
localMatrix(
|
|
1466
|
+
num2(node.id, "x", node.props.x),
|
|
1467
|
+
num2(node.id, "y", node.props.y),
|
|
1468
|
+
num2(node.id, "rotation", node.props.rotation ?? 0),
|
|
1469
|
+
num2(node.id, "scale", node.props.scale ?? 1),
|
|
1470
|
+
num2(node.id, "scaleX", node.props.scaleX ?? 1),
|
|
1471
|
+
num2(node.id, "scaleY", node.props.scaleY ?? 1),
|
|
1472
|
+
num2(node.id, "skewX", node.props.skewX ?? 0),
|
|
1473
|
+
num2(node.id, "skewY", node.props.skewY ?? 0)
|
|
1620
1474
|
)
|
|
1621
|
-
|
|
1475
|
+
);
|
|
1476
|
+
for (const child of node.children) if (walk(child, m)) return true;
|
|
1622
1477
|
}
|
|
1623
|
-
|
|
1478
|
+
return false;
|
|
1479
|
+
};
|
|
1480
|
+
for (const node of compiled.ir.nodes) if (walk(node, IDENTITY)) break;
|
|
1481
|
+
return result;
|
|
1624
1482
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
};
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
monitor: { width: 1120, height: 860 },
|
|
1649
|
-
tv: { width: 1340, height: 920 },
|
|
1650
|
-
foldable: { width: 800, height: 600 },
|
|
1651
|
-
terminal: { width: 916, height: 636 },
|
|
1652
|
-
car: { width: 1060, height: 600 }
|
|
1653
|
-
};
|
|
1654
|
-
var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
|
|
1655
|
-
function screenDims(name, o) {
|
|
1656
|
-
const d = SCREENS[name];
|
|
1657
|
-
const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
|
|
1658
|
-
return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
|
|
1659
|
-
}
|
|
1660
|
-
function deviceScreen(name, opts = {}) {
|
|
1661
|
-
const d = screenDims(name, opts);
|
|
1662
|
-
return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
|
|
1663
|
-
}
|
|
1664
|
-
function deviceScreenCenter(name, opts = {}) {
|
|
1665
|
-
const d = screenDims(name, opts);
|
|
1666
|
-
return { x: d.cx, y: d.cy };
|
|
1667
|
-
}
|
|
1668
|
-
function deviceBounds(name, opts = {}) {
|
|
1669
|
-
const b = BOUNDS[name];
|
|
1670
|
-
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
1671
|
-
}
|
|
1672
|
-
function deviceScreenPoint(name, opts, local) {
|
|
1673
|
-
const c = deviceScreenCenter(name, opts);
|
|
1674
|
-
const s = opts.scale ?? 1;
|
|
1675
|
-
return [(opts.x ?? 0) + s * (c.x + local[0]), (opts.y ?? 0) + s * (c.y + local[1])];
|
|
1676
|
-
}
|
|
1677
|
-
function screenGroup(id, p, o, cx, cy, dims, content) {
|
|
1678
|
-
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 } }, [
|
|
1679
|
-
rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
|
|
1680
|
-
group({ id: `${id}-content`, x: 0, y: 0 }, content)
|
|
1681
|
-
]);
|
|
1682
|
-
}
|
|
1683
|
-
function buildDevice(name, id, p, o, content) {
|
|
1684
|
-
const dims = screenDims(name, o);
|
|
1685
|
-
const sw = dims.width;
|
|
1686
|
-
const sh = dims.height;
|
|
1687
|
-
const screen = () => screenGroup(id, p, o, dims.cx, dims.cy, dims, content);
|
|
1688
|
-
switch (name) {
|
|
1689
|
-
case "phone":
|
|
1690
|
-
case "tablet": {
|
|
1691
|
-
const bezel = name === "phone" ? 20 : 28;
|
|
1692
|
-
const bodyW = sw + bezel * 2;
|
|
1693
|
-
const bodyH = sh + bezel * 2;
|
|
1694
|
-
const bodyR = name === "phone" ? 54 : 34;
|
|
1695
|
-
const land = isLandscape(name, o);
|
|
1696
|
-
const nodes = [
|
|
1697
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: bodyR }),
|
|
1698
|
-
screen()
|
|
1699
|
-
];
|
|
1700
|
-
if (name === "phone") {
|
|
1701
|
-
nodes.push(
|
|
1702
|
-
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 }),
|
|
1703
|
-
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 })
|
|
1704
|
-
);
|
|
1705
|
-
if (!land) {
|
|
1706
|
-
nodes.push(
|
|
1707
|
-
rect({ id: `${id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
|
|
1708
|
-
rect({ id: `${id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
|
|
1709
|
-
rect({ id: `${id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
} else {
|
|
1713
|
-
nodes.push(
|
|
1714
|
-
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 }),
|
|
1715
|
-
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 })
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
return nodes;
|
|
1719
|
-
}
|
|
1720
|
-
case "laptop": {
|
|
1721
|
-
const lidTop = dims.cy - (sh + 40) / 2;
|
|
1722
|
-
const keyRows = [0, 1, 2, 3].map(
|
|
1723
|
-
(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 })
|
|
1724
|
-
);
|
|
1725
|
-
return [
|
|
1726
|
-
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 }),
|
|
1727
|
-
rect({ id: `${id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
1728
|
-
rect({ id: `${id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
1729
|
-
...keyRows,
|
|
1730
|
-
rect({ id: `${id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
|
|
1731
|
-
rect({ id: `${id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
|
|
1732
|
-
screen(),
|
|
1733
|
-
ellipse({ id: `${id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
1734
|
-
rect({ id: `${id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: p.bodyStroke, strokeWidth: 2, radius: 18 })
|
|
1735
|
-
];
|
|
1736
|
-
}
|
|
1737
|
-
case "browser": {
|
|
1738
|
-
const winW = sw + 16;
|
|
1739
|
-
const winH = sh + 92;
|
|
1740
|
-
const barY = -winH / 2 + 24;
|
|
1741
|
-
return [
|
|
1742
|
-
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 }),
|
|
1743
|
-
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
|
|
1744
|
-
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
|
|
1745
|
-
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
|
|
1746
|
-
// an active tab tucked under the lights
|
|
1747
|
-
rect({ id: `${id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: o.screen ?? p.screen, radius: 8 }),
|
|
1748
|
-
text({ id: `${id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
1749
|
-
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 }),
|
|
1750
|
-
rect({ id: `${id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
|
|
1751
|
-
text({ id: `${id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
|
|
1752
|
-
screen()
|
|
1753
|
-
];
|
|
1754
|
-
}
|
|
1755
|
-
case "watch": {
|
|
1756
|
-
const bw = sw + 36;
|
|
1757
|
-
const bh = sh + 36;
|
|
1758
|
-
return [
|
|
1759
|
-
// straps (drawn behind the body) flaring out top & bottom
|
|
1760
|
-
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 }),
|
|
1761
|
-
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 }),
|
|
1762
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bw, height: bh, fill: p.body, stroke: p.bodyStroke, strokeWidth: 3, radius: 60 }),
|
|
1763
|
-
screen(),
|
|
1764
|
-
rect({ id: `${id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
|
|
1765
|
-
rect({ id: `${id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
|
|
1766
|
-
];
|
|
1767
|
-
}
|
|
1768
|
-
case "monitor": {
|
|
1769
|
-
const panelW = sw + 44;
|
|
1770
|
-
const panelH = sh + 60;
|
|
1771
|
-
return [
|
|
1772
|
-
rect({ id: `${id}-panel`, x: 0, y: 0, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 16 }),
|
|
1773
|
-
screen(),
|
|
1774
|
-
ellipse({ id: `${id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
|
|
1775
|
-
rect({ id: `${id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
|
|
1776
|
-
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 })
|
|
1777
|
-
];
|
|
1778
|
-
}
|
|
1779
|
-
case "tv": {
|
|
1780
|
-
const panelW = sw + 44;
|
|
1781
|
-
const panelH = sh + 48;
|
|
1782
|
-
const panelBottom = dims.cy + panelH / 2;
|
|
1783
|
-
return [
|
|
1784
|
-
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 }),
|
|
1785
|
-
screen(),
|
|
1786
|
-
ellipse({ id: `${id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
1787
|
-
rect({ id: `${id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
|
|
1788
|
-
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 })
|
|
1789
|
-
];
|
|
1790
|
-
}
|
|
1791
|
-
case "foldable": {
|
|
1792
|
-
const bodyW = sw + 40;
|
|
1793
|
-
const bodyH = sh + 40;
|
|
1794
|
-
return [
|
|
1795
|
-
rect({ id: `${id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
1796
|
-
rect({ id: `${id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
1797
|
-
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 28 }),
|
|
1798
|
-
screen(),
|
|
1799
|
-
rect({ id: `${id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
|
|
1800
|
-
ellipse({ id: `${id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
|
|
1801
|
-
ellipse({ id: `${id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
|
|
1802
|
-
];
|
|
1483
|
+
function evaluate(compiled, t) {
|
|
1484
|
+
const ops = [];
|
|
1485
|
+
const valueAt = (target, prop, fallback) => sampleProp(compiled, t, target, prop, fallback);
|
|
1486
|
+
const num2 = (target, prop, fallback) => {
|
|
1487
|
+
const v = valueAt(target, prop, fallback);
|
|
1488
|
+
return typeof v === "number" ? v : fallback;
|
|
1489
|
+
};
|
|
1490
|
+
const str = (target, prop, fallback) => {
|
|
1491
|
+
const v = valueAt(target, prop, fallback);
|
|
1492
|
+
return typeof v === "string" ? v : String(v);
|
|
1493
|
+
};
|
|
1494
|
+
const opt = (target, prop, base) => {
|
|
1495
|
+
const v = valueAt(target, prop, base ?? "");
|
|
1496
|
+
return v === "" && base === void 0 ? void 0 : String(v);
|
|
1497
|
+
};
|
|
1498
|
+
const effectFx = (id, p) => {
|
|
1499
|
+
const fx = {};
|
|
1500
|
+
if (p.blur !== void 0) fx.blur = num2(id, "blur", p.blur);
|
|
1501
|
+
if (p.shadowColor !== void 0) {
|
|
1502
|
+
fx.shadowColor = str(id, "shadowColor", p.shadowColor);
|
|
1503
|
+
fx.shadowBlur = num2(id, "shadowBlur", p.shadowBlur ?? 0);
|
|
1504
|
+
fx.shadowX = num2(id, "shadowX", p.shadowX ?? 0);
|
|
1505
|
+
fx.shadowY = num2(id, "shadowY", p.shadowY ?? 0);
|
|
1803
1506
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1507
|
+
if (p.blend !== void 0 && p.blend !== "normal") fx.blend = p.blend;
|
|
1508
|
+
return fx;
|
|
1509
|
+
};
|
|
1510
|
+
const persp = compiled.hasPerspective;
|
|
1511
|
+
const dPersp = persp ? num2("camera", "perspective", 0) : 0;
|
|
1512
|
+
const vx = persp ? compiled.ir.size.width / 2 : 0;
|
|
1513
|
+
const vy = persp ? compiled.ir.size.height / 2 : 0;
|
|
1514
|
+
const aperture = persp ? num2("camera", "aperture", 0) : 0;
|
|
1515
|
+
const focus = persp ? num2("camera", "focus", 0) : 0;
|
|
1516
|
+
const dofFx = (fx, depth, project) => {
|
|
1517
|
+
if (!project || aperture <= 0) return fx;
|
|
1518
|
+
const extra = aperture * Math.abs(depth - focus);
|
|
1519
|
+
if (extra <= 0) return fx;
|
|
1520
|
+
return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
|
|
1521
|
+
};
|
|
1522
|
+
const zSort = compiled.zSort;
|
|
1523
|
+
const depthOf = (node, zAcc) => zAcc + num2(node.id, "z", node.props.z ?? 0);
|
|
1524
|
+
const depthOrder = (children, zAcc) => [...children].sort((a, b) => depthOf(b, zAcc) - depthOf(a, zAcc));
|
|
1525
|
+
const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
|
|
1526
|
+
const id = node.id;
|
|
1527
|
+
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
1528
|
+
const fx = effectFx(id, node.props);
|
|
1529
|
+
if (node.type === "line") {
|
|
1530
|
+
const opacity2 = parentOpacity * num2(id, "opacity", node.props.opacity ?? 1);
|
|
1531
|
+
if (opacity2 <= 0) return;
|
|
1532
|
+
const progress = Math.max(0, Math.min(1, num2(id, "progress", node.props.progress ?? 1)));
|
|
1533
|
+
const x1 = num2(id, "x1", node.props.x1);
|
|
1534
|
+
const y1 = num2(id, "y1", node.props.y1);
|
|
1535
|
+
ops.push({
|
|
1536
|
+
type: "line",
|
|
1537
|
+
id,
|
|
1538
|
+
// a line carries no z/rotate of its own — it just inherits the subtree's depth
|
|
1539
|
+
transform: project ? projectDepth(parent, zAcc, vx, vy, dPersp) : parent,
|
|
1540
|
+
opacity: opacity2,
|
|
1541
|
+
x1,
|
|
1542
|
+
y1,
|
|
1543
|
+
x2: x1 + (num2(id, "x2", node.props.x2) - x1) * progress,
|
|
1544
|
+
y2: y1 + (num2(id, "y2", node.props.y2) - y1) * progress,
|
|
1545
|
+
stroke: str(id, "stroke", node.props.stroke),
|
|
1546
|
+
strokeWidth: num2(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
1547
|
+
// a line carries no z of its own — DOF uses the inherited subtree depth
|
|
1548
|
+
...dofFx(fx, zAcc, project),
|
|
1549
|
+
...clipSpread
|
|
1550
|
+
});
|
|
1551
|
+
return;
|
|
1816
1552
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1553
|
+
const opacity = parentOpacity * num2(id, "opacity", node.props.opacity ?? 1);
|
|
1554
|
+
if (opacity <= 0) return;
|
|
1555
|
+
let effScaleX = num2(id, "scaleX", node.props.scaleX ?? 1);
|
|
1556
|
+
let effScaleY = num2(id, "scaleY", node.props.scaleY ?? 1);
|
|
1557
|
+
let depth = zAcc;
|
|
1558
|
+
let rotX = 0;
|
|
1559
|
+
let rotY = 0;
|
|
1560
|
+
if (project) {
|
|
1561
|
+
rotX = num2(id, "rotateX", node.props.rotateX ?? 0);
|
|
1562
|
+
rotY = num2(id, "rotateY", node.props.rotateY ?? 0);
|
|
1563
|
+
depth = zAcc + num2(id, "z", node.props.z ?? 0);
|
|
1564
|
+
if (rotY !== 0) effScaleX *= Math.abs(Math.cos(rotY * DEG));
|
|
1565
|
+
if (rotX !== 0) effScaleY *= Math.abs(Math.cos(rotX * DEG));
|
|
1828
1566
|
}
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
{
|
|
1841
|
-
id,
|
|
1842
|
-
x: opts.x ?? 0,
|
|
1843
|
-
y: opts.y ?? 0,
|
|
1844
|
-
...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
|
|
1845
|
-
...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
|
|
1846
|
-
},
|
|
1847
|
-
children
|
|
1848
|
-
);
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
// ../core/src/cursor.ts
|
|
1852
|
-
var ARROW_D = "M0 0 L0 30 L8 23 L12.6 33 L17 31 L12.4 21.4 L21 21.4 Z";
|
|
1853
|
-
function cursor(opts = {}) {
|
|
1854
|
-
const id = opts.id ?? "cursor";
|
|
1855
|
-
const style = opts.style ?? "arrow";
|
|
1856
|
-
const fill = opts.fill ?? "#FFFFFF";
|
|
1857
|
-
const accent = opts.accent ?? "#FF5A1F";
|
|
1858
|
-
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" })];
|
|
1859
|
-
return group(
|
|
1860
|
-
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
1861
|
-
[
|
|
1862
|
-
// ripple ring (behind the pointer), emanates from the hotspot on click
|
|
1863
|
-
ellipse({ id: `${id}-ripple`, x: 0, y: 0, width: 30, height: 30, fill: "none", stroke: accent, strokeWidth: 3, opacity: 0, scale: 0, anchor: "center" }),
|
|
1864
|
-
// the pointer art lives in its own group so a click "tap" can scale it
|
|
1865
|
-
// independently of the cursor's resting scale
|
|
1866
|
-
group({ id: `${id}-art`, x: 0, y: 0 }, art)
|
|
1867
|
-
]
|
|
1868
|
-
);
|
|
1869
|
-
}
|
|
1870
|
-
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
1871
|
-
function cursorTo(id, from, to2, opts = {}) {
|
|
1872
|
-
const dx = to2[0] - from[0], dy = to2[1] - from[1];
|
|
1873
|
-
const dist = Math.hypot(dx, dy) || 1;
|
|
1874
|
-
const arc = opts.arc ?? 0.12;
|
|
1875
|
-
const mid = [(from[0] + to2[0]) / 2 + -dy / dist * arc * dist, (from[1] + to2[1]) / 2 + dx / dist * arc * dist];
|
|
1876
|
-
const duration = opts.duration ?? clamp(dist / 1400, 0.4, 0.9);
|
|
1877
|
-
return motionPath(id, [from, mid, to2], { duration, ease: opts.ease ?? "easeInOutCubic", curviness: 1, ...opts.label && { label: opts.label } });
|
|
1878
|
-
}
|
|
1879
|
-
function cursorPath(id, points, opts = {}) {
|
|
1880
|
-
return motionPath(id, points, {
|
|
1881
|
-
duration: opts.duration ?? clamp(points.length * 0.5, 0.5, 4),
|
|
1882
|
-
ease: opts.ease ?? "easeInOutCubic",
|
|
1883
|
-
curviness: opts.curviness ?? 1,
|
|
1884
|
-
...opts.label && { label: opts.label }
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
function clickBody(id, o) {
|
|
1888
|
-
const sp = Math.max(0.25, o.speed ?? 1);
|
|
1889
|
-
const d = (b) => b / sp;
|
|
1890
|
-
const out = [
|
|
1891
|
-
// the pointer taps
|
|
1892
|
-
seq(tween(`${id}-art`, { scale: 0.82 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(`${id}-art`, { scale: 1 }, { duration: d(0.1), ease: "easeOutBack" }))
|
|
1893
|
-
];
|
|
1894
|
-
if (o.ripple !== false) {
|
|
1895
|
-
out.push(seq(
|
|
1896
|
-
tween(`${id}-ripple`, { scale: 0.2, opacity: 0.55 }, { duration: 1e-3 }),
|
|
1897
|
-
par(
|
|
1898
|
-
tween(`${id}-ripple`, { scale: 5 }, { duration: d(0.5), ease: "easeOutCubic" }),
|
|
1899
|
-
tween(`${id}-ripple`, { opacity: 0 }, { duration: d(0.5), ease: "easeOutQuad" })
|
|
1567
|
+
const matrix = multiply(
|
|
1568
|
+
parent,
|
|
1569
|
+
localMatrix(
|
|
1570
|
+
num2(id, "x", node.props.x),
|
|
1571
|
+
num2(id, "y", node.props.y),
|
|
1572
|
+
num2(id, "rotation", node.props.rotation ?? 0),
|
|
1573
|
+
num2(id, "scale", node.props.scale ?? 1),
|
|
1574
|
+
effScaleX,
|
|
1575
|
+
effScaleY,
|
|
1576
|
+
num2(id, "skewX", node.props.skewX ?? 0),
|
|
1577
|
+
num2(id, "skewY", node.props.skewY ?? 0)
|
|
1900
1578
|
)
|
|
1901
|
-
)
|
|
1579
|
+
);
|
|
1580
|
+
const projDraw = (m, hw, hh) => {
|
|
1581
|
+
if (!project) return m;
|
|
1582
|
+
const tilted = rotX !== 0 || rotY !== 0 ? tiltSkew(m, rotX, rotY, hw, hh, dPersp) : m;
|
|
1583
|
+
return projectDepth(tilted, depth, vx, vy, dPersp);
|
|
1584
|
+
};
|
|
1585
|
+
const leafFx = dofFx(fx, depth, project);
|
|
1586
|
+
switch (node.type) {
|
|
1587
|
+
case "group": {
|
|
1588
|
+
const clipTf = projDraw(matrix, 0, 0);
|
|
1589
|
+
const childClips = node.props.clip ? [...clips, { transform: clipTf, shape: node.props.clip }] : clips;
|
|
1590
|
+
const hasFx = fx.blur !== void 0 || fx.shadowColor !== void 0 || fx.blend !== void 0;
|
|
1591
|
+
if (hasFx) ops.push({ type: "group-fx-push", id, transform: matrix, opacity, ...fx, ...clipSpread });
|
|
1592
|
+
if (node.props.matte && node.children.length >= 2) {
|
|
1593
|
+
ops.push({ type: "matte-push", id, transform: matrix, opacity, mode: node.props.matte, ...clipSpread });
|
|
1594
|
+
walk(node.children[0], matrix, opacity, childClips, depth, project);
|
|
1595
|
+
ops.push({ type: "matte-sep", id, transform: matrix, opacity });
|
|
1596
|
+
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
|
|
1597
|
+
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
1598
|
+
} else {
|
|
1599
|
+
const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
|
|
1600
|
+
for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
|
|
1601
|
+
}
|
|
1602
|
+
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
case "rect":
|
|
1606
|
+
case "ellipse": {
|
|
1607
|
+
const width = num2(id, "width", node.props.width);
|
|
1608
|
+
const height = num2(id, "height", node.props.height);
|
|
1609
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
1610
|
+
const strokeWidth = num2(id, "strokeWidth", node.props.strokeWidth ?? 1);
|
|
1611
|
+
const fillP = node.props.fill;
|
|
1612
|
+
const strokeP = node.props.stroke;
|
|
1613
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
1614
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
1615
|
+
ops.push({
|
|
1616
|
+
type: node.type,
|
|
1617
|
+
id,
|
|
1618
|
+
transform: projDraw(matrix, width / 2, height / 2),
|
|
1619
|
+
opacity,
|
|
1620
|
+
width,
|
|
1621
|
+
height,
|
|
1622
|
+
offsetX: -width * ax,
|
|
1623
|
+
offsetY: -height * ay,
|
|
1624
|
+
...fill !== void 0 && { fill },
|
|
1625
|
+
...stroke !== void 0 && { stroke, strokeWidth },
|
|
1626
|
+
...node.type === "rect" && { radius: num2(id, "radius", node.props.radius ?? 0) },
|
|
1627
|
+
...leafFx,
|
|
1628
|
+
...clipSpread
|
|
1629
|
+
});
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
case "image": {
|
|
1633
|
+
const width = num2(id, "width", node.props.width);
|
|
1634
|
+
const height = num2(id, "height", node.props.height);
|
|
1635
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
1636
|
+
ops.push({
|
|
1637
|
+
type: "image",
|
|
1638
|
+
id,
|
|
1639
|
+
transform: projDraw(matrix, width / 2, height / 2),
|
|
1640
|
+
opacity,
|
|
1641
|
+
src: str(id, "src", node.props.src),
|
|
1642
|
+
width,
|
|
1643
|
+
height,
|
|
1644
|
+
offsetX: -width * ax,
|
|
1645
|
+
offsetY: -height * ay,
|
|
1646
|
+
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
1647
|
+
...leafFx,
|
|
1648
|
+
...clipSpread
|
|
1649
|
+
});
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
case "video": {
|
|
1653
|
+
const width = num2(id, "width", node.props.width);
|
|
1654
|
+
const height = num2(id, "height", node.props.height);
|
|
1655
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
1656
|
+
const fps = compiled.ir.fps ?? 30;
|
|
1657
|
+
const start = node.props.start ?? 0;
|
|
1658
|
+
const rate = node.props.rate ?? 1;
|
|
1659
|
+
const clipStart = node.props.clipStart ?? 0;
|
|
1660
|
+
const srcT = clipStart + Math.max(0, t - start) * rate;
|
|
1661
|
+
const frame = Math.max(0, Math.round(srcT * fps));
|
|
1662
|
+
ops.push({
|
|
1663
|
+
type: "video",
|
|
1664
|
+
id,
|
|
1665
|
+
transform: projDraw(matrix, width / 2, height / 2),
|
|
1666
|
+
opacity,
|
|
1667
|
+
src: str(id, "src", node.props.src),
|
|
1668
|
+
width,
|
|
1669
|
+
height,
|
|
1670
|
+
offsetX: -width * ax,
|
|
1671
|
+
offsetY: -height * ay,
|
|
1672
|
+
frame,
|
|
1673
|
+
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
1674
|
+
...leafFx,
|
|
1675
|
+
...clipSpread
|
|
1676
|
+
});
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
case "path": {
|
|
1680
|
+
const ox = num2(id, "originX", node.props.originX ?? 0);
|
|
1681
|
+
const oy = num2(id, "originY", node.props.originY ?? 0);
|
|
1682
|
+
const fillP = node.props.fill;
|
|
1683
|
+
const strokeP = node.props.stroke;
|
|
1684
|
+
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
1685
|
+
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
1686
|
+
const dStr = str(id, "d", node.props.d);
|
|
1687
|
+
const needsBox = isGradient(fill) || isGradient(stroke);
|
|
1688
|
+
ops.push({
|
|
1689
|
+
type: "path",
|
|
1690
|
+
id,
|
|
1691
|
+
// origin-shift in local space, then project (no per-op extent → cos + VP only)
|
|
1692
|
+
transform: projDraw(ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]), 0, 0),
|
|
1693
|
+
opacity,
|
|
1694
|
+
d: dStr,
|
|
1695
|
+
progress: Math.max(0, Math.min(1, num2(id, "progress", node.props.progress ?? 1))),
|
|
1696
|
+
...fill !== void 0 && { fill },
|
|
1697
|
+
...stroke !== void 0 && { stroke, strokeWidth: num2(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
1698
|
+
...needsBox && { bbox: pathBBox(dStr) },
|
|
1699
|
+
...leafFx,
|
|
1700
|
+
...clipSpread
|
|
1701
|
+
});
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
case "text": {
|
|
1705
|
+
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
1706
|
+
const raw = valueAt(id, "content", node.props.content);
|
|
1707
|
+
const decimals = Math.max(
|
|
1708
|
+
0,
|
|
1709
|
+
Math.round(num2(id, "contentDecimals", node.props.contentDecimals ?? 0))
|
|
1710
|
+
);
|
|
1711
|
+
const body = typeof raw === "number" ? formatNumber(raw, decimals, node.props.contentThousands === true) : raw;
|
|
1712
|
+
ops.push({
|
|
1713
|
+
type: "text",
|
|
1714
|
+
id,
|
|
1715
|
+
transform: projDraw(matrix, 0, 0),
|
|
1716
|
+
opacity,
|
|
1717
|
+
// static affixes wrap the (possibly counting-up) body; absent ⇒ body unchanged
|
|
1718
|
+
content: (node.props.prefix ?? "") + body + (node.props.suffix ?? ""),
|
|
1719
|
+
fontFamily: str(id, "fontFamily", node.props.fontFamily),
|
|
1720
|
+
fontSize: num2(id, "fontSize", node.props.fontSize),
|
|
1721
|
+
fontWeight: num2(id, "fontWeight", node.props.fontWeight ?? 400),
|
|
1722
|
+
fill: str(id, "fill", node.props.fill ?? "#ffffff"),
|
|
1723
|
+
letterSpacing: num2(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
1724
|
+
align: TEXT_ALIGN[ax] ?? "left",
|
|
1725
|
+
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
1726
|
+
...leafFx,
|
|
1727
|
+
...clipSpread
|
|
1728
|
+
});
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
const cameraRoot = compiled.hasCamera ? cameraMatrix(
|
|
1734
|
+
{
|
|
1735
|
+
x: num2("camera", "x", compiled.ir.size.width / 2),
|
|
1736
|
+
y: num2("camera", "y", compiled.ir.size.height / 2),
|
|
1737
|
+
zoom: num2("camera", "zoom", 1),
|
|
1738
|
+
rotation: num2("camera", "rotation", 0)
|
|
1739
|
+
},
|
|
1740
|
+
compiled.ir.size
|
|
1741
|
+
) : IDENTITY;
|
|
1742
|
+
let roots = compiled.ir.nodes;
|
|
1743
|
+
if (zSort) {
|
|
1744
|
+
const isHud = (n3) => !!(n3.props.fixed && compiled.hasCamera);
|
|
1745
|
+
roots = [...depthOrder(compiled.ir.nodes.filter((n3) => !isHud(n3)), 0), ...compiled.ir.nodes.filter(isHud)];
|
|
1902
1746
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1747
|
+
for (const node of roots) {
|
|
1748
|
+
const root = compiled.hasCamera && node.props.fixed ? IDENTITY : cameraRoot;
|
|
1749
|
+
const project = persp && !(node.props.fixed && compiled.hasCamera);
|
|
1750
|
+
walk(node, root, 1, [], 0, project);
|
|
1905
1751
|
}
|
|
1906
|
-
return
|
|
1907
|
-
}
|
|
1908
|
-
function cursorClick(id, opts = {}) {
|
|
1909
|
-
return beat(opts.label ?? "cursor-click", {}, [par(...clickBody(id, opts))]);
|
|
1752
|
+
return ops;
|
|
1910
1753
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1754
|
+
|
|
1755
|
+
// ../core/src/autoFoley.ts
|
|
1756
|
+
var V_MIN = 360;
|
|
1757
|
+
var V_STOP = 60;
|
|
1758
|
+
var V_DECEL = 520;
|
|
1759
|
+
var V_MAX = 2600;
|
|
1760
|
+
var MIN_DUR = 0.1;
|
|
1761
|
+
var num = (c, t, id, prop, fb) => {
|
|
1762
|
+
const v = sampleProp(c, t, id, prop, fb);
|
|
1763
|
+
return typeof v === "number" ? v : fb;
|
|
1764
|
+
};
|
|
1765
|
+
function nodeSize(node) {
|
|
1766
|
+
const p = node.props;
|
|
1767
|
+
return Math.max(p.width ?? 0, p.height ?? 0, (p.radius ?? 0) * 2, p.fontSize ?? 0, 0);
|
|
1768
|
+
}
|
|
1769
|
+
var FAMILY = { whoosh: "move", swish: "move", thud: "hit", knock: "hit", pop: "pop" };
|
|
1770
|
+
var DEDUP_DT = 0.09;
|
|
1771
|
+
function autoFoley(compiled, opts = {}) {
|
|
1772
|
+
const master = opts.gain ?? 0.5;
|
|
1773
|
+
const sens = opts.sensitivity ?? 1;
|
|
1774
|
+
const wantWhoosh = opts.whoosh !== false;
|
|
1775
|
+
const wantImpact = opts.impact !== false;
|
|
1776
|
+
const wantPop = opts.pop !== false;
|
|
1777
|
+
const wantPan = opts.pan !== false;
|
|
1778
|
+
const vMin = V_MIN / sens, vStop = V_STOP, vDecel = V_DECEL / sens;
|
|
1779
|
+
const fps = compiled.ir.fps ?? 30;
|
|
1780
|
+
const N = Math.max(1, Math.ceil(compiled.duration * fps));
|
|
1781
|
+
const W = compiled.ir.size.width || 1920;
|
|
1782
|
+
const ids = opts.nodes ?? [...compiled.nodeById.keys()];
|
|
1783
|
+
const cands = [];
|
|
1784
|
+
const panOf = (x) => wantPan ? Math.max(-1, Math.min(1, x / W * 2 - 1)) : 0;
|
|
1785
|
+
const loud = (v) => Math.max(0.2, Math.min(1, (v - vMin) / (V_MAX - vMin)));
|
|
1786
|
+
for (const id of ids) {
|
|
1787
|
+
const node = compiled.nodeById.get(id);
|
|
1788
|
+
if (!node || node.type === "line") continue;
|
|
1789
|
+
const size = nodeSize(node);
|
|
1790
|
+
const xs = [], ys = [], ss = [], os = [];
|
|
1791
|
+
for (let i2 = 0; i2 <= N; i2++) {
|
|
1792
|
+
const t = i2 / fps;
|
|
1793
|
+
xs.push(num(compiled, t, id, "x", node.props.x ?? 0));
|
|
1794
|
+
ys.push(num(compiled, t, id, "y", node.props.y ?? 0));
|
|
1795
|
+
ss.push(num(compiled, t, id, "scale", node.props.scale ?? 1));
|
|
1796
|
+
os.push(num(compiled, t, id, "opacity", node.props.opacity ?? 1));
|
|
1797
|
+
}
|
|
1798
|
+
const speed = (i2) => i2 <= 0 ? 0 : Math.hypot(xs[i2] - xs[i2 - 1], ys[i2] - ys[i2 - 1]) * fps;
|
|
1799
|
+
let i = 1;
|
|
1800
|
+
while (i <= N) {
|
|
1801
|
+
if (speed(i) <= vMin) {
|
|
1802
|
+
i++;
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
const a = i;
|
|
1806
|
+
let peak = i, peakV = speed(i);
|
|
1807
|
+
while (i <= N && speed(i) > vStop) {
|
|
1808
|
+
const s = speed(i);
|
|
1809
|
+
if (s > peakV) {
|
|
1810
|
+
peakV = s;
|
|
1811
|
+
peak = i;
|
|
1812
|
+
}
|
|
1813
|
+
i++;
|
|
1814
|
+
}
|
|
1815
|
+
const b = i - 1;
|
|
1816
|
+
const durS = (b - a + 1) / fps;
|
|
1817
|
+
const visible = os[peak] > 0.1;
|
|
1818
|
+
if (peakV > vMin && durS >= MIN_DUR && visible) {
|
|
1819
|
+
if (wantWhoosh) {
|
|
1820
|
+
const quickFlick = durS < 0.25;
|
|
1821
|
+
cands.push({ t: peak / fps, sfx: quickFlick ? "swish" : "whoosh", gain: master * loud(peakV), pan: panOf(xs[peak]), rank: peakV });
|
|
1822
|
+
}
|
|
1823
|
+
const stopped = b >= N || speed(b + 1) < vStop && speed(Math.min(N, b + 2)) < vStop;
|
|
1824
|
+
const landsOnScreen = xs[b] >= 0 && xs[b] <= W && os[b] > 0.1;
|
|
1825
|
+
if (wantImpact && peakV > vDecel && stopped && landsOnScreen && b < N) {
|
|
1826
|
+
cands.push({ t: (b + 1) / fps, sfx: size > 220 ? "thud" : "knock", gain: master * loud(peakV), pan: panOf(xs[b]), rank: peakV * 1.1 });
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
if (wantPop && ss[0] < 0.25) {
|
|
1831
|
+
for (let k = 1; k <= N; k++) {
|
|
1832
|
+
if (ss[k - 1] < 0.5 && ss[k] >= 0.5 && os[k] > 0.05) {
|
|
1833
|
+
cands.push({ t: k / fps, sfx: "pop", gain: master * 0.7, pan: panOf(xs[k]), rank: 600 });
|
|
1834
|
+
break;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
cands.sort((p, q) => q.rank - p.rank);
|
|
1840
|
+
const kept = [];
|
|
1841
|
+
for (const c of cands) {
|
|
1842
|
+
const fam = FAMILY[c.sfx] ?? c.sfx;
|
|
1843
|
+
if (kept.some((k) => (FAMILY[k.sfx] ?? k.sfx) === fam && Math.abs(k.t - c.t) < DEDUP_DT)) continue;
|
|
1844
|
+
kept.push(c);
|
|
1845
|
+
}
|
|
1846
|
+
const max = opts.maxCues ?? 32;
|
|
1847
|
+
return kept.slice(0, max).map((c) => ({
|
|
1848
|
+
at: Number(c.t.toFixed(3)),
|
|
1849
|
+
sfx: c.sfx,
|
|
1850
|
+
gain: Number(c.gain.toFixed(3)),
|
|
1851
|
+
...c.pan !== 0 ? { pan: Number(c.pan.toFixed(3)) } : {}
|
|
1852
|
+
}));
|
|
1916
1853
|
}
|
|
1917
1854
|
|
|
1918
|
-
// ../core/src/
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
var LINE_W = 5;
|
|
1922
|
-
var GLOW_W = 16;
|
|
1923
|
-
var K = 0.5523;
|
|
1924
|
-
var n = (v) => Number(v.toFixed(2));
|
|
1925
|
-
function capsulePath(hw, len) {
|
|
1926
|
-
const yT = hw;
|
|
1927
|
-
const yB = Math.max(hw, len - hw);
|
|
1928
|
-
const k = hw * K;
|
|
1929
|
-
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`;
|
|
1855
|
+
// ../core/src/effects.ts
|
|
1856
|
+
function glow(color, blur = 24) {
|
|
1857
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: 0, shadowY: 0 };
|
|
1930
1858
|
}
|
|
1931
|
-
function
|
|
1932
|
-
|
|
1933
|
-
const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
|
|
1934
|
-
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`;
|
|
1859
|
+
function dropShadow(color, blur = 24, x = 0, y = 12) {
|
|
1860
|
+
return { shadowColor: color, shadowBlur: blur, shadowX: x, shadowY: y };
|
|
1935
1861
|
}
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
if (
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
if (
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
const
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
}
|
|
1953
|
-
function rig(root, opts = {}) {
|
|
1954
|
-
const id = opts.id ?? "rig";
|
|
1955
|
-
const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
|
|
1956
|
-
return group(
|
|
1957
|
-
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
1958
|
-
[buildBone(root, id, o)]
|
|
1959
|
-
);
|
|
1862
|
+
|
|
1863
|
+
// ../core/src/layout.ts
|
|
1864
|
+
function row(count, opts = {}) {
|
|
1865
|
+
if (count <= 0) return [];
|
|
1866
|
+
const center = opts.center ?? 0;
|
|
1867
|
+
if (count === 1) return [center];
|
|
1868
|
+
if (opts.span !== void 0) {
|
|
1869
|
+
const start2 = center - opts.span / 2;
|
|
1870
|
+
const pitch2 = opts.span / (count - 1);
|
|
1871
|
+
return Array.from({ length: count }, (_, i) => start2 + i * pitch2);
|
|
1872
|
+
}
|
|
1873
|
+
const iw = opts.itemWidth ?? 0;
|
|
1874
|
+
const gap = opts.gap ?? 0;
|
|
1875
|
+
const pitch = iw + gap;
|
|
1876
|
+
const total = count * iw + (count - 1) * gap;
|
|
1877
|
+
const start = center - total / 2 + iw / 2;
|
|
1878
|
+
return Array.from({ length: count }, (_, i) => start + i * pitch);
|
|
1960
1879
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1880
|
+
var column = row;
|
|
1881
|
+
function grid(rows, cols, opts = {}) {
|
|
1882
|
+
const axis = (center, gap, item, span) => ({
|
|
1883
|
+
center,
|
|
1884
|
+
...gap !== void 0 ? { gap } : {},
|
|
1885
|
+
...item !== void 0 ? { itemWidth: item } : {},
|
|
1886
|
+
...span !== void 0 ? { span } : {}
|
|
1887
|
+
});
|
|
1888
|
+
const xs = row(cols, axis(opts.center?.x ?? 0, opts.gapX, opts.cellW, opts.spanX));
|
|
1889
|
+
const ys = row(rows, axis(opts.center?.y ?? 0, opts.gapY, opts.cellH, opts.spanY));
|
|
1890
|
+
const out = [];
|
|
1891
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) out.push({ x: xs[c], y: ys[r] });
|
|
1964
1892
|
return out;
|
|
1965
1893
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const vy = upper + lower * Math.cos(theta2);
|
|
1978
|
-
const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
|
|
1979
|
-
const deg = (r) => r * 180 / Math.PI;
|
|
1980
|
-
return [deg(theta1), deg(theta2)];
|
|
1981
|
-
}
|
|
1982
|
-
function humanoid(opts = {}) {
|
|
1983
|
-
const line2 = opts.color ?? DEFAULT_LINE;
|
|
1984
|
-
const fill = opts.fill ?? DEFAULT_FILL;
|
|
1985
|
-
const glow2 = opts.glow;
|
|
1986
|
-
const blob = (jid, a, b, cy) => {
|
|
1987
|
-
const d = ovalPath(a, b, 0, cy);
|
|
1988
|
-
const nodes = [];
|
|
1989
|
-
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
1990
|
-
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
1991
|
-
return nodes;
|
|
1992
|
-
};
|
|
1993
|
-
const id = opts.id ?? "rig";
|
|
1994
|
-
const root = {
|
|
1995
|
-
name: "chest",
|
|
1996
|
-
at: [0, 0],
|
|
1997
|
-
shape: blob(`${id}-chest`, 44, 62, 22),
|
|
1998
|
-
children: [
|
|
1999
|
-
{ name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
|
|
2000
|
-
{ name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
|
|
2001
|
-
{ name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
|
|
2002
|
-
] },
|
|
2003
|
-
{ name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
|
|
2004
|
-
{ name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
|
|
2005
|
-
] },
|
|
2006
|
-
{ name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
|
|
2007
|
-
{ name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
|
|
2008
|
-
] },
|
|
2009
|
-
{ name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
|
|
2010
|
-
{ name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
|
|
2011
|
-
] }
|
|
2012
|
-
]
|
|
1894
|
+
|
|
1895
|
+
// ../core/src/montage.ts
|
|
1896
|
+
var VIDEO_EXT = /\.(mp4|mov|webm|m4v|mkv)$/i;
|
|
1897
|
+
var isVideoSrc = (src) => VIDEO_EXT.test(src);
|
|
1898
|
+
function makeRng(seed) {
|
|
1899
|
+
let a = seed >>> 0 || 2654435769;
|
|
1900
|
+
return () => {
|
|
1901
|
+
a = a + 1831565813 | 0;
|
|
1902
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
1903
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
1904
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2013
1905
|
};
|
|
2014
|
-
return rig(root, opts);
|
|
2015
1906
|
}
|
|
1907
|
+
var norm = (img) => typeof img === "string" ? { src: img } : img;
|
|
1908
|
+
function photoMontage(images, opts = {}) {
|
|
1909
|
+
const id = opts.id ?? "shot";
|
|
1910
|
+
const W = opts.size?.width ?? 1920;
|
|
1911
|
+
const H = opts.size?.height ?? 1080;
|
|
1912
|
+
const hold = Math.max(0.5, opts.hold ?? 3.2);
|
|
1913
|
+
const zoom = Math.max(1.001, opts.zoom ?? 1.18);
|
|
1914
|
+
const grade = opts.grade !== false;
|
|
1915
|
+
const rand2 = makeRng((opts.seed ?? 0) + 1);
|
|
1916
|
+
const slides = images.map(norm);
|
|
1917
|
+
const cx = W / 2;
|
|
1918
|
+
const cy = H / 2;
|
|
1919
|
+
const nodes = [];
|
|
1920
|
+
const shots = [];
|
|
1921
|
+
let clock = 0;
|
|
1922
|
+
slides.forEach((slide, i) => {
|
|
1923
|
+
const nid = `${id}-${i}`;
|
|
1924
|
+
const slideHold = Math.max(0.5, slide.hold ?? hold);
|
|
1925
|
+
const transition = Math.min(opts.transition ?? 0.6, slideHold * 0.9);
|
|
1926
|
+
const shotStart = clock;
|
|
1927
|
+
clock += slideHold;
|
|
1928
|
+
const kind = slide.ken ?? ["in", "out", "pan"][Math.floor(rand2() * 3)] ?? "in";
|
|
1929
|
+
const angle = rand2() * Math.PI * 2;
|
|
1930
|
+
const panFrac = 0.4 + rand2() * 0.35;
|
|
1931
|
+
const dx = Math.cos(angle);
|
|
1932
|
+
const dy = Math.sin(angle);
|
|
1933
|
+
let kA, kB;
|
|
1934
|
+
let xA, xB, yA, yB;
|
|
1935
|
+
if (kind === "pan") {
|
|
1936
|
+
kA = kB = zoom;
|
|
1937
|
+
const sx = dx * (zoom - 1) * (W / 2) * panFrac;
|
|
1938
|
+
const sy = dy * (zoom - 1) * (H / 2) * panFrac;
|
|
1939
|
+
xA = cx - sx;
|
|
1940
|
+
xB = cx + sx;
|
|
1941
|
+
yA = cy - sy;
|
|
1942
|
+
yB = cy + sy;
|
|
1943
|
+
} else {
|
|
1944
|
+
kA = kind === "in" ? 1 : zoom;
|
|
1945
|
+
kB = kind === "in" ? zoom : 1;
|
|
1946
|
+
xA = cx + dx * (kA - 1) * (W / 2) * panFrac;
|
|
1947
|
+
xB = cx + dx * (kB - 1) * (W / 2) * panFrac;
|
|
1948
|
+
yA = cy + dy * (kA - 1) * (H / 2) * panFrac;
|
|
1949
|
+
yB = cy + dy * (kB - 1) * (H / 2) * panFrac;
|
|
1950
|
+
}
|
|
1951
|
+
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 };
|
|
1952
|
+
nodes.push(
|
|
1953
|
+
isVideoSrc(slide.src) ? video({ ...box, start: shotStart, volume: slide.volume ?? 0 }) : image(box)
|
|
1954
|
+
);
|
|
1955
|
+
const ken = tween(
|
|
1956
|
+
nid,
|
|
1957
|
+
{ scale: kB, x: xB, y: yB },
|
|
1958
|
+
{ duration: slideHold, ease: "easeInOutQuad", label: `shot-${i}` }
|
|
1959
|
+
);
|
|
1960
|
+
const shot = i === 0 ? par(ken) : par(
|
|
1961
|
+
ken,
|
|
1962
|
+
tween(`${id}-${i - 1}`, { opacity: 0 }, { duration: transition, ease: "linear", label: `cross-${i}` }),
|
|
1963
|
+
tween(nid, { opacity: 1 }, { duration: transition, ease: "linear" })
|
|
1964
|
+
);
|
|
1965
|
+
shots.push(shot);
|
|
1966
|
+
});
|
|
1967
|
+
if (grade) {
|
|
1968
|
+
nodes.push(
|
|
1969
|
+
rect({
|
|
1970
|
+
id: `${id}-vignette`,
|
|
1971
|
+
x: 0,
|
|
1972
|
+
y: 0,
|
|
1973
|
+
width: W,
|
|
1974
|
+
height: H,
|
|
1975
|
+
fill: radialGradient(
|
|
1976
|
+
[
|
|
1977
|
+
{ offset: 0.55, color: "#FFFFFF" },
|
|
1978
|
+
{ offset: 1, color: "#6E6E6E" }
|
|
1979
|
+
],
|
|
1980
|
+
{ cx: 0.5, cy: 0.5, r: 0.72 }
|
|
1981
|
+
),
|
|
1982
|
+
blend: "multiply"
|
|
1983
|
+
})
|
|
1984
|
+
);
|
|
1985
|
+
nodes.push(
|
|
1986
|
+
rect({
|
|
1987
|
+
id: `${id}-scrim`,
|
|
1988
|
+
x: 0,
|
|
1989
|
+
y: 0,
|
|
1990
|
+
width: W,
|
|
1991
|
+
height: H,
|
|
1992
|
+
fill: linearGradient(
|
|
1993
|
+
[
|
|
1994
|
+
{ offset: 0, color: "#00000000" },
|
|
1995
|
+
{ offset: 0.62, color: "#00000000" },
|
|
1996
|
+
{ offset: 1, color: "#000000B0" }
|
|
1997
|
+
],
|
|
1998
|
+
{ angle: 90 }
|
|
1999
|
+
)
|
|
2000
|
+
})
|
|
2001
|
+
);
|
|
2002
|
+
}
|
|
2003
|
+
return { nodes, timeline: beat("montage", { nodes: nodes.map((n3) => n3.id) }, [seq(...shots)]) };
|
|
2004
|
+
}
|
|
2005
|
+
var videoMontage = photoMontage;
|
|
2016
2006
|
|
|
2017
|
-
// ../core/src/
|
|
2018
|
-
var
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2007
|
+
// ../core/src/presets.ts
|
|
2008
|
+
var PRESET_NAMES = [
|
|
2009
|
+
"draw-bloom",
|
|
2010
|
+
"punch-in",
|
|
2011
|
+
"rise-settle",
|
|
2012
|
+
"slide-bank",
|
|
2013
|
+
"reveal-orbit",
|
|
2014
|
+
"spin-forge"
|
|
2015
|
+
];
|
|
2016
|
+
function makeRng2(seed) {
|
|
2023
2017
|
let a = seed >>> 0 || 2654435769;
|
|
2024
2018
|
return () => {
|
|
2025
2019
|
a = a + 1831565813 | 0;
|
|
@@ -2028,1567 +2022,1675 @@ function makeRng3(seed) {
|
|
|
2028
2022
|
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2029
2023
|
};
|
|
2030
2024
|
}
|
|
2031
|
-
var
|
|
2032
|
-
|
|
2033
|
-
|
|
2025
|
+
var clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
2026
|
+
var SET = 1 / 120;
|
|
2027
|
+
function ctx(o) {
|
|
2028
|
+
const rand2 = makeRng2((o.seed ?? 0) + 1);
|
|
2034
2029
|
return {
|
|
2035
|
-
|
|
2036
|
-
label: o.label,
|
|
2037
|
-
e: clamp012(o.energy ?? 0.5),
|
|
2030
|
+
e: clamp01(o.energy ?? 0.5),
|
|
2038
2031
|
sp: Math.max(0.25, o.speed ?? 1),
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
at: o.at ?? [0, 0],
|
|
2042
|
-
travel: o.travel,
|
|
2032
|
+
it: clamp01(o.intensity ?? 0.5),
|
|
2033
|
+
from: o.from,
|
|
2043
2034
|
rand: rand2,
|
|
2044
|
-
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
2035
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp,
|
|
2036
|
+
g: o.target.group,
|
|
2037
|
+
cx: o.target.center[0],
|
|
2038
|
+
cy: o.target.center[1],
|
|
2039
|
+
s: o.target.baseScale,
|
|
2040
|
+
fills: o.target.fills,
|
|
2041
|
+
inks: o.target.inks
|
|
2045
2042
|
};
|
|
2046
2043
|
}
|
|
2047
|
-
var
|
|
2048
|
-
function
|
|
2049
|
-
|
|
2050
|
-
if (p < 0.5) {
|
|
2051
|
-
const u2 = p / 0.5;
|
|
2052
|
-
return [stride * (1 - 2 * u2), 138];
|
|
2053
|
-
}
|
|
2054
|
-
const u = (p - 0.5) / 0.5;
|
|
2055
|
-
return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
|
|
2056
|
-
}
|
|
2057
|
-
function gaitPose(ph, stride, lift, armSwing, facing) {
|
|
2058
|
-
const fl = footPos(ph, stride, lift);
|
|
2059
|
-
const fr = footPos(ph + 0.5, stride, lift);
|
|
2060
|
-
const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
|
|
2061
|
-
const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
|
|
2062
|
-
const swing = Math.cos(2 * Math.PI * ph);
|
|
2063
|
-
return {
|
|
2064
|
-
legUpperL: round(hipL),
|
|
2065
|
-
legLowerL: round(kneeL),
|
|
2066
|
-
legUpperR: round(hipR),
|
|
2067
|
-
legLowerR: round(kneeR),
|
|
2068
|
-
armUpperR: round(-10 - armSwing * swing),
|
|
2069
|
-
armLowerR: -16,
|
|
2070
|
-
armUpperL: round(10 + armSwing * swing),
|
|
2071
|
-
armLowerL: 16
|
|
2072
|
-
};
|
|
2044
|
+
var dur = (base, sp) => base / sp;
|
|
2045
|
+
function settleEase(e) {
|
|
2046
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
2073
2047
|
}
|
|
2074
|
-
function
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
for (let k = 0; k <= steps; k++) {
|
|
2085
|
-
const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
|
|
2086
|
-
keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
|
|
2087
|
-
}
|
|
2088
|
-
const total = intro + steps * d;
|
|
2089
|
-
const travel = c.travel ?? stride * 2;
|
|
2090
|
-
const children = [seq(...keys)];
|
|
2091
|
-
if (travel !== 0) {
|
|
2092
|
-
children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
|
|
2048
|
+
function fromVec(from, dist) {
|
|
2049
|
+
switch (from) {
|
|
2050
|
+
case "left":
|
|
2051
|
+
return [-dist, 0];
|
|
2052
|
+
case "right":
|
|
2053
|
+
return [dist, 0];
|
|
2054
|
+
case "top":
|
|
2055
|
+
return [0, -dist];
|
|
2056
|
+
default:
|
|
2057
|
+
return [0, dist];
|
|
2093
2058
|
}
|
|
2094
|
-
return beat(run ? "run" : "walk", {}, [par(...children)]);
|
|
2095
2059
|
}
|
|
2096
|
-
function
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
|
|
2102
|
-
const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
|
|
2103
|
-
const j = c.jit(0.03);
|
|
2104
|
-
return beat("jump", {}, [
|
|
2105
|
-
seq(
|
|
2106
|
-
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" })),
|
|
2107
|
-
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" })),
|
|
2108
|
-
poseTo(c.g, TUCK, { duration: dur2(0.22, c.sp) }),
|
|
2109
|
-
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" })),
|
|
2110
|
-
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" }))
|
|
2060
|
+
function fadeFills(c, base = 0.4, gap = 0.06) {
|
|
2061
|
+
return stagger(
|
|
2062
|
+
gap / c.sp,
|
|
2063
|
+
...c.fills.map(
|
|
2064
|
+
(id, i) => tween(id, { opacity: 1 }, { duration: dur(base, c.sp), ease: "easeOutQuad", ...i === 0 && { label: "reveal" } })
|
|
2111
2065
|
)
|
|
2112
|
-
|
|
2113
|
-
}
|
|
2114
|
-
function danceBeat(c) {
|
|
2115
|
-
const y0 = c.at[1];
|
|
2116
|
-
const sway = 8 + 6 * c.e;
|
|
2117
|
-
const armUp = 130 + 30 * c.e;
|
|
2118
|
-
const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
|
|
2119
|
-
const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
|
|
2120
|
-
const d = dur2(0.34, c.sp);
|
|
2121
|
-
const keys = [];
|
|
2122
|
-
for (let k = 0; k < c.cycles * 2; k++) {
|
|
2123
|
-
const pose = k % 2 === 0 ? A : B;
|
|
2124
|
-
keys.push(par(
|
|
2125
|
-
poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
|
|
2126
|
-
tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
|
|
2127
|
-
));
|
|
2128
|
-
}
|
|
2129
|
-
keys.push(tween(c.g, { y: y0 }, { duration: d }));
|
|
2130
|
-
return beat("dance", {}, [seq(...keys)]);
|
|
2131
|
-
}
|
|
2132
|
-
function waveBeat(c) {
|
|
2133
|
-
const n3 = 3 + Math.round(c.rand() * 2);
|
|
2134
|
-
const amp = 16 + 10 * c.e;
|
|
2135
|
-
const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur2(0.4, c.sp), ease: "easeOutBack" })];
|
|
2136
|
-
for (let k = 0; k < n3; k++) {
|
|
2137
|
-
steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur2(0.22, c.sp), ease: "easeInOutQuad" }));
|
|
2138
|
-
}
|
|
2139
|
-
steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur2(0.4, c.sp), ease: "easeInOutCubic" }));
|
|
2140
|
-
return beat("wave", {}, [seq(...steps)]);
|
|
2066
|
+
);
|
|
2141
2067
|
}
|
|
2142
|
-
function
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
|
|
2150
|
-
}
|
|
2151
|
-
return beat("cheer", {}, [seq(...keys)]);
|
|
2068
|
+
function drawInks(c) {
|
|
2069
|
+
return stagger(
|
|
2070
|
+
0.15 / c.sp,
|
|
2071
|
+
...c.inks.map(
|
|
2072
|
+
(id, i) => tween(id, { progress: 1 }, { duration: dur(1.3 + c.jit(0.2), c.sp), ease: "easeInOutQuad", ...i === 0 && { label: "draw" } })
|
|
2073
|
+
)
|
|
2074
|
+
);
|
|
2152
2075
|
}
|
|
2153
|
-
function
|
|
2154
|
-
const c =
|
|
2155
|
-
let tl;
|
|
2076
|
+
function motionPreset(name, opts) {
|
|
2077
|
+
const c = ctx(opts);
|
|
2156
2078
|
switch (name) {
|
|
2157
|
-
case "
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
case "
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2079
|
+
case "draw-bloom":
|
|
2080
|
+
return beat("draw-bloom", {}, [
|
|
2081
|
+
drawInks(c),
|
|
2082
|
+
fadeFills(c, 0.45),
|
|
2083
|
+
tween(c.g, { scale: c.s * (1.02 + 0.05 * c.e) }, { duration: dur(2.4, c.sp), ease: "easeInOutQuad", label: "settle" })
|
|
2084
|
+
]);
|
|
2085
|
+
case "punch-in": {
|
|
2086
|
+
const peak = c.s * (1 + 0.06 + 0.24 * c.e + c.jit(0.02));
|
|
2087
|
+
return beat("punch-in", {}, [
|
|
2088
|
+
par(
|
|
2089
|
+
fadeFills(c, 0.25),
|
|
2090
|
+
seq(
|
|
2091
|
+
tween(c.g, { scale: peak }, { duration: dur(0.45 + c.jit(0.05), c.sp), ease: "easeOutCubic", label: "punch" }),
|
|
2092
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.5, c.sp), ease: settleEase(c.e) })
|
|
2093
|
+
)
|
|
2094
|
+
)
|
|
2095
|
+
]);
|
|
2096
|
+
}
|
|
2097
|
+
case "rise-settle": {
|
|
2098
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2099
|
+
const dist = (220 + 260 * c.it) * es;
|
|
2100
|
+
const [dx, dy] = fromVec(c.from ?? "bottom", dist);
|
|
2101
|
+
const jx = c.jit(110);
|
|
2102
|
+
return beat("rise-settle", {}, [
|
|
2103
|
+
par(
|
|
2104
|
+
motionPath(
|
|
2105
|
+
c.g,
|
|
2106
|
+
[
|
|
2107
|
+
[c.cx + dx + jx, c.cy + dy],
|
|
2108
|
+
[c.cx + dx * 0.4 - jx * 0.6, c.cy + dy * 0.4],
|
|
2109
|
+
[c.cx, c.cy]
|
|
2110
|
+
],
|
|
2111
|
+
{ duration: dur(1.1, c.sp), ease: settleEase(c.e), label: "rise" }
|
|
2112
|
+
),
|
|
2113
|
+
fadeFills(c, 0.4)
|
|
2114
|
+
)
|
|
2115
|
+
]);
|
|
2116
|
+
}
|
|
2117
|
+
case "slide-bank": {
|
|
2118
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2119
|
+
const dist = (420 + 240 * c.it) * es;
|
|
2120
|
+
const [dx, dy] = fromVec(c.from ?? "left", dist);
|
|
2121
|
+
const arc = c.jit(140);
|
|
2122
|
+
const midx = c.jit(120);
|
|
2123
|
+
const move = dur(1.2, c.sp);
|
|
2124
|
+
return beat("slide-bank", {}, [
|
|
2125
|
+
par(
|
|
2126
|
+
motionPath(
|
|
2127
|
+
c.g,
|
|
2128
|
+
[
|
|
2129
|
+
[c.cx + dx, c.cy + dy],
|
|
2130
|
+
[c.cx + dx * 0.4 + midx, c.cy + dy * 0.4 - 70 - arc],
|
|
2131
|
+
[c.cx, c.cy]
|
|
2132
|
+
],
|
|
2133
|
+
{ duration: move, ease: settleEase(c.e), autoRotate: true, label: "slide" }
|
|
2134
|
+
),
|
|
2135
|
+
// level the bank out once it lands (authored after the path → wins for rotation)
|
|
2136
|
+
seq(wait(move), tween(c.g, { rotation: 0 }, { duration: dur(0.5, c.sp), ease: "easeOutCubic" })),
|
|
2137
|
+
fadeFills(c, 0.4)
|
|
2138
|
+
)
|
|
2139
|
+
]);
|
|
2140
|
+
}
|
|
2141
|
+
case "reveal-orbit": {
|
|
2142
|
+
const es = 0.65 + c.rand() * 0.7;
|
|
2143
|
+
const orbit = (180 + 160 * c.it) * es;
|
|
2144
|
+
const jx = c.jit(0.4);
|
|
2145
|
+
const jy = c.jit(0.4);
|
|
2146
|
+
return beat("reveal-orbit", {}, [
|
|
2147
|
+
drawInks(c),
|
|
2148
|
+
fadeFills(c, 0.45),
|
|
2149
|
+
par(
|
|
2150
|
+
motionPath(
|
|
2151
|
+
c.g,
|
|
2152
|
+
[
|
|
2153
|
+
[c.cx, c.cy],
|
|
2154
|
+
[c.cx - orbit * (1 + jx), c.cy - orbit * 0.8],
|
|
2155
|
+
[c.cx + orbit * (1 + jy), c.cy - orbit],
|
|
2156
|
+
[c.cx, c.cy]
|
|
2157
|
+
],
|
|
2158
|
+
{ duration: dur(1.7, c.sp), ease: "easeInOutCubic", label: "orbit" }
|
|
2159
|
+
),
|
|
2160
|
+
seq(
|
|
2161
|
+
tween(c.g, { scale: c.s * (1.12 + 0.1 * c.e) }, { duration: dur(0.85, c.sp), ease: "easeOutBack" }),
|
|
2162
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.85, c.sp), ease: "easeInOutQuad" })
|
|
2163
|
+
)
|
|
2164
|
+
)
|
|
2165
|
+
]);
|
|
2166
|
+
}
|
|
2167
|
+
case "spin-forge": {
|
|
2168
|
+
const turns = 1 + Math.round(c.it);
|
|
2169
|
+
const dir = c.rand() < 0.5 ? -1 : 1;
|
|
2170
|
+
const startRot = dir * 360 * turns;
|
|
2171
|
+
const peak = c.s * (1 + 0.05 + 0.2 * c.e);
|
|
2172
|
+
return beat("spin-forge", {}, [
|
|
2173
|
+
par(
|
|
2174
|
+
seq(
|
|
2175
|
+
tween(c.g, { scale: c.s * 0.2, rotation: startRot }, { duration: SET }),
|
|
2176
|
+
// establish (invisible)
|
|
2177
|
+
tween(c.g, { scale: peak, rotation: 0 }, { duration: dur(0.9, c.sp), ease: "easeOutBack", label: "spin" }),
|
|
2178
|
+
tween(c.g, { scale: c.s }, { duration: dur(0.3, c.sp), ease: "easeInOutQuad" })
|
|
2179
|
+
),
|
|
2180
|
+
seq(wait(SET), fadeFills(c, 0.3))
|
|
2181
|
+
)
|
|
2182
|
+
]);
|
|
2178
2183
|
}
|
|
2179
2184
|
}
|
|
2180
|
-
return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
|
|
2181
2185
|
}
|
|
2182
2186
|
|
|
2183
|
-
// ../core/src/
|
|
2184
|
-
var
|
|
2185
|
-
var
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
|
|
2199
|
-
}
|
|
2200
|
-
var DEF = {
|
|
2201
|
-
clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
|
|
2202
|
-
cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
|
|
2187
|
+
// ../core/src/devicePreset.ts
|
|
2188
|
+
var DEVICE_PRESET_NAMES = ["phone", "tablet", "laptop", "browser", "watch", "monitor", "tv", "foldable", "terminal", "car"];
|
|
2189
|
+
var DARK = { body: "#15161C", bodyStroke: "#2A2D38", screen: "#0E0F15", detail: "#3A3D48", chrome: "#1B1D24", chromeText: "#9AA0AD" };
|
|
2190
|
+
var LIGHT = { body: "#E7E9EE", bodyStroke: "#C3C7D1", screen: "#FFFFFF", detail: "#AEB3C0", chrome: "#F2F3F6", chromeText: "#5B606C" };
|
|
2191
|
+
var SCREENS = {
|
|
2192
|
+
phone: { width: 352, height: 736, radius: 38 },
|
|
2193
|
+
tablet: { width: 544, height: 764, radius: 18 },
|
|
2194
|
+
laptop: { width: 840, height: 520, radius: 8, cy: -150 },
|
|
2195
|
+
browser: { width: 984, height: 568, radius: 6, cy: 24 },
|
|
2196
|
+
watch: { width: 184, height: 224, radius: 44 },
|
|
2197
|
+
monitor: { width: 1056, height: 600, radius: 6 },
|
|
2198
|
+
tv: { width: 1280, height: 720, radius: 8, cy: -24 },
|
|
2199
|
+
foldable: { width: 760, height: 560, radius: 20 },
|
|
2200
|
+
terminal: { width: 900, height: 560, radius: 6, cy: 18 },
|
|
2201
|
+
car: { width: 1e3, height: 520, radius: 24 }
|
|
2203
2202
|
};
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
shoe,
|
|
2222
|
-
shoeSh: darken(shoe, 0.22),
|
|
2223
|
-
eye: "#2B313F",
|
|
2224
|
-
cheek: "#FF9E7E",
|
|
2225
|
-
white: "#FFFFFF",
|
|
2226
|
-
mouth: "#8A4233"
|
|
2227
|
-
};
|
|
2203
|
+
var BOUNDS = {
|
|
2204
|
+
phone: { width: 392, height: 812 },
|
|
2205
|
+
tablet: { width: 600, height: 820 },
|
|
2206
|
+
laptop: { width: 1100, height: 650 },
|
|
2207
|
+
browser: { width: 1e3, height: 660 },
|
|
2208
|
+
watch: { width: 220, height: 300 },
|
|
2209
|
+
monitor: { width: 1120, height: 860 },
|
|
2210
|
+
tv: { width: 1340, height: 920 },
|
|
2211
|
+
foldable: { width: 800, height: 600 },
|
|
2212
|
+
terminal: { width: 916, height: 636 },
|
|
2213
|
+
car: { width: 1060, height: 600 }
|
|
2214
|
+
};
|
|
2215
|
+
var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
|
|
2216
|
+
function screenDims(name, o) {
|
|
2217
|
+
const d = SCREENS[name];
|
|
2218
|
+
const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
|
|
2219
|
+
return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
|
|
2228
2220
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
return {
|
|
2233
|
-
upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
|
|
2234
|
-
forearm: (j) => [
|
|
2235
|
-
fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
|
|
2236
|
-
fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
|
|
2237
|
-
fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
|
|
2238
|
-
],
|
|
2239
|
-
thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
|
|
2240
|
-
shin: (j) => [
|
|
2241
|
-
fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
|
|
2242
|
-
fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
|
|
2243
|
-
fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
|
|
2244
|
-
],
|
|
2245
|
-
torso: (j) => [
|
|
2246
|
-
fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
|
|
2247
|
-
fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
|
|
2248
|
-
fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
|
|
2249
|
-
],
|
|
2250
|
-
head: (j) => [
|
|
2251
|
-
fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
|
|
2252
|
-
fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
|
|
2253
|
-
fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
|
|
2254
|
-
fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
|
|
2255
|
-
fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
|
|
2256
|
-
...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
|
|
2257
|
-
]
|
|
2258
|
-
};
|
|
2221
|
+
function deviceScreen(name, opts = {}) {
|
|
2222
|
+
const d = screenDims(name, opts);
|
|
2223
|
+
return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
|
|
2259
2224
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
return {
|
|
2264
|
-
upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
|
|
2265
|
-
forearm: (j) => [
|
|
2266
|
-
fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
|
|
2267
|
-
fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
|
|
2268
|
-
fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
|
|
2269
|
-
],
|
|
2270
|
-
thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
|
|
2271
|
-
shin: (j) => [
|
|
2272
|
-
fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
|
|
2273
|
-
fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
|
|
2274
|
-
fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
|
|
2275
|
-
],
|
|
2276
|
-
torso: (j) => [
|
|
2277
|
-
fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
|
|
2278
|
-
fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
|
|
2279
|
-
fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
|
|
2280
|
-
],
|
|
2281
|
-
head: (j) => [
|
|
2282
|
-
fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
|
|
2283
|
-
fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
|
|
2284
|
-
fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2285
|
-
fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2286
|
-
fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
|
|
2287
|
-
...face ? [
|
|
2288
|
-
fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
|
|
2289
|
-
fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
|
|
2290
|
-
fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
|
|
2291
|
-
fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
|
|
2292
|
-
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 })
|
|
2293
|
-
] : []
|
|
2294
|
-
]
|
|
2295
|
-
};
|
|
2225
|
+
function deviceScreenCenter(name, opts = {}) {
|
|
2226
|
+
const d = screenDims(name, opts);
|
|
2227
|
+
return { x: d.cx, y: d.cy };
|
|
2296
2228
|
}
|
|
2297
|
-
function
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
at: [x, -14],
|
|
2301
|
-
length: 60,
|
|
2302
|
-
width: 0,
|
|
2303
|
-
rotation: r1,
|
|
2304
|
-
shape: S.upperArm(`${id}-armUpper${side}`),
|
|
2305
|
-
children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
|
|
2306
|
-
});
|
|
2307
|
-
const leg = (side, x, r1, r2) => ({
|
|
2308
|
-
name: `legUpper${side}`,
|
|
2309
|
-
at: [x, 76],
|
|
2310
|
-
length: 76,
|
|
2311
|
-
width: 0,
|
|
2312
|
-
rotation: r1,
|
|
2313
|
-
shape: S.thigh(`${id}-legUpper${side}`),
|
|
2314
|
-
children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
|
|
2315
|
-
});
|
|
2316
|
-
return {
|
|
2317
|
-
name: "chest",
|
|
2318
|
-
at: [0, 0],
|
|
2319
|
-
shape: S.torso(`${id}-chest`),
|
|
2320
|
-
children: [
|
|
2321
|
-
{ name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
|
|
2322
|
-
arm("L", -40, 8, 6),
|
|
2323
|
-
arm("R", 40, -8, -6),
|
|
2324
|
-
leg("L", -20, 3, -2),
|
|
2325
|
-
leg("R", 20, -3, 2)
|
|
2326
|
-
]
|
|
2327
|
-
};
|
|
2229
|
+
function deviceBounds(name, opts = {}) {
|
|
2230
|
+
const b = BOUNDS[name];
|
|
2231
|
+
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
2328
2232
|
}
|
|
2329
|
-
function
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
const id = opts.id ?? "figure";
|
|
2334
|
-
const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
|
|
2335
|
-
const rigOpts = { id };
|
|
2336
|
-
if (opts.x !== void 0) rigOpts.x = opts.x;
|
|
2337
|
-
if (opts.y !== void 0) rigOpts.y = opts.y;
|
|
2338
|
-
if (opts.scale !== void 0) rigOpts.scale = opts.scale;
|
|
2339
|
-
if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
|
|
2340
|
-
return rig(buildSkeleton(id, parts), rigOpts);
|
|
2233
|
+
function deviceScreenPoint(name, opts, local) {
|
|
2234
|
+
const c = deviceScreenCenter(name, opts);
|
|
2235
|
+
const s = opts.scale ?? 1;
|
|
2236
|
+
return [(opts.x ?? 0) + s * (c.x + local[0]), (opts.y ?? 0) + s * (c.y + local[1])];
|
|
2341
2237
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
"
|
|
2355
|
-
"
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
"D": 72.17,
|
|
2383
|
-
"E": 60.11,
|
|
2384
|
-
"F": 59.03,
|
|
2385
|
-
"G": 74.61,
|
|
2386
|
-
"H": 74.32,
|
|
2387
|
-
"I": 26.86,
|
|
2388
|
-
"J": 57.08,
|
|
2389
|
-
"K": 67.19,
|
|
2390
|
-
"L": 56.54,
|
|
2391
|
-
"M": 90.33,
|
|
2392
|
-
"N": 75.34,
|
|
2393
|
-
"O": 76.46,
|
|
2394
|
-
"P": 63.87,
|
|
2395
|
-
"Q": 76.46,
|
|
2396
|
-
"R": 64.36,
|
|
2397
|
-
"S": 64.16,
|
|
2398
|
-
"T": 64.55,
|
|
2399
|
-
"U": 74.41,
|
|
2400
|
-
"V": 68.99,
|
|
2401
|
-
"W": 98.54,
|
|
2402
|
-
"X": 68.21,
|
|
2403
|
-
"Y": 67.87,
|
|
2404
|
-
"Z": 62.89,
|
|
2405
|
-
"[": 36.47,
|
|
2406
|
-
"\\": 36.04,
|
|
2407
|
-
"]": 36.47,
|
|
2408
|
-
"^": 47.12,
|
|
2409
|
-
"_": 45.61,
|
|
2410
|
-
"`": 32.28,
|
|
2411
|
-
"a": 56.15,
|
|
2412
|
-
"b": 61.23,
|
|
2413
|
-
"c": 57.13,
|
|
2414
|
-
"d": 61.23,
|
|
2415
|
-
"e": 58.3,
|
|
2416
|
-
"f": 37.01,
|
|
2417
|
-
"g": 61.33,
|
|
2418
|
-
"h": 59.13,
|
|
2419
|
-
"i": 24.22,
|
|
2420
|
-
"j": 24.22,
|
|
2421
|
-
"k": 54.88,
|
|
2422
|
-
"l": 24.22,
|
|
2423
|
-
"m": 87.6,
|
|
2424
|
-
"n": 59.08,
|
|
2425
|
-
"o": 59.96,
|
|
2426
|
-
"p": 61.23,
|
|
2427
|
-
"q": 61.23,
|
|
2428
|
-
"r": 37.65,
|
|
2429
|
-
"s": 52.78,
|
|
2430
|
-
"t": 32.71,
|
|
2431
|
-
"u": 59.13,
|
|
2432
|
-
"v": 56.2,
|
|
2433
|
-
"w": 81.84,
|
|
2434
|
-
"x": 54.59,
|
|
2435
|
-
"y": 56.2,
|
|
2436
|
-
"z": 55.22,
|
|
2437
|
-
"{": 42.63,
|
|
2438
|
-
"|": 33.25,
|
|
2439
|
-
"}": 42.63,
|
|
2440
|
-
"~": 66.16
|
|
2441
|
-
},
|
|
2442
|
-
"700": {
|
|
2443
|
-
"0": 67.43,
|
|
2444
|
-
"1": 43.12,
|
|
2445
|
-
"2": 62.94,
|
|
2446
|
-
"3": 64.55,
|
|
2447
|
-
"4": 67.63,
|
|
2448
|
-
"5": 62.21,
|
|
2449
|
-
"6": 64.94,
|
|
2450
|
-
"7": 58.15,
|
|
2451
|
-
"8": 65.09,
|
|
2452
|
-
"9": 64.94,
|
|
2453
|
-
" ": 23.68,
|
|
2454
|
-
"!": 33.79,
|
|
2455
|
-
'"': 55.13,
|
|
2456
|
-
"#": 64.89,
|
|
2457
|
-
"$": 65.48,
|
|
2458
|
-
"%": 101.56,
|
|
2459
|
-
"&": 67.19,
|
|
2460
|
-
"'": 33.89,
|
|
2461
|
-
"(": 37.7,
|
|
2462
|
-
")": 37.7,
|
|
2463
|
-
"*": 55.91,
|
|
2464
|
-
"+": 67.87,
|
|
2465
|
-
",": 33.4,
|
|
2466
|
-
"-": 46.78,
|
|
2467
|
-
".": 33.4,
|
|
2468
|
-
"/": 38.82,
|
|
2469
|
-
":": 33.4,
|
|
2470
|
-
";": 34.28,
|
|
2471
|
-
"<": 67.87,
|
|
2472
|
-
"=": 67.87,
|
|
2473
|
-
">": 67.87,
|
|
2474
|
-
"?": 55.96,
|
|
2475
|
-
"@": 101.61,
|
|
2476
|
-
"A": 74.66,
|
|
2477
|
-
"B": 66.16,
|
|
2478
|
-
"C": 73.97,
|
|
2479
|
-
"D": 72.22,
|
|
2480
|
-
"E": 60.74,
|
|
2481
|
-
"F": 58.69,
|
|
2482
|
-
"G": 75.05,
|
|
2483
|
-
"H": 74.71,
|
|
2484
|
-
"I": 28.08,
|
|
2485
|
-
"J": 58.45,
|
|
2486
|
-
"K": 71.92,
|
|
2487
|
-
"L": 56.54,
|
|
2488
|
-
"M": 93.16,
|
|
2489
|
-
"N": 76.22,
|
|
2490
|
-
"O": 77.05,
|
|
2491
|
-
"P": 64.79,
|
|
2492
|
-
"Q": 77.69,
|
|
2493
|
-
"R": 65.67,
|
|
2494
|
-
"S": 65.48,
|
|
2495
|
-
"T": 66.75,
|
|
2496
|
-
"U": 73.19,
|
|
2497
|
-
"V": 74.66,
|
|
2498
|
-
"W": 103.76,
|
|
2499
|
-
"X": 73.83,
|
|
2500
|
-
"Y": 73.1,
|
|
2501
|
-
"Z": 66.41,
|
|
2502
|
-
"[": 37.7,
|
|
2503
|
-
"\\": 38.82,
|
|
2504
|
-
"]": 37.7,
|
|
2505
|
-
"^": 48.68,
|
|
2506
|
-
"_": 47.61,
|
|
2507
|
-
"`": 36.52,
|
|
2508
|
-
"a": 58.06,
|
|
2509
|
-
"b": 63.04,
|
|
2510
|
-
"c": 58.84,
|
|
2511
|
-
"d": 63.04,
|
|
2512
|
-
"e": 59.57,
|
|
2513
|
-
"f": 39.79,
|
|
2514
|
-
"g": 63.18,
|
|
2515
|
-
"h": 62.26,
|
|
2516
|
-
"i": 27.1,
|
|
2517
|
-
"j": 27.1,
|
|
2518
|
-
"k": 58.01,
|
|
2519
|
-
"l": 27.1,
|
|
2520
|
-
"m": 91.26,
|
|
2521
|
-
"n": 62.26,
|
|
2522
|
-
"o": 61.33,
|
|
2523
|
-
"p": 63.04,
|
|
2524
|
-
"q": 63.04,
|
|
2525
|
-
"r": 40.72,
|
|
2526
|
-
"s": 56.01,
|
|
2527
|
-
"t": 36.62,
|
|
2528
|
-
"u": 62.26,
|
|
2529
|
-
"v": 59.96,
|
|
2530
|
-
"w": 85.01,
|
|
2531
|
-
"x": 58.01,
|
|
2532
|
-
"y": 60.21,
|
|
2533
|
-
"z": 57.28,
|
|
2534
|
-
"{": 46.88,
|
|
2535
|
-
"|": 37.16,
|
|
2536
|
-
"}": 46.88,
|
|
2537
|
-
"~": 67.87
|
|
2538
|
-
},
|
|
2539
|
-
"800": {
|
|
2540
|
-
"0": 69.19,
|
|
2541
|
-
"1": 44.14,
|
|
2542
|
-
"2": 63.77,
|
|
2543
|
-
"3": 65.67,
|
|
2544
|
-
"4": 68.85,
|
|
2545
|
-
"5": 63.38,
|
|
2546
|
-
"6": 66.16,
|
|
2547
|
-
"7": 58.79,
|
|
2548
|
-
"8": 66.41,
|
|
2549
|
-
"9": 66.16,
|
|
2550
|
-
" ": 21.88,
|
|
2551
|
-
"!": 35.84,
|
|
2552
|
-
'"': 58.64,
|
|
2553
|
-
"#": 65.53,
|
|
2554
|
-
"$": 66.02,
|
|
2555
|
-
"%": 102.93,
|
|
2556
|
-
"&": 68.31,
|
|
2557
|
-
"'": 35.45,
|
|
2558
|
-
"(": 38.23,
|
|
2559
|
-
")": 38.23,
|
|
2560
|
-
"*": 58.25,
|
|
2561
|
-
"+": 68.55,
|
|
2562
|
-
",": 35.25,
|
|
2563
|
-
"-": 47.12,
|
|
2564
|
-
".": 35.25,
|
|
2565
|
-
"/": 39.99,
|
|
2566
|
-
":": 35.25,
|
|
2567
|
-
";": 35.99,
|
|
2568
|
-
"<": 68.55,
|
|
2569
|
-
"=": 68.55,
|
|
2570
|
-
">": 68.55,
|
|
2571
|
-
"?": 57.91,
|
|
2572
|
-
"@": 103.61,
|
|
2573
|
-
"A": 76.95,
|
|
2574
|
-
"B": 66.46,
|
|
2575
|
-
"C": 74.37,
|
|
2576
|
-
"D": 72.27,
|
|
2577
|
-
"E": 60.99,
|
|
2578
|
-
"F": 58.54,
|
|
2579
|
-
"G": 75.24,
|
|
2580
|
-
"H": 74.85,
|
|
2581
|
-
"I": 28.56,
|
|
2582
|
-
"J": 58.98,
|
|
2583
|
-
"K": 73.83,
|
|
2584
|
-
"L": 56.54,
|
|
2585
|
-
"M": 94.34,
|
|
2586
|
-
"N": 76.56,
|
|
2587
|
-
"O": 77.29,
|
|
2588
|
-
"P": 65.19,
|
|
2589
|
-
"Q": 78.17,
|
|
2590
|
-
"R": 66.21,
|
|
2591
|
-
"S": 66.02,
|
|
2592
|
-
"T": 67.68,
|
|
2593
|
-
"U": 72.71,
|
|
2594
|
-
"V": 76.95,
|
|
2595
|
-
"W": 105.86,
|
|
2596
|
-
"X": 76.12,
|
|
2597
|
-
"Y": 75.2,
|
|
2598
|
-
"Z": 67.87,
|
|
2599
|
-
"[": 38.23,
|
|
2600
|
-
"\\": 39.99,
|
|
2601
|
-
"]": 38.23,
|
|
2602
|
-
"^": 49.32,
|
|
2603
|
-
"_": 48.44,
|
|
2604
|
-
"`": 38.23,
|
|
2605
|
-
"a": 58.84,
|
|
2606
|
-
"b": 63.77,
|
|
2607
|
-
"c": 59.52,
|
|
2608
|
-
"d": 63.77,
|
|
2609
|
-
"e": 60.06,
|
|
2610
|
-
"f": 40.97,
|
|
2611
|
-
"g": 63.92,
|
|
2612
|
-
"h": 63.53,
|
|
2613
|
-
"i": 28.32,
|
|
2614
|
-
"j": 28.32,
|
|
2615
|
-
"k": 59.28,
|
|
2616
|
-
"l": 28.32,
|
|
2617
|
-
"m": 92.72,
|
|
2618
|
-
"n": 63.53,
|
|
2619
|
-
"o": 61.91,
|
|
2620
|
-
"p": 63.77,
|
|
2621
|
-
"q": 63.77,
|
|
2622
|
-
"r": 41.99,
|
|
2623
|
-
"s": 57.32,
|
|
2624
|
-
"t": 38.18,
|
|
2625
|
-
"u": 63.53,
|
|
2626
|
-
"v": 61.52,
|
|
2627
|
-
"w": 86.28,
|
|
2628
|
-
"x": 59.42,
|
|
2629
|
-
"y": 61.82,
|
|
2630
|
-
"z": 58.11,
|
|
2631
|
-
"{": 48.63,
|
|
2632
|
-
"|": 38.77,
|
|
2633
|
-
"}": 48.63,
|
|
2634
|
-
"~": 68.55
|
|
2635
|
-
}
|
|
2636
|
-
};
|
|
2637
|
-
var INTER_FALLBACK = {
|
|
2638
|
-
"400": 56.16,
|
|
2639
|
-
"700": 58.74,
|
|
2640
|
-
"800": 59.79
|
|
2641
|
-
};
|
|
2642
|
-
|
|
2643
|
-
// ../core/src/textFx.ts
|
|
2644
|
-
var clamp013 = (v) => Math.max(0, Math.min(1, v));
|
|
2645
|
-
var fract = (v) => v - Math.floor(v);
|
|
2646
|
-
var rand = (i, salt) => fract(Math.sin(i * 127.1 + salt * 311.7) * 43758.5453);
|
|
2647
|
-
var dur3 = (base, sp) => base / sp;
|
|
2648
|
-
var SCRAMBLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@";
|
|
2649
|
-
var advance = (ch, weight, fontSize) => (INTER_ADVANCE[weight]?.[ch] ?? INTER_FALLBACK[weight]) * (fontSize / 100);
|
|
2650
|
-
function splitText(textStr, opts) {
|
|
2651
|
-
const { id, x, y, fontSize } = opts;
|
|
2652
|
-
const weight = opts.fontWeight ?? 800;
|
|
2653
|
-
const fill = opts.fill ?? "#FFFFFF";
|
|
2654
|
-
const ls = opts.letterSpacing ?? 0;
|
|
2655
|
-
const align = opts.align ?? "center";
|
|
2656
|
-
const unit = opts.unit ?? "glyph";
|
|
2657
|
-
const opacity = opts.opacity ?? 0;
|
|
2658
|
-
const chars = [...textStr];
|
|
2659
|
-
let total = 0;
|
|
2660
|
-
chars.forEach((ch, i) => {
|
|
2661
|
-
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
2662
|
-
});
|
|
2663
|
-
let cursor2 = align === "center" ? x - total / 2 : x;
|
|
2664
|
-
const glyphs = [];
|
|
2665
|
-
const nodes = [];
|
|
2666
|
-
const mk = (ch, cx, adv, lsProp) => {
|
|
2667
|
-
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
2668
|
-
glyphs.push(g);
|
|
2669
|
-
nodes.push(
|
|
2670
|
-
text({
|
|
2671
|
-
id: g.id,
|
|
2672
|
-
x: cx,
|
|
2673
|
-
y,
|
|
2674
|
-
content: ch,
|
|
2675
|
-
fontFamily: "Inter",
|
|
2676
|
-
fontSize,
|
|
2677
|
-
fontWeight: weight,
|
|
2678
|
-
fill,
|
|
2679
|
-
anchor: "center",
|
|
2680
|
-
opacity,
|
|
2681
|
-
...lsProp ? { letterSpacing: lsProp } : {}
|
|
2682
|
-
})
|
|
2683
|
-
);
|
|
2684
|
-
};
|
|
2685
|
-
if (unit === "word") {
|
|
2686
|
-
let i = 0;
|
|
2687
|
-
while (i < chars.length) {
|
|
2688
|
-
if (chars[i] === " ") {
|
|
2689
|
-
cursor2 += advance(" ", weight, fontSize) + ls;
|
|
2690
|
-
i++;
|
|
2691
|
-
continue;
|
|
2692
|
-
}
|
|
2693
|
-
let word = "";
|
|
2694
|
-
let w = 0;
|
|
2695
|
-
const startCursor = cursor2;
|
|
2696
|
-
while (i < chars.length && chars[i] !== " ") {
|
|
2697
|
-
const a = advance(chars[i], weight, fontSize);
|
|
2698
|
-
word += chars[i];
|
|
2699
|
-
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
2700
|
-
i++;
|
|
2238
|
+
function screenGroup(id, p, o, cx, cy, dims, content) {
|
|
2239
|
+
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 } }, [
|
|
2240
|
+
rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
|
|
2241
|
+
group({ id: `${id}-content`, x: 0, y: 0 }, content)
|
|
2242
|
+
]);
|
|
2243
|
+
}
|
|
2244
|
+
function buildDevice(name, id, p, o, content) {
|
|
2245
|
+
const dims = screenDims(name, o);
|
|
2246
|
+
const sw = dims.width;
|
|
2247
|
+
const sh = dims.height;
|
|
2248
|
+
const screen = () => screenGroup(id, p, o, dims.cx, dims.cy, dims, content);
|
|
2249
|
+
switch (name) {
|
|
2250
|
+
case "phone":
|
|
2251
|
+
case "tablet": {
|
|
2252
|
+
const bezel = name === "phone" ? 20 : 28;
|
|
2253
|
+
const bodyW = sw + bezel * 2;
|
|
2254
|
+
const bodyH = sh + bezel * 2;
|
|
2255
|
+
const bodyR = name === "phone" ? 54 : 34;
|
|
2256
|
+
const land = isLandscape(name, o);
|
|
2257
|
+
const nodes = [
|
|
2258
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: bodyR }),
|
|
2259
|
+
screen()
|
|
2260
|
+
];
|
|
2261
|
+
if (name === "phone") {
|
|
2262
|
+
nodes.push(
|
|
2263
|
+
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 }),
|
|
2264
|
+
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 })
|
|
2265
|
+
);
|
|
2266
|
+
if (!land) {
|
|
2267
|
+
nodes.push(
|
|
2268
|
+
rect({ id: `${id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
|
|
2269
|
+
rect({ id: `${id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
|
|
2270
|
+
rect({ id: `${id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
|
|
2271
|
+
);
|
|
2272
|
+
}
|
|
2273
|
+
} else {
|
|
2274
|
+
nodes.push(
|
|
2275
|
+
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 }),
|
|
2276
|
+
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 })
|
|
2277
|
+
);
|
|
2701
2278
|
}
|
|
2702
|
-
|
|
2703
|
-
|
|
2279
|
+
return nodes;
|
|
2280
|
+
}
|
|
2281
|
+
case "laptop": {
|
|
2282
|
+
const lidTop = dims.cy - (sh + 40) / 2;
|
|
2283
|
+
const keyRows = [0, 1, 2, 3].map(
|
|
2284
|
+
(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 })
|
|
2285
|
+
);
|
|
2286
|
+
return [
|
|
2287
|
+
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 }),
|
|
2288
|
+
rect({ id: `${id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
2289
|
+
rect({ id: `${id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
2290
|
+
...keyRows,
|
|
2291
|
+
rect({ id: `${id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
|
|
2292
|
+
rect({ id: `${id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
|
|
2293
|
+
screen(),
|
|
2294
|
+
ellipse({ id: `${id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
2295
|
+
rect({ id: `${id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: p.bodyStroke, strokeWidth: 2, radius: 18 })
|
|
2296
|
+
];
|
|
2297
|
+
}
|
|
2298
|
+
case "browser": {
|
|
2299
|
+
const winW = sw + 16;
|
|
2300
|
+
const winH = sh + 92;
|
|
2301
|
+
const barY = -winH / 2 + 24;
|
|
2302
|
+
return [
|
|
2303
|
+
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 }),
|
|
2304
|
+
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
|
|
2305
|
+
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
|
|
2306
|
+
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
|
|
2307
|
+
// an active tab tucked under the lights
|
|
2308
|
+
rect({ id: `${id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: o.screen ?? p.screen, radius: 8 }),
|
|
2309
|
+
text({ id: `${id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
2310
|
+
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 }),
|
|
2311
|
+
rect({ id: `${id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
|
|
2312
|
+
text({ id: `${id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
|
|
2313
|
+
screen()
|
|
2314
|
+
];
|
|
2315
|
+
}
|
|
2316
|
+
case "watch": {
|
|
2317
|
+
const bw = sw + 36;
|
|
2318
|
+
const bh = sh + 36;
|
|
2319
|
+
return [
|
|
2320
|
+
// straps (drawn behind the body) flaring out top & bottom
|
|
2321
|
+
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 }),
|
|
2322
|
+
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 }),
|
|
2323
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bw, height: bh, fill: p.body, stroke: p.bodyStroke, strokeWidth: 3, radius: 60 }),
|
|
2324
|
+
screen(),
|
|
2325
|
+
rect({ id: `${id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
|
|
2326
|
+
rect({ id: `${id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
|
|
2327
|
+
];
|
|
2328
|
+
}
|
|
2329
|
+
case "monitor": {
|
|
2330
|
+
const panelW = sw + 44;
|
|
2331
|
+
const panelH = sh + 60;
|
|
2332
|
+
return [
|
|
2333
|
+
rect({ id: `${id}-panel`, x: 0, y: 0, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 16 }),
|
|
2334
|
+
screen(),
|
|
2335
|
+
ellipse({ id: `${id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
|
|
2336
|
+
rect({ id: `${id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
|
|
2337
|
+
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 })
|
|
2338
|
+
];
|
|
2339
|
+
}
|
|
2340
|
+
case "tv": {
|
|
2341
|
+
const panelW = sw + 44;
|
|
2342
|
+
const panelH = sh + 48;
|
|
2343
|
+
const panelBottom = dims.cy + panelH / 2;
|
|
2344
|
+
return [
|
|
2345
|
+
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 }),
|
|
2346
|
+
screen(),
|
|
2347
|
+
ellipse({ id: `${id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
2348
|
+
rect({ id: `${id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
|
|
2349
|
+
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 })
|
|
2350
|
+
];
|
|
2351
|
+
}
|
|
2352
|
+
case "foldable": {
|
|
2353
|
+
const bodyW = sw + 40;
|
|
2354
|
+
const bodyH = sh + 40;
|
|
2355
|
+
return [
|
|
2356
|
+
rect({ id: `${id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
2357
|
+
rect({ id: `${id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
2358
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 28 }),
|
|
2359
|
+
screen(),
|
|
2360
|
+
rect({ id: `${id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
|
|
2361
|
+
ellipse({ id: `${id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
|
|
2362
|
+
ellipse({ id: `${id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
|
|
2363
|
+
];
|
|
2364
|
+
}
|
|
2365
|
+
case "terminal": {
|
|
2366
|
+
const winW = sw + 16;
|
|
2367
|
+
const winH = sh + 76;
|
|
2368
|
+
return [
|
|
2369
|
+
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 }),
|
|
2370
|
+
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 28, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FF5F57" }),
|
|
2371
|
+
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 50, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FEBC2E" }),
|
|
2372
|
+
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 72, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#28C840" }),
|
|
2373
|
+
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 }),
|
|
2374
|
+
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 }),
|
|
2375
|
+
screen()
|
|
2376
|
+
];
|
|
2377
|
+
}
|
|
2378
|
+
case "car": {
|
|
2379
|
+
const bodyW = sw + 60;
|
|
2380
|
+
const bodyH = sh + 60;
|
|
2381
|
+
return [
|
|
2382
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 40 }),
|
|
2383
|
+
ellipse({ id: `${id}-knob`, x: -bodyW / 2 + 18, y: 0, anchor: "center", width: 22, height: 22, fill: p.body, stroke: p.detail, strokeWidth: 3 }),
|
|
2384
|
+
screen(),
|
|
2385
|
+
ellipse({ id: `${id}-btn1`, x: -44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
2386
|
+
ellipse({ id: `${id}-btn2`, x: 0, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
2387
|
+
ellipse({ id: `${id}-btn3`, x: 44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail })
|
|
2388
|
+
];
|
|
2704
2389
|
}
|
|
2705
|
-
} else {
|
|
2706
|
-
chars.forEach((ch) => {
|
|
2707
|
-
const a = advance(ch, weight, fontSize);
|
|
2708
|
-
if (ch !== " ") mk(ch, cursor2 + a / 2, a);
|
|
2709
|
-
cursor2 += a + ls;
|
|
2710
|
-
});
|
|
2711
2390
|
}
|
|
2712
|
-
return { nodes, glyphs, ids: glyphs.map((g) => g.id), width: total, x, y, fontSize };
|
|
2713
2391
|
}
|
|
2714
|
-
var
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2392
|
+
var urlText = (url) => {
|
|
2393
|
+
const u = url ?? "reframe.video";
|
|
2394
|
+
return u.length > 70 ? `${u.slice(0, 67)}\u2026` : u;
|
|
2395
|
+
};
|
|
2396
|
+
function devicePreset(name, opts = {}) {
|
|
2397
|
+
const id = opts.id ?? "device";
|
|
2398
|
+
const p = opts.color === "light" ? LIGHT : DARK;
|
|
2399
|
+
const children = buildDevice(name, id, p, opts, opts.content ?? []);
|
|
2400
|
+
return group(
|
|
2401
|
+
{
|
|
2402
|
+
id,
|
|
2403
|
+
x: opts.x ?? 0,
|
|
2404
|
+
y: opts.y ?? 0,
|
|
2405
|
+
...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
|
|
2406
|
+
...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
|
|
2407
|
+
},
|
|
2408
|
+
children
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// ../core/src/cursor.ts
|
|
2413
|
+
var ARROW_D = "M0 0 L0 30 L8 23 L12.6 33 L17 31 L12.4 21.4 L21 21.4 Z";
|
|
2414
|
+
function cursor(opts = {}) {
|
|
2415
|
+
const id = opts.id ?? "cursor";
|
|
2416
|
+
const style = opts.style ?? "arrow";
|
|
2417
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
2418
|
+
const accent = opts.accent ?? "#FF5A1F";
|
|
2419
|
+
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" })];
|
|
2420
|
+
return group(
|
|
2421
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
2422
|
+
[
|
|
2423
|
+
// ripple ring (behind the pointer), emanates from the hotspot on click
|
|
2424
|
+
ellipse({ id: `${id}-ripple`, x: 0, y: 0, width: 30, height: 30, fill: "none", stroke: accent, strokeWidth: 3, opacity: 0, scale: 0, anchor: "center" }),
|
|
2425
|
+
// the pointer art lives in its own group so a click "tap" can scale it
|
|
2426
|
+
// independently of the cursor's resting scale
|
|
2427
|
+
group({ id: `${id}-art`, x: 0, y: 0 }, art)
|
|
2428
|
+
]
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
2432
|
+
function cursorTo(id, from, to2, opts = {}) {
|
|
2433
|
+
const dx = to2[0] - from[0], dy = to2[1] - from[1];
|
|
2434
|
+
const dist = Math.hypot(dx, dy) || 1;
|
|
2435
|
+
const arc = opts.arc ?? 0.12;
|
|
2436
|
+
const mid = [(from[0] + to2[0]) / 2 + -dy / dist * arc * dist, (from[1] + to2[1]) / 2 + dx / dist * arc * dist];
|
|
2437
|
+
const duration = opts.duration ?? clamp(dist / 1400, 0.4, 0.9);
|
|
2438
|
+
return motionPath(id, [from, mid, to2], { duration, ease: opts.ease ?? "easeInOutCubic", curviness: 1, ...opts.label && { label: opts.label } });
|
|
2439
|
+
}
|
|
2440
|
+
function cursorPath(id, points, opts = {}) {
|
|
2441
|
+
return motionPath(id, points, {
|
|
2442
|
+
duration: opts.duration ?? clamp(points.length * 0.5, 0.5, 4),
|
|
2443
|
+
ease: opts.ease ?? "easeInOutCubic",
|
|
2444
|
+
curviness: opts.curviness ?? 1,
|
|
2445
|
+
...opts.label && { label: opts.label }
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
function clickBody(id, o) {
|
|
2449
|
+
const sp = Math.max(0.25, o.speed ?? 1);
|
|
2450
|
+
const d = (b) => b / sp;
|
|
2451
|
+
const out = [
|
|
2452
|
+
// the pointer taps
|
|
2453
|
+
seq(tween(`${id}-art`, { scale: 0.82 }, { duration: d(0.08), ease: "easeOutQuad" }), tween(`${id}-art`, { scale: 1 }, { duration: d(0.1), ease: "easeOutBack" }))
|
|
2454
|
+
];
|
|
2455
|
+
if (o.ripple !== false) {
|
|
2456
|
+
out.push(seq(
|
|
2457
|
+
tween(`${id}-ripple`, { scale: 0.2, opacity: 0.55 }, { duration: 1e-3 }),
|
|
2458
|
+
par(
|
|
2459
|
+
tween(`${id}-ripple`, { scale: 5 }, { duration: d(0.5), ease: "easeOutCubic" }),
|
|
2460
|
+
tween(`${id}-ripple`, { opacity: 0 }, { duration: d(0.5), ease: "easeOutQuad" })
|
|
2461
|
+
)
|
|
2462
|
+
));
|
|
2463
|
+
}
|
|
2464
|
+
if (o.press) {
|
|
2465
|
+
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" })));
|
|
2769
2466
|
}
|
|
2467
|
+
return out;
|
|
2468
|
+
}
|
|
2469
|
+
function cursorClick(id, opts = {}) {
|
|
2470
|
+
return beat(opts.label ?? "cursor-click", {}, [par(...clickBody(id, opts))]);
|
|
2471
|
+
}
|
|
2472
|
+
function cursorDouble(id, opts = {}) {
|
|
2473
|
+
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
2474
|
+
return beat(opts.label ?? "cursor-double", {}, [
|
|
2475
|
+
seq(par(...clickBody(id, { ...opts, ripple: false })), wait(0.12 / sp), par(...clickBody(id, opts)))
|
|
2476
|
+
]);
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// ../core/src/rig.ts
|
|
2480
|
+
var DEFAULT_LINE = "#FFE3D2";
|
|
2481
|
+
var DEFAULT_FILL = "#0E1424";
|
|
2482
|
+
var LINE_W = 5;
|
|
2483
|
+
var GLOW_W = 16;
|
|
2484
|
+
var K = 0.5523;
|
|
2485
|
+
var n = (v) => Number(v.toFixed(2));
|
|
2486
|
+
function capsulePath(hw, len) {
|
|
2487
|
+
const yT = hw;
|
|
2488
|
+
const yB = Math.max(hw, len - hw);
|
|
2489
|
+
const k = hw * K;
|
|
2490
|
+
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`;
|
|
2491
|
+
}
|
|
2492
|
+
function ovalPath(a, b, cx = 0, cy = 0) {
|
|
2493
|
+
const ka = n(a * K), kb = n(b * K);
|
|
2494
|
+
const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
|
|
2495
|
+
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`;
|
|
2496
|
+
}
|
|
2497
|
+
function boneShape(jointId, bone, o) {
|
|
2498
|
+
if (bone.shape) return bone.shape;
|
|
2499
|
+
const len = bone.length ?? 0;
|
|
2500
|
+
if (len <= 0) return [];
|
|
2501
|
+
const d = capsulePath((bone.width ?? 20) / 2, len);
|
|
2502
|
+
const nodes = [];
|
|
2503
|
+
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 }));
|
|
2504
|
+
nodes.push(path({ id: `${jointId}-shape`, d, x: 0, y: 0, fill: o.fill, stroke: o.color, strokeWidth: LINE_W }));
|
|
2505
|
+
return nodes;
|
|
2506
|
+
}
|
|
2507
|
+
function buildBone(bone, id, o) {
|
|
2508
|
+
const jointId = `${id}-${bone.name}`;
|
|
2509
|
+
return group(
|
|
2510
|
+
{ id: jointId, x: bone.at[0], y: bone.at[1], rotation: bone.rotation ?? 0 },
|
|
2511
|
+
[...boneShape(jointId, bone, o), ...(bone.children ?? []).map((c) => buildBone(c, id, o))]
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
function rig(root, opts = {}) {
|
|
2515
|
+
const id = opts.id ?? "rig";
|
|
2516
|
+
const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
|
|
2517
|
+
return group(
|
|
2518
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
2519
|
+
[buildBone(root, id, o)]
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
2522
|
+
function rigPose(id, pose) {
|
|
2523
|
+
const out = {};
|
|
2524
|
+
for (const [name, deg] of Object.entries(pose)) out[`${id}-${name}`] = { rotation: deg };
|
|
2525
|
+
return out;
|
|
2526
|
+
}
|
|
2527
|
+
function poseTo(id, pose, opts = {}) {
|
|
2528
|
+
const tweens = Object.entries(pose).map(
|
|
2529
|
+
([name, deg]) => tween(`${id}-${name}`, { rotation: deg }, { duration: opts.duration ?? 0.5, ease: opts.ease ?? "easeInOutCubic" })
|
|
2530
|
+
);
|
|
2531
|
+
return opts.stagger ? stagger(opts.stagger, ...tweens) : par(...tweens);
|
|
2532
|
+
}
|
|
2533
|
+
function ikReach(upper, lower, dx, dy, flip = false) {
|
|
2534
|
+
const D = Math.hypot(dx, dy);
|
|
2535
|
+
const cos2 = Math.max(-1, Math.min(1, (D * D - upper * upper - lower * lower) / (2 * upper * lower)));
|
|
2536
|
+
const theta2 = (flip ? -1 : 1) * Math.acos(cos2);
|
|
2537
|
+
const vx = -lower * Math.sin(theta2);
|
|
2538
|
+
const vy = upper + lower * Math.cos(theta2);
|
|
2539
|
+
const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
|
|
2540
|
+
const deg = (r) => r * 180 / Math.PI;
|
|
2541
|
+
return [deg(theta1), deg(theta2)];
|
|
2542
|
+
}
|
|
2543
|
+
function humanoid(opts = {}) {
|
|
2544
|
+
const line2 = opts.color ?? DEFAULT_LINE;
|
|
2545
|
+
const fill = opts.fill ?? DEFAULT_FILL;
|
|
2546
|
+
const glow2 = opts.glow;
|
|
2547
|
+
const blob = (jid, a, b, cy) => {
|
|
2548
|
+
const d = ovalPath(a, b, 0, cy);
|
|
2549
|
+
const nodes = [];
|
|
2550
|
+
if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
2551
|
+
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
2552
|
+
return nodes;
|
|
2553
|
+
};
|
|
2554
|
+
const id = opts.id ?? "rig";
|
|
2555
|
+
const root = {
|
|
2556
|
+
name: "chest",
|
|
2557
|
+
at: [0, 0],
|
|
2558
|
+
shape: blob(`${id}-chest`, 44, 62, 22),
|
|
2559
|
+
children: [
|
|
2560
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
|
|
2561
|
+
{ name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
|
|
2562
|
+
{ name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
|
|
2563
|
+
] },
|
|
2564
|
+
{ name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
|
|
2565
|
+
{ name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
|
|
2566
|
+
] },
|
|
2567
|
+
{ name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
|
|
2568
|
+
{ name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
|
|
2569
|
+
] },
|
|
2570
|
+
{ name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
|
|
2571
|
+
{ name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
|
|
2572
|
+
] }
|
|
2573
|
+
]
|
|
2574
|
+
};
|
|
2575
|
+
return rig(root, opts);
|
|
2770
2576
|
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2577
|
+
|
|
2578
|
+
// ../core/src/characterPreset.ts
|
|
2579
|
+
var CHARACTER_PRESET_NAMES = ["walk", "run", "jump", "dance", "wave", "cheer"];
|
|
2580
|
+
var THIGH = 76;
|
|
2581
|
+
var SHIN = 72;
|
|
2582
|
+
var clamp012 = (x) => Math.max(0, Math.min(1, x));
|
|
2583
|
+
function makeRng3(seed) {
|
|
2584
|
+
let a = seed >>> 0 || 2654435769;
|
|
2585
|
+
return () => {
|
|
2586
|
+
a = a + 1831565813 | 0;
|
|
2587
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
2588
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
2589
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2590
|
+
};
|
|
2775
2591
|
}
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
});
|
|
2592
|
+
var dur2 = (base, sp) => base / sp;
|
|
2593
|
+
function ctx2(o) {
|
|
2594
|
+
const rand2 = makeRng3((o.seed ?? 0) + 1);
|
|
2595
|
+
return {
|
|
2596
|
+
g: o.target,
|
|
2597
|
+
label: o.label,
|
|
2598
|
+
e: clamp012(o.energy ?? 0.5),
|
|
2599
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
2600
|
+
cycles: Math.max(1, Math.round(o.cycles ?? 4)),
|
|
2601
|
+
facing: o.facing ?? 1,
|
|
2602
|
+
at: o.at ?? [0, 0],
|
|
2603
|
+
travel: o.travel,
|
|
2604
|
+
rand: rand2,
|
|
2605
|
+
jit: (amp) => (rand2() - 0.5) * 2 * amp
|
|
2606
|
+
};
|
|
2792
2607
|
}
|
|
2793
|
-
var
|
|
2794
|
-
function
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
tween(g.id, { x: g.x + (rs(21) - 0.5) * 1100 * (0.6 + c.e), y: g.y + (rs(22) - 0.5) * 760 }, { duration: dur3(0.7, c.sp), ease: "easeInCubic" }),
|
|
2800
|
-
tween(g.id, { rotation: (rs(23) - 0.5) * 300, opacity: 0 }, { duration: dur3(0.7, c.sp), ease: "easeInQuad" })
|
|
2801
|
-
);
|
|
2802
|
-
case "fly":
|
|
2803
|
-
return par(
|
|
2804
|
-
tween(g.id, { x: g.x + dir[0] * 1200, y: g.y + dir[1] * 1200 }, { duration: dur3(0.6, c.sp), ease: "easeInCubic" }),
|
|
2805
|
-
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2806
|
-
);
|
|
2807
|
-
case "dissolve":
|
|
2808
|
-
return seq(wait(rs(31) * 0.5), par(
|
|
2809
|
-
tween(g.id, { opacity: 0 }, { duration: dur3(0.4, c.sp), ease: "easeInQuad" }),
|
|
2810
|
-
tween(g.id, { scale: 1.4 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
2811
|
-
));
|
|
2812
|
-
case "fall":
|
|
2813
|
-
return par(
|
|
2814
|
-
tween(g.id, { y: g.y + 700 + rs(41) * 200 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" }),
|
|
2815
|
-
tween(g.id, { rotation: (rs(42) - 0.5) * 120, opacity: 0 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" })
|
|
2816
|
-
);
|
|
2817
|
-
case "collapse":
|
|
2818
|
-
return par(
|
|
2819
|
-
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur3(0.5, c.sp), ease: "easeInBack" }),
|
|
2820
|
-
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
2821
|
-
);
|
|
2608
|
+
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
2609
|
+
function footPos(p, stride, lift) {
|
|
2610
|
+
p = (p % 1 + 1) % 1;
|
|
2611
|
+
if (p < 0.5) {
|
|
2612
|
+
const u2 = p / 0.5;
|
|
2613
|
+
return [stride * (1 - 2 * u2), 138];
|
|
2822
2614
|
}
|
|
2615
|
+
const u = (p - 0.5) / 0.5;
|
|
2616
|
+
return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
|
|
2823
2617
|
}
|
|
2824
|
-
function
|
|
2825
|
-
const
|
|
2826
|
-
const
|
|
2827
|
-
const
|
|
2828
|
-
const
|
|
2829
|
-
const
|
|
2830
|
-
return
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
file: `keypress-${KEYS[i % KEYS.length]}.wav`,
|
|
2841
|
-
gain: gain + 0.2 * rand(i, 31)
|
|
2842
|
-
}));
|
|
2843
|
-
}
|
|
2844
|
-
|
|
2845
|
-
// ../core/src/motionOps.ts
|
|
2846
|
-
var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
|
|
2847
|
-
var clamp014 = (n3) => Math.max(0, Math.min(1, n3));
|
|
2848
|
-
function settleEase2(e) {
|
|
2849
|
-
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
2618
|
+
function gaitPose(ph, stride, lift, armSwing, facing) {
|
|
2619
|
+
const fl = footPos(ph, stride, lift);
|
|
2620
|
+
const fr = footPos(ph + 0.5, stride, lift);
|
|
2621
|
+
const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
|
|
2622
|
+
const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
|
|
2623
|
+
const swing = Math.cos(2 * Math.PI * ph);
|
|
2624
|
+
return {
|
|
2625
|
+
legUpperL: round(hipL),
|
|
2626
|
+
legLowerL: round(kneeL),
|
|
2627
|
+
legUpperR: round(hipR),
|
|
2628
|
+
legLowerR: round(kneeR),
|
|
2629
|
+
armUpperR: round(-10 - armSwing * swing),
|
|
2630
|
+
armLowerR: -16,
|
|
2631
|
+
armUpperL: round(10 + armSwing * swing),
|
|
2632
|
+
armLowerL: 16
|
|
2633
|
+
};
|
|
2850
2634
|
}
|
|
2851
|
-
function
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2635
|
+
function gait(c, run) {
|
|
2636
|
+
const stride = (run ? 34 : 24) + (run ? 40 : 30) * c.e + c.jit(3);
|
|
2637
|
+
const lift = run ? 40 : 26;
|
|
2638
|
+
const armSwing = (run ? 26 : 16) + 20 * c.e;
|
|
2639
|
+
const halfDur = (run ? 0.26 : 0.42) + c.jit(0.02);
|
|
2640
|
+
const lean = run ? c.facing * -6 : 0;
|
|
2641
|
+
const steps = c.cycles * 2;
|
|
2642
|
+
const d = dur2(halfDur, c.sp);
|
|
2643
|
+
const intro = dur2(0.16, c.sp);
|
|
2644
|
+
const keys = [];
|
|
2645
|
+
for (let k = 0; k <= steps; k++) {
|
|
2646
|
+
const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
|
|
2647
|
+
keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
|
|
2648
|
+
}
|
|
2649
|
+
const total = intro + steps * d;
|
|
2650
|
+
const travel = c.travel ?? stride * 2;
|
|
2651
|
+
const children = [seq(...keys)];
|
|
2652
|
+
if (travel !== 0) {
|
|
2653
|
+
children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
|
|
2861
2654
|
}
|
|
2655
|
+
return beat(run ? "run" : "walk", {}, [par(...children)]);
|
|
2862
2656
|
}
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
const
|
|
2866
|
-
const
|
|
2867
|
-
const
|
|
2868
|
-
const
|
|
2869
|
-
const
|
|
2870
|
-
const
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
}
|
|
2894
|
-
|
|
2895
|
-
const [dx, dy] = fromVec2(opts.from ?? "left", 320 * amt);
|
|
2896
|
-
return {
|
|
2897
|
-
setup: { [target]: { x: b.x + dx, y: b.y + dy, opacity: 0 } },
|
|
2898
|
-
timeline: beat(label, {}, [
|
|
2899
|
-
par(
|
|
2900
|
-
tween(target, { x: b.x, y: b.y }, { duration: d(0.7), ease: settleEase2(e) }),
|
|
2901
|
-
tween(target, { opacity: 1 }, { duration: d(0.4), ease: "easeOutQuad" })
|
|
2902
|
-
)
|
|
2903
|
-
])
|
|
2904
|
-
};
|
|
2905
|
-
}
|
|
2906
|
-
case "fade":
|
|
2907
|
-
return {
|
|
2908
|
-
setup: { [target]: { opacity: 0 } },
|
|
2909
|
-
timeline: beat(label, {}, [tween(target, { opacity: 1 }, { duration: d(0.6), ease: "easeOutQuad" })])
|
|
2910
|
-
};
|
|
2911
|
-
case "draw-on":
|
|
2912
|
-
return {
|
|
2913
|
-
setup: { [target]: { progress: 0 } },
|
|
2914
|
-
timeline: beat(label, {}, [tween(target, { progress: 1 }, { duration: d(1.3), ease: "easeInOutQuad" })])
|
|
2915
|
-
};
|
|
2916
|
-
case "pulse": {
|
|
2917
|
-
const hi = b.scale * (1 + 0.12 * amt);
|
|
2918
|
-
const pulses = 2 + Math.round(amt);
|
|
2919
|
-
const steps = [];
|
|
2920
|
-
for (let i = 0; i < pulses; i++) {
|
|
2921
|
-
steps.push(tween(target, { scale: hi }, { duration: d(0.22), ease: "easeOutQuad" }));
|
|
2922
|
-
steps.push(tween(target, { scale: b.scale }, { duration: d(0.22), ease: "easeInQuad" }));
|
|
2923
|
-
}
|
|
2924
|
-
return { timeline: beat(label, {}, [seq(...steps)]) };
|
|
2925
|
-
}
|
|
2657
|
+
function jumpBeat(c) {
|
|
2658
|
+
const h = 120 + 150 * c.e;
|
|
2659
|
+
const [y0] = [c.at[1]];
|
|
2660
|
+
const CROUCH = { legUpperL: 18, legLowerL: 54, legUpperR: -18, legLowerR: 54, armUpperL: 28, armUpperR: -28 };
|
|
2661
|
+
const LAUNCH = { legUpperL: 0, legLowerL: 0, legUpperR: 0, legLowerR: 0, armUpperL: 150, armUpperR: -150 };
|
|
2662
|
+
const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
|
|
2663
|
+
const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
|
|
2664
|
+
const j = c.jit(0.03);
|
|
2665
|
+
return beat("jump", {}, [
|
|
2666
|
+
seq(
|
|
2667
|
+
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" })),
|
|
2668
|
+
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" })),
|
|
2669
|
+
poseTo(c.g, TUCK, { duration: dur2(0.22, c.sp) }),
|
|
2670
|
+
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" })),
|
|
2671
|
+
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" }))
|
|
2672
|
+
)
|
|
2673
|
+
]);
|
|
2674
|
+
}
|
|
2675
|
+
function danceBeat(c) {
|
|
2676
|
+
const y0 = c.at[1];
|
|
2677
|
+
const sway = 8 + 6 * c.e;
|
|
2678
|
+
const armUp = 130 + 30 * c.e;
|
|
2679
|
+
const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
|
|
2680
|
+
const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
|
|
2681
|
+
const d = dur2(0.34, c.sp);
|
|
2682
|
+
const keys = [];
|
|
2683
|
+
for (let k = 0; k < c.cycles * 2; k++) {
|
|
2684
|
+
const pose = k % 2 === 0 ? A : B;
|
|
2685
|
+
keys.push(par(
|
|
2686
|
+
poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
|
|
2687
|
+
tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
|
|
2688
|
+
));
|
|
2926
2689
|
}
|
|
2690
|
+
keys.push(tween(c.g, { y: y0 }, { duration: d }));
|
|
2691
|
+
return beat("dance", {}, [seq(...keys)]);
|
|
2927
2692
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
warp: 0.5,
|
|
2938
|
-
// ui
|
|
2939
|
-
tick: 0.03,
|
|
2940
|
-
click: 0.05,
|
|
2941
|
-
blip: 0.1,
|
|
2942
|
-
pop: 0.12,
|
|
2943
|
-
select: 0.18,
|
|
2944
|
-
// impact
|
|
2945
|
-
thud: 0.25,
|
|
2946
|
-
boom: 0.6,
|
|
2947
|
-
knock: 0.14,
|
|
2948
|
-
sub: 0.7,
|
|
2949
|
-
// positive
|
|
2950
|
-
chime: 0.7,
|
|
2951
|
-
ding: 0.5,
|
|
2952
|
-
coin: 0.3,
|
|
2953
|
-
sparkle: 0.6,
|
|
2954
|
-
shimmer: 0.9,
|
|
2955
|
-
success: 0.6,
|
|
2956
|
-
// alert
|
|
2957
|
-
zap: 0.22,
|
|
2958
|
-
error: 0.4,
|
|
2959
|
-
// tech
|
|
2960
|
-
glitch: 0.3,
|
|
2961
|
-
static: 0.18,
|
|
2962
|
-
scan: 0.45,
|
|
2963
|
-
powerup: 0.4,
|
|
2964
|
-
powerdown: 0.5,
|
|
2965
|
-
// rhythm / foley
|
|
2966
|
-
snare: 0.18,
|
|
2967
|
-
hat: 0.05,
|
|
2968
|
-
bubble: 0.16,
|
|
2969
|
-
notify: 0.45,
|
|
2970
|
-
camera: 0.18
|
|
2971
|
-
};
|
|
2972
|
-
var FILE_CUE_DURATION = 0.4;
|
|
2973
|
-
function collectClipAudio(ir, duration, warnings) {
|
|
2974
|
-
const out = [];
|
|
2975
|
-
const walk = (nodes) => {
|
|
2976
|
-
for (const node of nodes) {
|
|
2977
|
-
if (node.type === "video") {
|
|
2978
|
-
const gain = node.props.volume ?? 1;
|
|
2979
|
-
const start = node.props.start ?? 0;
|
|
2980
|
-
if (gain <= 0) continue;
|
|
2981
|
-
if (start >= duration) {
|
|
2982
|
-
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
2983
|
-
continue;
|
|
2984
|
-
}
|
|
2985
|
-
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain, fadeIn: node.props.fadeIn ?? 0, pan: node.props.pan ?? 0 });
|
|
2986
|
-
}
|
|
2987
|
-
if (node.type === "group") walk(node.children);
|
|
2988
|
-
}
|
|
2989
|
-
};
|
|
2990
|
-
walk(ir.nodes);
|
|
2991
|
-
return out;
|
|
2693
|
+
function waveBeat(c) {
|
|
2694
|
+
const n3 = 3 + Math.round(c.rand() * 2);
|
|
2695
|
+
const amp = 16 + 10 * c.e;
|
|
2696
|
+
const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur2(0.4, c.sp), ease: "easeOutBack" })];
|
|
2697
|
+
for (let k = 0; k < n3; k++) {
|
|
2698
|
+
steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur2(0.22, c.sp), ease: "easeInOutQuad" }));
|
|
2699
|
+
}
|
|
2700
|
+
steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur2(0.4, c.sp), ease: "easeInOutCubic" }));
|
|
2701
|
+
return beat("wave", {}, [seq(...steps)]);
|
|
2992
2702
|
}
|
|
2993
|
-
function
|
|
2994
|
-
const
|
|
2995
|
-
const
|
|
2996
|
-
const
|
|
2997
|
-
const
|
|
2998
|
-
|
|
2999
|
-
|
|
2703
|
+
function cheerBeat(c) {
|
|
2704
|
+
const y0 = c.at[1];
|
|
2705
|
+
const UP = { armUpperL: 152, armLowerL: 8, armUpperR: -152, armLowerR: -8 };
|
|
2706
|
+
const d = dur2(0.3, c.sp);
|
|
2707
|
+
const keys = [poseTo(c.g, UP, { duration: dur2(0.35, c.sp), ease: "easeOutBack" })];
|
|
2708
|
+
for (let k = 0; k < c.cycles; k++) {
|
|
2709
|
+
keys.push(par(tween(c.g, { y: y0 - 28 }, { duration: d, ease: "easeOutQuad" }), poseTo(c.g, { armUpperL: 160, armUpperR: -160 }, { duration: d })));
|
|
2710
|
+
keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
|
|
3000
2711
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
2712
|
+
return beat("cheer", {}, [seq(...keys)]);
|
|
2713
|
+
}
|
|
2714
|
+
function characterPreset(name, opts) {
|
|
2715
|
+
const c = ctx2(opts);
|
|
2716
|
+
let tl;
|
|
2717
|
+
switch (name) {
|
|
2718
|
+
case "walk":
|
|
2719
|
+
tl = gait(c, false);
|
|
2720
|
+
break;
|
|
2721
|
+
case "run":
|
|
2722
|
+
tl = gait(c, true);
|
|
2723
|
+
break;
|
|
2724
|
+
case "jump":
|
|
2725
|
+
tl = jumpBeat(c);
|
|
2726
|
+
break;
|
|
2727
|
+
case "dance":
|
|
2728
|
+
tl = danceBeat(c);
|
|
2729
|
+
break;
|
|
2730
|
+
case "wave":
|
|
2731
|
+
tl = waveBeat(c);
|
|
2732
|
+
break;
|
|
2733
|
+
case "cheer":
|
|
2734
|
+
tl = cheerBeat(c);
|
|
2735
|
+
break;
|
|
2736
|
+
default: {
|
|
2737
|
+
const _exhaustive = name;
|
|
2738
|
+
throw new Error(`unknown characterPreset "${_exhaustive}"`);
|
|
3022
2739
|
}
|
|
3023
|
-
cues.push({
|
|
3024
|
-
t,
|
|
3025
|
-
gain: cue.gain ?? 1,
|
|
3026
|
-
duration: cueDuration,
|
|
3027
|
-
fadeIn: cue.fadeIn ?? 0,
|
|
3028
|
-
fadeOut: cue.fadeOut ?? 0,
|
|
3029
|
-
pan: cue.pan ?? 0,
|
|
3030
|
-
source: cue.sfx ? (
|
|
3031
|
-
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
3032
|
-
// (pitch/texture); an explicit params.seed always wins.
|
|
3033
|
-
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
3034
|
-
) : { kind: "file", path: cue.file }
|
|
3035
|
-
});
|
|
3036
2740
|
}
|
|
3037
|
-
|
|
3038
|
-
return {
|
|
3039
|
-
duration,
|
|
3040
|
-
bgm: resolveBgm(audio.bgm),
|
|
3041
|
-
cues,
|
|
3042
|
-
duckWindows: mergeDuckWindows(cues, duration),
|
|
3043
|
-
clipAudio,
|
|
3044
|
-
warnings
|
|
3045
|
-
};
|
|
2741
|
+
return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
|
|
3046
2742
|
}
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
}
|
|
3055
|
-
return duckWindows;
|
|
2743
|
+
|
|
2744
|
+
// ../core/src/figure.ts
|
|
2745
|
+
var K2 = 0.5523;
|
|
2746
|
+
var n2 = (v) => Number(v.toFixed(2));
|
|
2747
|
+
function limb(a, b, y0, y1) {
|
|
2748
|
+
const ka = n2(a * K2), kb = n2(b * K2);
|
|
2749
|
+
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`;
|
|
3056
2750
|
}
|
|
3057
|
-
function
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
2751
|
+
function rrect(a, b, y0, y1, r) {
|
|
2752
|
+
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`;
|
|
2753
|
+
}
|
|
2754
|
+
function darken(hex, f) {
|
|
2755
|
+
const h = hex.replace("#", "");
|
|
2756
|
+
const v = parseInt(h.length === 3 ? [...h].map((c) => c + c).join("") : h, 16);
|
|
2757
|
+
const ch = (s) => Math.max(0, Math.min(255, Math.round((v >> s & 255) * (1 - f))));
|
|
2758
|
+
const hx = (x) => x.toString(16).padStart(2, "0");
|
|
2759
|
+
return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
|
|
2760
|
+
}
|
|
2761
|
+
var DEF = {
|
|
2762
|
+
clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
|
|
2763
|
+
cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
|
|
2764
|
+
};
|
|
2765
|
+
function resolvePal(style, p = {}) {
|
|
2766
|
+
const d = DEF[style];
|
|
2767
|
+
const accent = p.accent ?? d.accent;
|
|
2768
|
+
const top = p.top ?? (style === "clean" ? accent : d.top);
|
|
2769
|
+
const skin = p.skin ?? d.skin;
|
|
2770
|
+
const hair = p.hair ?? d.hair;
|
|
2771
|
+
const pants = p.pants ?? d.pants;
|
|
2772
|
+
const shoe = p.shoe ?? d.shoe;
|
|
2773
|
+
return {
|
|
2774
|
+
skin,
|
|
2775
|
+
skinSh: darken(skin, 0.12),
|
|
2776
|
+
hair,
|
|
2777
|
+
hairSh: darken(hair, 0.14),
|
|
2778
|
+
top,
|
|
2779
|
+
topSh: darken(top, 0.12),
|
|
2780
|
+
pants,
|
|
2781
|
+
pantsSh: darken(pants, 0.14),
|
|
2782
|
+
shoe,
|
|
2783
|
+
shoeSh: darken(shoe, 0.22),
|
|
2784
|
+
eye: "#2B313F",
|
|
2785
|
+
cheek: "#FF9E7E",
|
|
2786
|
+
white: "#FFFFFF",
|
|
2787
|
+
mouth: "#8A4233"
|
|
3063
2788
|
};
|
|
2789
|
+
}
|
|
2790
|
+
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 } : {} });
|
|
2791
|
+
function cleanParts(p, face) {
|
|
2792
|
+
const HC = -42;
|
|
3064
2793
|
return {
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
2794
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
|
|
2795
|
+
forearm: (j) => [
|
|
2796
|
+
fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
|
|
2797
|
+
fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
|
|
2798
|
+
fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
|
|
2799
|
+
],
|
|
2800
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
|
|
2801
|
+
shin: (j) => [
|
|
2802
|
+
fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
|
|
2803
|
+
fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
|
|
2804
|
+
fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
|
|
2805
|
+
],
|
|
2806
|
+
torso: (j) => [
|
|
2807
|
+
fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
|
|
2808
|
+
fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
|
|
2809
|
+
fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
|
|
2810
|
+
],
|
|
2811
|
+
head: (j) => [
|
|
2812
|
+
fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
|
|
2813
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
|
|
2814
|
+
fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
|
|
2815
|
+
fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
|
|
2816
|
+
fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
|
|
2817
|
+
...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
|
|
2818
|
+
]
|
|
3070
2819
|
};
|
|
3071
2820
|
}
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
const
|
|
3075
|
-
const warnings = [];
|
|
3076
|
-
const cues = [];
|
|
3077
|
-
const clipAudio = [];
|
|
3078
|
-
for (const placement of comp.scenes) {
|
|
3079
|
-
const plan = resolveAudioPlan(placement.compiled);
|
|
3080
|
-
if (!plan) continue;
|
|
3081
|
-
if (plan.bgm) {
|
|
3082
|
-
warnings.push(`scene "${placement.id}": per-scene bgm ignored \u2014 set bgm at the composition level`);
|
|
3083
|
-
}
|
|
3084
|
-
for (const w of plan.warnings) warnings.push(`scene "${placement.id}": ${w}`);
|
|
3085
|
-
for (const cue of plan.cues) {
|
|
3086
|
-
const t = cue.t + placement.start;
|
|
3087
|
-
if (t >= duration) continue;
|
|
3088
|
-
cues.push({ ...cue, t });
|
|
3089
|
-
}
|
|
3090
|
-
for (const clip of plan.clipAudio) {
|
|
3091
|
-
const start = clip.start + placement.start;
|
|
3092
|
-
if (start >= duration) continue;
|
|
3093
|
-
clipAudio.push({ ...clip, start });
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
for (const [index, cue] of (audio?.cues ?? []).entries()) {
|
|
3097
|
-
if (typeof cue.at !== "number") {
|
|
3098
|
-
warnings.push(`composition cue[${index}]: "at" must be an absolute number (no composition labels) \u2014 dropped`);
|
|
3099
|
-
continue;
|
|
3100
|
-
}
|
|
3101
|
-
const t = Math.max(0, cue.at + (cue.offset ?? 0));
|
|
3102
|
-
const cueDuration = cue.sfx ? SFX_DURATION[cue.sfx] : FILE_CUE_DURATION;
|
|
3103
|
-
if (t >= duration) {
|
|
3104
|
-
warnings.push(`composition cue[${index}] at ${t.toFixed(2)}s past the composition end \u2014 dropped`);
|
|
3105
|
-
continue;
|
|
3106
|
-
}
|
|
3107
|
-
cues.push({
|
|
3108
|
-
t,
|
|
3109
|
-
gain: cue.gain ?? 1,
|
|
3110
|
-
duration: cueDuration,
|
|
3111
|
-
fadeIn: cue.fadeIn ?? 0,
|
|
3112
|
-
fadeOut: cue.fadeOut ?? 0,
|
|
3113
|
-
pan: cue.pan ?? 0,
|
|
3114
|
-
source: cue.sfx ? (
|
|
3115
|
-
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
3116
|
-
// (pitch/texture); an explicit params.seed always wins.
|
|
3117
|
-
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
3118
|
-
) : { kind: "file", path: cue.file }
|
|
3119
|
-
});
|
|
3120
|
-
}
|
|
3121
|
-
if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
|
|
3122
|
-
cues.sort((a, b) => a.t - b.t);
|
|
2821
|
+
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";
|
|
2822
|
+
function cuteParts(p, face) {
|
|
2823
|
+
const HC = -50;
|
|
3123
2824
|
return {
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
2825
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
|
|
2826
|
+
forearm: (j) => [
|
|
2827
|
+
fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
|
|
2828
|
+
fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
|
|
2829
|
+
fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
|
|
2830
|
+
],
|
|
2831
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
|
|
2832
|
+
shin: (j) => [
|
|
2833
|
+
fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
|
|
2834
|
+
fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
|
|
2835
|
+
fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
|
|
2836
|
+
],
|
|
2837
|
+
torso: (j) => [
|
|
2838
|
+
fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
|
|
2839
|
+
fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
|
|
2840
|
+
fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
|
|
2841
|
+
],
|
|
2842
|
+
head: (j) => [
|
|
2843
|
+
fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
|
|
2844
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
|
|
2845
|
+
fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2846
|
+
fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
2847
|
+
fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
|
|
2848
|
+
...face ? [
|
|
2849
|
+
fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
|
|
2850
|
+
fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
|
|
2851
|
+
fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
|
|
2852
|
+
fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
|
|
2853
|
+
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 })
|
|
2854
|
+
] : []
|
|
2855
|
+
]
|
|
3130
2856
|
};
|
|
3131
2857
|
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
}
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
}
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
2858
|
+
function buildSkeleton(id, S) {
|
|
2859
|
+
const arm = (side, x, r1, r2) => ({
|
|
2860
|
+
name: `armUpper${side}`,
|
|
2861
|
+
at: [x, -14],
|
|
2862
|
+
length: 60,
|
|
2863
|
+
width: 0,
|
|
2864
|
+
rotation: r1,
|
|
2865
|
+
shape: S.upperArm(`${id}-armUpper${side}`),
|
|
2866
|
+
children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
|
|
2867
|
+
});
|
|
2868
|
+
const leg = (side, x, r1, r2) => ({
|
|
2869
|
+
name: `legUpper${side}`,
|
|
2870
|
+
at: [x, 76],
|
|
2871
|
+
length: 76,
|
|
2872
|
+
width: 0,
|
|
2873
|
+
rotation: r1,
|
|
2874
|
+
shape: S.thigh(`${id}-legUpper${side}`),
|
|
2875
|
+
children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
|
|
2876
|
+
});
|
|
2877
|
+
return {
|
|
2878
|
+
name: "chest",
|
|
2879
|
+
at: [0, 0],
|
|
2880
|
+
shape: S.torso(`${id}-chest`),
|
|
2881
|
+
children: [
|
|
2882
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
|
|
2883
|
+
arm("L", -40, 8, 6),
|
|
2884
|
+
arm("R", 40, -8, -6),
|
|
2885
|
+
leg("L", -20, 3, -2),
|
|
2886
|
+
leg("R", 20, -3, 2)
|
|
2887
|
+
]
|
|
2888
|
+
};
|
|
3153
2889
|
}
|
|
3154
|
-
function
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
2890
|
+
function figure(opts = {}) {
|
|
2891
|
+
const style = opts.style ?? "clean";
|
|
2892
|
+
const pal = resolvePal(style, opts.palette);
|
|
2893
|
+
const face = opts.face ?? true;
|
|
2894
|
+
const id = opts.id ?? "figure";
|
|
2895
|
+
const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
|
|
2896
|
+
const rigOpts = { id };
|
|
2897
|
+
if (opts.x !== void 0) rigOpts.x = opts.x;
|
|
2898
|
+
if (opts.y !== void 0) rigOpts.y = opts.y;
|
|
2899
|
+
if (opts.scale !== void 0) rigOpts.scale = opts.scale;
|
|
2900
|
+
if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
|
|
2901
|
+
return rig(buildSkeleton(id, parts), rigOpts);
|
|
3160
2902
|
}
|
|
3161
2903
|
|
|
3162
|
-
// ../core/src/
|
|
3163
|
-
var
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
2904
|
+
// ../core/src/textMetrics.ts
|
|
2905
|
+
var INTER_ADVANCE = {
|
|
2906
|
+
"400": {
|
|
2907
|
+
"0": 63.09,
|
|
2908
|
+
"1": 40.67,
|
|
2909
|
+
"2": 60.99,
|
|
2910
|
+
"3": 61.77,
|
|
2911
|
+
"4": 64.6,
|
|
2912
|
+
"5": 59.33,
|
|
2913
|
+
"6": 62.01,
|
|
2914
|
+
"7": 56.59,
|
|
2915
|
+
"8": 61.87,
|
|
2916
|
+
"9": 62.01,
|
|
2917
|
+
" ": 28.13,
|
|
2918
|
+
"!": 28.76,
|
|
2919
|
+
'"': 46.58,
|
|
2920
|
+
"#": 63.33,
|
|
2921
|
+
"$": 64.16,
|
|
2922
|
+
"%": 98.19,
|
|
2923
|
+
"&": 64.4,
|
|
2924
|
+
"'": 29.98,
|
|
2925
|
+
"(": 36.47,
|
|
2926
|
+
")": 36.47,
|
|
2927
|
+
"*": 50.1,
|
|
2928
|
+
"+": 66.16,
|
|
2929
|
+
",": 28.81,
|
|
2930
|
+
"-": 46,
|
|
2931
|
+
".": 28.81,
|
|
2932
|
+
"/": 36.04,
|
|
2933
|
+
":": 28.81,
|
|
2934
|
+
";": 30.18,
|
|
2935
|
+
"<": 66.16,
|
|
2936
|
+
"=": 66.16,
|
|
2937
|
+
">": 66.16,
|
|
2938
|
+
"?": 51.12,
|
|
2939
|
+
"@": 96.58,
|
|
2940
|
+
"A": 68.99,
|
|
2941
|
+
"B": 65.43,
|
|
2942
|
+
"C": 73.05,
|
|
2943
|
+
"D": 72.17,
|
|
2944
|
+
"E": 60.11,
|
|
2945
|
+
"F": 59.03,
|
|
2946
|
+
"G": 74.61,
|
|
2947
|
+
"H": 74.32,
|
|
2948
|
+
"I": 26.86,
|
|
2949
|
+
"J": 57.08,
|
|
2950
|
+
"K": 67.19,
|
|
2951
|
+
"L": 56.54,
|
|
2952
|
+
"M": 90.33,
|
|
2953
|
+
"N": 75.34,
|
|
2954
|
+
"O": 76.46,
|
|
2955
|
+
"P": 63.87,
|
|
2956
|
+
"Q": 76.46,
|
|
2957
|
+
"R": 64.36,
|
|
2958
|
+
"S": 64.16,
|
|
2959
|
+
"T": 64.55,
|
|
2960
|
+
"U": 74.41,
|
|
2961
|
+
"V": 68.99,
|
|
2962
|
+
"W": 98.54,
|
|
2963
|
+
"X": 68.21,
|
|
2964
|
+
"Y": 67.87,
|
|
2965
|
+
"Z": 62.89,
|
|
2966
|
+
"[": 36.47,
|
|
2967
|
+
"\\": 36.04,
|
|
2968
|
+
"]": 36.47,
|
|
2969
|
+
"^": 47.12,
|
|
2970
|
+
"_": 45.61,
|
|
2971
|
+
"`": 32.28,
|
|
2972
|
+
"a": 56.15,
|
|
2973
|
+
"b": 61.23,
|
|
2974
|
+
"c": 57.13,
|
|
2975
|
+
"d": 61.23,
|
|
2976
|
+
"e": 58.3,
|
|
2977
|
+
"f": 37.01,
|
|
2978
|
+
"g": 61.33,
|
|
2979
|
+
"h": 59.13,
|
|
2980
|
+
"i": 24.22,
|
|
2981
|
+
"j": 24.22,
|
|
2982
|
+
"k": 54.88,
|
|
2983
|
+
"l": 24.22,
|
|
2984
|
+
"m": 87.6,
|
|
2985
|
+
"n": 59.08,
|
|
2986
|
+
"o": 59.96,
|
|
2987
|
+
"p": 61.23,
|
|
2988
|
+
"q": 61.23,
|
|
2989
|
+
"r": 37.65,
|
|
2990
|
+
"s": 52.78,
|
|
2991
|
+
"t": 32.71,
|
|
2992
|
+
"u": 59.13,
|
|
2993
|
+
"v": 56.2,
|
|
2994
|
+
"w": 81.84,
|
|
2995
|
+
"x": 54.59,
|
|
2996
|
+
"y": 56.2,
|
|
2997
|
+
"z": 55.22,
|
|
2998
|
+
"{": 42.63,
|
|
2999
|
+
"|": 33.25,
|
|
3000
|
+
"}": 42.63,
|
|
3001
|
+
"~": 66.16
|
|
3002
|
+
},
|
|
3003
|
+
"700": {
|
|
3004
|
+
"0": 67.43,
|
|
3005
|
+
"1": 43.12,
|
|
3006
|
+
"2": 62.94,
|
|
3007
|
+
"3": 64.55,
|
|
3008
|
+
"4": 67.63,
|
|
3009
|
+
"5": 62.21,
|
|
3010
|
+
"6": 64.94,
|
|
3011
|
+
"7": 58.15,
|
|
3012
|
+
"8": 65.09,
|
|
3013
|
+
"9": 64.94,
|
|
3014
|
+
" ": 23.68,
|
|
3015
|
+
"!": 33.79,
|
|
3016
|
+
'"': 55.13,
|
|
3017
|
+
"#": 64.89,
|
|
3018
|
+
"$": 65.48,
|
|
3019
|
+
"%": 101.56,
|
|
3020
|
+
"&": 67.19,
|
|
3021
|
+
"'": 33.89,
|
|
3022
|
+
"(": 37.7,
|
|
3023
|
+
")": 37.7,
|
|
3024
|
+
"*": 55.91,
|
|
3025
|
+
"+": 67.87,
|
|
3026
|
+
",": 33.4,
|
|
3027
|
+
"-": 46.78,
|
|
3028
|
+
".": 33.4,
|
|
3029
|
+
"/": 38.82,
|
|
3030
|
+
":": 33.4,
|
|
3031
|
+
";": 34.28,
|
|
3032
|
+
"<": 67.87,
|
|
3033
|
+
"=": 67.87,
|
|
3034
|
+
">": 67.87,
|
|
3035
|
+
"?": 55.96,
|
|
3036
|
+
"@": 101.61,
|
|
3037
|
+
"A": 74.66,
|
|
3038
|
+
"B": 66.16,
|
|
3039
|
+
"C": 73.97,
|
|
3040
|
+
"D": 72.22,
|
|
3041
|
+
"E": 60.74,
|
|
3042
|
+
"F": 58.69,
|
|
3043
|
+
"G": 75.05,
|
|
3044
|
+
"H": 74.71,
|
|
3045
|
+
"I": 28.08,
|
|
3046
|
+
"J": 58.45,
|
|
3047
|
+
"K": 71.92,
|
|
3048
|
+
"L": 56.54,
|
|
3049
|
+
"M": 93.16,
|
|
3050
|
+
"N": 76.22,
|
|
3051
|
+
"O": 77.05,
|
|
3052
|
+
"P": 64.79,
|
|
3053
|
+
"Q": 77.69,
|
|
3054
|
+
"R": 65.67,
|
|
3055
|
+
"S": 65.48,
|
|
3056
|
+
"T": 66.75,
|
|
3057
|
+
"U": 73.19,
|
|
3058
|
+
"V": 74.66,
|
|
3059
|
+
"W": 103.76,
|
|
3060
|
+
"X": 73.83,
|
|
3061
|
+
"Y": 73.1,
|
|
3062
|
+
"Z": 66.41,
|
|
3063
|
+
"[": 37.7,
|
|
3064
|
+
"\\": 38.82,
|
|
3065
|
+
"]": 37.7,
|
|
3066
|
+
"^": 48.68,
|
|
3067
|
+
"_": 47.61,
|
|
3068
|
+
"`": 36.52,
|
|
3069
|
+
"a": 58.06,
|
|
3070
|
+
"b": 63.04,
|
|
3071
|
+
"c": 58.84,
|
|
3072
|
+
"d": 63.04,
|
|
3073
|
+
"e": 59.57,
|
|
3074
|
+
"f": 39.79,
|
|
3075
|
+
"g": 63.18,
|
|
3076
|
+
"h": 62.26,
|
|
3077
|
+
"i": 27.1,
|
|
3078
|
+
"j": 27.1,
|
|
3079
|
+
"k": 58.01,
|
|
3080
|
+
"l": 27.1,
|
|
3081
|
+
"m": 91.26,
|
|
3082
|
+
"n": 62.26,
|
|
3083
|
+
"o": 61.33,
|
|
3084
|
+
"p": 63.04,
|
|
3085
|
+
"q": 63.04,
|
|
3086
|
+
"r": 40.72,
|
|
3087
|
+
"s": 56.01,
|
|
3088
|
+
"t": 36.62,
|
|
3089
|
+
"u": 62.26,
|
|
3090
|
+
"v": 59.96,
|
|
3091
|
+
"w": 85.01,
|
|
3092
|
+
"x": 58.01,
|
|
3093
|
+
"y": 60.21,
|
|
3094
|
+
"z": 57.28,
|
|
3095
|
+
"{": 46.88,
|
|
3096
|
+
"|": 37.16,
|
|
3097
|
+
"}": 46.88,
|
|
3098
|
+
"~": 67.87
|
|
3099
|
+
},
|
|
3100
|
+
"800": {
|
|
3101
|
+
"0": 69.19,
|
|
3102
|
+
"1": 44.14,
|
|
3103
|
+
"2": 63.77,
|
|
3104
|
+
"3": 65.67,
|
|
3105
|
+
"4": 68.85,
|
|
3106
|
+
"5": 63.38,
|
|
3107
|
+
"6": 66.16,
|
|
3108
|
+
"7": 58.79,
|
|
3109
|
+
"8": 66.41,
|
|
3110
|
+
"9": 66.16,
|
|
3111
|
+
" ": 21.88,
|
|
3112
|
+
"!": 35.84,
|
|
3113
|
+
'"': 58.64,
|
|
3114
|
+
"#": 65.53,
|
|
3115
|
+
"$": 66.02,
|
|
3116
|
+
"%": 102.93,
|
|
3117
|
+
"&": 68.31,
|
|
3118
|
+
"'": 35.45,
|
|
3119
|
+
"(": 38.23,
|
|
3120
|
+
")": 38.23,
|
|
3121
|
+
"*": 58.25,
|
|
3122
|
+
"+": 68.55,
|
|
3123
|
+
",": 35.25,
|
|
3124
|
+
"-": 47.12,
|
|
3125
|
+
".": 35.25,
|
|
3126
|
+
"/": 39.99,
|
|
3127
|
+
":": 35.25,
|
|
3128
|
+
";": 35.99,
|
|
3129
|
+
"<": 68.55,
|
|
3130
|
+
"=": 68.55,
|
|
3131
|
+
">": 68.55,
|
|
3132
|
+
"?": 57.91,
|
|
3133
|
+
"@": 103.61,
|
|
3134
|
+
"A": 76.95,
|
|
3135
|
+
"B": 66.46,
|
|
3136
|
+
"C": 74.37,
|
|
3137
|
+
"D": 72.27,
|
|
3138
|
+
"E": 60.99,
|
|
3139
|
+
"F": 58.54,
|
|
3140
|
+
"G": 75.24,
|
|
3141
|
+
"H": 74.85,
|
|
3142
|
+
"I": 28.56,
|
|
3143
|
+
"J": 58.98,
|
|
3144
|
+
"K": 73.83,
|
|
3145
|
+
"L": 56.54,
|
|
3146
|
+
"M": 94.34,
|
|
3147
|
+
"N": 76.56,
|
|
3148
|
+
"O": 77.29,
|
|
3149
|
+
"P": 65.19,
|
|
3150
|
+
"Q": 78.17,
|
|
3151
|
+
"R": 66.21,
|
|
3152
|
+
"S": 66.02,
|
|
3153
|
+
"T": 67.68,
|
|
3154
|
+
"U": 72.71,
|
|
3155
|
+
"V": 76.95,
|
|
3156
|
+
"W": 105.86,
|
|
3157
|
+
"X": 76.12,
|
|
3158
|
+
"Y": 75.2,
|
|
3159
|
+
"Z": 67.87,
|
|
3160
|
+
"[": 38.23,
|
|
3161
|
+
"\\": 39.99,
|
|
3162
|
+
"]": 38.23,
|
|
3163
|
+
"^": 49.32,
|
|
3164
|
+
"_": 48.44,
|
|
3165
|
+
"`": 38.23,
|
|
3166
|
+
"a": 58.84,
|
|
3167
|
+
"b": 63.77,
|
|
3168
|
+
"c": 59.52,
|
|
3169
|
+
"d": 63.77,
|
|
3170
|
+
"e": 60.06,
|
|
3171
|
+
"f": 40.97,
|
|
3172
|
+
"g": 63.92,
|
|
3173
|
+
"h": 63.53,
|
|
3174
|
+
"i": 28.32,
|
|
3175
|
+
"j": 28.32,
|
|
3176
|
+
"k": 59.28,
|
|
3177
|
+
"l": 28.32,
|
|
3178
|
+
"m": 92.72,
|
|
3179
|
+
"n": 63.53,
|
|
3180
|
+
"o": 61.91,
|
|
3181
|
+
"p": 63.77,
|
|
3182
|
+
"q": 63.77,
|
|
3183
|
+
"r": 41.99,
|
|
3184
|
+
"s": 57.32,
|
|
3185
|
+
"t": 38.18,
|
|
3186
|
+
"u": 63.53,
|
|
3187
|
+
"v": 61.52,
|
|
3188
|
+
"w": 86.28,
|
|
3189
|
+
"x": 59.42,
|
|
3190
|
+
"y": 61.82,
|
|
3191
|
+
"z": 58.11,
|
|
3192
|
+
"{": 48.63,
|
|
3193
|
+
"|": 38.77,
|
|
3194
|
+
"}": 48.63,
|
|
3195
|
+
"~": 68.55
|
|
3196
|
+
}
|
|
3221
3197
|
};
|
|
3222
|
-
var
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
function
|
|
3236
|
-
const
|
|
3237
|
-
const
|
|
3238
|
-
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3198
|
+
var INTER_FALLBACK = {
|
|
3199
|
+
"400": 56.16,
|
|
3200
|
+
"700": 58.74,
|
|
3201
|
+
"800": 59.79
|
|
3202
|
+
};
|
|
3203
|
+
|
|
3204
|
+
// ../core/src/textFx.ts
|
|
3205
|
+
var clamp013 = (v) => Math.max(0, Math.min(1, v));
|
|
3206
|
+
var fract = (v) => v - Math.floor(v);
|
|
3207
|
+
var rand = (i, salt) => fract(Math.sin(i * 127.1 + salt * 311.7) * 43758.5453);
|
|
3208
|
+
var dur3 = (base, sp) => base / sp;
|
|
3209
|
+
var SCRAMBLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@";
|
|
3210
|
+
var advance = (ch, weight, fontSize) => (INTER_ADVANCE[weight]?.[ch] ?? INTER_FALLBACK[weight]) * (fontSize / 100);
|
|
3211
|
+
function splitText(textStr, opts) {
|
|
3212
|
+
const { id, x, y, fontSize } = opts;
|
|
3213
|
+
const weight = opts.fontWeight ?? 800;
|
|
3214
|
+
const fill = opts.fill ?? "#FFFFFF";
|
|
3215
|
+
const ls = opts.letterSpacing ?? 0;
|
|
3216
|
+
const align = opts.align ?? "center";
|
|
3217
|
+
const unit = opts.unit ?? "glyph";
|
|
3218
|
+
const opacity = opts.opacity ?? 0;
|
|
3219
|
+
const chars = [...textStr];
|
|
3220
|
+
let total = 0;
|
|
3221
|
+
chars.forEach((ch, i) => {
|
|
3222
|
+
total += advance(ch, weight, fontSize) + (i < chars.length - 1 ? ls : 0);
|
|
3223
|
+
});
|
|
3224
|
+
let cursor2 = align === "center" ? x - total / 2 : x;
|
|
3225
|
+
const glyphs = [];
|
|
3226
|
+
const nodes = [];
|
|
3227
|
+
const mk = (ch, cx, adv, lsProp) => {
|
|
3228
|
+
const g = { id: `${id}-${glyphs.length}`, ch, x: cx, y, advance: adv, i: glyphs.length };
|
|
3229
|
+
glyphs.push(g);
|
|
3230
|
+
nodes.push(
|
|
3231
|
+
text({
|
|
3232
|
+
id: g.id,
|
|
3233
|
+
x: cx,
|
|
3234
|
+
y,
|
|
3235
|
+
content: ch,
|
|
3236
|
+
fontFamily: "Inter",
|
|
3237
|
+
fontSize,
|
|
3238
|
+
fontWeight: weight,
|
|
3239
|
+
fill,
|
|
3240
|
+
anchor: "center",
|
|
3241
|
+
opacity,
|
|
3242
|
+
...lsProp ? { letterSpacing: lsProp } : {}
|
|
3243
|
+
})
|
|
3244
|
+
);
|
|
3245
|
+
};
|
|
3246
|
+
if (unit === "word") {
|
|
3247
|
+
let i = 0;
|
|
3248
|
+
while (i < chars.length) {
|
|
3249
|
+
if (chars[i] === " ") {
|
|
3250
|
+
cursor2 += advance(" ", weight, fontSize) + ls;
|
|
3251
|
+
i++;
|
|
3252
|
+
continue;
|
|
3262
3253
|
}
|
|
3254
|
+
let word = "";
|
|
3255
|
+
let w = 0;
|
|
3256
|
+
const startCursor = cursor2;
|
|
3257
|
+
while (i < chars.length && chars[i] !== " ") {
|
|
3258
|
+
const a = advance(chars[i], weight, fontSize);
|
|
3259
|
+
word += chars[i];
|
|
3260
|
+
w += a + (chars[i + 1] && chars[i + 1] !== " " ? ls : 0);
|
|
3261
|
+
i++;
|
|
3262
|
+
}
|
|
3263
|
+
mk(word, startCursor + w / 2, w, ls);
|
|
3264
|
+
cursor2 = startCursor + w + ls;
|
|
3263
3265
|
}
|
|
3266
|
+
} else {
|
|
3267
|
+
chars.forEach((ch) => {
|
|
3268
|
+
const a = advance(ch, weight, fontSize);
|
|
3269
|
+
if (ch !== " ") mk(ch, cursor2 + a / 2, a);
|
|
3270
|
+
cursor2 += a + ls;
|
|
3271
|
+
});
|
|
3264
3272
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3273
|
+
return { nodes, glyphs, ids: glyphs.map((g) => g.id), width: total, x, y, fontSize };
|
|
3274
|
+
}
|
|
3275
|
+
var ctx3 = (o) => ({
|
|
3276
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
3277
|
+
e: clamp013(o.energy ?? 0.5),
|
|
3278
|
+
seed: o.seed ?? 0,
|
|
3279
|
+
fs: 0,
|
|
3280
|
+
stag: o.stagger
|
|
3281
|
+
});
|
|
3282
|
+
var IN_STAGGER = { typewriter: 0.065, cascade: 0.04, rise: 0.03, bounce: 0.045, assemble: 0.05, decode: 0.05 };
|
|
3283
|
+
function glyphIn(name, g, c) {
|
|
3284
|
+
const set = (props) => tween(g.id, props, { duration: 1e-3 });
|
|
3285
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
3286
|
+
switch (name) {
|
|
3287
|
+
case "typewriter":
|
|
3288
|
+
return tween(g.id, { opacity: 1 }, { duration: dur3(0.04, c.sp), ease: "linear" });
|
|
3289
|
+
case "cascade":
|
|
3290
|
+
return seq(
|
|
3291
|
+
set({ y: g.y + 56, opacity: 0 }),
|
|
3292
|
+
par(
|
|
3293
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.22, c.sp), ease: "easeOutQuad" }),
|
|
3294
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.34, c.sp), ease: "easeOutCubic" })
|
|
3295
|
+
)
|
|
3296
|
+
);
|
|
3297
|
+
case "rise":
|
|
3298
|
+
return seq(
|
|
3299
|
+
set({ y: g.y + 36, opacity: 0 }),
|
|
3300
|
+
par(
|
|
3301
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.3, c.sp), ease: "easeOutQuad" }),
|
|
3302
|
+
tween(g.id, { y: g.y }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
3303
|
+
)
|
|
3304
|
+
);
|
|
3305
|
+
case "bounce":
|
|
3306
|
+
return seq(
|
|
3307
|
+
set({ y: g.y - 80 * (0.6 + c.e), opacity: 0, scale: 0.7 }),
|
|
3308
|
+
par(
|
|
3309
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.2, c.sp), ease: "easeOutQuad" }),
|
|
3310
|
+
tween(g.id, { y: g.y, scale: 1 }, { duration: dur3(0.7, c.sp), ease: "easeOutBounce" })
|
|
3311
|
+
)
|
|
3312
|
+
);
|
|
3313
|
+
case "assemble":
|
|
3314
|
+
return seq(
|
|
3315
|
+
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 }),
|
|
3316
|
+
par(
|
|
3317
|
+
tween(g.id, { opacity: 1 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" }),
|
|
3318
|
+
tween(g.id, { x: g.x, y: g.y, rotation: 0, scale: 1 }, { duration: dur3(0.8, c.sp), ease: "easeOutExpo" })
|
|
3319
|
+
)
|
|
3320
|
+
);
|
|
3321
|
+
case "decode": {
|
|
3322
|
+
const steps = 4 + Math.floor(rs(7) * 3);
|
|
3323
|
+
const flicker = [set({ opacity: 1 })];
|
|
3324
|
+
for (let k = 0; k < steps; k++) {
|
|
3325
|
+
flicker.push(tween(g.id, { content: SCRAMBLE[Math.floor(rand(g.i, 20 + k + c.seed) * SCRAMBLE.length)] }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
3279
3326
|
}
|
|
3327
|
+
flicker.push(tween(g.id, { content: g.ch }, { duration: dur3(0.05, c.sp), ease: "linear" }));
|
|
3328
|
+
return seq(...flicker);
|
|
3280
3329
|
}
|
|
3281
3330
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3331
|
+
}
|
|
3332
|
+
function textIn(name, block, opts = {}) {
|
|
3333
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
3334
|
+
const interval = (c.stag ?? IN_STAGGER[name]) / c.sp;
|
|
3335
|
+
return beat(opts.label ?? `text-in-${name}`, {}, [stagger(interval, ...block.glyphs.map((g) => glyphIn(name, g, c)))]);
|
|
3336
|
+
}
|
|
3337
|
+
function textLoop(name, block, opts = {}) {
|
|
3338
|
+
const win = { ...opts.from !== void 0 && { from: opts.from }, ...opts.until !== void 0 && { until: opts.until }, ...opts.ramp !== void 0 && { ramp: opts.ramp } };
|
|
3339
|
+
const f = opts.frequency ?? (name === "wave" ? 0.9 : name === "shimmer" ? 1.4 : 0.7);
|
|
3340
|
+
const ps = opts.phaseStep ?? 0.55;
|
|
3341
|
+
return block.glyphs.map((g, i) => {
|
|
3342
|
+
switch (name) {
|
|
3343
|
+
case "wave":
|
|
3344
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 9, frequency: f, phase: i * ps }, win);
|
|
3345
|
+
case "shimmer":
|
|
3346
|
+
return oscillate(g.id, "opacity", { amplitude: opts.amplitude ?? 0.25, frequency: f, phase: i * ps }, win);
|
|
3347
|
+
case "wobble":
|
|
3348
|
+
return oscillate(g.id, "rotation", { amplitude: opts.amplitude ?? 6, frequency: f, phase: i * ps }, win);
|
|
3349
|
+
case "float":
|
|
3350
|
+
return oscillate(g.id, "y", { amplitude: opts.amplitude ?? 5, frequency: f, phase: i * ps }, win);
|
|
3286
3351
|
}
|
|
3352
|
+
});
|
|
3353
|
+
}
|
|
3354
|
+
var OUT_STAGGER = { shatter: 0.02, fly: 0.012, dissolve: 0, fall: 0.02, collapse: 0.02 };
|
|
3355
|
+
function glyphOut(name, g, c, block, dir) {
|
|
3356
|
+
const rs = (salt) => rand(g.i, salt + c.seed);
|
|
3357
|
+
switch (name) {
|
|
3358
|
+
case "shatter":
|
|
3359
|
+
return par(
|
|
3360
|
+
tween(g.id, { x: g.x + (rs(21) - 0.5) * 1100 * (0.6 + c.e), y: g.y + (rs(22) - 0.5) * 760 }, { duration: dur3(0.7, c.sp), ease: "easeInCubic" }),
|
|
3361
|
+
tween(g.id, { rotation: (rs(23) - 0.5) * 300, opacity: 0 }, { duration: dur3(0.7, c.sp), ease: "easeInQuad" })
|
|
3362
|
+
);
|
|
3363
|
+
case "fly":
|
|
3364
|
+
return par(
|
|
3365
|
+
tween(g.id, { x: g.x + dir[0] * 1200, y: g.y + dir[1] * 1200 }, { duration: dur3(0.6, c.sp), ease: "easeInCubic" }),
|
|
3366
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
3367
|
+
);
|
|
3368
|
+
case "dissolve":
|
|
3369
|
+
return seq(wait(rs(31) * 0.5), par(
|
|
3370
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.4, c.sp), ease: "easeInQuad" }),
|
|
3371
|
+
tween(g.id, { scale: 1.4 }, { duration: dur3(0.4, c.sp), ease: "easeOutQuad" })
|
|
3372
|
+
));
|
|
3373
|
+
case "fall":
|
|
3374
|
+
return par(
|
|
3375
|
+
tween(g.id, { y: g.y + 700 + rs(41) * 200 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" }),
|
|
3376
|
+
tween(g.id, { rotation: (rs(42) - 0.5) * 120, opacity: 0 }, { duration: dur3(0.8, c.sp), ease: "easeInQuad" })
|
|
3377
|
+
);
|
|
3378
|
+
case "collapse":
|
|
3379
|
+
return par(
|
|
3380
|
+
tween(g.id, { x: block.x, y: block.y, scale: 0.2 }, { duration: dur3(0.5, c.sp), ease: "easeInBack" }),
|
|
3381
|
+
tween(g.id, { opacity: 0 }, { duration: dur3(0.5, c.sp), ease: "easeInQuad" })
|
|
3382
|
+
);
|
|
3287
3383
|
}
|
|
3288
|
-
return value;
|
|
3289
3384
|
}
|
|
3290
|
-
function
|
|
3291
|
-
const
|
|
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
|
-
return false;
|
|
3318
|
-
};
|
|
3319
|
-
for (const node of compiled.ir.nodes) if (walk(node, IDENTITY)) break;
|
|
3320
|
-
return result;
|
|
3385
|
+
function textOut(name, block, opts = {}) {
|
|
3386
|
+
const c = { ...ctx3(opts), fs: block.fontSize };
|
|
3387
|
+
const dir = opts.dir ?? [0, -1];
|
|
3388
|
+
const steps = block.glyphs.map((g) => glyphOut(name, g, c, block, dir));
|
|
3389
|
+
const interval = (c.stag ?? OUT_STAGGER[name]) / c.sp;
|
|
3390
|
+
const body = interval > 0 ? stagger(interval, ...steps) : par(...steps);
|
|
3391
|
+
return beat(opts.label ?? `text-out-${name}`, {}, [body]);
|
|
3392
|
+
}
|
|
3393
|
+
function textTypeCues(block, opts) {
|
|
3394
|
+
const interval = opts.interval ?? 0.065;
|
|
3395
|
+
const gain = opts.gain ?? 0.4;
|
|
3396
|
+
const off = opts.offset ?? 0;
|
|
3397
|
+
const KEYS = ["001", "004", "007", "010", "014"];
|
|
3398
|
+
return block.glyphs.map((g, i) => ({
|
|
3399
|
+
at: opts.at,
|
|
3400
|
+
offset: off + i * interval,
|
|
3401
|
+
file: `keypress-${KEYS[i % KEYS.length]}.wav`,
|
|
3402
|
+
gain: gain + 0.2 * rand(i, 31)
|
|
3403
|
+
}));
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// ../core/src/motionOps.ts
|
|
3407
|
+
var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
|
|
3408
|
+
var clamp014 = (n3) => Math.max(0, Math.min(1, n3));
|
|
3409
|
+
function settleEase2(e) {
|
|
3410
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
3321
3411
|
}
|
|
3322
|
-
function
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
const
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
const dofFx = (fx, depth, project) => {
|
|
3356
|
-
if (!project || aperture <= 0) return fx;
|
|
3357
|
-
const extra = aperture * Math.abs(depth - focus);
|
|
3358
|
-
if (extra <= 0) return fx;
|
|
3359
|
-
return { ...fx, blur: z0((fx.blur ?? 0) + extra) };
|
|
3360
|
-
};
|
|
3361
|
-
const zSort = compiled.zSort;
|
|
3362
|
-
const depthOf = (node, zAcc) => zAcc + num(node.id, "z", node.props.z ?? 0);
|
|
3363
|
-
const depthOrder = (children, zAcc) => [...children].sort((a, b) => depthOf(b, zAcc) - depthOf(a, zAcc));
|
|
3364
|
-
const walk = (node, parent, parentOpacity, clips, zAcc, project) => {
|
|
3365
|
-
const id = node.id;
|
|
3366
|
-
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
3367
|
-
const fx = effectFx(id, node.props);
|
|
3368
|
-
if (node.type === "line") {
|
|
3369
|
-
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
3370
|
-
if (opacity2 <= 0) return;
|
|
3371
|
-
const progress = Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1)));
|
|
3372
|
-
const x1 = num(id, "x1", node.props.x1);
|
|
3373
|
-
const y1 = num(id, "y1", node.props.y1);
|
|
3374
|
-
ops.push({
|
|
3375
|
-
type: "line",
|
|
3376
|
-
id,
|
|
3377
|
-
// a line carries no z/rotate of its own — it just inherits the subtree's depth
|
|
3378
|
-
transform: project ? projectDepth(parent, zAcc, vx, vy, dPersp) : parent,
|
|
3379
|
-
opacity: opacity2,
|
|
3380
|
-
x1,
|
|
3381
|
-
y1,
|
|
3382
|
-
x2: x1 + (num(id, "x2", node.props.x2) - x1) * progress,
|
|
3383
|
-
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
3384
|
-
stroke: str(id, "stroke", node.props.stroke),
|
|
3385
|
-
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
3386
|
-
// a line carries no z of its own — DOF uses the inherited subtree depth
|
|
3387
|
-
...dofFx(fx, zAcc, project),
|
|
3388
|
-
...clipSpread
|
|
3389
|
-
});
|
|
3390
|
-
return;
|
|
3412
|
+
function fromVec2(from, dist) {
|
|
3413
|
+
switch (from) {
|
|
3414
|
+
case "right":
|
|
3415
|
+
return [dist, 0];
|
|
3416
|
+
case "top":
|
|
3417
|
+
return [0, -dist];
|
|
3418
|
+
case "bottom":
|
|
3419
|
+
return [0, dist];
|
|
3420
|
+
default:
|
|
3421
|
+
return [-dist, 0];
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
var motionOpLabel = (name, target) => `op-${name}-${target}`;
|
|
3425
|
+
function motionOp(name, target, opts = {}) {
|
|
3426
|
+
const e = clamp014(opts.energy ?? 0.5);
|
|
3427
|
+
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
3428
|
+
const amt = opts.amount ?? 1;
|
|
3429
|
+
const b = { scale: 1, x: 0, y: 0, rotation: 0, ...opts.base };
|
|
3430
|
+
const d = (base) => base / sp;
|
|
3431
|
+
const label = motionOpLabel(name, target);
|
|
3432
|
+
switch (name) {
|
|
3433
|
+
case "rotate":
|
|
3434
|
+
return { timeline: beat(label, {}, [tween(target, { rotation: b.rotation + 360 * amt }, { duration: d(1), ease: settleEase2(e) })]) };
|
|
3435
|
+
case "zoom": {
|
|
3436
|
+
const peak = b.scale * (1 + 0.22 * amt);
|
|
3437
|
+
return {
|
|
3438
|
+
timeline: beat(label, {}, [
|
|
3439
|
+
seq(
|
|
3440
|
+
tween(target, { scale: peak }, { duration: d(0.4), ease: "easeOutBack" }),
|
|
3441
|
+
tween(target, { scale: b.scale }, { duration: d(0.45), ease: "easeInOutQuad" })
|
|
3442
|
+
)
|
|
3443
|
+
])
|
|
3444
|
+
};
|
|
3391
3445
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3446
|
+
case "ken-burns":
|
|
3447
|
+
return {
|
|
3448
|
+
timeline: beat(label, {}, [
|
|
3449
|
+
par(
|
|
3450
|
+
tween(target, { scale: b.scale * (1 + 0.1 * amt) }, { duration: d(3), ease: "easeInOutQuad" }),
|
|
3451
|
+
tween(target, { x: b.x + 26 * amt, y: b.y - 16 * amt }, { duration: d(3), ease: "easeInOutQuad" })
|
|
3452
|
+
)
|
|
3453
|
+
])
|
|
3454
|
+
};
|
|
3455
|
+
case "slide-in": {
|
|
3456
|
+
const [dx, dy] = fromVec2(opts.from ?? "left", 320 * amt);
|
|
3457
|
+
return {
|
|
3458
|
+
setup: { [target]: { x: b.x + dx, y: b.y + dy, opacity: 0 } },
|
|
3459
|
+
timeline: beat(label, {}, [
|
|
3460
|
+
par(
|
|
3461
|
+
tween(target, { x: b.x, y: b.y }, { duration: d(0.7), ease: settleEase2(e) }),
|
|
3462
|
+
tween(target, { opacity: 1 }, { duration: d(0.4), ease: "easeOutQuad" })
|
|
3463
|
+
)
|
|
3464
|
+
])
|
|
3465
|
+
};
|
|
3405
3466
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
)
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
};
|
|
3424
|
-
const leafFx = dofFx(fx, depth, project);
|
|
3425
|
-
switch (node.type) {
|
|
3426
|
-
case "group": {
|
|
3427
|
-
const clipTf = projDraw(matrix, 0, 0);
|
|
3428
|
-
const childClips = node.props.clip ? [...clips, { transform: clipTf, shape: node.props.clip }] : clips;
|
|
3429
|
-
const hasFx = fx.blur !== void 0 || fx.shadowColor !== void 0 || fx.blend !== void 0;
|
|
3430
|
-
if (hasFx) ops.push({ type: "group-fx-push", id, transform: matrix, opacity, ...fx, ...clipSpread });
|
|
3431
|
-
if (node.props.matte && node.children.length >= 2) {
|
|
3432
|
-
ops.push({ type: "matte-push", id, transform: matrix, opacity, mode: node.props.matte, ...clipSpread });
|
|
3433
|
-
walk(node.children[0], matrix, opacity, childClips, depth, project);
|
|
3434
|
-
ops.push({ type: "matte-sep", id, transform: matrix, opacity });
|
|
3435
|
-
for (let i = 1; i < node.children.length; i++) walk(node.children[i], matrix, opacity, childClips, depth, project);
|
|
3436
|
-
ops.push({ type: "matte-pop", id, transform: matrix, opacity });
|
|
3437
|
-
} else {
|
|
3438
|
-
const kids = zSort && project ? depthOrder(node.children, depth) : node.children;
|
|
3439
|
-
for (const child of kids) walk(child, matrix, opacity, childClips, depth, project);
|
|
3440
|
-
}
|
|
3441
|
-
if (hasFx) ops.push({ type: "group-fx-pop", id, transform: matrix, opacity });
|
|
3442
|
-
return;
|
|
3443
|
-
}
|
|
3444
|
-
case "rect":
|
|
3445
|
-
case "ellipse": {
|
|
3446
|
-
const width = num(id, "width", node.props.width);
|
|
3447
|
-
const height = num(id, "height", node.props.height);
|
|
3448
|
-
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
3449
|
-
const strokeWidth = num(id, "strokeWidth", node.props.strokeWidth ?? 1);
|
|
3450
|
-
const fillP = node.props.fill;
|
|
3451
|
-
const strokeP = node.props.stroke;
|
|
3452
|
-
const fill = isGradient(fillP) ? fillP : opt(id, "fill", fillP);
|
|
3453
|
-
const stroke = isGradient(strokeP) ? strokeP : opt(id, "stroke", strokeP);
|
|
3454
|
-
ops.push({
|
|
3455
|
-
type: node.type,
|
|
3456
|
-
id,
|
|
3457
|
-
transform: projDraw(matrix, width / 2, height / 2),
|
|
3458
|
-
opacity,
|
|
3459
|
-
width,
|
|
3460
|
-
height,
|
|
3461
|
-
offsetX: -width * ax,
|
|
3462
|
-
offsetY: -height * ay,
|
|
3463
|
-
...fill !== void 0 && { fill },
|
|
3464
|
-
...stroke !== void 0 && { stroke, strokeWidth },
|
|
3465
|
-
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
3466
|
-
...leafFx,
|
|
3467
|
-
...clipSpread
|
|
3468
|
-
});
|
|
3469
|
-
return;
|
|
3470
|
-
}
|
|
3471
|
-
case "image": {
|
|
3472
|
-
const width = num(id, "width", node.props.width);
|
|
3473
|
-
const height = num(id, "height", node.props.height);
|
|
3474
|
-
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
3475
|
-
ops.push({
|
|
3476
|
-
type: "image",
|
|
3477
|
-
id,
|
|
3478
|
-
transform: projDraw(matrix, width / 2, height / 2),
|
|
3479
|
-
opacity,
|
|
3480
|
-
src: str(id, "src", node.props.src),
|
|
3481
|
-
width,
|
|
3482
|
-
height,
|
|
3483
|
-
offsetX: -width * ax,
|
|
3484
|
-
offsetY: -height * ay,
|
|
3485
|
-
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
3486
|
-
...leafFx,
|
|
3487
|
-
...clipSpread
|
|
3488
|
-
});
|
|
3489
|
-
return;
|
|
3490
|
-
}
|
|
3491
|
-
case "video": {
|
|
3492
|
-
const width = num(id, "width", node.props.width);
|
|
3493
|
-
const height = num(id, "height", node.props.height);
|
|
3494
|
-
const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
|
|
3495
|
-
const fps = compiled.ir.fps ?? 30;
|
|
3496
|
-
const start = node.props.start ?? 0;
|
|
3497
|
-
const rate = node.props.rate ?? 1;
|
|
3498
|
-
const clipStart = node.props.clipStart ?? 0;
|
|
3499
|
-
const srcT = clipStart + Math.max(0, t - start) * rate;
|
|
3500
|
-
const frame = Math.max(0, Math.round(srcT * fps));
|
|
3501
|
-
ops.push({
|
|
3502
|
-
type: "video",
|
|
3503
|
-
id,
|
|
3504
|
-
transform: projDraw(matrix, width / 2, height / 2),
|
|
3505
|
-
opacity,
|
|
3506
|
-
src: str(id, "src", node.props.src),
|
|
3507
|
-
width,
|
|
3508
|
-
height,
|
|
3509
|
-
offsetX: -width * ax,
|
|
3510
|
-
offsetY: -height * ay,
|
|
3511
|
-
frame,
|
|
3512
|
-
...node.props.fit && node.props.fit !== "fill" ? { fit: node.props.fit } : {},
|
|
3513
|
-
...leafFx,
|
|
3514
|
-
...clipSpread
|
|
3515
|
-
});
|
|
3516
|
-
return;
|
|
3467
|
+
case "fade":
|
|
3468
|
+
return {
|
|
3469
|
+
setup: { [target]: { opacity: 0 } },
|
|
3470
|
+
timeline: beat(label, {}, [tween(target, { opacity: 1 }, { duration: d(0.6), ease: "easeOutQuad" })])
|
|
3471
|
+
};
|
|
3472
|
+
case "draw-on":
|
|
3473
|
+
return {
|
|
3474
|
+
setup: { [target]: { progress: 0 } },
|
|
3475
|
+
timeline: beat(label, {}, [tween(target, { progress: 1 }, { duration: d(1.3), ease: "easeInOutQuad" })])
|
|
3476
|
+
};
|
|
3477
|
+
case "pulse": {
|
|
3478
|
+
const hi = b.scale * (1 + 0.12 * amt);
|
|
3479
|
+
const pulses = 2 + Math.round(amt);
|
|
3480
|
+
const steps = [];
|
|
3481
|
+
for (let i = 0; i < pulses; i++) {
|
|
3482
|
+
steps.push(tween(target, { scale: hi }, { duration: d(0.22), ease: "easeOutQuad" }));
|
|
3483
|
+
steps.push(tween(target, { scale: b.scale }, { duration: d(0.22), ease: "easeInQuad" }));
|
|
3517
3484
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3485
|
+
return { timeline: beat(label, {}, [seq(...steps)]) };
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// ../core/src/audio.ts
|
|
3491
|
+
var SFX_DURATION = {
|
|
3492
|
+
// transition
|
|
3493
|
+
whoosh: 0.35,
|
|
3494
|
+
swish: 0.32,
|
|
3495
|
+
swoosh: 0.35,
|
|
3496
|
+
rise: 0.5,
|
|
3497
|
+
riser: 0.85,
|
|
3498
|
+
warp: 0.5,
|
|
3499
|
+
// ui
|
|
3500
|
+
tick: 0.03,
|
|
3501
|
+
click: 0.05,
|
|
3502
|
+
blip: 0.1,
|
|
3503
|
+
pop: 0.12,
|
|
3504
|
+
select: 0.18,
|
|
3505
|
+
// impact
|
|
3506
|
+
thud: 0.25,
|
|
3507
|
+
boom: 0.6,
|
|
3508
|
+
knock: 0.14,
|
|
3509
|
+
sub: 0.7,
|
|
3510
|
+
// positive
|
|
3511
|
+
chime: 0.7,
|
|
3512
|
+
ding: 0.5,
|
|
3513
|
+
coin: 0.3,
|
|
3514
|
+
sparkle: 0.6,
|
|
3515
|
+
shimmer: 0.9,
|
|
3516
|
+
success: 0.6,
|
|
3517
|
+
// alert
|
|
3518
|
+
zap: 0.22,
|
|
3519
|
+
error: 0.4,
|
|
3520
|
+
// tech
|
|
3521
|
+
glitch: 0.3,
|
|
3522
|
+
static: 0.18,
|
|
3523
|
+
scan: 0.45,
|
|
3524
|
+
powerup: 0.4,
|
|
3525
|
+
powerdown: 0.5,
|
|
3526
|
+
// rhythm / foley
|
|
3527
|
+
snare: 0.18,
|
|
3528
|
+
hat: 0.05,
|
|
3529
|
+
bubble: 0.16,
|
|
3530
|
+
notify: 0.45,
|
|
3531
|
+
camera: 0.18
|
|
3532
|
+
};
|
|
3533
|
+
var FILE_CUE_DURATION = 0.4;
|
|
3534
|
+
function collectClipAudio(ir, duration, warnings) {
|
|
3535
|
+
const out = [];
|
|
3536
|
+
const walk = (nodes) => {
|
|
3537
|
+
for (const node of nodes) {
|
|
3538
|
+
if (node.type === "video") {
|
|
3539
|
+
const gain = node.props.volume ?? 1;
|
|
3540
|
+
const start = node.props.start ?? 0;
|
|
3541
|
+
if (gain <= 0) continue;
|
|
3542
|
+
if (start >= duration) {
|
|
3543
|
+
warnings.push(`video "${node.id}": start ${start.toFixed(2)}s past the scene end \u2014 audio dropped`);
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
out.push({ nodeId: node.id, src: node.props.src, start, rate: node.props.rate ?? 1, clipStart: node.props.clipStart ?? 0, gain, fadeIn: node.props.fadeIn ?? 0, pan: node.props.pan ?? 0 });
|
|
3542
3547
|
}
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
});
|
|
3568
|
-
|
|
3548
|
+
if (node.type === "group") walk(node.children);
|
|
3549
|
+
}
|
|
3550
|
+
};
|
|
3551
|
+
walk(ir.nodes);
|
|
3552
|
+
return out;
|
|
3553
|
+
}
|
|
3554
|
+
function resolveAudioPlan(compiled) {
|
|
3555
|
+
const audio = compiled.ir.audio;
|
|
3556
|
+
const warnings = [];
|
|
3557
|
+
const duration = compiled.duration;
|
|
3558
|
+
const clipAudio = collectClipAudio(compiled.ir, duration, warnings);
|
|
3559
|
+
const autoCues = audio?.autoFoley ? autoFoley(compiled, audio.autoFoley === true ? {} : audio.autoFoley) : [];
|
|
3560
|
+
const manualCues = [...audio?.cues ?? [], ...autoCues];
|
|
3561
|
+
if (!audio || !audio.bgm && manualCues.length === 0) {
|
|
3562
|
+
return clipAudio.length === 0 ? null : { duration, bgm: null, cues: [], duckWindows: [], clipAudio, warnings };
|
|
3563
|
+
}
|
|
3564
|
+
const cues = [];
|
|
3565
|
+
for (const [index, cue] of manualCues.entries()) {
|
|
3566
|
+
let anchor;
|
|
3567
|
+
if (typeof cue.at === "number") {
|
|
3568
|
+
anchor = cue.at;
|
|
3569
|
+
} else {
|
|
3570
|
+
const span = compiled.labelTimes.get(cue.at);
|
|
3571
|
+
if (!span) {
|
|
3572
|
+
warnings.push(`cue[${index}]: unknown label "${cue.at}" \u2014 cue dropped`);
|
|
3573
|
+
continue;
|
|
3569
3574
|
}
|
|
3575
|
+
anchor = span.t0;
|
|
3570
3576
|
}
|
|
3577
|
+
const t = Math.max(0, anchor + (cue.offset ?? 0));
|
|
3578
|
+
const cueDuration = cue.sfx ? SFX_DURATION[cue.sfx] : FILE_CUE_DURATION;
|
|
3579
|
+
if (t >= duration) {
|
|
3580
|
+
warnings.push(`cue[${index}] at ${t.toFixed(2)}s starts past the scene end (${duration.toFixed(2)}s) \u2014 dropped`);
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
if (t + cueDuration > duration) {
|
|
3584
|
+
warnings.push(`cue[${index}] at ${t.toFixed(2)}s extends past the scene end \u2014 it will be truncated`);
|
|
3585
|
+
}
|
|
3586
|
+
cues.push({
|
|
3587
|
+
t,
|
|
3588
|
+
gain: cue.gain ?? 1,
|
|
3589
|
+
duration: cueDuration,
|
|
3590
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
3591
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
3592
|
+
pan: cue.pan ?? 0,
|
|
3593
|
+
source: cue.sfx ? (
|
|
3594
|
+
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
3595
|
+
// (pitch/texture); an explicit params.seed always wins.
|
|
3596
|
+
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
3597
|
+
) : { kind: "file", path: cue.file }
|
|
3598
|
+
});
|
|
3599
|
+
}
|
|
3600
|
+
cues.sort((a, b) => a.t - b.t);
|
|
3601
|
+
return {
|
|
3602
|
+
duration,
|
|
3603
|
+
bgm: resolveBgm(audio.bgm),
|
|
3604
|
+
cues,
|
|
3605
|
+
duckWindows: mergeDuckWindows(cues, duration),
|
|
3606
|
+
clipAudio,
|
|
3607
|
+
warnings
|
|
3571
3608
|
};
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
) : IDENTITY;
|
|
3581
|
-
let roots = compiled.ir.nodes;
|
|
3582
|
-
if (zSort) {
|
|
3583
|
-
const isHud = (n3) => !!(n3.props.fixed && compiled.hasCamera);
|
|
3584
|
-
roots = [...depthOrder(compiled.ir.nodes.filter((n3) => !isHud(n3)), 0), ...compiled.ir.nodes.filter(isHud)];
|
|
3609
|
+
}
|
|
3610
|
+
function mergeDuckWindows(cues, duration) {
|
|
3611
|
+
const duckWindows = [];
|
|
3612
|
+
for (const cue of cues) {
|
|
3613
|
+
const window = { t0: cue.t, t1: Math.min(duration, cue.t + cue.duration) };
|
|
3614
|
+
const last = duckWindows[duckWindows.length - 1];
|
|
3615
|
+
if (last && window.t0 <= last.t1 + 0.1) last.t1 = Math.max(last.t1, window.t1);
|
|
3616
|
+
else duckWindows.push(window);
|
|
3585
3617
|
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3618
|
+
return duckWindows;
|
|
3619
|
+
}
|
|
3620
|
+
function resolveBgm(b) {
|
|
3621
|
+
if (!b) return null;
|
|
3622
|
+
const duck = b.duck === false ? null : {
|
|
3623
|
+
depth: b.duck?.depth ?? 0.5,
|
|
3624
|
+
attack: b.duck?.attack ?? 0.05,
|
|
3625
|
+
release: b.duck?.release ?? 0.25
|
|
3626
|
+
};
|
|
3627
|
+
return {
|
|
3628
|
+
source: b.file ? { kind: "file", path: b.file } : { kind: "synth", name: b.synth ?? "ambient-pad" },
|
|
3629
|
+
gain: b.gain ?? 0.5,
|
|
3630
|
+
fadeIn: b.fadeIn ?? 0,
|
|
3631
|
+
fadeOut: b.fadeOut ?? 0,
|
|
3632
|
+
duck
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
function resolveCompositionAudioPlan(comp) {
|
|
3636
|
+
const audio = comp.ir.audio;
|
|
3637
|
+
const duration = comp.duration;
|
|
3638
|
+
const warnings = [];
|
|
3639
|
+
const cues = [];
|
|
3640
|
+
const clipAudio = [];
|
|
3641
|
+
for (const placement of comp.scenes) {
|
|
3642
|
+
const plan = resolveAudioPlan(placement.compiled);
|
|
3643
|
+
if (!plan) continue;
|
|
3644
|
+
if (plan.bgm) {
|
|
3645
|
+
warnings.push(`scene "${placement.id}": per-scene bgm ignored \u2014 set bgm at the composition level`);
|
|
3646
|
+
}
|
|
3647
|
+
for (const w of plan.warnings) warnings.push(`scene "${placement.id}": ${w}`);
|
|
3648
|
+
for (const cue of plan.cues) {
|
|
3649
|
+
const t = cue.t + placement.start;
|
|
3650
|
+
if (t >= duration) continue;
|
|
3651
|
+
cues.push({ ...cue, t });
|
|
3652
|
+
}
|
|
3653
|
+
for (const clip of plan.clipAudio) {
|
|
3654
|
+
const start = clip.start + placement.start;
|
|
3655
|
+
if (start >= duration) continue;
|
|
3656
|
+
clipAudio.push({ ...clip, start });
|
|
3657
|
+
}
|
|
3590
3658
|
}
|
|
3591
|
-
|
|
3659
|
+
for (const [index, cue] of (audio?.cues ?? []).entries()) {
|
|
3660
|
+
if (typeof cue.at !== "number") {
|
|
3661
|
+
warnings.push(`composition cue[${index}]: "at" must be an absolute number (no composition labels) \u2014 dropped`);
|
|
3662
|
+
continue;
|
|
3663
|
+
}
|
|
3664
|
+
const t = Math.max(0, cue.at + (cue.offset ?? 0));
|
|
3665
|
+
const cueDuration = cue.sfx ? SFX_DURATION[cue.sfx] : FILE_CUE_DURATION;
|
|
3666
|
+
if (t >= duration) {
|
|
3667
|
+
warnings.push(`composition cue[${index}] at ${t.toFixed(2)}s past the composition end \u2014 dropped`);
|
|
3668
|
+
continue;
|
|
3669
|
+
}
|
|
3670
|
+
cues.push({
|
|
3671
|
+
t,
|
|
3672
|
+
gain: cue.gain ?? 1,
|
|
3673
|
+
duration: cueDuration,
|
|
3674
|
+
fadeIn: cue.fadeIn ?? 0,
|
|
3675
|
+
fadeOut: cue.fadeOut ?? 0,
|
|
3676
|
+
pan: cue.pan ?? 0,
|
|
3677
|
+
source: cue.sfx ? (
|
|
3678
|
+
// auto-vary: default the seed to the cue's order so repeated sfx differ
|
|
3679
|
+
// (pitch/texture); an explicit params.seed always wins.
|
|
3680
|
+
{ kind: "sfx", name: cue.sfx, params: { seed: index, ...cue.params } }
|
|
3681
|
+
) : { kind: "file", path: cue.file }
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
if (!audio?.bgm && cues.length === 0 && clipAudio.length === 0) return null;
|
|
3685
|
+
cues.sort((a, b) => a.t - b.t);
|
|
3686
|
+
return {
|
|
3687
|
+
duration,
|
|
3688
|
+
bgm: resolveBgm(audio?.bgm),
|
|
3689
|
+
cues,
|
|
3690
|
+
duckWindows: mergeDuckWindows(cues, duration),
|
|
3691
|
+
clipAudio,
|
|
3692
|
+
warnings
|
|
3693
|
+
};
|
|
3592
3694
|
}
|
|
3593
3695
|
|
|
3594
3696
|
// ../core/src/assets.ts
|
|
@@ -3692,6 +3794,7 @@ export {
|
|
|
3692
3794
|
SFX_DURATION,
|
|
3693
3795
|
SFX_NAMES,
|
|
3694
3796
|
SceneValidationError,
|
|
3797
|
+
autoFoley,
|
|
3695
3798
|
beat,
|
|
3696
3799
|
cameraFit,
|
|
3697
3800
|
cameraMatrix,
|