reframe-video 0.1.0 → 0.1.2
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 -0
- package/assets/sfx/bgm-song21.mp3 +0 -0
- package/assets/sfx/pop.wav +0 -0
- package/assets/sfx/rise.wav +0 -0
- package/assets/sfx/shimmer.wav +0 -0
- package/assets/sfx/thud.wav +0 -0
- package/assets/sfx/tick.wav +0 -0
- package/assets/sfx/whoosh.wav +0 -0
- package/dist/analyze.js +44 -1
- package/dist/bin.js +371 -35
- package/dist/browserEntry.js +311 -8
- package/dist/cli.js +305 -21
- package/dist/index.js +566 -16
- package/dist/renderer-canvas.js +58 -3
- package/dist/trace-cli.js +677 -0
- package/dist/types/assets.d.ts +9 -0
- package/dist/types/compile.d.ts +15 -1
- package/dist/types/compose.d.ts +12 -2
- package/dist/types/dsl.d.ts +30 -1
- package/dist/types/evaluate.d.ts +17 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/ir.d.ts +82 -1
- package/dist/types/motion.d.ts +54 -0
- package/dist/types/path.d.ts +15 -0
- package/dist/types/presets.d.ts +41 -0
- package/guides/edsl-guide.md +24 -0
- package/package.json +1 -1
- package/preview/src/main.ts +111 -6
- package/preview/src/panel.ts +37 -3
- package/preview/src/store.ts +28 -2
- package/preview/src/virtual.d.ts +9 -1
- package/preview/vite.config.ts +3 -2
package/dist/bin.js
CHANGED
|
@@ -10,16 +10,100 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// ../core/src/ir.ts
|
|
13
|
-
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION;
|
|
13
|
+
var DEFAULT_TO_DURATION, DEFAULT_TWEEN_DURATION, DEFAULT_MOTIONPATH_DURATION;
|
|
14
14
|
var init_ir = __esm({
|
|
15
15
|
"../core/src/ir.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
DEFAULT_TO_DURATION = 0.5;
|
|
18
18
|
DEFAULT_TWEEN_DURATION = 0.5;
|
|
19
|
+
DEFAULT_MOTIONPATH_DURATION = 1;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// ../core/src/path.ts
|
|
24
|
+
function locate(segCount, u) {
|
|
25
|
+
if (segCount <= 0) return { i: 0, t: 0 };
|
|
26
|
+
const clamped = Math.max(0, Math.min(1, u));
|
|
27
|
+
const scaled = clamped * segCount;
|
|
28
|
+
let i = Math.floor(scaled);
|
|
29
|
+
if (i >= segCount) i = segCount - 1;
|
|
30
|
+
return { i, t: scaled - i };
|
|
31
|
+
}
|
|
32
|
+
function controls(points, closed, i) {
|
|
33
|
+
const n = points.length;
|
|
34
|
+
const at = (k) => {
|
|
35
|
+
if (closed) return points[(k % n + n) % n];
|
|
36
|
+
return points[Math.max(0, Math.min(n - 1, k))];
|
|
37
|
+
};
|
|
38
|
+
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
39
|
+
}
|
|
40
|
+
function segCountOf(points, closed) {
|
|
41
|
+
const n = points.length;
|
|
42
|
+
if (n < 2) return 0;
|
|
43
|
+
return closed ? n : n - 1;
|
|
44
|
+
}
|
|
45
|
+
function pathPoint(points, closed, u) {
|
|
46
|
+
const n = points.length;
|
|
47
|
+
if (n === 0) return [0, 0];
|
|
48
|
+
if (n === 1) return [points[0][0], points[0][1]];
|
|
49
|
+
const segs = segCountOf(points, closed);
|
|
50
|
+
const { i, t } = locate(segs, u);
|
|
51
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
52
|
+
const t2 = t * t;
|
|
53
|
+
const t3 = t2 * t;
|
|
54
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
55
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
56
|
+
}
|
|
57
|
+
function pathTangentAngle(points, closed, u) {
|
|
58
|
+
const n = points.length;
|
|
59
|
+
if (n < 2) return 0;
|
|
60
|
+
const segs = segCountOf(points, closed);
|
|
61
|
+
const { i, t } = locate(segs, u);
|
|
62
|
+
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
63
|
+
const t2 = t * t;
|
|
64
|
+
const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
|
|
65
|
+
const dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
66
|
+
const dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
67
|
+
if (dx === 0 && dy === 0) return 0;
|
|
68
|
+
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
69
|
+
}
|
|
70
|
+
var init_path = __esm({
|
|
71
|
+
"../core/src/path.ts"() {
|
|
72
|
+
"use strict";
|
|
19
73
|
}
|
|
20
74
|
});
|
|
21
75
|
|
|
22
76
|
// ../core/src/compile.ts
|
|
77
|
+
function scaleTimeline(tl, k) {
|
|
78
|
+
switch (tl.kind) {
|
|
79
|
+
case "seq":
|
|
80
|
+
case "par":
|
|
81
|
+
return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
82
|
+
case "stagger":
|
|
83
|
+
return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
|
|
84
|
+
case "wait":
|
|
85
|
+
return { ...tl, duration: tl.duration * k };
|
|
86
|
+
case "tween":
|
|
87
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
|
|
88
|
+
case "motionPath":
|
|
89
|
+
return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
|
|
90
|
+
case "to":
|
|
91
|
+
return {
|
|
92
|
+
...tl,
|
|
93
|
+
duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
|
|
94
|
+
...tl.stagger !== void 0 && { stagger: tl.stagger * k }
|
|
95
|
+
};
|
|
96
|
+
case "beat":
|
|
97
|
+
return {
|
|
98
|
+
...tl,
|
|
99
|
+
children: tl.children.map((c) => scaleTimeline(c, k)),
|
|
100
|
+
...tl.gap !== void 0 && { gap: tl.gap * k }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function orderBeats(children) {
|
|
105
|
+
return children.map((c, i) => ({ c, i, key: c.kind === "beat" && c.order !== void 0 ? c.order : i })).sort((a, b) => a.key - b.key || a.i - b.i).map((x) => x.c);
|
|
106
|
+
}
|
|
23
107
|
function compileScene(ir) {
|
|
24
108
|
const nodeById = /* @__PURE__ */ new Map();
|
|
25
109
|
const nodeOrder = [];
|
|
@@ -48,6 +132,7 @@ function compileScene(ir) {
|
|
|
48
132
|
}
|
|
49
133
|
}
|
|
50
134
|
const segments = /* @__PURE__ */ new Map();
|
|
135
|
+
const motionPaths = /* @__PURE__ */ new Map();
|
|
51
136
|
const current = new Map(initialValues);
|
|
52
137
|
const pushSegment = (seg) => {
|
|
53
138
|
const k = key(seg.target, seg.prop);
|
|
@@ -64,6 +149,50 @@ function compileScene(ir) {
|
|
|
64
149
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
65
150
|
};
|
|
66
151
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
152
|
+
const beatTimes = /* @__PURE__ */ new Map();
|
|
153
|
+
const durationOf = (tl, start) => {
|
|
154
|
+
switch (tl.kind) {
|
|
155
|
+
case "seq": {
|
|
156
|
+
let t = start;
|
|
157
|
+
for (const child of orderBeats(tl.children)) t = durationOf(child, t);
|
|
158
|
+
return t;
|
|
159
|
+
}
|
|
160
|
+
case "par": {
|
|
161
|
+
let end = start;
|
|
162
|
+
for (const child of tl.children) end = Math.max(end, durationOf(child, start));
|
|
163
|
+
return end;
|
|
164
|
+
}
|
|
165
|
+
case "stagger": {
|
|
166
|
+
let end = start;
|
|
167
|
+
tl.children.forEach((child, i) => {
|
|
168
|
+
end = Math.max(end, durationOf(child, start + i * tl.interval));
|
|
169
|
+
});
|
|
170
|
+
return end;
|
|
171
|
+
}
|
|
172
|
+
case "wait":
|
|
173
|
+
return start + tl.duration;
|
|
174
|
+
case "tween":
|
|
175
|
+
return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
|
|
176
|
+
case "motionPath":
|
|
177
|
+
return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
|
|
178
|
+
case "to": {
|
|
179
|
+
const override = ir.states?.[tl.state] ?? {};
|
|
180
|
+
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
181
|
+
const si = tl.stagger ?? 0;
|
|
182
|
+
const targets = nodeOrder.filter(
|
|
183
|
+
(id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
|
|
184
|
+
);
|
|
185
|
+
return start + duration + Math.max(0, targets.length - 1) * si;
|
|
186
|
+
}
|
|
187
|
+
case "beat": {
|
|
188
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
189
|
+
const natural = durationOf(grouping, 0);
|
|
190
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
|
|
191
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
192
|
+
return beatStart + k * natural;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
67
196
|
const walk = (tl, start) => {
|
|
68
197
|
const end = walkInner(tl, start);
|
|
69
198
|
if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
|
|
@@ -73,9 +202,19 @@ function compileScene(ir) {
|
|
|
73
202
|
switch (tl.kind) {
|
|
74
203
|
case "seq": {
|
|
75
204
|
let t = start;
|
|
76
|
-
for (const child of tl.children) t = walk(child, t);
|
|
205
|
+
for (const child of orderBeats(tl.children)) t = walk(child, t);
|
|
77
206
|
return t;
|
|
78
207
|
}
|
|
208
|
+
case "beat": {
|
|
209
|
+
const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
|
|
210
|
+
const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
|
|
211
|
+
const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
|
|
212
|
+
const beatStart = tl.at ?? start + (tl.gap ?? 0);
|
|
213
|
+
const end = walk(inner, beatStart);
|
|
214
|
+
beatTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
215
|
+
labelTimes.set(tl.name, { t0: beatStart, t1: end });
|
|
216
|
+
return end;
|
|
217
|
+
}
|
|
79
218
|
case "par": {
|
|
80
219
|
let end = start;
|
|
81
220
|
for (const child of tl.children) end = Math.max(end, walk(child, start));
|
|
@@ -105,6 +244,23 @@ function compileScene(ir) {
|
|
|
105
244
|
}
|
|
106
245
|
return start + duration;
|
|
107
246
|
}
|
|
247
|
+
case "motionPath": {
|
|
248
|
+
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
249
|
+
const points = tl.points;
|
|
250
|
+
const closed = tl.closed ?? false;
|
|
251
|
+
const autoRotate = tl.autoRotate ?? false;
|
|
252
|
+
const rotateOffset = tl.rotateOffset ?? 0;
|
|
253
|
+
let list = motionPaths.get(tl.target);
|
|
254
|
+
if (!list) motionPaths.set(tl.target, list = []);
|
|
255
|
+
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
256
|
+
if (points.length > 0) {
|
|
257
|
+
const [ex, ey] = pathPoint(points, closed, 1);
|
|
258
|
+
current.set(key(tl.target, "x"), ex);
|
|
259
|
+
current.set(key(tl.target, "y"), ey);
|
|
260
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
261
|
+
}
|
|
262
|
+
return start + duration;
|
|
263
|
+
}
|
|
108
264
|
case "to": {
|
|
109
265
|
const override = ir.states?.[tl.state] ?? {};
|
|
110
266
|
const duration = tl.duration ?? DEFAULT_TO_DURATION;
|
|
@@ -133,14 +289,17 @@ function compileScene(ir) {
|
|
|
133
289
|
};
|
|
134
290
|
const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
|
|
135
291
|
for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
292
|
+
for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
|
|
136
293
|
return {
|
|
137
294
|
ir,
|
|
138
295
|
duration: ir.duration ?? inferredEnd,
|
|
139
296
|
segments,
|
|
297
|
+
motionPaths,
|
|
140
298
|
initialValues,
|
|
141
299
|
nodeById,
|
|
142
300
|
nodeOrder,
|
|
143
|
-
labelTimes
|
|
301
|
+
labelTimes,
|
|
302
|
+
beatTimes
|
|
144
303
|
};
|
|
145
304
|
}
|
|
146
305
|
var key;
|
|
@@ -148,6 +307,7 @@ var init_compile = __esm({
|
|
|
148
307
|
"../core/src/compile.ts"() {
|
|
149
308
|
"use strict";
|
|
150
309
|
init_ir();
|
|
310
|
+
init_path();
|
|
151
311
|
key = (target, prop) => `${target}.${prop}`;
|
|
152
312
|
}
|
|
153
313
|
});
|
|
@@ -232,9 +392,39 @@ function validateScene(ir) {
|
|
|
232
392
|
problems.push(`${path}: tween duration must be > 0`);
|
|
233
393
|
}
|
|
234
394
|
break;
|
|
395
|
+
case "motionPath": {
|
|
396
|
+
const node = nodeById.get(tl.target);
|
|
397
|
+
if (!node) {
|
|
398
|
+
problems.push(
|
|
399
|
+
`${path}: motionPath targets unknown node "${tl.target}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
400
|
+
);
|
|
401
|
+
} else if (node.type === "line") {
|
|
402
|
+
problems.push(`${path}: motionPath cannot target a line (no x/y) \u2014 "${tl.target}"`);
|
|
403
|
+
}
|
|
404
|
+
if (tl.points.length < 1) problems.push(`${path}: motionPath "${tl.target}" needs at least 1 point`);
|
|
405
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
406
|
+
problems.push(`${path}: motionPath "${tl.target}" duration must be > 0`);
|
|
407
|
+
}
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
235
410
|
case "wait":
|
|
236
411
|
if (tl.duration < 0) problems.push(`${path}: wait duration must be >= 0`);
|
|
237
412
|
break;
|
|
413
|
+
case "beat":
|
|
414
|
+
if (labels.has(tl.name)) {
|
|
415
|
+
problems.push(
|
|
416
|
+
`${path}: duplicate timeline label "${tl.name}" (beat name) \u2014 labels are overlay addresses and must be unique`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
labels.add(tl.name);
|
|
420
|
+
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
421
|
+
problems.push(`${path}: beat "${tl.name}" duration must be > 0`);
|
|
422
|
+
}
|
|
423
|
+
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
424
|
+
problems.push(`${path}: beat "${tl.name}" scale must be > 0`);
|
|
425
|
+
}
|
|
426
|
+
tl.children.forEach((c, i) => checkTimeline(c, `${path}.beat(${tl.name})[${i}]`));
|
|
427
|
+
break;
|
|
238
428
|
}
|
|
239
429
|
};
|
|
240
430
|
if (ir.timeline) checkTimeline(ir.timeline, "timeline");
|
|
@@ -283,6 +473,8 @@ var init_validate = __esm({
|
|
|
283
473
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
284
474
|
line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
|
|
285
475
|
text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
|
|
476
|
+
image: [...COMMON_PROPS, "src", "width", "height"],
|
|
477
|
+
path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
|
|
286
478
|
group: COMMON_PROPS
|
|
287
479
|
};
|
|
288
480
|
SceneValidationError = class extends Error {
|
|
@@ -429,13 +621,16 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
429
621
|
const byLabel = /* @__PURE__ */ new Map();
|
|
430
622
|
const walkTimeline = (tl) => {
|
|
431
623
|
if ("label" in tl && tl.label !== void 0) byLabel.set(tl.label, tl);
|
|
624
|
+
if (tl.kind === "beat") byLabel.set(tl.name, tl);
|
|
432
625
|
if ("children" in tl) tl.children.forEach(walkTimeline);
|
|
433
626
|
};
|
|
434
627
|
if (ir.timeline) walkTimeline(ir.timeline);
|
|
435
628
|
const PATCHABLE = {
|
|
436
629
|
to: ["duration", "ease", "stagger"],
|
|
437
630
|
tween: ["duration", "ease"],
|
|
438
|
-
wait: ["duration"]
|
|
631
|
+
wait: ["duration"],
|
|
632
|
+
motionPath: ["points", "duration", "ease"],
|
|
633
|
+
beat: ["at", "gap", "scale", "duration", "order"]
|
|
439
634
|
};
|
|
440
635
|
let timingPatched = false;
|
|
441
636
|
for (const [label, patch] of Object.entries(overlay.timeline)) {
|
|
@@ -459,7 +654,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
459
654
|
}
|
|
460
655
|
step[key2] = value;
|
|
461
656
|
applied(`timeline.${label}.${key2}`, "set");
|
|
462
|
-
if (
|
|
657
|
+
if (["duration", "stagger", "at", "gap", "scale", "order"].includes(key2)) timingPatched = true;
|
|
463
658
|
}
|
|
464
659
|
}
|
|
465
660
|
if (timingPatched && overlay.scene?.duration === void 0) {
|
|
@@ -483,6 +678,16 @@ var init_compose = __esm({
|
|
|
483
678
|
}
|
|
484
679
|
});
|
|
485
680
|
|
|
681
|
+
// ../core/src/presets.ts
|
|
682
|
+
var SET;
|
|
683
|
+
var init_presets = __esm({
|
|
684
|
+
"../core/src/presets.ts"() {
|
|
685
|
+
"use strict";
|
|
686
|
+
init_dsl();
|
|
687
|
+
SET = 1 / 120;
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
486
691
|
// ../core/src/audio.ts
|
|
487
692
|
function resolveAudioPlan(compiled) {
|
|
488
693
|
const audio = compiled.ir.audio;
|
|
@@ -568,10 +773,23 @@ var init_behaviors = __esm({
|
|
|
568
773
|
});
|
|
569
774
|
|
|
570
775
|
// ../core/src/interpolate.ts
|
|
571
|
-
|
|
776
|
+
function easeOutBounce(u) {
|
|
777
|
+
const n1 = 7.5625;
|
|
778
|
+
const d1 = 2.75;
|
|
779
|
+
if (u < 1 / d1) return n1 * u * u;
|
|
780
|
+
if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
|
|
781
|
+
if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
|
|
782
|
+
return n1 * (u -= 2.625 / d1) * u + 0.984375;
|
|
783
|
+
}
|
|
784
|
+
var BACK_C1, BACK_C2, BACK_C3, ELASTIC_C4, ELASTIC_C5, EASE_TABLE, EASE_NAMES;
|
|
572
785
|
var init_interpolate = __esm({
|
|
573
786
|
"../core/src/interpolate.ts"() {
|
|
574
787
|
"use strict";
|
|
788
|
+
BACK_C1 = 1.70158;
|
|
789
|
+
BACK_C2 = BACK_C1 * 1.525;
|
|
790
|
+
BACK_C3 = BACK_C1 + 1;
|
|
791
|
+
ELASTIC_C4 = 2 * Math.PI / 3;
|
|
792
|
+
ELASTIC_C5 = 2 * Math.PI / 4.5;
|
|
575
793
|
EASE_TABLE = {
|
|
576
794
|
linear: (u) => u,
|
|
577
795
|
easeInQuad: (u) => u * u,
|
|
@@ -585,7 +803,20 @@ var init_interpolate = __esm({
|
|
|
585
803
|
easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
|
|
586
804
|
easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
|
|
587
805
|
easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
|
|
588
|
-
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
|
|
806
|
+
easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
|
|
807
|
+
// --- expressive eases (GSAP's signature feel) — standard Penner equations ---
|
|
808
|
+
// back: overshoots past the target then settles (pop / snap)
|
|
809
|
+
easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
|
|
810
|
+
easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
|
|
811
|
+
easeInOutBack: (u) => u < 0.5 ? (2 * u) ** 2 * ((BACK_C2 + 1) * 2 * u - BACK_C2) / 2 : ((2 * u - 2) ** 2 * ((BACK_C2 + 1) * (2 * u - 2) + BACK_C2) + 2) / 2,
|
|
812
|
+
// elastic: rings around the target before settling (playful spring)
|
|
813
|
+
easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
|
|
814
|
+
easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
|
|
815
|
+
easeInOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? -(2 ** (20 * u - 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5)) / 2 : 2 ** (-20 * u + 10) * Math.sin((20 * u - 11.125) * ELASTIC_C5) / 2 + 1,
|
|
816
|
+
// bounce: drops and bounces to rest (lands without overshoot)
|
|
817
|
+
easeInBounce: (u) => 1 - easeOutBounce(1 - u),
|
|
818
|
+
easeOutBounce,
|
|
819
|
+
easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
|
|
589
820
|
};
|
|
590
821
|
EASE_NAMES = Object.keys(EASE_TABLE);
|
|
591
822
|
}
|
|
@@ -597,6 +828,52 @@ var init_evaluate = __esm({
|
|
|
597
828
|
"use strict";
|
|
598
829
|
init_behaviors();
|
|
599
830
|
init_interpolate();
|
|
831
|
+
init_path();
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// ../core/src/assets.ts
|
|
836
|
+
function collectImageSrcs(ir) {
|
|
837
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
838
|
+
const imageIds = /* @__PURE__ */ new Set();
|
|
839
|
+
const walkNodes = (nodes) => {
|
|
840
|
+
for (const node of nodes) {
|
|
841
|
+
if (node.type === "image") {
|
|
842
|
+
imageIds.add(node.id);
|
|
843
|
+
srcs.add(node.props.src);
|
|
844
|
+
}
|
|
845
|
+
if (node.type === "group") walkNodes(node.children);
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
walkNodes(ir.nodes);
|
|
849
|
+
for (const overrides of Object.values(ir.states ?? {})) {
|
|
850
|
+
for (const [nodeId, props] of Object.entries(overrides)) {
|
|
851
|
+
if (imageIds.has(nodeId) && typeof props.src === "string") srcs.add(props.src);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const walkTimeline = (step) => {
|
|
855
|
+
if (!step) return;
|
|
856
|
+
if (step.kind === "seq" || step.kind === "par" || step.kind === "stagger") {
|
|
857
|
+
for (const child of step.children) walkTimeline(child);
|
|
858
|
+
} else if (step.kind === "tween" && imageIds.has(step.target)) {
|
|
859
|
+
const src = step.props.src;
|
|
860
|
+
if (typeof src === "string") srcs.add(src);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
walkTimeline(ir.timeline);
|
|
864
|
+
return [...srcs];
|
|
865
|
+
}
|
|
866
|
+
var init_assets = __esm({
|
|
867
|
+
"../core/src/assets.ts"() {
|
|
868
|
+
"use strict";
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
// ../core/src/motion.ts
|
|
873
|
+
var init_motion = __esm({
|
|
874
|
+
"../core/src/motion.ts"() {
|
|
875
|
+
"use strict";
|
|
876
|
+
init_dsl();
|
|
600
877
|
}
|
|
601
878
|
});
|
|
602
879
|
|
|
@@ -609,10 +886,14 @@ var init_src = __esm({
|
|
|
609
886
|
init_validate();
|
|
610
887
|
init_compose();
|
|
611
888
|
init_compile();
|
|
889
|
+
init_path();
|
|
890
|
+
init_presets();
|
|
612
891
|
init_audio();
|
|
613
892
|
init_evaluate();
|
|
614
893
|
init_interpolate();
|
|
615
894
|
init_behaviors();
|
|
895
|
+
init_assets();
|
|
896
|
+
init_motion();
|
|
616
897
|
}
|
|
617
898
|
});
|
|
618
899
|
|
|
@@ -988,12 +1269,12 @@ async function encodeMp4(framesDir, fps, outFile) {
|
|
|
988
1269
|
"+faststart",
|
|
989
1270
|
outFile
|
|
990
1271
|
];
|
|
991
|
-
await new Promise((
|
|
1272
|
+
await new Promise((resolve5, reject) => {
|
|
992
1273
|
const proc = spawn2("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
993
1274
|
let stderr = "";
|
|
994
1275
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
995
1276
|
proc.on("close", (code) => {
|
|
996
|
-
if (code === 0)
|
|
1277
|
+
if (code === 0) resolve5();
|
|
997
1278
|
else reject(new Error(`ffmpeg exited with ${code}:
|
|
998
1279
|
${stderr.slice(-2e3)}`));
|
|
999
1280
|
});
|
|
@@ -1036,6 +1317,45 @@ var init_fonts = __esm({
|
|
|
1036
1317
|
}
|
|
1037
1318
|
});
|
|
1038
1319
|
|
|
1320
|
+
// ../render-cli/src/images.ts
|
|
1321
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1322
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1323
|
+
import { extname, isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
|
|
1324
|
+
async function buildImageAssets(ir, sceneDir) {
|
|
1325
|
+
const assets = {};
|
|
1326
|
+
for (const src of collectImageSrcs(ir)) {
|
|
1327
|
+
const mime = MIME[extname(src).toLowerCase()];
|
|
1328
|
+
if (!mime) {
|
|
1329
|
+
throw new Error(
|
|
1330
|
+
`image "${src}": unsupported format "${extname(src)}" \u2014 supported: ${Object.keys(MIME).join(" ")}`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
const candidates = [isAbsolute2(src) ? src : null, resolve2(sceneDir, src)].filter(
|
|
1334
|
+
(c) => c !== null
|
|
1335
|
+
);
|
|
1336
|
+
const found = candidates.find((c) => existsSync2(c));
|
|
1337
|
+
if (!found) {
|
|
1338
|
+
throw new Error(`image "${src}" not found (tried: ${candidates.join(", ")})`);
|
|
1339
|
+
}
|
|
1340
|
+
const data = await readFile2(found);
|
|
1341
|
+
assets[src] = `data:${mime};base64,${data.toString("base64")}`;
|
|
1342
|
+
}
|
|
1343
|
+
return assets;
|
|
1344
|
+
}
|
|
1345
|
+
var MIME;
|
|
1346
|
+
var init_images = __esm({
|
|
1347
|
+
"../render-cli/src/images.ts"() {
|
|
1348
|
+
"use strict";
|
|
1349
|
+
init_src();
|
|
1350
|
+
MIME = {
|
|
1351
|
+
".png": "image/png",
|
|
1352
|
+
".jpg": "image/jpeg",
|
|
1353
|
+
".jpeg": "image/jpeg",
|
|
1354
|
+
".webp": "image/webp"
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1039
1359
|
// ../render-cli/src/vclock.ts
|
|
1040
1360
|
var VCLOCK_SOURCE;
|
|
1041
1361
|
var init_vclock = __esm({
|
|
@@ -1141,8 +1461,8 @@ async function withPage(size, fn) {
|
|
|
1141
1461
|
async function browserBundle() {
|
|
1142
1462
|
if (bundleCache) return bundleCache;
|
|
1143
1463
|
if (true) {
|
|
1144
|
-
const { readFile:
|
|
1145
|
-
bundleCache = await
|
|
1464
|
+
const { readFile: readFile5 } = await import("node:fs/promises");
|
|
1465
|
+
bundleCache = await readFile5(
|
|
1146
1466
|
join4(dirname4(fileURLToPath3(import.meta.url)), "browserEntry.js"),
|
|
1147
1467
|
"utf8"
|
|
1148
1468
|
);
|
|
@@ -1161,6 +1481,7 @@ async function browserBundle() {
|
|
|
1161
1481
|
}
|
|
1162
1482
|
async function captureIr(ir, opts) {
|
|
1163
1483
|
await mkdir2(opts.framesDir, { recursive: true });
|
|
1484
|
+
const assets = await buildImageAssets(ir, opts.sceneDir ?? process.cwd());
|
|
1164
1485
|
const bundle = await browserBundle();
|
|
1165
1486
|
return withPage(ir.size, async (page) => {
|
|
1166
1487
|
await page.setContent(
|
|
@@ -1169,8 +1490,8 @@ async function captureIr(ir, opts) {
|
|
|
1169
1490
|
await injectFonts(page);
|
|
1170
1491
|
await page.addScriptTag({ content: bundle });
|
|
1171
1492
|
const info = await page.evaluate(
|
|
1172
|
-
(sceneIr) => window.__reframe.init(sceneIr),
|
|
1173
|
-
ir
|
|
1493
|
+
([sceneIr, imageAssets]) => window.__reframe.init(sceneIr, imageAssets),
|
|
1494
|
+
[ir, assets]
|
|
1174
1495
|
);
|
|
1175
1496
|
const fps = opts.fps ?? info.fps;
|
|
1176
1497
|
const duration = opts.duration ?? info.duration;
|
|
@@ -1187,6 +1508,7 @@ var init_frameLoop = __esm({
|
|
|
1187
1508
|
"../render-cli/src/frameLoop.ts"() {
|
|
1188
1509
|
"use strict";
|
|
1189
1510
|
init_fonts();
|
|
1511
|
+
init_images();
|
|
1190
1512
|
init_vclock();
|
|
1191
1513
|
init_reframeGlobal();
|
|
1192
1514
|
framePath = (dir, i) => join4(dir, `${String(i).padStart(5, "0")}.png`);
|
|
@@ -1202,9 +1524,9 @@ __export(batch_exports, {
|
|
|
1202
1524
|
parseCsv: () => parseCsv,
|
|
1203
1525
|
runBatch: () => runBatch
|
|
1204
1526
|
});
|
|
1205
|
-
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as
|
|
1527
|
+
import { mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile3, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
|
|
1206
1528
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
1207
|
-
import { join as join5 } from "node:path";
|
|
1529
|
+
import { join as join5, dirname as dirname5 } from "node:path";
|
|
1208
1530
|
function overlayFromFlat(row, name) {
|
|
1209
1531
|
const doc = { reframeOverlay: 1, name };
|
|
1210
1532
|
for (const [key2, raw] of Object.entries(row)) {
|
|
@@ -1278,7 +1600,7 @@ function parseCsv(text) {
|
|
|
1278
1600
|
});
|
|
1279
1601
|
}
|
|
1280
1602
|
async function loadRows(path) {
|
|
1281
|
-
const text = await
|
|
1603
|
+
const text = await readFile3(path, "utf8");
|
|
1282
1604
|
if (path.endsWith(".csv")) return parseCsv(text);
|
|
1283
1605
|
const parsed = JSON.parse(text);
|
|
1284
1606
|
if (!Array.isArray(parsed)) throw new Error(`${path}: expected a JSON array of row objects`);
|
|
@@ -1304,6 +1626,7 @@ async function runBatch(scene, rows, opts) {
|
|
|
1304
1626
|
try {
|
|
1305
1627
|
const captured = await captureIr(ir, {
|
|
1306
1628
|
framesDir,
|
|
1629
|
+
...opts.scenePath !== void 0 && { sceneDir: dirname5(opts.scenePath) },
|
|
1307
1630
|
...opts.fps !== void 0 && { fps: opts.fps }
|
|
1308
1631
|
});
|
|
1309
1632
|
if (plan) {
|
|
@@ -1362,12 +1685,12 @@ __export(loadScene_exports, {
|
|
|
1362
1685
|
loadScene: () => loadScene
|
|
1363
1686
|
});
|
|
1364
1687
|
import { build as build2 } from "esbuild";
|
|
1365
|
-
import { readFile as
|
|
1366
|
-
import { dirname as
|
|
1688
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
1689
|
+
import { dirname as dirname6, resolve as resolve3 } from "node:path";
|
|
1367
1690
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
1368
1691
|
async function loadScene(path) {
|
|
1369
1692
|
if (path.endsWith(".json")) {
|
|
1370
|
-
const ir = JSON.parse(await
|
|
1693
|
+
const ir = JSON.parse(await readFile4(path, "utf8"));
|
|
1371
1694
|
validateScene(ir);
|
|
1372
1695
|
return ir;
|
|
1373
1696
|
}
|
|
@@ -1401,24 +1724,25 @@ var init_loadScene = __esm({
|
|
|
1401
1724
|
"../render-cli/src/loadScene.ts"() {
|
|
1402
1725
|
"use strict";
|
|
1403
1726
|
init_src();
|
|
1404
|
-
HERE =
|
|
1405
|
-
CORE_ENTRY = true ?
|
|
1727
|
+
HERE = dirname6(fileURLToPath4(import.meta.url));
|
|
1728
|
+
CORE_ENTRY = true ? resolve3(HERE, "index.js") : resolve3(HERE, "..", "..", "core", "src", "index.ts");
|
|
1406
1729
|
}
|
|
1407
1730
|
});
|
|
1408
1731
|
|
|
1409
1732
|
// ../render-cli/src/reframe.ts
|
|
1410
1733
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
1411
|
-
import { existsSync as
|
|
1734
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
1412
1735
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
1413
|
-
import { basename, isAbsolute as
|
|
1414
|
-
import { dirname as
|
|
1736
|
+
import { basename, isAbsolute as isAbsolute3, join as join6, resolve as resolve4 } from "node:path";
|
|
1737
|
+
import { dirname as dirname7 } from "node:path";
|
|
1415
1738
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
1416
1739
|
var PACKAGED = true;
|
|
1417
|
-
var HERE2 =
|
|
1418
|
-
var ROOT2 = PACKAGED ?
|
|
1740
|
+
var HERE2 = dirname7(fileURLToPath5(import.meta.url));
|
|
1741
|
+
var ROOT2 = PACKAGED ? resolve4(HERE2, "..") : resolve4(HERE2, "..", "..", "..");
|
|
1419
1742
|
var USER_CWD = process.env.INIT_CWD ?? process.cwd();
|
|
1420
1743
|
var RENDER_CLI = PACKAGED ? join6(ROOT2, "dist", "cli.js") : join6(ROOT2, "packages", "render-cli", "src", "cli.ts");
|
|
1421
1744
|
var ANALYZE = PACKAGED ? join6(ROOT2, "dist", "analyze.js") : join6(ROOT2, "benchmark", "harness", "motion", "analyze.ts");
|
|
1745
|
+
var TRACE = PACKAGED ? join6(ROOT2, "dist", "trace-cli.js") : join6(ROOT2, "benchmark", "harness", "motion", "trace-cli.ts");
|
|
1422
1746
|
var CMD = PACKAGED ? "reframe" : "pnpm reframe";
|
|
1423
1747
|
var USAGE = `reframe \u2014 declarative motion graphics
|
|
1424
1748
|
|
|
@@ -1428,10 +1752,11 @@ usage:
|
|
|
1428
1752
|
${CMD} preview open the scrub/edit UI (lists scenes in your directory)
|
|
1429
1753
|
${CMD} new <scene-name> scaffold <scene-name>.ts in your directory
|
|
1430
1754
|
${CMD} motion <mp4|framesDir> motion-profile a rendered clip
|
|
1755
|
+
${CMD} trace <ref.mp4> [--apply scene.ts] extract a video's motion structure \u2192 MotionSketch / timeline
|
|
1431
1756
|
${CMD} guide [--regen] print the scene-authoring guide (for you or your AI)
|
|
1432
1757
|
${CMD} demo run the edit-survival demo (3 mp4s into out/)
|
|
1433
1758
|
`;
|
|
1434
|
-
var userPath = (p) =>
|
|
1759
|
+
var userPath = (p) => isAbsolute3(p) ? p : resolve4(USER_CWD, p);
|
|
1435
1760
|
function fail(message) {
|
|
1436
1761
|
console.error(`error: ${message}`);
|
|
1437
1762
|
process.exit(2);
|
|
@@ -1533,7 +1858,7 @@ async function main() {
|
|
|
1533
1858
|
|
|
1534
1859
|
${USAGE}`);
|
|
1535
1860
|
const inputPath = userPath(input);
|
|
1536
|
-
if (!
|
|
1861
|
+
if (!existsSync3(inputPath)) fail(`no such file: ${inputPath}`);
|
|
1537
1862
|
const mode = /\.(ts|json)$/.test(input) ? "ir" : /\.html$/.test(input) ? "html" : null;
|
|
1538
1863
|
if (!mode) {
|
|
1539
1864
|
fail(`cannot infer render mode from "${input}" \u2014 expected .ts/.json (reframe scene) or .html (GSAP page)`);
|
|
@@ -1561,7 +1886,7 @@ ${USAGE}`);
|
|
|
1561
1886
|
if (!sceneArg || !dataArg) fail(`usage: ${CMD} batch <scene.ts> <data.json|csv> [...]`);
|
|
1562
1887
|
const scenePath = userPath(sceneArg);
|
|
1563
1888
|
const dataPath = userPath(dataArg);
|
|
1564
|
-
for (const p of [scenePath, dataPath]) if (!
|
|
1889
|
+
for (const p of [scenePath, dataPath]) if (!existsSync3(p)) fail(`no such file: ${p}`);
|
|
1565
1890
|
preflightFfmpeg();
|
|
1566
1891
|
let outDir = PACKAGED ? join6(USER_CWD, "out", "batch") : join6(ROOT2, "out", "batch");
|
|
1567
1892
|
let concurrency = 3;
|
|
@@ -1576,10 +1901,10 @@ ${USAGE}`);
|
|
|
1576
1901
|
}
|
|
1577
1902
|
const { loadRows: loadRows2, runBatch: runBatch2 } = await Promise.resolve().then(() => (init_batch(), batch_exports));
|
|
1578
1903
|
const { loadScene: loadScene2 } = await Promise.resolve().then(() => (init_loadScene(), loadScene_exports));
|
|
1579
|
-
const { readFile:
|
|
1904
|
+
const { readFile: readFile5 } = await import("node:fs/promises");
|
|
1580
1905
|
const scene = await loadScene2(scenePath);
|
|
1581
1906
|
const baseOverlays = await Promise.all(
|
|
1582
|
-
baseOverlayPaths.map(async (p) => JSON.parse(await
|
|
1907
|
+
baseOverlayPaths.map(async (p) => JSON.parse(await readFile5(p, "utf8")))
|
|
1583
1908
|
);
|
|
1584
1909
|
const rows = await loadRows2(dataPath);
|
|
1585
1910
|
if (rows.length === 0) fail(`${dataPath}: no data rows`);
|
|
@@ -1610,7 +1935,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1610
1935
|
if (PACKAGED) {
|
|
1611
1936
|
const { createRequire } = await import("node:module");
|
|
1612
1937
|
const vitePkg = createRequire(import.meta.url).resolve("vite/package.json");
|
|
1613
|
-
const viteBin = join6(
|
|
1938
|
+
const viteBin = join6(dirname7(vitePkg), "bin", "vite.js");
|
|
1614
1939
|
process.exit(
|
|
1615
1940
|
await run(process.execPath, [viteBin, join6(ROOT2, "preview")], {
|
|
1616
1941
|
env: { REFRAME_SCENE_DIR: USER_CWD }
|
|
@@ -1633,7 +1958,7 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1633
1958
|
const targetDir = inRepo ? join6(ROOT2, "examples", "scenes") : USER_CWD;
|
|
1634
1959
|
const target = join6(targetDir, `${name}.ts`);
|
|
1635
1960
|
const shown = inRepo ? `examples/scenes/${name}.ts` : `${name}.ts`;
|
|
1636
|
-
if (
|
|
1961
|
+
if (existsSync3(target)) fail(`${shown} already exists`);
|
|
1637
1962
|
const id = name.split("-")[0] ?? name;
|
|
1638
1963
|
await writeFile5(target, SCENE_TEMPLATE(name, id));
|
|
1639
1964
|
console.log(`created ${shown}
|
|
@@ -1650,10 +1975,21 @@ ${results.length - failed} rendered (${orphaned} with orphans), ${failed} failed
|
|
|
1650
1975
|
await (PACKAGED ? run(process.execPath, [ANALYZE, userPath(input), ...rest.slice(1)]) : run("npx", ["tsx", ANALYZE, userPath(input), ...rest.slice(1)]))
|
|
1651
1976
|
);
|
|
1652
1977
|
}
|
|
1978
|
+
case "trace": {
|
|
1979
|
+
const input = rest[0];
|
|
1980
|
+
if (!input || input.startsWith("-")) fail(`usage: ${CMD} trace <ref.mp4> [--apply scene.ts] [-o out.json]`);
|
|
1981
|
+
preflightFfmpeg();
|
|
1982
|
+
const args = rest.slice(1).map(
|
|
1983
|
+
(a, i) => rest.slice(1)[i - 1] === "--apply" || rest.slice(1)[i - 1] === "-o" ? userPath(a) : a
|
|
1984
|
+
);
|
|
1985
|
+
process.exit(
|
|
1986
|
+
await (PACKAGED ? run(process.execPath, [TRACE, userPath(input), ...args]) : run("npx", ["tsx", TRACE, userPath(input), ...args]))
|
|
1987
|
+
);
|
|
1988
|
+
}
|
|
1653
1989
|
case "guide": {
|
|
1654
1990
|
const file = rest.includes("--regen") ? PACKAGED ? join6(ROOT2, "guides", "regen-contract.md") : join6(ROOT2, "docs", "regen-contract.md") : PACKAGED ? join6(ROOT2, "guides", "edsl-guide.md") : join6(ROOT2, "benchmark", "guides", "edsl-guide.md");
|
|
1655
|
-
const { readFile:
|
|
1656
|
-
process.stdout.write(await
|
|
1991
|
+
const { readFile: readFile5 } = await import("node:fs/promises");
|
|
1992
|
+
process.stdout.write(await readFile5(file, "utf8"));
|
|
1657
1993
|
return;
|
|
1658
1994
|
}
|
|
1659
1995
|
case "demo":
|