@vosjs/cli 0.53.0 → 0.53.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/{chunk-WGBRKQPB.js → chunk-ZARSNMVY.js} +7 -2
- package/dist/chunk-ZARSNMVY.js.map +1 -0
- package/dist/cli.js +3 -3
- package/dist/index.js +1 -1
- package/dist/{run-RFFLVLVQ.js → run-W5UTMIFU.js} +2 -2
- package/package.json +8 -8
- package/skills/VERSION +1 -1
- package/skills/vos-port/SKILL.md +55 -5
- package/skills/vos-port/references/elements-and-context.md +591 -0
- package/skills/vos-port/references/hyperframes.md +1 -0
- package/dist/chunk-WGBRKQPB.js.map +0 -1
- /package/dist/{run-RFFLVLVQ.js.map → run-W5UTMIFU.js.map} +0 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
<!-- Generated by scripts/build-element-reference.mjs from @vosjs/core 0.25.1, @vosjs/elements 0.8.2 and @vosjs/timeline 0.4.1. Do not edit by hand: re-run the script. -->
|
|
2
|
+
|
|
3
|
+
# Elements, the context and eases: the declarations
|
|
4
|
+
|
|
5
|
+
What a program's config and functions accept, copied from the published
|
|
6
|
+
type declarations so a port never has to read `node_modules`. The notes
|
|
7
|
+
between the blocks are the facts the declarations do not say; the units
|
|
8
|
+
are in SKILL.md's "coordinates, measured" and the draw order in its
|
|
9
|
+
"Layering".
|
|
10
|
+
|
|
11
|
+
## The config
|
|
12
|
+
|
|
13
|
+
A `config.json` is this shape, functions as strings. `elements` is typed
|
|
14
|
+
loosely here; its members are the element types below.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
interface FontFaceDecl {
|
|
18
|
+
/** Family name as used in `font.family` / canvas font strings. */
|
|
19
|
+
family: string;
|
|
20
|
+
/** Font file URL (woff2/woff/ttf). Must be reachable from render pages. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** CSS weight the file carries (default 'normal'). One decl per weight. */
|
|
23
|
+
weight?: number | string;
|
|
24
|
+
style?: 'normal' | 'italic';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* JSON-serializable version of VosConfig.
|
|
29
|
+
* Functions are stored as strings that can be embedded in the compiled template.
|
|
30
|
+
*
|
|
31
|
+
* Note: Elements are typed loosely as Record<string, unknown>[] for JSON
|
|
32
|
+
* transport compatibility. The actual ElementConfig type validation happens
|
|
33
|
+
* at compile time in compileVosConfig.
|
|
34
|
+
*/
|
|
35
|
+
interface VosConfigJson {
|
|
36
|
+
/**
|
|
37
|
+
* Which schema era this config was written against.
|
|
38
|
+
*
|
|
39
|
+
* Required, because this is the CANONICAL shape: what is stored, served
|
|
40
|
+
* and read back later, when nobody can tell the era from the file itself.
|
|
41
|
+
* `migrateConfig` stamps it, so the type you hand a storer always has one.
|
|
42
|
+
* The authoring shape that may omit it is `AuthoredVosConfigJson`.
|
|
43
|
+
*/
|
|
44
|
+
version: number;
|
|
45
|
+
/**
|
|
46
|
+
* Total duration of one animation cycle in seconds.
|
|
47
|
+
*/
|
|
48
|
+
duration: number;
|
|
49
|
+
/** Scene configuration (background, fog) */
|
|
50
|
+
scene?: SceneConfig;
|
|
51
|
+
/** Camera configuration */
|
|
52
|
+
camera: CameraConfig;
|
|
53
|
+
/** Post-processing effects */
|
|
54
|
+
postprocessing?: PostprocessingEffect[];
|
|
55
|
+
/** Declare per-layer effect types for addon imports */
|
|
56
|
+
perLayerEffects?: PostprocessingEffect[];
|
|
57
|
+
/** Enable per-frame render group rebuild when zIndex changes at runtime */
|
|
58
|
+
dynamicLayers?: boolean;
|
|
59
|
+
/** 2D Elements rendered as textured planes (loosely typed for JSON transport) */
|
|
60
|
+
elements?: Record<string, unknown>[];
|
|
61
|
+
/** Declarative world-space 3D objects (primitives / GLB). */
|
|
62
|
+
objects?: Record<string, unknown>[];
|
|
63
|
+
/**
|
|
64
|
+
* Webfont faces to register and load BEFORE anything rasterizes text.
|
|
65
|
+
* Canvas text silently falls back to a default font when a family isn't
|
|
66
|
+
* loaded — headless render environments have near-zero system fonts, so any
|
|
67
|
+
* non-generic family used by text elements (or setup-drawn canvases) should
|
|
68
|
+
* be declared here with a self-hosted URL. Loading is awaited capped and
|
|
69
|
+
* fail-open: a dead URL degrades to fallback stacks, never a hung page.
|
|
70
|
+
*/
|
|
71
|
+
fonts?: FontFaceDecl[];
|
|
72
|
+
/**
|
|
73
|
+
* Arbitrary input data made available to functions as `ctx.data`.
|
|
74
|
+
* The shape is the author's/app's, not vos's — vos passes it through verbatim.
|
|
75
|
+
* Overridable at runtime via `initVos(container, deps)` `deps.data` (so a live
|
|
76
|
+
* editor can update data without recompiling); `config.data` is the baked default.
|
|
77
|
+
* @example { cursor: [{ t: 0, x: 10, y: 20, type: 'down' }] }
|
|
78
|
+
*/
|
|
79
|
+
data?: Record<string, unknown>;
|
|
80
|
+
/**
|
|
81
|
+
* Async setup hook as a string.
|
|
82
|
+
* @example "(ctx) => { const loader = new ctx.loaders.FontLoader(); ... }"
|
|
83
|
+
*/
|
|
84
|
+
setup?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Create scene content function as a string.
|
|
87
|
+
* @example "(ctx, setupData) => { const { THREE, scene } = ctx; ... }"
|
|
88
|
+
*/
|
|
89
|
+
createContent: string;
|
|
90
|
+
/**
|
|
91
|
+
* Create GSAP timeline function as a string.
|
|
92
|
+
* @example "(ctx, content, duration) => { const tl = ctx.gsap.timeline(); ... }"
|
|
93
|
+
*/
|
|
94
|
+
createTimeline: string;
|
|
95
|
+
/**
|
|
96
|
+
* Optional per-frame update function as a string.
|
|
97
|
+
* @example "(ctx, content, deltaTime) => { content.refs.uniforms.iTime.value += deltaTime; }"
|
|
98
|
+
*/
|
|
99
|
+
onFrame?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Evaluate the program at `f(t)`, as a string: `(t, data) => number`, a
|
|
102
|
+
* pure function of the OUTPUT time and `ctx.data`. Slow motion, ramps,
|
|
103
|
+
* reverse, freeze frames, ping-pong loops. See `VosConfig.retime`.
|
|
104
|
+
*/
|
|
105
|
+
retime?: string;
|
|
106
|
+
/**
|
|
107
|
+
* The program stack: more programs on this context, run after the main one
|
|
108
|
+
* in array order, each with its own `ctx.data` and error boundary. A HUD, a
|
|
109
|
+
* subtitle pass, a watermark, an overlay a remixer adds without touching the
|
|
110
|
+
* main program's code. No timeline of their own — one master clock.
|
|
111
|
+
*/
|
|
112
|
+
stack?: ProgramEntryJson[];
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Elements (`config.elements[]`)
|
|
117
|
+
|
|
118
|
+
`content`, `font.family` and `font.color` of a text element are the three
|
|
119
|
+
fields that take a `{ "$data": "<key>" }` binding, and the only ones that
|
|
120
|
+
re-resolve on a data edit; every other field is read once at build.
|
|
121
|
+
`font.size`, `letterSpacing`, `transform.translateX/Y`, `stroke.width`
|
|
122
|
+
and `shadow.blur` are design pixels (a 1080-high frame). There is no
|
|
123
|
+
`mask`, `clip`, `blend` or group field on any element: SKILL.md's
|
|
124
|
+
"honest gaps" says what to do instead.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
/**
|
|
128
|
+
* `{$data: key}` binding — the value resolves from the host's data object at
|
|
129
|
+
* render time and re-resolves on setData (a pure data edit, no recompile).
|
|
130
|
+
*/
|
|
131
|
+
interface DataRef {
|
|
132
|
+
$data: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Base interface for all element types
|
|
137
|
+
*/
|
|
138
|
+
interface BaseElement {
|
|
139
|
+
/** Unique identifier for referencing in createTimeline */
|
|
140
|
+
id?: string;
|
|
141
|
+
type: 'text' | 'image' | 'svg' | 'video' | 'audio';
|
|
142
|
+
/** Position on screen */
|
|
143
|
+
position: ElementPosition;
|
|
144
|
+
/** Transform origin */
|
|
145
|
+
anchor?: Anchor;
|
|
146
|
+
/** Layer order (higher = on top, default: 100) */
|
|
147
|
+
zIndex?: number;
|
|
148
|
+
/** Opacity 0-1 */
|
|
149
|
+
opacity?: number;
|
|
150
|
+
/** 3D transform */
|
|
151
|
+
transform?: Transform;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Position can be pixels, percentages, or preset strings
|
|
156
|
+
*/
|
|
157
|
+
type ElementPosition = {
|
|
158
|
+
x: number;
|
|
159
|
+
y: number;
|
|
160
|
+
} | {
|
|
161
|
+
x: string;
|
|
162
|
+
y: string;
|
|
163
|
+
} | PositionPreset;
|
|
164
|
+
|
|
165
|
+
type PositionPreset = 'center' | 'top-left' | 'top-center' | 'top-right' | 'center-left' | 'center-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
166
|
+
|
|
167
|
+
type Anchor = 'center' | 'top-left' | 'top' | 'top-right' | 'left' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right';
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 3D transform properties
|
|
171
|
+
*/
|
|
172
|
+
interface Transform {
|
|
173
|
+
translateX?: number;
|
|
174
|
+
translateY?: number;
|
|
175
|
+
translateZ?: number;
|
|
176
|
+
rotateX?: number;
|
|
177
|
+
rotateY?: number;
|
|
178
|
+
rotateZ?: number;
|
|
179
|
+
rotation?: number;
|
|
180
|
+
scale?: number;
|
|
181
|
+
scaleX?: number;
|
|
182
|
+
scaleY?: number;
|
|
183
|
+
perspective?: number;
|
|
184
|
+
origin?: {
|
|
185
|
+
x: string;
|
|
186
|
+
y: string;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
interface TextElement extends BaseElement {
|
|
191
|
+
type: 'text';
|
|
192
|
+
/** Text content (supports \n for multiline), or a `{$data}` binding */
|
|
193
|
+
content: string | DataRef;
|
|
194
|
+
font?: {
|
|
195
|
+
family?: string | DataRef;
|
|
196
|
+
size?: number;
|
|
197
|
+
weight?: number | string;
|
|
198
|
+
style?: 'normal' | 'italic';
|
|
199
|
+
color?: string | DataRef;
|
|
200
|
+
letterSpacing?: number;
|
|
201
|
+
lineHeight?: number;
|
|
202
|
+
align?: 'left' | 'center' | 'right';
|
|
203
|
+
};
|
|
204
|
+
stroke?: {
|
|
205
|
+
color: string;
|
|
206
|
+
width: number;
|
|
207
|
+
};
|
|
208
|
+
shadow?: {
|
|
209
|
+
color: string;
|
|
210
|
+
blur: number;
|
|
211
|
+
offsetX?: number;
|
|
212
|
+
offsetY?: number;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Split text into segments for per-character/word/line animation.
|
|
216
|
+
* When split is defined, the element exposes a `segments` array
|
|
217
|
+
* of ElementProps for animating individual parts.
|
|
218
|
+
*/
|
|
219
|
+
split?: {
|
|
220
|
+
/** Type of split: 'chars', 'words', or 'lines' */
|
|
221
|
+
type: 'chars' | 'words' | 'lines';
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
interface ImageElement extends BaseElement {
|
|
226
|
+
type: 'image';
|
|
227
|
+
/** URL, base64, or imported asset path */
|
|
228
|
+
src: string;
|
|
229
|
+
size?: {
|
|
230
|
+
width?: number | 'auto';
|
|
231
|
+
height?: number | 'auto';
|
|
232
|
+
fit?: 'contain' | 'cover' | 'fill';
|
|
233
|
+
};
|
|
234
|
+
filters?: {
|
|
235
|
+
brightness?: number;
|
|
236
|
+
contrast?: number;
|
|
237
|
+
saturate?: number;
|
|
238
|
+
blur?: number;
|
|
239
|
+
hueRotate?: number;
|
|
240
|
+
grayscale?: number;
|
|
241
|
+
};
|
|
242
|
+
borderRadius?: number;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
interface SVGElement extends BaseElement {
|
|
246
|
+
type: 'svg';
|
|
247
|
+
/** SVG string, URL, or imported asset */
|
|
248
|
+
src: string;
|
|
249
|
+
size?: {
|
|
250
|
+
width?: number | 'auto';
|
|
251
|
+
height?: number | 'auto';
|
|
252
|
+
};
|
|
253
|
+
/** Override colors in SVG */
|
|
254
|
+
colors?: Record<string, string>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
interface VideoElement extends BaseElement {
|
|
258
|
+
type: 'video';
|
|
259
|
+
src: string;
|
|
260
|
+
size?: {
|
|
261
|
+
width?: number | 'auto';
|
|
262
|
+
height?: number | 'auto';
|
|
263
|
+
fit?: 'contain' | 'cover' | 'fill';
|
|
264
|
+
};
|
|
265
|
+
loop?: boolean;
|
|
266
|
+
muted?: boolean;
|
|
267
|
+
playbackRate?: number;
|
|
268
|
+
startTime?: number;
|
|
269
|
+
/**
|
|
270
|
+
* Decode strategy:
|
|
271
|
+
* - 'html5' (default): HTMLVideoElement + VideoTexture (legacy; not frame-accurate)
|
|
272
|
+
* - 'webcodecs': frame-accurate WebCodecs decode (deterministic export/scrub)
|
|
273
|
+
* - 'auto': webcodecs when available, else html5
|
|
274
|
+
*/
|
|
275
|
+
frameSource?: 'auto' | 'webcodecs' | 'html5';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Non-visual element that plays an audio file synced to the master clock.
|
|
280
|
+
* Drive it like an html5 video: set `playing` and/or animate `currentTime`
|
|
281
|
+
* in createTimeline; playback honors the global pause/seek state, and
|
|
282
|
+
* `props.gain` (0-1) is animatable for fades. Renders no pixels — position,
|
|
283
|
+
* anchor and transform are accepted for BaseElement compatibility but ignored.
|
|
284
|
+
*/
|
|
285
|
+
interface AudioElement extends Omit<BaseElement, 'position'> {
|
|
286
|
+
type: 'audio';
|
|
287
|
+
/** Audio file URL (anything the browser's media stack decodes) */
|
|
288
|
+
src: string;
|
|
289
|
+
/** Ignored (audio renders no pixels) */
|
|
290
|
+
position?: ElementPosition;
|
|
291
|
+
/** Initial volume 0-1 (default: 1); animatable via props.gain */
|
|
292
|
+
gain?: number;
|
|
293
|
+
loop?: boolean;
|
|
294
|
+
/** Offset into the source when playback begins, seconds (default: 0) */
|
|
295
|
+
startTime?: number;
|
|
296
|
+
/**
|
|
297
|
+
* A gain envelope over OUTPUT time: `[t, gain]` points, linear between
|
|
298
|
+
* them, held flat outside, multiplied with `props.gain`. Fades, ducking, a
|
|
299
|
+
* bed that swells under a title, as data the offline renderer replays
|
|
300
|
+
* (`@vosjs/core/audio`) and live playback follows frame by frame.
|
|
301
|
+
*/
|
|
302
|
+
gainEnvelope?: Array<[t: number, gain: number]>;
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## An element at runtime (`ctx.elements.get(id)`)
|
|
307
|
+
|
|
308
|
+
What `createTimeline` and `onFrame` animate: `el.props` (and
|
|
309
|
+
`el.segments[i]` under `split`). `props.x` / `props.y` are RENDER pixels
|
|
310
|
+
from the frame centre with `y` down, and `rotation` is degrees
|
|
311
|
+
counter-clockwise, unlike the config's design-pixel `transform`.
|
|
312
|
+
|
|
313
|
+
Writing any of `content`, `fontSize`, `fontFamily`, `fontWeight`, `fontStyle`, `letterSpacing`, `color`, `strokeColor`, `strokeWidth` on a text element's `props`
|
|
314
|
+
re-rasters it (the runtime's own list), so a scramble or a counter writes
|
|
315
|
+
`el.props.content` in `onFrame` and a colour change sets `el.props.color`.
|
|
316
|
+
The tween dialect animates numbers only: tween `fontSize` and
|
|
317
|
+
`letterSpacing`, SET the strings. `props.zIndex` is settable too.
|
|
318
|
+
|
|
319
|
+
Every such write queues a re-raster, even of an unchanged value, so guard
|
|
320
|
+
a per-frame write: `if (el.props.content !== next) el.props.content = next`.
|
|
321
|
+
A `split` text element does not re-raster at all: these writes are ignored
|
|
322
|
+
on it, so text that changes and text that animates per letter are two
|
|
323
|
+
elements.
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
/**
|
|
327
|
+
* GSAP-animatable properties exposed on each element instance
|
|
328
|
+
*/
|
|
329
|
+
interface ElementProps {
|
|
330
|
+
x: number;
|
|
331
|
+
y: number;
|
|
332
|
+
z: number;
|
|
333
|
+
opacity: number;
|
|
334
|
+
scale: number;
|
|
335
|
+
scaleX: number;
|
|
336
|
+
scaleY: number;
|
|
337
|
+
rotation: number;
|
|
338
|
+
rotationX: number;
|
|
339
|
+
rotationY: number;
|
|
340
|
+
/** Layer order inside the element's overlay scene (writes `renderOrder`) */
|
|
341
|
+
zIndex?: number;
|
|
342
|
+
content?: string;
|
|
343
|
+
fontSize?: number;
|
|
344
|
+
fontFamily?: string;
|
|
345
|
+
fontWeight?: number | string;
|
|
346
|
+
fontStyle?: 'normal' | 'italic';
|
|
347
|
+
letterSpacing?: number;
|
|
348
|
+
color?: string;
|
|
349
|
+
strokeColor?: string;
|
|
350
|
+
strokeWidth?: number;
|
|
351
|
+
/** Current playback position in seconds (animatable with GSAP) */
|
|
352
|
+
currentTime?: number;
|
|
353
|
+
/** Video duration in seconds (read-only) */
|
|
354
|
+
readonly duration?: number;
|
|
355
|
+
/** Whether the video is playing (controls native playback) */
|
|
356
|
+
playing?: boolean;
|
|
357
|
+
/** Start offset for video playback */
|
|
358
|
+
startOffset?: number;
|
|
359
|
+
/** Volume 0-1 (audio elements; animatable for fades) */
|
|
360
|
+
gain?: number;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Runtime element instance with animatable props and methods
|
|
365
|
+
*/
|
|
366
|
+
interface ElementInstance {
|
|
367
|
+
/** Original configuration */
|
|
368
|
+
config: ElementConfig;
|
|
369
|
+
/** Three.js mesh (textured plane) */
|
|
370
|
+
mesh: THREE.Mesh;
|
|
371
|
+
/** DOM node for SplitText (text elements only) - typed as unknown for portability */
|
|
372
|
+
node: unknown;
|
|
373
|
+
/** GSAP-animatable properties */
|
|
374
|
+
props: ElementProps;
|
|
375
|
+
/**
|
|
376
|
+
* Split text segments (only available when split config is defined).
|
|
377
|
+
* Each segment has its own ElementProps for individual animation.
|
|
378
|
+
*/
|
|
379
|
+
segments?: ElementProps[];
|
|
380
|
+
/** Update element content (text or image src) */
|
|
381
|
+
setContent: (content: string) => void;
|
|
382
|
+
/**
|
|
383
|
+
* Re-resolve `{$data}`-bound props against fresh data (called by the
|
|
384
|
+
* compiled module's setData). Returns true when a change was picked up.
|
|
385
|
+
*/
|
|
386
|
+
updateData?: (data: Record<string, unknown> | null | undefined) => boolean;
|
|
387
|
+
/**
|
|
388
|
+
* Re-raster with unchanged values — the late-webfont hook (a data-carried
|
|
389
|
+
* face landing after first paint re-draws over the fallback stack).
|
|
390
|
+
*/
|
|
391
|
+
refreshRaster?: () => boolean;
|
|
392
|
+
/** Re-rasterize canvas-backed textures for a new output resolution */
|
|
393
|
+
updateResolution?: (resolution: unknown) => boolean;
|
|
394
|
+
/** Remove element from scene */
|
|
395
|
+
destroy: () => void;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## The context (`ctx`)
|
|
400
|
+
|
|
401
|
+
`setup(ctx)` gets a `SetupContext` and may return assets;
|
|
402
|
+
`createContent(ctx, setupData)` returns a `ContentResult`;
|
|
403
|
+
`createTimeline(ctx, content, duration)` returns the timeline;
|
|
404
|
+
`onFrame(ctx, content, deltaTime)` runs every frame, `ctx.time` being the
|
|
405
|
+
program's time in seconds. `ctx.data` is read-only: a knob changes it from outside,
|
|
406
|
+
and `onFrame` reads the new value on the next frame. Read `ctx.data` on
|
|
407
|
+
every frame, never a copy taken in `createContent`.
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
/**
|
|
411
|
+
* Resolution configuration passed to animations
|
|
412
|
+
*/
|
|
413
|
+
interface Resolution {
|
|
414
|
+
width: number;
|
|
415
|
+
height: number;
|
|
416
|
+
pixelRatio: number;
|
|
417
|
+
/** Physical drawing buffer width (width × pixelRatio) - for shader uniforms */
|
|
418
|
+
drawingBufferWidth: number;
|
|
419
|
+
/** Physical drawing buffer height (height × pixelRatio) - for shader uniforms */
|
|
420
|
+
drawingBufferHeight: number;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Loaders registry - common Three.js loaders
|
|
425
|
+
*/
|
|
426
|
+
interface LoadersRegistry {
|
|
427
|
+
FontLoader: any;
|
|
428
|
+
TextureLoader: any;
|
|
429
|
+
GLTFLoader: any;
|
|
430
|
+
HDRLoader: any;
|
|
431
|
+
CubeTextureLoader: any;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Utilities registry - common Three.js utilities
|
|
436
|
+
*/
|
|
437
|
+
interface UtilsRegistry {
|
|
438
|
+
MeshSurfaceSampler: any;
|
|
439
|
+
BufferGeometryUtils: any;
|
|
440
|
+
TextGeometry: any;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Context available during async setup phase
|
|
445
|
+
*/
|
|
446
|
+
interface SetupContext {
|
|
447
|
+
THREE: typeof THREE;
|
|
448
|
+
resolution: Resolution;
|
|
449
|
+
loaders: LoadersRegistry;
|
|
450
|
+
utils: UtilsRegistry;
|
|
451
|
+
/**
|
|
452
|
+
* Read-only input data exposed to all functions as `ctx.data`.
|
|
453
|
+
* Sourced from `config.data`, overridable at runtime by `initVos` `deps.data`.
|
|
454
|
+
* Always defined (defaults to `{}`). Shape is the author's/app's, not vos's.
|
|
455
|
+
*/
|
|
456
|
+
data: Readonly<Record<string, unknown>>;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Context available during animation creation
|
|
461
|
+
*/
|
|
462
|
+
interface VosContext extends SetupContext {
|
|
463
|
+
gsap: typeof gsap;
|
|
464
|
+
scene: THREE.Scene;
|
|
465
|
+
camera: THREE.Camera;
|
|
466
|
+
renderer: THREE.WebGLRenderer;
|
|
467
|
+
/** Dedicated scene for 2D overlay elements (rendered on top of main scene) */
|
|
468
|
+
overlayScene: THREE.Scene;
|
|
469
|
+
/** Orthographic camera for 2D overlay (pixel-space: 1 unit = 1 pixel) */
|
|
470
|
+
overlayCamera: THREE.OrthographicCamera;
|
|
471
|
+
composer?: unknown;
|
|
472
|
+
/** Element instances for timeline animations */
|
|
473
|
+
elements: Map<string, ElementInstance>;
|
|
474
|
+
/** Current playback time in seconds (available in onFrame) */
|
|
475
|
+
time: number;
|
|
476
|
+
/** Playback progress 0-1 (available in onFrame) */
|
|
477
|
+
progress: number;
|
|
478
|
+
/**
|
|
479
|
+
* The OUTPUT time in seconds: what the transport shows and the capture
|
|
480
|
+
* counts. Equal to `time` unless the config carries a `retime`, in which
|
|
481
|
+
* case `time` is `retime(outputTime, data)` — the program's own time —
|
|
482
|
+
* while `outputTime` keeps counting the output. Stack entries read
|
|
483
|
+
* `ctx.time` as output time (they are output-anchored by contract).
|
|
484
|
+
*/
|
|
485
|
+
outputTime: number;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Result from createContent function
|
|
490
|
+
*/
|
|
491
|
+
interface ContentResult {
|
|
492
|
+
/** Objects added to scene */
|
|
493
|
+
objects: THREE.Object3D[];
|
|
494
|
+
/** Named references for timeline animations */
|
|
495
|
+
refs?: Record<string, unknown>;
|
|
496
|
+
/** Cleanup function for content-specific resources */
|
|
497
|
+
dispose?: () => void;
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## The timeline (what `createTimeline` returns)
|
|
502
|
+
|
|
503
|
+
Author it as GSAP: `const tl = ctx.gsap.timeline({ paused: true })`, tweens
|
|
504
|
+
on `el.props`, `tl.addLabel(name, t)` per scene, return `tl`. This is the
|
|
505
|
+
whole surface the engine calls on it.
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
/**
|
|
509
|
+
* Structural master-clock interface the engine seeks each frame.
|
|
510
|
+
*
|
|
511
|
+
* This is the ONLY surface the runtime uses from the timeline object returned by
|
|
512
|
+
* `createTimeline` (pause/seek/play + transport queries + carrier retiming). It is
|
|
513
|
+
* satisfied structurally by `gsap.core.Timeline` today, so authoring against real
|
|
514
|
+
* GSAP is unchanged — but the public API no longer hard-depends on the `gsap` type,
|
|
515
|
+
* which lets an alternate deterministic backend provide a conformant timeline later
|
|
516
|
+
* without a breaking change. Method shorthand is intentional (bivariant params) so
|
|
517
|
+
* GSAP's overloaded signatures remain structurally assignable.
|
|
518
|
+
*/
|
|
519
|
+
interface VosTimeline {
|
|
520
|
+
/** Pause playback (frame-stepped export pauses before the first frame). */
|
|
521
|
+
pause(): unknown;
|
|
522
|
+
/** Resume playback. */
|
|
523
|
+
play(): unknown;
|
|
524
|
+
/** Seek to `time` seconds. `suppressEvents=false` fires onUpdate callbacks. */
|
|
525
|
+
seek(time: number, suppressEvents?: boolean): unknown;
|
|
526
|
+
/** Rebuild/clear children (used by the vosCarrier duration-retime path). */
|
|
527
|
+
clear(): unknown;
|
|
528
|
+
/** Set playback rate. */
|
|
529
|
+
timeScale(value: number): unknown;
|
|
530
|
+
/** Current playhead in seconds. */
|
|
531
|
+
time(): number;
|
|
532
|
+
/** Normalized progress 0..1. */
|
|
533
|
+
progress(): number;
|
|
534
|
+
/**
|
|
535
|
+
* Optional (the vos tween backend): re-apply a tween-timing overlay over the
|
|
536
|
+
* recorded tweens, from the recording every time. The bridge's
|
|
537
|
+
* SET_TWEEN_EDITS rides it; absent on a gsap timeline.
|
|
538
|
+
*/
|
|
539
|
+
applyEdits?(edits: readonly Record<string, unknown>[]): unknown;
|
|
540
|
+
/** Configured duration in seconds. */
|
|
541
|
+
duration(): number;
|
|
542
|
+
/** Total duration including repeats (seconds). */
|
|
543
|
+
totalDuration(): number;
|
|
544
|
+
/** Attach/read a lifecycle callback (engine uses onUpdate). */
|
|
545
|
+
eventCallback(type: string, callback?: (...args: any[]) => void): unknown;
|
|
546
|
+
/** Opaque author-attached marker (e.g. `{ vosCarrier: true }`). */
|
|
547
|
+
data?: unknown;
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
## Eases
|
|
552
|
+
|
|
553
|
+
An `ease` string anywhere GSAP takes one. Beyond the names below the
|
|
554
|
+
engine accepts the parameterized forms `back.out(1.7)`,
|
|
555
|
+
`elastic.out(1, 0.3)`, `steps(5)`, and `css-bezier(x1, y1, x2, y2)`,
|
|
556
|
+
which is CSS's `cubic-bezier` and Remotion's `Easing.bezier` with the same
|
|
557
|
+
four numbers (`cubic-bezier` itself is not a name). A bare family
|
|
558
|
+
(`'power2'`) means `.out`. **An unknown name is not an error: it plays
|
|
559
|
+
linear.** `vos check` warns `unknown-ease` for a literal `ease: '…'` it
|
|
560
|
+
cannot resolve; through `@vosjs/core` 0.25.0 that warning also fired on
|
|
561
|
+
`css-bezier`, wrongly, so ignore it there. An ease held in a variable is
|
|
562
|
+
not checked: the side-by-side is where it shows.
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
/**
|
|
566
|
+
* Value types for deterministic timeline math.
|
|
567
|
+
*
|
|
568
|
+
* These are meant to be EMBEDDED inside an app's own document schema — @vosjs/timeline
|
|
569
|
+
* is an evaluation library, not a document format. Everything is JSON-serializable
|
|
570
|
+
* (eases are registry names, never functions) so the same values travel through
|
|
571
|
+
* `ctx.data` into a running vos program and evaluate identically on both sides.
|
|
572
|
+
*/
|
|
573
|
+
type EaseFamily = 'power1' | 'power2' | 'power3' | 'power4' | 'sine' | 'expo' | 'circ' | 'back' | 'elastic' | 'bounce';
|
|
574
|
+
|
|
575
|
+
type EaseDirection = 'in' | 'out' | 'inOut';
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Serializable easing name. The vocabulary (and the curves) match GSAP's so one
|
|
579
|
+
* ease language spans freeform function-strings and declarative keyframes.
|
|
580
|
+
*/
|
|
581
|
+
type EaseName = 'none' | 'linear' | `${EaseFamily}.${EaseDirection}`;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Resolve an ease by name, including parameterized forms (`back.out(1.7)`,
|
|
585
|
+
* `elastic.out(1, 0.3)`, `steps(5)`) and GSAP's bare-family default
|
|
586
|
+
* (`'power2'` → `power2.out`). Unknown names fall back to linear — evaluation
|
|
587
|
+
* must never throw per-frame inside a running program; authoring layers are
|
|
588
|
+
* expected to validate names at edit time instead.
|
|
589
|
+
*/
|
|
590
|
+
function resolveEase(name: string | undefined): EaseFn;
|
|
591
|
+
```
|
|
@@ -21,6 +21,7 @@ units (render pixels, y down, degrees counter-clockwise).
|
|
|
21
21
|
| a sub-composition or scene `div` with a time window | `tl.addLabel(name, t)` and its elements tweened in and out inside the window |
|
|
22
22
|
| an `onUpdate` proxy clock that draws (`renderAll(t)`) | `onFrame(ctx)`, reading `ctx.time`, for exactly those procedural parts |
|
|
23
23
|
| GSAP ease names (`power3.out`, `back.out(1.7)`, `expo.inOut`) | the same names |
|
|
24
|
+
| a CSS `cubic-bezier(a, b, c, d)` timing | `ease: 'css-bezier(a, b, c, d)'` (exact; spelled `cubic-bezier` it plays linear) |
|
|
24
25
|
| CSS `--chrome` set by the timeline | a `data` value read in `onFrame`, or a tween on the element that shows it |
|
|
25
26
|
| faces resolved by the runtime from CSS families | `fonts: [{ family, weight, url }]` from the catalog |
|
|
26
27
|
| `mix-blend-mode`, `clip-path`, `overflow: hidden` reveals | GAPS: the painter, or an opacity approximation, said |
|