reframe-video 0.1.1 → 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.
@@ -3,9 +3,88 @@
3
3
  // ../core/src/ir.ts
4
4
  var DEFAULT_TO_DURATION = 0.5;
5
5
  var DEFAULT_TWEEN_DURATION = 0.5;
6
+ var DEFAULT_MOTIONPATH_DURATION = 1;
7
+
8
+ // ../core/src/path.ts
9
+ function locate(segCount, u) {
10
+ if (segCount <= 0) return { i: 0, t: 0 };
11
+ const clamped = Math.max(0, Math.min(1, u));
12
+ const scaled = clamped * segCount;
13
+ let i = Math.floor(scaled);
14
+ if (i >= segCount) i = segCount - 1;
15
+ return { i, t: scaled - i };
16
+ }
17
+ function controls(points, closed, i) {
18
+ const n = points.length;
19
+ const at = (k) => {
20
+ if (closed) return points[(k % n + n) % n];
21
+ return points[Math.max(0, Math.min(n - 1, k))];
22
+ };
23
+ return [at(i - 1), at(i), at(i + 1), at(i + 2)];
24
+ }
25
+ function segCountOf(points, closed) {
26
+ const n = points.length;
27
+ if (n < 2) return 0;
28
+ return closed ? n : n - 1;
29
+ }
30
+ function pathPoint(points, closed, u) {
31
+ const n = points.length;
32
+ if (n === 0) return [0, 0];
33
+ if (n === 1) return [points[0][0], points[0][1]];
34
+ const segs = segCountOf(points, closed);
35
+ const { i, t } = locate(segs, u);
36
+ const [p0, p1, p2, p3] = controls(points, closed, i);
37
+ const t2 = t * t;
38
+ const t3 = t2 * t;
39
+ 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);
40
+ return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
41
+ }
42
+ function pathTangentAngle(points, closed, u) {
43
+ const n = points.length;
44
+ if (n < 2) return 0;
45
+ const segs = segCountOf(points, closed);
46
+ const { i, t } = locate(segs, u);
47
+ const [p0, p1, p2, p3] = controls(points, closed, i);
48
+ const t2 = t * t;
49
+ 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);
50
+ const dx = d(p0[0], p1[0], p2[0], p3[0]);
51
+ const dy = d(p0[1], p1[1], p2[1], p3[1]);
52
+ if (dx === 0 && dy === 0) return 0;
53
+ return Math.atan2(dy, dx) * 180 / Math.PI;
54
+ }
6
55
 
7
56
  // ../core/src/compile.ts
8
57
  var key = (target, prop) => `${target}.${prop}`;
58
+ function scaleTimeline(tl, k) {
59
+ switch (tl.kind) {
60
+ case "seq":
61
+ case "par":
62
+ return { ...tl, children: tl.children.map((c) => scaleTimeline(c, k)) };
63
+ case "stagger":
64
+ return { ...tl, interval: tl.interval * k, children: tl.children.map((c) => scaleTimeline(c, k)) };
65
+ case "wait":
66
+ return { ...tl, duration: tl.duration * k };
67
+ case "tween":
68
+ return { ...tl, duration: (tl.duration ?? DEFAULT_TWEEN_DURATION) * k };
69
+ case "motionPath":
70
+ return { ...tl, duration: (tl.duration ?? DEFAULT_MOTIONPATH_DURATION) * k };
71
+ case "to":
72
+ return {
73
+ ...tl,
74
+ duration: (tl.duration ?? DEFAULT_TO_DURATION) * k,
75
+ ...tl.stagger !== void 0 && { stagger: tl.stagger * k }
76
+ };
77
+ case "beat":
78
+ return {
79
+ ...tl,
80
+ children: tl.children.map((c) => scaleTimeline(c, k)),
81
+ ...tl.gap !== void 0 && { gap: tl.gap * k }
82
+ };
83
+ }
84
+ }
85
+ function orderBeats(children) {
86
+ 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);
87
+ }
9
88
  function compileScene(ir) {
10
89
  const nodeById = /* @__PURE__ */ new Map();
11
90
  const nodeOrder = [];
@@ -34,6 +113,7 @@
34
113
  }
35
114
  }
36
115
  const segments = /* @__PURE__ */ new Map();
116
+ const motionPaths = /* @__PURE__ */ new Map();
37
117
  const current = new Map(initialValues);
38
118
  const pushSegment = (seg) => {
39
119
  const k = key(seg.target, seg.prop);
@@ -50,6 +130,50 @@
50
130
  throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
51
131
  };
52
132
  const labelTimes = /* @__PURE__ */ new Map();
133
+ const beatTimes = /* @__PURE__ */ new Map();
134
+ const durationOf = (tl, start) => {
135
+ switch (tl.kind) {
136
+ case "seq": {
137
+ let t = start;
138
+ for (const child of orderBeats(tl.children)) t = durationOf(child, t);
139
+ return t;
140
+ }
141
+ case "par": {
142
+ let end = start;
143
+ for (const child of tl.children) end = Math.max(end, durationOf(child, start));
144
+ return end;
145
+ }
146
+ case "stagger": {
147
+ let end = start;
148
+ tl.children.forEach((child, i) => {
149
+ end = Math.max(end, durationOf(child, start + i * tl.interval));
150
+ });
151
+ return end;
152
+ }
153
+ case "wait":
154
+ return start + tl.duration;
155
+ case "tween":
156
+ return start + (tl.duration ?? DEFAULT_TWEEN_DURATION);
157
+ case "motionPath":
158
+ return start + (tl.duration ?? DEFAULT_MOTIONPATH_DURATION);
159
+ case "to": {
160
+ const override = ir.states?.[tl.state] ?? {};
161
+ const duration = tl.duration ?? DEFAULT_TO_DURATION;
162
+ const si = tl.stagger ?? 0;
163
+ const targets = nodeOrder.filter(
164
+ (id) => id in override && (tl.filter === void 0 || tl.filter.includes(id))
165
+ );
166
+ return start + duration + Math.max(0, targets.length - 1) * si;
167
+ }
168
+ case "beat": {
169
+ const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
170
+ const natural = durationOf(grouping, 0);
171
+ const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, natural) : 1);
172
+ const beatStart = tl.at ?? start + (tl.gap ?? 0);
173
+ return beatStart + k * natural;
174
+ }
175
+ }
176
+ };
53
177
  const walk = (tl, start) => {
54
178
  const end = walkInner(tl, start);
55
179
  if ("label" in tl && tl.label !== void 0) labelTimes.set(tl.label, { t0: start, t1: end });
@@ -59,9 +183,19 @@
59
183
  switch (tl.kind) {
60
184
  case "seq": {
61
185
  let t = start;
62
- for (const child of tl.children) t = walk(child, t);
186
+ for (const child of orderBeats(tl.children)) t = walk(child, t);
63
187
  return t;
64
188
  }
189
+ case "beat": {
190
+ const grouping = { kind: tl.parallel ? "par" : "seq", children: tl.children };
191
+ const k = tl.scale ?? (tl.duration !== void 0 ? tl.duration / Math.max(1e-9, durationOf(grouping, 0)) : 1);
192
+ const inner = k === 1 ? grouping : scaleTimeline(grouping, k);
193
+ const beatStart = tl.at ?? start + (tl.gap ?? 0);
194
+ const end = walk(inner, beatStart);
195
+ beatTimes.set(tl.name, { t0: beatStart, t1: end });
196
+ labelTimes.set(tl.name, { t0: beatStart, t1: end });
197
+ return end;
198
+ }
65
199
  case "par": {
66
200
  let end = start;
67
201
  for (const child of tl.children) end = Math.max(end, walk(child, start));
@@ -91,6 +225,23 @@
91
225
  }
92
226
  return start + duration;
93
227
  }
228
+ case "motionPath": {
229
+ const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
230
+ const points = tl.points;
231
+ const closed = tl.closed ?? false;
232
+ const autoRotate = tl.autoRotate ?? false;
233
+ const rotateOffset = tl.rotateOffset ?? 0;
234
+ let list = motionPaths.get(tl.target);
235
+ if (!list) motionPaths.set(tl.target, list = []);
236
+ list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
237
+ if (points.length > 0) {
238
+ const [ex, ey] = pathPoint(points, closed, 1);
239
+ current.set(key(tl.target, "x"), ex);
240
+ current.set(key(tl.target, "y"), ey);
241
+ if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
242
+ }
243
+ return start + duration;
244
+ }
94
245
  case "to": {
95
246
  const override = ir.states?.[tl.state] ?? {};
96
247
  const duration = tl.duration ?? DEFAULT_TO_DURATION;
@@ -119,14 +270,17 @@
119
270
  };
120
271
  const inferredEnd = ir.timeline ? walk(ir.timeline, 0) : 0;
121
272
  for (const list of segments.values()) list.sort((a, b) => a.t0 - b.t0);
273
+ for (const list of motionPaths.values()) list.sort((a, b) => a.t0 - b.t0);
122
274
  return {
123
275
  ir,
124
276
  duration: ir.duration ?? inferredEnd,
125
277
  segments,
278
+ motionPaths,
126
279
  initialValues,
127
280
  nodeById,
128
281
  nodeOrder,
129
- labelTimes
282
+ labelTimes,
283
+ beatTimes
130
284
  };
131
285
  }
132
286
 
@@ -138,9 +292,13 @@
138
292
  line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
139
293
  text: [...COMMON_PROPS, "content", "contentDecimals", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
140
294
  image: [...COMMON_PROPS, "src", "width", "height"],
295
+ path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
141
296
  group: COMMON_PROPS
142
297
  };
143
298
 
299
+ // ../core/src/presets.ts
300
+ var SET = 1 / 120;
301
+
144
302
  // ../core/src/behaviors.ts
145
303
  function sampleBehavior(b, t) {
146
304
  switch (b.name) {
@@ -171,6 +329,19 @@
171
329
  }
172
330
 
173
331
  // ../core/src/interpolate.ts
332
+ var BACK_C1 = 1.70158;
333
+ var BACK_C2 = BACK_C1 * 1.525;
334
+ var BACK_C3 = BACK_C1 + 1;
335
+ var ELASTIC_C4 = 2 * Math.PI / 3;
336
+ var ELASTIC_C5 = 2 * Math.PI / 4.5;
337
+ function easeOutBounce(u) {
338
+ const n1 = 7.5625;
339
+ const d1 = 2.75;
340
+ if (u < 1 / d1) return n1 * u * u;
341
+ if (u < 2 / d1) return n1 * (u -= 1.5 / d1) * u + 0.75;
342
+ if (u < 2.5 / d1) return n1 * (u -= 2.25 / d1) * u + 0.9375;
343
+ return n1 * (u -= 2.625 / d1) * u + 0.984375;
344
+ }
174
345
  var EASE_TABLE = {
175
346
  linear: (u) => u,
176
347
  easeInQuad: (u) => u * u,
@@ -184,7 +355,20 @@
184
355
  easeInOutQuart: (u) => u < 0.5 ? 8 * u ** 4 : 1 - (-2 * u + 2) ** 4 / 2,
185
356
  easeInExpo: (u) => u === 0 ? 0 : 2 ** (10 * u - 10),
186
357
  easeOutExpo: (u) => u === 1 ? 1 : 1 - 2 ** (-10 * u),
187
- easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2
358
+ easeInOutExpo: (u) => u === 0 ? 0 : u === 1 ? 1 : u < 0.5 ? 2 ** (20 * u - 10) / 2 : (2 - 2 ** (-20 * u + 10)) / 2,
359
+ // --- expressive eases (GSAP's signature feel) — standard Penner equations ---
360
+ // back: overshoots past the target then settles (pop / snap)
361
+ easeInBack: (u) => BACK_C3 * u ** 3 - BACK_C1 * u * u,
362
+ easeOutBack: (u) => 1 + BACK_C3 * (u - 1) ** 3 + BACK_C1 * (u - 1) ** 2,
363
+ 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,
364
+ // elastic: rings around the target before settling (playful spring)
365
+ easeInElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : -(2 ** (10 * u - 10)) * Math.sin((u * 10 - 10.75) * ELASTIC_C4),
366
+ easeOutElastic: (u) => u === 0 ? 0 : u === 1 ? 1 : 2 ** (-10 * u) * Math.sin((u * 10 - 0.75) * ELASTIC_C4) + 1,
367
+ 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,
368
+ // bounce: drops and bounces to rest (lands without overshoot)
369
+ easeInBounce: (u) => 1 - easeOutBounce(1 - u),
370
+ easeOutBounce,
371
+ easeInOutBounce: (u) => u < 0.5 ? (1 - easeOutBounce(1 - 2 * u)) / 2 : (1 + easeOutBounce(2 * u - 1)) / 2
188
372
  };
189
373
  var EASE_NAMES = Object.keys(EASE_TABLE);
190
374
  function resolveEase(ease) {
@@ -299,6 +483,7 @@
299
483
  const ops = [];
300
484
  const valueAt = (target, prop, fallback) => {
301
485
  let value = compiled2.initialValues.get(`${target}.${prop}`) ?? fallback;
486
+ let segStart = Number.NEGATIVE_INFINITY;
302
487
  const segs = compiled2.segments.get(`${target}.${prop}`);
303
488
  if (segs) {
304
489
  let active;
@@ -307,6 +492,7 @@
307
492
  else break;
308
493
  }
309
494
  if (active) {
495
+ segStart = active.t0;
310
496
  if (t >= active.t1) {
311
497
  value = active.to;
312
498
  } else {
@@ -315,6 +501,23 @@
315
501
  }
316
502
  }
317
503
  }
504
+ if (prop === "x" || prop === "y" || prop === "rotation") {
505
+ const drivers = compiled2.motionPaths.get(target);
506
+ if (drivers) {
507
+ let active;
508
+ for (const d of drivers) {
509
+ if (d.t0 <= t) active = d;
510
+ else break;
511
+ }
512
+ if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
513
+ const span = active.t1 - active.t0;
514
+ const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
515
+ if (prop === "x") value = pathPoint(active.points, active.closed, u)[0];
516
+ else if (prop === "y") value = pathPoint(active.points, active.closed, u)[1];
517
+ else value = pathTangentAngle(active.points, active.closed, u) + active.rotateOffset;
518
+ }
519
+ }
520
+ }
318
521
  for (const b of compiled2.ir.behaviors ?? []) {
319
522
  if (b.target === target && b.prop === prop && typeof value === "number") {
320
523
  const envelope = behaviorEnvelope(b, t);
@@ -412,6 +615,23 @@
412
615
  });
413
616
  return;
414
617
  }
618
+ case "path": {
619
+ const ox = num(id, "originX", node.props.originX ?? 0);
620
+ const oy = num(id, "originY", node.props.originY ?? 0);
621
+ const fill = opt(id, "fill", node.props.fill);
622
+ const stroke = opt(id, "stroke", node.props.stroke);
623
+ ops.push({
624
+ type: "path",
625
+ id,
626
+ transform: ox === 0 && oy === 0 ? matrix : multiply(matrix, [1, 0, 0, 1, -ox, -oy]),
627
+ opacity,
628
+ d: str(id, "d", node.props.d),
629
+ progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
630
+ ...fill !== void 0 && { fill },
631
+ ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) }
632
+ });
633
+ return;
634
+ }
415
635
  case "text": {
416
636
  const [ax, ay] = ANCHOR_FACTORS[node.props.anchor ?? "top-left"];
417
637
  const raw = valueAt(id, "content", node.props.content);
@@ -527,6 +747,29 @@
527
747
  }
528
748
  break;
529
749
  }
750
+ case "path": {
751
+ const p = new Path2D(op.d);
752
+ if (op.fill) {
753
+ ctx2.fillStyle = op.fill;
754
+ ctx2.fill(p);
755
+ }
756
+ if (op.stroke && (op.strokeWidth ?? 1) > 0) {
757
+ ctx2.strokeStyle = op.stroke;
758
+ ctx2.lineWidth = op.strokeWidth ?? 1;
759
+ ctx2.lineJoin = "round";
760
+ ctx2.lineCap = "round";
761
+ if (op.progress < 1) {
762
+ const len = pathLength(op.d);
763
+ if (len > 0) {
764
+ ctx2.setLineDash([len, len]);
765
+ ctx2.lineDashOffset = len * (1 - op.progress);
766
+ }
767
+ }
768
+ ctx2.stroke(p);
769
+ ctx2.setLineDash([]);
770
+ }
771
+ break;
772
+ }
530
773
  case "text": {
531
774
  ctx2.font = `${op.fontWeight} ${op.fontSize}px ${quoteFamily(op.fontFamily)}`;
532
775
  if (op.letterSpacing) ctx2.letterSpacing = `${op.letterSpacing}px`;
@@ -544,6 +787,19 @@
544
787
  function quoteFamily(family) {
545
788
  return family.includes(" ") && !family.includes('"') ? `"${family}"` : family;
546
789
  }
790
+ var pathLengthCache = /* @__PURE__ */ new Map();
791
+ function pathLength(d) {
792
+ const hit = pathLengthCache.get(d);
793
+ if (hit !== void 0) return hit;
794
+ let len = 0;
795
+ if (typeof document !== "undefined") {
796
+ const el = document.createElementNS("http://www.w3.org/2000/svg", "path");
797
+ el.setAttribute("d", d);
798
+ len = el.getTotalLength();
799
+ }
800
+ pathLengthCache.set(d, len);
801
+ return len;
802
+ }
547
803
 
548
804
  // ../render-cli/src/browserEntry.ts
549
805
  var compiled = null;