reframe-video 0.6.1 → 0.6.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/dist/bin.js CHANGED
@@ -377,6 +377,8 @@ function validateScene(ir) {
377
377
  const props = node.props;
378
378
  checkPaint(`node "${node.id}" fill`, props.fill);
379
379
  checkPaint(`node "${node.id}" stroke`, props.stroke);
380
+ if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
381
+ if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
380
382
  if (node.type === "group") {
381
383
  const clip = node.props.clip;
382
384
  if (clip) {
@@ -591,16 +593,17 @@ function validateComposition(comp) {
591
593
  }
592
594
  if (problems.length > 0) throw new SceneValidationError(problems);
593
595
  }
594
- var COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
596
+ var FX_PROPS, COMMON_PROPS, CAMERA_PROPS, PROPS_BY_TYPE, SceneValidationError, TRANSITIONS;
595
597
  var init_validate = __esm({
596
598
  "../core/src/validate.ts"() {
597
599
  "use strict";
598
- COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
600
+ FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
601
+ COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
599
602
  CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
600
603
  PROPS_BY_TYPE = {
601
604
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
602
605
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
603
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
606
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
604
607
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
605
608
  image: [...COMMON_PROPS, "src", "width", "height"],
606
609
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -928,6 +931,13 @@ var init_gradient = __esm({
928
931
  }
929
932
  });
930
933
 
934
+ // ../core/src/effects.ts
935
+ var init_effects = __esm({
936
+ "../core/src/effects.ts"() {
937
+ "use strict";
938
+ }
939
+ });
940
+
931
941
  // ../core/src/presets.ts
932
942
  function makeRng(seed) {
933
943
  let a = seed >>> 0 || 2654435769;
@@ -1385,6 +1395,7 @@ var init_src = __esm({
1385
1395
  init_path();
1386
1396
  init_camera();
1387
1397
  init_gradient();
1398
+ init_effects();
1388
1399
  init_presets();
1389
1400
  init_devicePreset();
1390
1401
  init_cursor();
@@ -334,11 +334,12 @@
334
334
  }
335
335
 
336
336
  // ../core/src/validate.ts
337
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
337
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
338
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
338
339
  var PROPS_BY_TYPE = {
339
340
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
340
341
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
341
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
342
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
342
343
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
343
344
  image: [...COMMON_PROPS, "src", "width", "height"],
344
345
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -679,9 +680,21 @@
679
680
  const v = valueAt(target, prop, base ?? "");
680
681
  return v === "" && base === void 0 ? void 0 : String(v);
681
682
  };
683
+ const effectFx = (id, p) => {
684
+ const fx = {};
685
+ if (p.blur !== void 0) fx.blur = num(id, "blur", p.blur);
686
+ if (p.shadowColor !== void 0) {
687
+ fx.shadowColor = str(id, "shadowColor", p.shadowColor);
688
+ fx.shadowBlur = num(id, "shadowBlur", p.shadowBlur ?? 0);
689
+ fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
690
+ fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
691
+ }
692
+ return fx;
693
+ };
682
694
  const walk = (node, parent, parentOpacity, clips) => {
683
695
  const id = node.id;
684
696
  const clipSpread = clips.length > 0 ? { clips } : void 0;
697
+ const fx = effectFx(id, node.props);
685
698
  if (node.type === "line") {
686
699
  const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
687
700
  if (opacity2 <= 0) return;
@@ -699,6 +712,7 @@
699
712
  y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
700
713
  stroke: str(id, "stroke", node.props.stroke),
701
714
  strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
715
+ ...fx,
702
716
  ...clipSpread
703
717
  });
704
718
  return;
@@ -746,6 +760,7 @@
746
760
  ...fill !== void 0 && { fill },
747
761
  ...stroke !== void 0 && { stroke, strokeWidth },
748
762
  ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
763
+ ...fx,
749
764
  ...clipSpread
750
765
  });
751
766
  return;
@@ -764,6 +779,7 @@
764
779
  height,
765
780
  offsetX: -width * ax,
766
781
  offsetY: -height * ay,
782
+ ...fx,
767
783
  ...clipSpread
768
784
  });
769
785
  return;
@@ -787,6 +803,7 @@
787
803
  ...fill !== void 0 && { fill },
788
804
  ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
789
805
  ...needsBox && { bbox: pathBBox(dStr) },
806
+ ...fx,
790
807
  ...clipSpread
791
808
  });
792
809
  return;
@@ -811,6 +828,7 @@
811
828
  letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
812
829
  align: TEXT_ALIGN[ax] ?? "left",
813
830
  baseline: TEXT_BASELINE[ay] ?? "top",
831
+ ...fx,
814
832
  ...clipSpread
815
833
  });
816
834
  return;
@@ -889,6 +907,13 @@
889
907
  }
890
908
  ctx2.setTransform(...op.transform);
891
909
  ctx2.globalAlpha = Math.max(0, Math.min(1, op.opacity));
910
+ if (op.blur) ctx2.filter = `blur(${op.blur}px)`;
911
+ if (op.shadowColor) {
912
+ ctx2.shadowColor = op.shadowColor;
913
+ ctx2.shadowBlur = op.shadowBlur ?? 0;
914
+ ctx2.shadowOffsetX = op.shadowX ?? 0;
915
+ ctx2.shadowOffsetY = op.shadowY ?? 0;
916
+ }
892
917
  switch (op.type) {
893
918
  case "rect": {
894
919
  const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
package/dist/cli.js CHANGED
@@ -324,12 +324,13 @@ function compileScene(ir) {
324
324
  }
325
325
 
326
326
  // ../core/src/validate.ts
327
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
327
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
328
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
328
329
  var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
329
330
  var PROPS_BY_TYPE = {
330
331
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
331
332
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
332
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
333
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
333
334
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
334
335
  image: [...COMMON_PROPS, "src", "width", "height"],
335
336
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -375,6 +376,8 @@ function validateScene(ir) {
375
376
  const props = node.props;
376
377
  checkPaint(`node "${node.id}" fill`, props.fill);
377
378
  checkPaint(`node "${node.id}" stroke`, props.stroke);
379
+ if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
380
+ if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
378
381
  if (node.type === "group") {
379
382
  const clip = node.props.clip;
380
383
  if (clip) {
package/dist/index.js CHANGED
@@ -334,12 +334,13 @@ function compileScene(ir) {
334
334
  }
335
335
 
336
336
  // ../core/src/validate.ts
337
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
337
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
338
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
338
339
  var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
339
340
  var PROPS_BY_TYPE = {
340
341
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
341
342
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
342
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
343
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
343
344
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
344
345
  image: [...COMMON_PROPS, "src", "width", "height"],
345
346
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -385,6 +386,8 @@ function validateScene(ir) {
385
386
  const props = node.props;
386
387
  checkPaint(`node "${node.id}" fill`, props.fill);
387
388
  checkPaint(`node "${node.id}" stroke`, props.stroke);
389
+ if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
390
+ if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
388
391
  if (node.type === "group") {
389
392
  const clip = node.props.clip;
390
393
  if (clip) {
@@ -987,6 +990,14 @@ function conicGradient(stops, opts = {}) {
987
990
  };
988
991
  }
989
992
 
993
+ // ../core/src/effects.ts
994
+ function glow(color, blur = 24) {
995
+ return { shadowColor: color, shadowBlur: blur, shadowX: 0, shadowY: 0 };
996
+ }
997
+ function dropShadow(color, blur = 24, x = 0, y = 12) {
998
+ return { shadowColor: color, shadowBlur: blur, shadowX: x, shadowY: y };
999
+ }
1000
+
990
1001
  // ../core/src/presets.ts
991
1002
  var PRESET_NAMES = [
992
1003
  "draw-bloom",
@@ -1526,11 +1537,11 @@ function ikReach(upper, lower, dx, dy, flip = false) {
1526
1537
  function humanoid(opts = {}) {
1527
1538
  const line2 = opts.color ?? DEFAULT_LINE;
1528
1539
  const fill = opts.fill ?? DEFAULT_FILL;
1529
- const glow = opts.glow;
1540
+ const glow2 = opts.glow;
1530
1541
  const blob = (jid, a, b, cy) => {
1531
1542
  const d = ovalPath(a, b, 0, cy);
1532
1543
  const nodes = [];
1533
- if (glow) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow, strokeWidth: GLOW_W, opacity: 0.18 }));
1544
+ if (glow2) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow2, strokeWidth: GLOW_W, opacity: 0.18 }));
1534
1545
  nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
1535
1546
  return nodes;
1536
1547
  };
@@ -2938,9 +2949,21 @@ function evaluate(compiled, t) {
2938
2949
  const v = valueAt(target, prop, base ?? "");
2939
2950
  return v === "" && base === void 0 ? void 0 : String(v);
2940
2951
  };
2952
+ const effectFx = (id, p) => {
2953
+ const fx = {};
2954
+ if (p.blur !== void 0) fx.blur = num(id, "blur", p.blur);
2955
+ if (p.shadowColor !== void 0) {
2956
+ fx.shadowColor = str(id, "shadowColor", p.shadowColor);
2957
+ fx.shadowBlur = num(id, "shadowBlur", p.shadowBlur ?? 0);
2958
+ fx.shadowX = num(id, "shadowX", p.shadowX ?? 0);
2959
+ fx.shadowY = num(id, "shadowY", p.shadowY ?? 0);
2960
+ }
2961
+ return fx;
2962
+ };
2941
2963
  const walk = (node, parent, parentOpacity, clips) => {
2942
2964
  const id = node.id;
2943
2965
  const clipSpread = clips.length > 0 ? { clips } : void 0;
2966
+ const fx = effectFx(id, node.props);
2944
2967
  if (node.type === "line") {
2945
2968
  const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
2946
2969
  if (opacity2 <= 0) return;
@@ -2958,6 +2981,7 @@ function evaluate(compiled, t) {
2958
2981
  y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
2959
2982
  stroke: str(id, "stroke", node.props.stroke),
2960
2983
  strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
2984
+ ...fx,
2961
2985
  ...clipSpread
2962
2986
  });
2963
2987
  return;
@@ -3005,6 +3029,7 @@ function evaluate(compiled, t) {
3005
3029
  ...fill !== void 0 && { fill },
3006
3030
  ...stroke !== void 0 && { stroke, strokeWidth },
3007
3031
  ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
3032
+ ...fx,
3008
3033
  ...clipSpread
3009
3034
  });
3010
3035
  return;
@@ -3023,6 +3048,7 @@ function evaluate(compiled, t) {
3023
3048
  height,
3024
3049
  offsetX: -width * ax,
3025
3050
  offsetY: -height * ay,
3051
+ ...fx,
3026
3052
  ...clipSpread
3027
3053
  });
3028
3054
  return;
@@ -3046,6 +3072,7 @@ function evaluate(compiled, t) {
3046
3072
  ...fill !== void 0 && { fill },
3047
3073
  ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
3048
3074
  ...needsBox && { bbox: pathBBox(dStr) },
3075
+ ...fx,
3049
3076
  ...clipSpread
3050
3077
  });
3051
3078
  return;
@@ -3070,6 +3097,7 @@ function evaluate(compiled, t) {
3070
3097
  letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
3071
3098
  align: TEXT_ALIGN[ax] ?? "left",
3072
3099
  baseline: TEXT_BASELINE[ay] ?? "top",
3100
+ ...fx,
3073
3101
  ...clipSpread
3074
3102
  });
3075
3103
  return;
@@ -3204,10 +3232,12 @@ export {
3204
3232
  deviceScreen,
3205
3233
  deviceScreenCenter,
3206
3234
  deviceScreenPoint,
3235
+ dropShadow,
3207
3236
  ellipse,
3208
3237
  evaluate,
3209
3238
  figure,
3210
3239
  formatComposeReport,
3240
+ glow,
3211
3241
  group,
3212
3242
  humanoid,
3213
3243
  ikReach,
package/dist/labels.js CHANGED
@@ -318,12 +318,13 @@ function compileScene(ir) {
318
318
  }
319
319
 
320
320
  // ../core/src/validate.ts
321
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
321
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
322
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
322
323
  var CAMERA_PROPS = ["x", "y", "zoom", "rotation"];
323
324
  var PROPS_BY_TYPE = {
324
325
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
325
326
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
326
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
327
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
327
328
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
328
329
  image: [...COMMON_PROPS, "src", "width", "height"],
329
330
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -369,6 +370,8 @@ function validateScene(ir) {
369
370
  const props = node.props;
370
371
  checkPaint(`node "${node.id}" fill`, props.fill);
371
372
  checkPaint(`node "${node.id}" stroke`, props.stroke);
373
+ if (typeof props.blur === "number" && props.blur < 0) problems.push(`node "${node.id}": blur must be >= 0`);
374
+ if (typeof props.shadowBlur === "number" && props.shadowBlur < 0) problems.push(`node "${node.id}": shadowBlur must be >= 0`);
372
375
  if (node.type === "group") {
373
376
  const clip = node.props.clip;
374
377
  if (clip) {
@@ -55,6 +55,13 @@ function drawDisplayList(ctx, ops, images) {
55
55
  }
56
56
  ctx.setTransform(...op.transform);
57
57
  ctx.globalAlpha = Math.max(0, Math.min(1, op.opacity));
58
+ if (op.blur) ctx.filter = `blur(${op.blur}px)`;
59
+ if (op.shadowColor) {
60
+ ctx.shadowColor = op.shadowColor;
61
+ ctx.shadowBlur = op.shadowBlur ?? 0;
62
+ ctx.shadowOffsetX = op.shadowX ?? 0;
63
+ ctx.shadowOffsetY = op.shadowY ?? 0;
64
+ }
58
65
  switch (op.type) {
59
66
  case "rect": {
60
67
  const box = { x: op.offsetX, y: op.offsetY, w: op.width, h: op.height };
package/dist/trace-cli.js CHANGED
@@ -6,11 +6,12 @@ import { resolve as resolve2 } from "node:path";
6
6
  import { pathToFileURL } from "node:url";
7
7
 
8
8
  // ../core/src/validate.ts
9
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed"];
9
+ var FX_PROPS = ["blur", "shadowColor", "shadowBlur", "shadowX", "shadowY"];
10
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor", "fixed", ...FX_PROPS];
10
11
  var PROPS_BY_TYPE = {
11
12
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
12
13
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
13
- line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress"],
14
+ line: ["x1", "y1", "x2", "y2", "stroke", "strokeWidth", "opacity", "progress", ...FX_PROPS],
14
15
  text: [...COMMON_PROPS, "content", "contentDecimals", "contentThousands", "fontFamily", "fontSize", "fontWeight", "fill", "letterSpacing"],
15
16
  image: [...COMMON_PROPS, "src", "width", "height"],
16
17
  path: [...COMMON_PROPS, "d", "fill", "stroke", "strokeWidth", "progress", "originX", "originY"],
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Drop-shadow / outer-glow sugar. These return a partial-props object you spread
3
+ * into a shape node; the underlying `shadow*` props stay animatable (e.g. pulse a
4
+ * glow with `oscillate(id, "shadowBlur", …)`). Units are screen pixels.
5
+ */
6
+ import type { BaseProps } from "./ir.js";
7
+ type ShadowProps = Pick<BaseProps, "shadowColor" | "shadowBlur" | "shadowX" | "shadowY">;
8
+ /** An outer glow — a shadow with no offset. `rect({ …, ...glow("#FFD24B", 28) })`. */
9
+ export declare function glow(color: string, blur?: number): ShadowProps;
10
+ /** A drop shadow (offset downward by default). `rect({ …, ...dropShadow("#0008", 40, 0, 16) })`. */
11
+ export declare function dropShadow(color: string, blur?: number, x?: number, y?: number): ShadowProps;
12
+ export {};
@@ -24,6 +24,12 @@ interface OpBase {
24
24
  opacity: number;
25
25
  /** Clip regions from ancestor groups (intersected by the renderer). */
26
26
  clips?: ClipRegion[];
27
+ /** Paint effects (screen-pixel space). Present only when authored. */
28
+ blur?: number;
29
+ shadowColor?: string;
30
+ shadowBlur?: number;
31
+ shadowX?: number;
32
+ shadowY?: number;
27
33
  }
28
34
  export type DisplayOp = (OpBase & {
29
35
  type: "rect";
@@ -7,6 +7,7 @@ export { compileScene, type CompiledScene, type PropertySegment, type LabelSpan,
7
7
  export { pathPoint, pathTangentAngle, type Pt } from "./path.js";
8
8
  export { cameraTo, cameraMatrix, CAMERA_ID, CAMERA_PROPS } from "./camera.js";
9
9
  export { linearGradient, radialGradient, conicGradient, isGradient } from "./gradient.js";
10
+ export { glow, dropShadow } from "./effects.js";
10
11
  export { motionPreset, PRESET_NAMES, type PresetName, type PresetRig, type PresetOpts } from "./presets.js";
11
12
  export { devicePreset, deviceScreen, deviceScreenCenter, deviceBounds, deviceScreenPoint, DEVICE_PRESET_NAMES, type DevicePresetName, type DevicePresetOpts } from "./devicePreset.js";
12
13
  export { cursor, cursorTo, cursorPath, cursorClick, cursorDouble, type CursorStyle, type CursorOpts, type CursorToOpts, type CursorPathOpts, type CursorClickOpts } from "./cursor.js";
@@ -36,6 +36,17 @@ export interface BaseProps {
36
36
  * for HUD / titles / watermark layers. No-op when the scene has no camera.
37
37
  */
38
38
  fixed?: boolean;
39
+ /**
40
+ * Paint effects (animatable scalars, in screen pixels — not transformed by the
41
+ * node's rotation/scale or the camera, so a shadow keeps a consistent light
42
+ * direction). `shadowColor` enables a drop shadow / outer glow (`glow`/`dropShadow`
43
+ * helpers). No-op on a `group` (use a child; group/composite blur is a later add).
44
+ */
45
+ blur?: number;
46
+ shadowColor?: string;
47
+ shadowBlur?: number;
48
+ shadowX?: number;
49
+ shadowY?: number;
39
50
  }
40
51
  /**
41
52
  * A paint is a solid color string OR a gradient. Coordinates are normalized to the
@@ -93,6 +104,12 @@ export interface LineProps {
93
104
  progress?: number;
94
105
  /** Pin to the screen so the scene `camera` does not move it (top-level only). */
95
106
  fixed?: boolean;
107
+ /** Paint effects (px, screen-space) — see BaseProps. */
108
+ blur?: number;
109
+ shadowColor?: string;
110
+ shadowBlur?: number;
111
+ shadowX?: number;
112
+ shadowY?: number;
96
113
  }
97
114
  export interface TextProps extends BaseProps {
98
115
  /** Numbers interpolate (count-up) and render via toFixed(contentDecimals). */
@@ -172,6 +172,28 @@ ellipse({ id: "ring", /* … */ fill: "none", stroke: linearGradient(["#3AA0FF",
172
172
  gradient sweeps/stretches with it. Color-string fills still tween as today.
173
173
  - text fill and line stroke are color-only for now. See `examples/scenes/gradient-demo.ts`.
174
174
 
175
+ ## Shadow, glow & blur
176
+
177
+ Drawable nodes (rect / ellipse / path / text / image / line) take animatable paint
178
+ effects, in **screen pixels** (not transformed by the node or camera, so a shadow
179
+ keeps a consistent light direction):
180
+
181
+ ```ts
182
+ rect({ id: "card", /* … */, ...dropShadow("#000000", 64, 0, 34) }) // drop shadow
183
+ ellipse({ id: "orb", /* … */, fill: radialGradient([...]), shadowColor: "#FFC24B", shadowBlur: 22 })
184
+ oscillate("orb", "shadowBlur", { amplitude: 16, frequency: 0.9 }) // PULSING glow
185
+ rect({ id: "card", /* … */, blur: 18 }); tween("card", { blur: 0 }, { duration: 1 }) // focus pull
186
+ ```
187
+
188
+ - Props: `blur` (gaussian blur of the shape), `shadowColor` (turns the shadow/glow
189
+ on), `shadowBlur`, `shadowX`, `shadowY`. All **animatable** — `tween`/`oscillate`
190
+ them for pulsing glows, focus pulls, etc. (set a base value first so there's
191
+ something to animate from).
192
+ - Sugar: `glow(color, blur)` (offset 0) and `dropShadow(color, blur, x, y)` return
193
+ a partial you spread into props (`...glow("#FFD24B", 28)`); still animatable.
194
+ - No-op on a `group` (apply to a child; group/composite blur is a later add). See
195
+ `examples/scenes/shadow-demo.ts`.
196
+
175
197
  ## Character rig (skeleton, poses, IK)
176
198
 
177
199
  A first-class, declarative character rig that **compiles to plain IR** (nested
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reframe-video",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "Declarative motion graphics that AI can write and humans can tweak — human edits survive AI regeneration. Deterministic mp4 renders from a plain-data scene format.",
5
5
  "keywords": [
6
6
  "motion-graphics",