@vosjs/cli 0.24.0 → 0.25.1

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/cli.js CHANGED
@@ -495,7 +495,7 @@ async function cmdCheck(argv) {
495
495
  return result.ok ? EXIT_OK : EXIT_ERROR;
496
496
  }
497
497
  async function delegate(argv, viaAlias = false) {
498
- const { run } = await import("./run-UZW5LPJQ.js");
498
+ const { run } = await import("./run-NTVAMME2.js");
499
499
  if (viaAlias && argv[0]) {
500
500
  process.stderr.write(
501
501
  `note: "vos voila ${argv[0]}" is now "vos ${argv[0]}".
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  validateActions,
31
31
  waitForPageDone,
32
32
  writeSyncState
33
- } from "./chunk-NOR522G5.js";
33
+ } from "./chunk-GHJ2BFQG.js";
34
34
  import {
35
35
  BrowserUnavailableError,
36
36
  configDuration,
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "./chunk-NOR522G5.js";
4
+ } from "./chunk-GHJ2BFQG.js";
5
5
  import "./chunk-NHKHTIDR.js";
6
6
  export {
7
7
  run
8
8
  };
9
- //# sourceMappingURL=run-UZW5LPJQ.js.map
9
+ //# sourceMappingURL=run-NTVAMME2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vosjs/cli",
3
- "version": "0.24.0",
3
+ "version": "0.25.1",
4
4
  "description": "The vos CLI: record the real product from a scripted browser flow, auto-zoom from the cursor track, cut as data in doc.json, render deterministic video and stills, deliver a release's media per destination spec, and sync with vos.so. One binary, every verb, MIT.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,10 +50,10 @@
50
50
  "playwright": "^1.49.0",
51
51
  "@vosjs/core": "^0.23.6",
52
52
  "@vosjs/editor": "^1.3.1",
53
- "@vosjs/elements": "^0.8.1",
54
53
  "@vosjs/render-core": "^0.2.5",
55
54
  "@vosjs/shared": "^0.4.1",
56
- "@vosjs/studio-core": "^0.14.0",
55
+ "@vosjs/elements": "^0.8.1",
56
+ "@vosjs/studio-core": "^0.15.1",
57
57
  "@vosjs/tween": "^0.8.2",
58
58
  "@vosjs/timeline": "^0.4.1"
59
59
  },
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://vos.so/schemas/doc.schema.json",
4
- "title": "vosso doc.json (the studio document a recording document or a program document)",
5
- "description": "The editable document a vos carries beside its config: a RECORDING document (a take: `source` plus the tracks and layers) or a PROGRAM document (`program.config` plus the shared layers). Time is SECONDS everywhere. Edit this file, then re-run `vos render` renders are deterministic, only your edits change the output. `vos validate <dir>` checks these contracts plus semantic lints (overlap, footage bounds).",
4
+ "title": "vosso doc.json (the studio document \u2014 a recording document or a program document)",
5
+ "description": "The editable document a vos carries beside its config: a RECORDING document (a take: `source` plus the tracks and layers) or a PROGRAM document (`program.config` plus the shared layers). Time is SECONDS everywhere. Edit this file, then re-run `vos render` \u2014 renders are deterministic, only your edits change the output. `vos validate <dir>` checks these contracts plus semantic lints (overlap, footage bounds).",
6
6
  "oneOf": [
7
7
  {
8
8
  "title": "Recording document",
@@ -22,7 +22,7 @@
22
22
  "properties": {
23
23
  "source": {
24
24
  "type": "object",
25
- "description": "The recording this doc edits. Written by record/plan treat as read-only except videoKey/camKey/micKey when relinking assets. micKey (extension takes recorded after the audio split) is a separately-recorded mic sidecar: the voice, gained by micGain; the recording's own audio track is then system/tab audio, gained by systemGain.",
25
+ "description": "The recording this doc edits. Written by record/plan \u2014 treat as read-only except videoKey/camKey/micKey when relinking assets. micKey (extension takes recorded after the audio split) is a separately-recorded mic sidecar: the voice, gained by micGain; the recording's own audio track is then system/tab audio, gained by systemGain.",
26
26
  "additionalProperties": true
27
27
  },
28
28
  "segments": {
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "zoom": {
66
66
  "type": "array",
67
- "description": "Zoom regions (SOURCE time, footage-anchored, non-overlapping). The camera ramps in around `in`, holds until `out`, ramps out or pans straight to the next span when the gap is short. Planner-made spans have source:'auto' (replaced on re-plan); set source:'manual' on any span you add or edit so it survives.",
67
+ "description": "Zoom regions (SOURCE time, footage-anchored, non-overlapping). The camera ramps in around `in`, holds until `out`, ramps out \u2014 or pans straight to the next span when the gap is short. Planner-made spans have source:'auto' (replaced on re-plan); set source:'manual' on any span you add or edit so it survives.",
68
68
  "items": {
69
69
  "type": "object",
70
70
  "required": ["id", "in", "out", "level", "cx", "cy"],
@@ -80,13 +80,13 @@
80
80
  },
81
81
  "out": {
82
82
  "type": "number",
83
- "description": "SOURCE seconds, > in ( 0.3s span reads well)"
83
+ "description": "SOURCE seconds, > in (\u2265 0.3s span reads well)"
84
84
  },
85
85
  "level": {
86
86
  "type": "number",
87
87
  "minimum": 1,
88
88
  "maximum": 5,
89
- "description": "zoom level; 1.4–2.8 reads well"
89
+ "description": "zoom level; 1.4\u20132.8 reads well"
90
90
  },
91
91
  "cx": {
92
92
  "type": "number",
@@ -106,7 +106,7 @@
106
106
  },
107
107
  "transition": {
108
108
  "enum": ["instant", "fast", "smooth", "slow"],
109
- "description": "transition SPEED for this span's ramps (in, out, and a chained pan arriving here). Absent = 'smooth' (the camera style's stock motion); 'instant' = hard cut; 'fast' half, 'slow' 1.6×."
109
+ "description": "transition SPEED for this span's ramps (in, out, and a chained pan arriving here). Absent = 'smooth' (the camera style's stock motion); 'instant' = hard cut; 'fast' \u2248 half, 'slow' \u2248 1.6\u00d7."
110
110
  },
111
111
  "focusMode": {
112
112
  "enum": ["manual", "auto"],
@@ -134,7 +134,7 @@
134
134
  "description": "seconds from that edge to the span's `in` (negative = before it)"
135
135
  }
136
136
  },
137
- "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
137
+ "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only \u2014 `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
138
138
  },
139
139
  "media": {
140
140
  "type": "string",
@@ -155,7 +155,7 @@
155
155
  "cut",
156
156
  "none"
157
157
  ],
158
- "description": "Camera style preset driving planner + camera motion. Absent = 'glide'. 'none' disables auto-zoom planning (manual spans keep default motion). 'keynote' and 'drift' are tilt-forward styles: they carry a tilt personality (keynote = medium lean toward each zoom's focus with tilt ramps matched to the zoom ramps; drift = subtle lean, slower 1.6s ramps, longer chains) in the studio, picking one stamps tiltStyle and plans auto tilt spans; in doc.json, set tiltStyle + tilt spans yourself (the style still shapes tilt MOTION at render)."
158
+ "description": "Camera style preset driving planner + camera motion. Absent = 'glide'. 'none' disables auto-zoom planning (manual spans keep default motion). 'keynote' and 'drift' are tilt-forward styles: they carry a tilt personality (keynote = medium lean toward each zoom's focus with tilt ramps matched to the zoom ramps; drift = subtle lean, slower 1.6s ramps, longer chains) \u2014 in the studio, picking one stamps tiltStyle and plans auto tilt spans; in doc.json, set tiltStyle + tilt spans yourself (the style still shapes tilt MOTION at render)."
159
159
  },
160
160
  "zoomParams": {
161
161
  "type": "object",
@@ -164,12 +164,12 @@
164
164
  },
165
165
  "speedParams": {
166
166
  "type": "object",
167
- "description": "Auto-speed planner overrides: idleMin/idleRate/typingMin/typingRate/scrollMin/scrollRate. Absent = defaults (idle ≥5s→4×, typing ≥3s→3×, scroll ≥2.5s→2×).",
167
+ "description": "Auto-speed planner overrides: idleMin/idleRate/typingMin/typingRate/scrollMin/scrollRate. Absent = defaults (idle \u22655s\u21924\u00d7, typing \u22653s\u21923\u00d7, scroll \u22652.5s\u21922\u00d7).",
168
168
  "additionalProperties": true
169
169
  },
170
170
  "tilt": {
171
171
  "type": "array",
172
- "description": "Card tilt regions (SOURCE time, footage-anchored, non-overlapping). While a span is active the 3D card leans to its pose; between spans it returns to FLAT (there is no static card pose: a lean is a moment on the timeline). Ramps ~0.9s in / ~0.8s out; spans ~1.35s apart swing pose-to-pose. Direction: +rx brings the TOP edge toward the camera, +ry the LEFT edge to lean toward a right-side focus use NEGATIVE ry. Wand-made spans have source:'auto' (replaced when Dynamic tilt re-runs); set source:'manual' on spans you add or edit.",
172
+ "description": "Card tilt regions (SOURCE time, footage-anchored, non-overlapping). While a span is active the 3D card leans to its pose; between spans it returns to FLAT (there is no static card pose: a lean is a moment on the timeline). Ramps ~0.9s in / ~0.8s out; spans \u2264 ~1.35s apart swing pose-to-pose. Direction: +rx brings the TOP edge toward the camera, +ry the LEFT edge \u2014 to lean toward a right-side focus use NEGATIVE ry. Wand-made spans have source:'auto' (replaced when Dynamic tilt re-runs); set source:'manual' on spans you add or edit.",
173
173
  "items": {
174
174
  "type": "object",
175
175
  "required": ["id", "in", "out", "rx", "ry"],
@@ -185,13 +185,13 @@
185
185
  },
186
186
  "out": {
187
187
  "type": "number",
188
- "description": "SOURCE seconds, > in ( 0.8s so the pose can settle)"
188
+ "description": "SOURCE seconds, > in (\u2265 0.8s so the pose can settle)"
189
189
  },
190
190
  "rx": {
191
191
  "type": "number",
192
192
  "minimum": -45,
193
193
  "maximum": 45,
194
- "description": "pose DEGREES about the horizontal axis (+ = top edge toward camera). NOT radians, NOT fractions; ±5..18° reads premium."
194
+ "description": "pose DEGREES about the horizontal axis (+ = top edge toward camera). NOT radians, NOT fractions; \u00b15..18\u00b0 reads premium."
195
195
  },
196
196
  "ry": {
197
197
  "type": "number",
@@ -229,7 +229,7 @@
229
229
  "description": "seconds from that edge to the span's `in` (negative = before it)"
230
230
  }
231
231
  },
232
- "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
232
+ "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only \u2014 `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
233
233
  },
234
234
  "media": {
235
235
  "type": "string",
@@ -241,7 +241,7 @@
241
241
  },
242
242
  "tiltStyle": {
243
243
  "enum": ["off", "subtle", "medium", "strong"],
244
- "description": "Dynamic-tilt wand intensity: derives source:'auto' tilt spans FROM the zoom spans, leaning toward each zoom's focus (max 5/9/14° per axis). 'off'/absent = no auto tilt; manual spans work either way."
244
+ "description": "Dynamic-tilt wand intensity: derives source:'auto' tilt spans FROM the zoom spans, leaning toward each zoom's focus (max 5/9/14\u00b0 per axis). 'off'/absent = no auto tilt; manual spans work either way."
245
245
  },
246
246
  "rejected": {
247
247
  "type": "array",
@@ -312,21 +312,21 @@
312
312
  "type": "number",
313
313
  "minimum": 0,
314
314
  "maximum": 1,
315
- "description": "master gain for the recording's own system/tab audio track split takes (source.micKey) only; legacy takes have one track on micGain. Absent = 1. 0 = muted."
315
+ "description": "master gain for the recording's own system/tab audio track \u2014 split takes (source.micKey) only; legacy takes have one track on micGain. Absent = 1. 0 = muted."
316
316
  },
317
317
  "cursor": {
318
318
  "type": "object",
319
- "description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, click effects). See CursorStyle in @vosjs/studio-core. `visible: false` hides the drawn dot only the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click.",
319
+ "description": "Cursor rendering (visible, hideWhenIdle, smoothing, size, click effects). See CursorStyle in @vosjs/studio-core. `visible: false` hides the drawn dot only \u2014 the track still drives cursor-follow zoom, and click effects keep drawing (silence those with clickFx.style: \"none\"). `hideWhenIdle` (default true) fades the dot out after ~1s parked and back in when it moves; scrolling is not movement, and it is always back at full opacity on a click.",
320
320
  "additionalProperties": true
321
321
  },
322
322
  "cam": {
323
323
  "type": "object",
324
- "description": "Webcam bubble style + SOURCE-time window the bubble's REST pose. Only applies when the take has a cam track. Placement: x/y (bubble CENTER as frame fractions, the zoom cx/cy convention) when present, else the `position` corner anchor; size = diameter as a fraction of frame height. Look: shape circle|rounded; radius (rounded corner, design px, default 18); border { width design px (default 3; 0 = none), color } (default white at 0.9); shadow none|soft|strong (default soft). Animate the pose over time with camMotion spans.",
324
+ "description": "Webcam bubble style + SOURCE-time window \u2014 the bubble's REST pose. Only applies when the take has a cam track. Placement: x/y (bubble CENTER as frame fractions, the zoom cx/cy convention) when present, else the `position` corner anchor; size = diameter as a fraction of frame height. Look: shape circle|rounded; radius (rounded corner, design px, default 18); border { width design px (default 3; 0 = none), color } (default white at 0.9); shadow none|soft|strong (default soft). Animate the pose over time with camMotion spans.",
325
325
  "additionalProperties": true
326
326
  },
327
327
  "camMotion": {
328
328
  "type": "array",
329
- "description": "Animated cam layout (SOURCE time, footage-anchored, non-overlapping): while a span is active the webcam bubble holds its pose; outside spans it rests at doc.cam; spans ~1.2s apart in output time morph pose-to-pose. Ramps ~0.65s, settled at span start (a cam move frames what follows). Absent pose fields inherit the rest pose, so a span may move without resizing. Only renders when the take has a cam track (source.camKey).",
329
+ "description": "Animated cam layout (SOURCE time, footage-anchored, non-overlapping): while a span is active the webcam bubble holds its pose; outside spans it rests at doc.cam; spans \u2264 ~1.2s apart in output time morph pose-to-pose. Ramps ~0.65s, settled at span start (a cam move frames what follows). Absent pose fields inherit the rest pose, so a span may move without resizing. Only renders when the take has a cam track (source.camKey).",
330
330
  "items": {
331
331
  "type": "object",
332
332
  "required": ["id", "in", "out"],
@@ -342,7 +342,7 @@
342
342
  },
343
343
  "out": {
344
344
  "type": "number",
345
- "description": "SOURCE seconds, > in ( 0.5s so the move can settle)"
345
+ "description": "SOURCE seconds, > in (\u2265 0.5s so the move can settle)"
346
346
  },
347
347
  "x": {
348
348
  "type": "number",
@@ -389,11 +389,11 @@
389
389
  "properties": {
390
390
  "background": {
391
391
  "type": "string",
392
- "description": "CSS background (gradient/color) the underlay/fallback, always painted under backgroundMedia"
392
+ "description": "CSS background (gradient/color) \u2014 the underlay/fallback, always painted under backgroundMedia"
393
393
  },
394
394
  "backgroundMedia": {
395
395
  "type": ["object", "null"],
396
- "description": "Optional media layer drawn OVER the CSS background and UNDER the card: a vos animation baked to a seamless loop, or a still image. Video time is OUTPUT-anchored modulo the loop (bgT = t % duration) trims/speed never retime it. Draws outside the zoom transform. `key` may be a file inside the take dir (e.g. \"/bg.webm\"), a public https://assets.vos.so/... loop, or an /api/... URL. Fail-open: if it can't load, the CSS background still shows.",
396
+ "description": "Optional media layer drawn OVER the CSS background and UNDER the card: a vos animation baked to a seamless loop, or a still image. Video time is OUTPUT-anchored modulo the loop (bgT = t % duration) \u2014 trims/speed never retime it. Draws outside the zoom transform. `key` may be a file inside the take dir (e.g. \"/bg.webm\"), a public https://assets.vos.so/... loop, or an /api/... URL. Fail-open: if it can't load, the CSS background still shows.",
397
397
  "required": ["kind", "key", "dim"],
398
398
  "properties": {
399
399
  "kind": {
@@ -406,7 +406,7 @@
406
406
  "duration": {
407
407
  "type": "number",
408
408
  "exclusiveMinimum": 0,
409
- "description": "loop length in seconds (video only required for the modulo loop)"
409
+ "description": "loop length in seconds (video only \u2014 required for the modulo loop)"
410
410
  },
411
411
  "vosId": {
412
412
  "type": "string",
@@ -429,7 +429,7 @@
429
429
  "type": "number",
430
430
  "minimum": 0,
431
431
  "maximum": 100,
432
- "description": "V2: blur radius in design px softens the media behind the card"
432
+ "description": "V2: blur radius in design px \u2014 softens the media behind the card"
433
433
  }
434
434
  },
435
435
  "additionalProperties": true
@@ -512,7 +512,7 @@
512
512
  "fit": {
513
513
  "type": "string",
514
514
  "enum": ["contain", "cover"],
515
- "description": "How footage meets an off-ratio frame. contain (default) letterboxes the card onto the background; cover makes the padded area the card and fills it with footage, cropped around `focus` what a 440x280 store tile or a 2.5:1 marquee demands"
515
+ "description": "How footage meets an off-ratio frame. contain (default) letterboxes the card onto the background; cover makes the padded area the card and fills it with footage, cropped around `focus` \u2014 what a 440x280 store tile or a 2.5:1 marquee demands"
516
516
  },
517
517
  "focus": {
518
518
  "type": "object",
@@ -615,14 +615,57 @@
615
615
  },
616
616
  "media": {
617
617
  "type": "array",
618
- "description": "The take's OTHER media (concat, a layer's source): each the `source` shape (a media plus the facts only a capture has) with an `id` a segment or a source-anchored span names. The primary stays `source`. Absent = one media.",
618
+ "description": "The take's OTHER media (concat, a layer's source): each the `source` shape (a media plus the facts only a capture has) with an `id` a segment or a source-anchored span names. The primary stays `source`. Absent = one media. Each may carry `frame`, its own card.",
619
619
  "items": {
620
620
  "type": "object",
621
621
  "additionalProperties": true,
622
622
  "properties": {
623
623
  "id": {
624
624
  "type": "string",
625
- "description": "stable identity a segment or a span names (m1, m2, )"
625
+ "description": "stable identity a segment or a span names (m1, m2, \u2026)"
626
+ },
627
+ "frame": {
628
+ "type": "object",
629
+ "description": "This media's OWN card over the take's frame while it plays: its placement and size (`inset`), the browser bar, the corner, the shadow, the border, the cover fit and its focus. Frame-wide fields (aspectRatio, padding, background, backgroundMedia, anim) stay the take's. Absent fields fall to the take's frame; the bar names this media's recorded page, and a media with no page wears no bar unless `browserBar` says so.",
630
+ "additionalProperties": false,
631
+ "properties": {
632
+ "browserBar": {
633
+ "$ref": "#/oneOf/0/properties/frame/properties/browserBar"
634
+ },
635
+ "radius": {
636
+ "$ref": "#/oneOf/0/properties/frame/properties/radius"
637
+ },
638
+ "shadow": {
639
+ "$ref": "#/oneOf/0/properties/frame/properties/shadow"
640
+ },
641
+ "shadowContact": {
642
+ "$ref": "#/oneOf/0/properties/frame/properties/shadowContact"
643
+ },
644
+ "shadowColor": {
645
+ "$ref": "#/oneOf/0/properties/frame/properties/shadowColor"
646
+ },
647
+ "inset": {
648
+ "$ref": "#/oneOf/0/properties/frame/properties/inset"
649
+ },
650
+ "border": {
651
+ "$ref": "#/oneOf/0/properties/frame/properties/border"
652
+ },
653
+ "borderWidth": {
654
+ "$ref": "#/oneOf/0/properties/frame/properties/borderWidth"
655
+ },
656
+ "borderColor": {
657
+ "$ref": "#/oneOf/0/properties/frame/properties/borderColor"
658
+ },
659
+ "fit": {
660
+ "$ref": "#/oneOf/0/properties/frame/properties/fit"
661
+ },
662
+ "focus": {
663
+ "$ref": "#/oneOf/0/properties/frame/properties/focus"
664
+ },
665
+ "focusFollow": {
666
+ "$ref": "#/oneOf/0/properties/frame/properties/focusFollow"
667
+ }
668
+ }
626
669
  }
627
670
  },
628
671
  "required": ["id"]
@@ -756,7 +799,7 @@
756
799
  },
757
800
  "overlays": {
758
801
  "type": "array",
759
- "description": "Compositor v2 (V1): screen-space text clips drawn ABOVE the card, outside the zoom transform. OUTPUT-anchored (start is final-cut seconds trims/speed never retime a title). Optional; absent = none.",
802
+ "description": "Compositor v2 (V1): screen-space text clips drawn ABOVE the card, outside the zoom transform. OUTPUT-anchored (start is final-cut seconds \u2014 trims/speed never retime a title). Optional; absent = none.",
760
803
  "items": {
761
804
  "type": "object",
762
805
  "required": ["id", "kind", "start", "duration", "transform"],
@@ -797,13 +840,13 @@
797
840
  },
798
841
  "family": {
799
842
  "type": "string",
800
- "description": "font family override a catalog family name (GET https://vos.so/api/fonts). Unknown names fail open to the preset stack on the render fleet"
843
+ "description": "font family override \u2014 a catalog family name (GET https://vos.so/api/fonts). Unknown names fail open to the preset stack on the render fleet"
801
844
  },
802
845
  "weight": {
803
846
  "type": "number",
804
847
  "minimum": 100,
805
848
  "maximum": 900,
806
- "description": "weight override snapped to the nearest weight the catalog hosts for the family (weights are files, not synthesis)"
849
+ "description": "weight override \u2014 snapped to the nearest weight the catalog hosts for the family (weights are files, not synthesis)"
807
850
  },
808
851
  "italic": {
809
852
  "type": "boolean",
@@ -885,13 +928,13 @@
885
928
  "type": "number",
886
929
  "minimum": -0.5,
887
930
  "maximum": 1.5,
888
- "description": "anchor CENTER as a FRACTION of the frame width [0..1] (0.5 = center at ANY aspect the zoom cx/cy convention; NOT pixels)"
931
+ "description": "anchor CENTER as a FRACTION of the frame width [0..1] (0.5 = center at ANY aspect \u2014 the zoom cx/cy convention; NOT pixels)"
889
932
  },
890
933
  "y": {
891
934
  "type": "number",
892
935
  "minimum": -0.5,
893
936
  "maximum": 1.5,
894
- "description": "anchor CENTER as a FRACTION of the frame height [0..1] (lower-third 0.82; NOT pixels)"
937
+ "description": "anchor CENTER as a FRACTION of the frame height [0..1] (lower-third \u2248 0.82; NOT pixels)"
895
938
  },
896
939
  "scale": {
897
940
  "type": "number",
@@ -959,7 +1002,7 @@
959
1002
  },
960
1003
  "motion": {
961
1004
  "type": "array",
962
- "description": "Pose keyframes (MO): the clip's transform animated over CLIP-LOCAL time. Values interpolate across the gap between poses (~symmetric in-out ease; a hold is two identical poses); the base transform is the value before the first pose, and absent pose fields inherit it. Pure f(t) scrub, export and chunked server renders agree.",
1005
+ "description": "Pose keyframes (MO): the clip's transform animated over CLIP-LOCAL time. Values interpolate across the gap between poses (~symmetric in-out ease; a hold is two identical poses); the base transform is the value before the first pose, and absent pose fields inherit it. Pure f(t) \u2014 scrub, export and chunked server renders agree.",
963
1006
  "items": {
964
1007
  "type": "object",
965
1008
  "required": ["at"],
@@ -1005,7 +1048,7 @@
1005
1048
  },
1006
1049
  "fx": {
1007
1050
  "type": "object",
1008
- "description": "text kind only: entrance animation evaluated per unit pure f(t), segmentation baked at lowering (scrub/seek/server chunks agree by construction)",
1051
+ "description": "text kind only: entrance animation evaluated per unit \u2014 pure f(t), segmentation baked at lowering (scrub/seek/server chunks agree by construction)",
1009
1052
  "properties": {
1010
1053
  "fx": {
1011
1054
  "enum": ["fade", "rise", "pop", "blur", "typewriter"],
@@ -1013,7 +1056,7 @@
1013
1056
  },
1014
1057
  "unit": {
1015
1058
  "enum": ["block", "line", "word", "char"],
1016
- "description": "what animates as one thing (default block the whole text; char is grapheme-safe)"
1059
+ "description": "what animates as one thing (default block \u2014 the whole text; char is grapheme-safe)"
1017
1060
  },
1018
1061
  "direction": {
1019
1062
  "enum": ["forward", "reverse", "center"],
@@ -1023,7 +1066,7 @@
1023
1066
  "type": "number",
1024
1067
  "minimum": 0,
1025
1068
  "maximum": 2,
1026
- "description": "seconds between unit starts (defaults: typewriter 0.05, others 0.06 when unit block; clamped so the entrance fits the clip)"
1069
+ "description": "seconds between unit starts (defaults: typewriter 0.05, others 0.06 when unit \u2260 block; clamped so the entrance fits the clip)"
1027
1070
  },
1028
1071
  "duration": {
1029
1072
  "type": "number",
@@ -1037,13 +1080,13 @@
1037
1080
  },
1038
1081
  "key": {
1039
1082
  "type": "string",
1040
- "description": "image/video kinds: media URL or take-dir file (e.g. \"/logo.png\", \"/clip.webm\") required for media"
1083
+ "description": "image/video kinds: media URL or take-dir file (e.g. \"/logo.png\", \"/clip.webm\") \u2014 required for media"
1041
1084
  },
1042
1085
  "width": {
1043
1086
  "type": "number",
1044
1087
  "exclusiveMinimum": 0,
1045
1088
  "maximum": 1,
1046
- "description": "media kinds: base width as a FRACTION of the frame width (default 0.35); height follows the media's aspect; × transform.scale"
1089
+ "description": "media kinds: base width as a FRACTION of the frame width (default 0.35); height follows the media's aspect; \u00d7 transform.scale"
1047
1090
  },
1048
1091
  "radius": {
1049
1092
  "type": "number",
@@ -1065,7 +1108,7 @@
1065
1108
  },
1066
1109
  "objects": {
1067
1110
  "type": "array",
1068
- "description": "Compositor v2 (V3): world-space 3D props between the card and the overlays the drafted V4 engine spec. Curated primitives, GLB models by key, and extruded 3D text (TX7).",
1111
+ "description": "Compositor v2 (V3): world-space 3D props between the card and the overlays \u2014 the drafted V4 engine spec. Curated primitives, GLB models by key, and extruded 3D text (TX7).",
1069
1112
  "items": {
1070
1113
  "type": "object",
1071
1114
  "required": ["id", "asset", "transform3d"],
@@ -1091,7 +1134,7 @@
1091
1134
  },
1092
1135
  "key": {
1093
1136
  "type": "string",
1094
- "description": "gltf kind: GLB URL or take-dir file (e.g. \"/model.glb\") bbox-normalized so scale means the same as primitives"
1137
+ "description": "gltf kind: GLB URL or take-dir file (e.g. \"/model.glb\") \u2014 bbox-normalized so scale means the same as primitives"
1095
1138
  },
1096
1139
  "text": {
1097
1140
  "type": "string",
@@ -1343,7 +1386,7 @@
1343
1386
  "properties": {
1344
1387
  "id": {
1345
1388
  "type": "string",
1346
- "description": "stable identity (f0, f1, )"
1389
+ "description": "stable identity (f0, f1, \u2026)"
1347
1390
  },
1348
1391
  "at": {
1349
1392
  "type": "number",
@@ -1390,7 +1433,7 @@
1390
1433
  },
1391
1434
  "speed": {
1392
1435
  "type": "array",
1393
- "description": "Speed-change spans (SOURCE time, footage-anchored, non-overlapping). Absent = all 1×. The planner proposes source:'auto' spans for typing passages, scroll runs and idle gaps (ids s0); set source:'manual' on spans you add or edit so they survive a re-plan (absent source also counts as manual).",
1436
+ "description": "Speed-change spans (SOURCE time, footage-anchored, non-overlapping). Absent = all 1\u00d7. The planner proposes source:'auto' spans for typing passages, scroll runs and idle gaps (ids s0\u2026); set source:'manual' on spans you add or edit so they survive a re-plan (absent source also counts as manual).",
1394
1437
  "items": {
1395
1438
  "type": "object",
1396
1439
  "required": ["id", "in", "out", "rate"],
@@ -1433,7 +1476,7 @@
1433
1476
  "description": "seconds from that edge to the span's `in` (negative = before it)"
1434
1477
  }
1435
1478
  },
1436
- "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
1479
+ "description": "Re-record tie: `vos plan --reuse` re-times this span onto a NEW recording of the same script by resolving the step in the new meta.steps. Metadata only \u2014 `in`/`out` stay authoritative and lowering never reads it, so it costs nothing at render time"
1437
1480
  },
1438
1481
  "media": {
1439
1482
  "type": "string",