@clipkit/protocol 1.0.0

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.
@@ -0,0 +1,1326 @@
1
+ export declare const OUTPUT_FORMATS: readonly ["mp4", "gif"];
2
+ export type OutputFormat = (typeof OUTPUT_FORMATS)[number];
3
+ export declare const ELEMENT_TYPES: readonly ["video", "image", "text", "shape", "audio", "group", "caption", "particles"];
4
+ export type ElementType = (typeof ELEMENT_TYPES)[number];
5
+ export declare const UNITS: readonly ["px", "%", "vw", "vh", "vmin", "vmax"];
6
+ export type Unit = (typeof UNITS)[number];
7
+ export declare const EASING_FUNCTIONS: readonly ["linear", "ease", "ease-in", "ease-out", "ease-in-out", "ease-in-cubic", "ease-out-cubic", "ease-in-out-cubic", "ease-in-quad", "ease-out-quad", "ease-in-out-quad", "ease-in-quart", "ease-out-quart", "ease-in-out-quart", "ease-in-quint", "ease-out-quint", "ease-in-out-quint", "ease-in-sine", "ease-out-sine", "ease-in-out-sine", "ease-in-expo", "ease-out-expo", "ease-in-out-expo", "ease-in-circ", "ease-out-circ", "ease-in-out-circ", "ease-in-back", "ease-out-back", "ease-in-out-back", "spring", "elastic-in", "elastic-out", "elastic-in-out", "bounce-in", "bounce-out", "bounce-in-out"];
8
+ export type EasingFunction = (typeof EASING_FUNCTIONS)[number];
9
+ /**
10
+ * An easing value: a named curve from EASING_FUNCTIONS, or a parametric
11
+ * form —
12
+ * `cubic-bezier(x1, y1, x2, y2)` — CSS timing-function semantics
13
+ * (x1/x2 clamped to [0, 1]; y1/y2 unbounded for overshoot).
14
+ * `steps(n)` — n equidistant steps, jump-at-end (CSS `steps(n, end)`).
15
+ */
16
+ export type Easing = EasingFunction | `cubic-bezier(${string})` | `steps(${string})`;
17
+ export declare const ANIMATION_TYPES: readonly ["fade-in", "fade-out", "slide-left-in", "slide-right-in", "slide-up-in", "slide-down-in", "slide-left-out", "slide-right-out", "slide-up-out", "slide-down-out", "scale-in", "scale-out", "rotate-in", "rotate-out", "bounce-in", "bounce-out", "spin", "shake", "wiggle", "squash", "pan", "shift", "drift", "breathe", "orbit", "text-appear", "text-slide", "text-fly", "text-typewriter", "text-wave", "text-flip"];
18
+ export type AnimationType = (typeof ANIMATION_TYPES)[number];
19
+ export declare const CAPTION_STYLES: readonly ["tiktok_bounce", "fade_reveal", "kinetic_typewriter", "word_pop"];
20
+ export type CaptionStyle = (typeof CAPTION_STYLES)[number];
21
+ /**
22
+ * Tier-A expression (CKP/1.0, §Expressions). A numeric property may be
23
+ * `{ expr: "..." }` — a PURE function of the element's local time `t` and its
24
+ * own index/params (`i`, `n`, `dur`, `value`). No element references, no runtime
25
+ * inputs: deterministic across renderers, and bakeable to keyframes. The scope is
26
+ * closed (a fixed set of math/motion functions); anything else is a parse error
27
+ * and the property falls back to its base value. See PROTOCOL.md §Expressions.
28
+ */
29
+ export interface Expr {
30
+ expr: string;
31
+ }
32
+ export interface Keyframe {
33
+ time: number | string;
34
+ value: number | string | [number, number] | [number, number, number];
35
+ easing?: Easing;
36
+ /**
37
+ * Spatial bezier handles for `property: "position"` paths (§6.7),
38
+ * RELATIVE to this keyframe's value: `out_tangent` shapes the curve
39
+ * leaving this keyframe, `in_tangent` the curve arriving at it.
40
+ * Omitted handles default to the straight-line third-points, so a
41
+ * path without handles is polyline motion. On a 3D path a
42
+ * 2-component handle's z defaults to the straight-line third-point
43
+ * in z; 3-component handles on a 2D path are invalid. Ignored on
44
+ * scalar keyframes.
45
+ */
46
+ in_tangent?: [number, number] | [number, number, number];
47
+ out_tangent?: [number, number] | [number, number, number];
48
+ }
49
+ export interface Animation {
50
+ type: AnimationType;
51
+ duration?: number;
52
+ easing?: Easing;
53
+ /**
54
+ * Unit granularity for text-* animations: animate per letter or per
55
+ * word. Defaults per type (see ANIMATION_TYPES). Ignored on
56
+ * non-text animations.
57
+ */
58
+ split?: 'letter' | 'word';
59
+ /**
60
+ * Seconds between successive units' start times (text-* animations).
61
+ * Defaults: 0.09 for word splits, 0.035 for letter splits.
62
+ */
63
+ stagger?: number;
64
+ time?: 'start' | 'end' | number;
65
+ /** Oscillation frequency in Hz (shake, wiggle). Defaults: shake 8, wiggle 2. */
66
+ frequency?: number;
67
+ /** Total rotation in degrees (spin), wobble amplitude in degrees (wiggle), or starting flip angle in degrees (text-flip, default 90). */
68
+ rotation?: number;
69
+ /** Rotation axis for text-flip: 'x' (flip up, default), 'y' (swing in), 'z' (in-plane spin). CKP/1.0. */
70
+ axis?: 'x' | 'y' | 'z';
71
+ /** Travel distance in pixels (pan, shift) or shake amplitude in pixels. Defaults: pan/shift 200, shake 24. */
72
+ distance?: number;
73
+ /** Motion direction (pan, shift). Default 'right'. */
74
+ direction?: 'left' | 'right' | 'up' | 'down';
75
+ /** Squash depth in [0, 1] (squash) or scale amplitude (breathe, default 0.05). */
76
+ scale?: number;
77
+ /** Lattice seed for `drift`'s normative noise (integer ≥ 0). Default 0. */
78
+ seed?: number;
79
+ }
80
+ export interface KeyframeAnimation {
81
+ property: string;
82
+ keyframes: Keyframe[];
83
+ easing?: Easing;
84
+ /**
85
+ * Repeat the keyframe pattern for the element's whole life (§6.3):
86
+ * `true` wraps local time modulo the last keyframe's time;
87
+ * `'ping-pong'` reflects it (forward, backward, forward, …).
88
+ */
89
+ loop?: boolean | 'ping-pong';
90
+ /**
91
+ * For `property: "position"` paths (§6.7): rotate the element to the
92
+ * path's travel direction (tangent), added to its own rotation.
93
+ * Strictly in-plane: on 3D paths the tangent's xy projection is
94
+ * used and dz is ignored — auto_orient never derives x_rotation or
95
+ * y_rotation. Default false.
96
+ */
97
+ auto_orient?: boolean;
98
+ }
99
+ /**
100
+ * Mosaic: the element's pixels quantize to square cells; every pixel in
101
+ * a cell takes the color sampled at the cell's center.
102
+ */
103
+ export interface PixelateEffect {
104
+ type: 'pixelate';
105
+ /** Cell size in canvas pixels. Default 8, minimum 1. Animatable. */
106
+ cell_size?: number | Keyframe[] | Expr;
107
+ }
108
+ /**
109
+ * Ordered dithering: each color channel quantizes to `levels` values,
110
+ * thresholded by the normative 4×4 Bayer matrix, producing the classic
111
+ * retro crosshatch. levels: 2 = 1-bit per channel.
112
+ */
113
+ export interface DitherEffect {
114
+ type: 'dither';
115
+ /** Quantization levels per channel. Default 4, minimum 2. Animatable. */
116
+ levels?: number | Keyframe[] | Expr;
117
+ /**
118
+ * Size of each Bayer dither cell in LOGICAL pixels. Default 2. Larger
119
+ * = chunkier, more visible retro dots; 1 = ultra-fine. Resolution-
120
+ * independent (the dot size is stable across preview DPI / export, and
121
+ * survives the editor's fit-to-stage downscale). Animatable.
122
+ */
123
+ pixel_size?: number | Keyframe[] | Expr;
124
+ }
125
+ /**
126
+ * Print-style halftone: a rotated grid of dots, each dot's radius
127
+ * proportional to the underlying luminance, colored with the cell's
128
+ * sampled color. Coverage outside dots is transparent.
129
+ */
130
+ export interface HalftoneEffect {
131
+ type: 'halftone';
132
+ /** Dot-grid cell size in canvas pixels. Default 8. Animatable. */
133
+ cell_size?: number | Keyframe[] | Expr;
134
+ /** Grid rotation in degrees. Default 45. Animatable. */
135
+ angle?: number | Keyframe[] | Expr;
136
+ }
137
+ /**
138
+ * ASCII-art: cells map to glyphs from a fixed 10-step density ramp,
139
+ * tinted with the cell's sampled color. Glyph shapes come from the
140
+ * protocol's embedded 8×8 bitmap font (normative — identical pixels on
141
+ * every platform, no system-font dependence).
142
+ */
143
+ export interface AsciiEffect {
144
+ type: 'ascii';
145
+ /** Cell size in canvas pixels. Default 12, minimum 4. Animatable. */
146
+ cell_size?: number | Keyframe[] | Expr;
147
+ }
148
+ /**
149
+ * Liquid glass: the element becomes a refractive, frosted pane over
150
+ * its BACKDROP — the pixels already drawn beneath it in draw order.
151
+ *
152
+ * THE EXCEPTION (§4.7): every other effect reads only the element's
153
+ * own rendered pixels; glass additionally reads the backdrop. It gets
154
+ * this carve-out because the effect is widely known and in high
155
+ * demand, and there is no proper decomposition — refraction needs the
156
+ * pixels behind the pane. No other effect type reads the backdrop.
157
+ *
158
+ * The element's own rendered shape is the lens: its alpha masks the
159
+ * pane (text and path shapes work too) and the refraction normal comes
160
+ * from the alpha field's gradient. The element's fill COLOR is unused
161
+ * under glass — tint with the `tint` param instead.
162
+ */
163
+ export interface GlassEffect {
164
+ type: 'glass';
165
+ /** Backdrop Gaussian blur σ in px (frosting). Default 0 (clear glass). Animatable. */
166
+ blur_radius?: number | Keyframe[] | Expr;
167
+ /**
168
+ * Refraction strength — approximate rim displacement in canvas px
169
+ * (0 = flat frosted panel). The magnitude is used: displacement is
170
+ * always toward the pane's interior, so glass magnifies what's
171
+ * under it and never pulls in content from outside its footprint.
172
+ * Default 21. Animatable.
173
+ */
174
+ refraction?: number | Keyframe[] | Expr;
175
+ /** How far the lens curl reaches inward, in px. Default 40. Animatable. */
176
+ edge_width?: number | Keyframe[] | Expr;
177
+ /**
178
+ * Intensity of the glass lighting rig — Blinn-Phong speculars,
179
+ * Fresnel edge whitening, and the thin top-lit stroke — 0..1.
180
+ * Default 0.35. Animatable.
181
+ */
182
+ edge_highlight?: number | Keyframe[] | Expr;
183
+ /**
184
+ * Drop-shadow strength 0..1, drawn ONLY outside the pane's footprint
185
+ * (real glass never frosts its own shadow — prefer this over a
186
+ * shadow sibling beneath the lens). Default 0.3. Animatable.
187
+ */
188
+ shadow?: number | Keyframe[] | Expr;
189
+ /**
190
+ * Chromatic dispersion: R/G/B refract at strengths
191
+ * refraction × (1−d, 1, 1+d), producing the subtle rainbow fringe
192
+ * of real glass at the lens edges. Default 0.05 (0 = off). Try 0.2.
193
+ * Animatable.
194
+ */
195
+ dispersion?: number | Keyframe[] | Expr;
196
+ /**
197
+ * Saturation boost applied to the frosted backdrop (Rec. 709, same
198
+ * math as the `saturation` filter) so content behind the glass stays
199
+ * vibrant instead of going muddy. 1 = unchanged (default).
200
+ * Animatable.
201
+ */
202
+ backdrop_saturation?: number | Keyframe[] | Expr;
203
+ /** Tint drawn over the glass (use alpha for strength, e.g. "#FFFFFF22"). */
204
+ tint?: string;
205
+ /**
206
+ * Lens cross-section: 'pill' (biconvex — light refracts at entry and
207
+ * exit, the default card/button look) or 'dome' (flat bottom,
208
+ * curved top — with edge_width = the shape's radius this is a
209
+ * half-sphere magnifier).
210
+ */
211
+ mode?: 'pill' | 'dome';
212
+ }
213
+ /**
214
+ * Outer glow: the element's silhouette, blurred and tinted, composited
215
+ * BENEATH the element. The classic AE layer style.
216
+ */
217
+ export interface GlowEffect {
218
+ type: 'glow';
219
+ /** Glow reach — Gaussian σ of the silhouette blur, px. Default 20. Animatable. */
220
+ radius?: number | Keyframe[] | Expr;
221
+ /** Glow strength multiplier. Default 1. Animatable. */
222
+ intensity?: number | Keyframe[] | Expr;
223
+ /** Glow color. Default "#FFFFFF". */
224
+ color?: string;
225
+ }
226
+ /**
227
+ * Drop shadow on ANY element (shapes have a native `shadow`; this one
228
+ * works on text, images, shapes, groups — the blurred silhouette, offset
229
+ * and tinted, composited beneath the element).
230
+ */
231
+ export interface DropShadowEffect {
232
+ type: 'drop_shadow';
233
+ /** Shadow offset in px. Defaults (0, 12). Animatable. */
234
+ offset_x?: number | Keyframe[] | Expr;
235
+ offset_y?: number | Keyframe[] | Expr;
236
+ /** Shadow softness — Gaussian σ in px. Default 18. Animatable. */
237
+ blur?: number | Keyframe[] | Expr;
238
+ /** Shadow color. Default "#000000". */
239
+ color?: string;
240
+ /** Shadow opacity 0..1. Default 0.6. Animatable. */
241
+ opacity?: number | Keyframe[] | Expr;
242
+ }
243
+ /**
244
+ * Outline stroke around the element's alpha silhouette (outside the
245
+ * edge), on any element type.
246
+ */
247
+ export interface StrokeEffect {
248
+ type: 'stroke';
249
+ /** Stroke width in px. Default 4. Animatable. */
250
+ width?: number | Keyframe[] | Expr;
251
+ /** Stroke color. Default "#FFFFFF". */
252
+ color?: string;
253
+ }
254
+ /**
255
+ * Chroma key: pixels whose chroma (BT.709 CbCr) is close to `color`
256
+ * become transparent — green-screen / blue-screen removal. The alpha
257
+ * ramp is linear from `tolerance` to `tolerance + softness` in CbCr
258
+ * distance. Spill suppression caps the key color's dominant channel at
259
+ * the max of the other two, scaled by `spill`.
260
+ */
261
+ export interface ChromaKeyEffect {
262
+ type: 'chroma_key';
263
+ /** The screen color to remove. Default "#00FF00". */
264
+ color?: string;
265
+ /** CbCr distance fully removed. Default 0.18. Animatable. */
266
+ tolerance?: number | Keyframe[] | Expr;
267
+ /** Ramp width above tolerance (0 = hard edge). Default 0.1. Animatable. */
268
+ softness?: number | Keyframe[] | Expr;
269
+ /** Spill suppression strength 0..1. Default 0.5. Animatable. */
270
+ spill?: number | Keyframe[] | Expr;
271
+ }
272
+ /**
273
+ * Luma key: pixels darker than `threshold` (BT.709 luma of the
274
+ * straight-alpha color) become transparent; `invert` keys out bright
275
+ * pixels instead. The classic way to lift white-on-black mattes,
276
+ * flares, and smoke elements.
277
+ */
278
+ export interface LumaKeyEffect {
279
+ type: 'luma_key';
280
+ /** Luma below this is fully removed (0..1). Default 0.5. Animatable. */
281
+ threshold?: number | Keyframe[] | Expr;
282
+ /** Ramp width above threshold (0 = hard edge). Default 0.1. Animatable. */
283
+ softness?: number | Keyframe[] | Expr;
284
+ /** Key out BRIGHT pixels instead of dark. Default false. */
285
+ invert?: boolean;
286
+ }
287
+ /**
288
+ * Levels: the classic five-param grade, per channel on the
289
+ * straight-alpha color: `x = clamp((c − in_black) / (in_white −
290
+ * in_black)); y = x^(1/gamma); out = out_black + y × (out_white −
291
+ * out_black)`. gamma > 1 brightens mids (Photoshop semantics).
292
+ */
293
+ export interface LevelsEffect {
294
+ type: 'levels';
295
+ /** Input black point 0..1. Default 0. Animatable. */
296
+ in_black?: number | Keyframe[] | Expr;
297
+ /** Input white point 0..1. Default 1. Animatable. */
298
+ in_white?: number | Keyframe[] | Expr;
299
+ /** Mid-tone gamma (> 0; > 1 brightens). Default 1. Animatable. */
300
+ gamma?: number | Keyframe[] | Expr;
301
+ /** Output black point 0..1. Default 0. Animatable. */
302
+ out_black?: number | Keyframe[] | Expr;
303
+ /** Output white point 0..1. Default 1. Animatable. */
304
+ out_white?: number | Keyframe[] | Expr;
305
+ }
306
+ /**
307
+ * Color lookup table: a .cube file (3D LUT) applied to the element's
308
+ * straight-alpha color with trilinear interpolation over the lattice.
309
+ * The asset loads like any other (http(s), relative, or data: URI).
310
+ * Unloadable or malformed LUTs skip the pass with a warning.
311
+ */
312
+ export interface LutEffect {
313
+ type: 'lut';
314
+ /** URL of the .cube file (LUT_3D_SIZE, 0..1 domain). */
315
+ source: string;
316
+ /** Blend between original (0) and graded (1) color. Default 1. Animatable. */
317
+ intensity?: number | Keyframe[] | Expr;
318
+ }
319
+ /**
320
+ * Fractal noise: fills the element's alpha footprint with seeded,
321
+ * animatable value-noise fBM (grayscale; chain `levels` / `lut` /
322
+ * `hue_rotate` to color it). The noise function is NORMATIVE (§4.7) —
323
+ * an integer PCG hash over the lattice — so the same seed produces the
324
+ * same pixels on every runtime. Animate `evolution` to make the noise
325
+ * churn in place; animate `offset_x`/`offset_y` to scroll it.
326
+ */
327
+ export interface FractalNoiseEffect {
328
+ type: 'fractal_noise';
329
+ /** Feature size in canvas px (one noise lattice cell). Default 100. Animatable. */
330
+ scale?: number | Keyframe[] | Expr;
331
+ /** Third noise axis — animate for in-place churn. Default 0. Animatable. */
332
+ evolution?: number | Keyframe[] | Expr;
333
+ /** Scroll offsets in canvas px. Default 0. Animatable. */
334
+ offset_x?: number | Keyframe[] | Expr;
335
+ offset_y?: number | Keyframe[] | Expr;
336
+ /** fBM octaves, integer 1–8. Default 4. */
337
+ octaves?: number;
338
+ /** Lattice seed, integer ≥ 0. Default 0. */
339
+ seed?: number;
340
+ }
341
+ /**
342
+ * Turbulent displace: warps the element's own pixels by a seeded noise
343
+ * vector field — wavy text, heat shimmer, organic wobble. Same
344
+ * normative noise as `fractal_noise`. Animate `evolution` for motion.
345
+ */
346
+ export interface TurbulentDisplaceEffect {
347
+ type: 'turbulent_displace';
348
+ /** Max displacement in canvas px. Default 16. Animatable. */
349
+ amount?: number | Keyframe[] | Expr;
350
+ /** Feature size of the displacement field in canvas px. Default 120. Animatable. */
351
+ scale?: number | Keyframe[] | Expr;
352
+ /** Third noise axis — animate for churn. Default 0. Animatable. */
353
+ evolution?: number | Keyframe[] | Expr;
354
+ /** fBM octaves, integer 1–8. Default 2. */
355
+ octaves?: number;
356
+ /** Lattice seed, integer ≥ 0. Default 0. */
357
+ seed?: number;
358
+ }
359
+ /**
360
+ * A stylize pass over the element's rendered pixels. Effects run in
361
+ * array order, after the filter fields (§4.6 → §4.7). Params accept
362
+ * keyframes evaluated against element-local time.
363
+ */
364
+ export type Effect = PixelateEffect | DitherEffect | HalftoneEffect | AsciiEffect | GlassEffect | GlowEffect | DropShadowEffect | StrokeEffect | ChromaKeyEffect | LumaKeyEffect | LevelsEffect | LutEffect | FractalNoiseEffect | TurbulentDisplaceEffect;
365
+ export interface BaseElement {
366
+ id?: string;
367
+ name?: string;
368
+ type: ElementType;
369
+ /**
370
+ * Paint order within the element's container, like an After Effects
371
+ * layer: each element owns a UNIQUE `layer` integer in 1..1000 and
372
+ * LOWER numbers draw in front (layer 1 is on top). Required. `z`
373
+ * (depth) takes precedence; `layer` orders elements within equal
374
+ * depth. Unique per container (top-level `elements`, each group's
375
+ * `elements`, each group mask's `elements`).
376
+ */
377
+ layer: number;
378
+ time?: number | string;
379
+ duration?: number | string | 'auto' | 'end';
380
+ /** When false, the element is not rendered at all. Default true. */
381
+ visible?: boolean;
382
+ x?: number | string | Keyframe[] | Expr;
383
+ y?: number | string | Keyframe[] | Expr;
384
+ x_anchor?: number | string;
385
+ y_anchor?: number | string;
386
+ width?: number | string | Keyframe[] | Expr;
387
+ height?: number | string | Keyframe[] | Expr;
388
+ /**
389
+ * Width / height ratio. When exactly one of width/height is set, the
390
+ * other derives from this ratio (e.g. width 800 + aspect_ratio 16/9 →
391
+ * height 450). Ignored when both or neither dimension is set.
392
+ */
393
+ aspect_ratio?: number;
394
+ /**
395
+ * Rotation in the element's plane, degrees clockwise. Exact alias for
396
+ * `z_rotation` (CKP/1.0): authoring BOTH on one element is a
397
+ * validation error. Animatable.
398
+ */
399
+ rotation?: number | Keyframe[] | Expr;
400
+ /**
401
+ * 3D rotation (CKP/1.0, §4.4): degrees around the element's local z
402
+ * axis. Same slot as `rotation` — use one or the other.
403
+ */
404
+ z_rotation?: number | Keyframe[] | Expr;
405
+ /**
406
+ * 3D rotation (CKP/1.0, §4.4): degrees around the element's local
407
+ * x axis (positive tips the top edge away from the viewer). Without a
408
+ * Source camera the projection has no perspective (affine
409
+ * foreshortening only). Animatable.
410
+ */
411
+ x_rotation?: number | Keyframe[] | Expr;
412
+ /**
413
+ * 3D rotation (CKP/1.0, §4.4): degrees around the element's local
414
+ * y axis (positive turns the right edge away from the viewer).
415
+ * Animatable.
416
+ */
417
+ y_rotation?: number | Keyframe[] | Expr;
418
+ /**
419
+ * Position offset along the z axis in pixels (CKP/1.0, §4.4);
420
+ * positive moves TOWARD the viewer. Visible only under a Source
421
+ * camera (it feeds the perspective divide); does NOT affect paint
422
+ * order. Animatable.
423
+ */
424
+ z?: number | Keyframe[] | Expr;
425
+ scale?: number | Keyframe[] | Expr;
426
+ /**
427
+ * Per-axis scale factors, multiplied with the uniform `scale`. A number
428
+ * is a factor (1 = unscaled); a string percentage ("50%") is parsed as
429
+ * factor/100. Animatable via keyframe_animations.
430
+ */
431
+ x_scale?: number | string | Keyframe[] | Expr;
432
+ y_scale?: number | string | Keyframe[] | Expr;
433
+ /**
434
+ * Shear in DEGREES, following CSS `skewX(...)` / `skewY(...)`
435
+ * semantics: positive x_skew moves the bottom edge right; positive
436
+ * y_skew moves the right edge down. Animatable.
437
+ */
438
+ x_skew?: number | Keyframe[] | Expr;
439
+ y_skew?: number | Keyframe[] | Expr;
440
+ /** Opacity 0..1 (CSS convention). Default 1 (opaque). Animatable. */
441
+ opacity?: number | Keyframe[] | Expr;
442
+ /**
443
+ * How this element's pixels combine with what's already on the
444
+ * canvas beneath it (element-local, like opacity). Follows the W3C
445
+ * Compositing & Blending separable-blend definitions:
446
+ * 'normal' source-over (default)
447
+ * 'multiply' darkens — white is neutral
448
+ * 'screen' lightens — black is neutral
449
+ * 'add' additive glow — black is neutral
450
+ * 'overlay' multiply where backdrop is dark, screen where light
451
+ * 'hard-light' overlay with source and backdrop swapped
452
+ * 'soft-light' a gentler overlay (soft burn/dodge)
453
+ * 'overlay'/'hard-light'/'soft-light' are piecewise on the backdrop/
454
+ * source and can't be done with fixed-function GPU blending — the
455
+ * runtime isolates the element to a layer and composites it against a
456
+ * snapshot of the backdrop.
457
+ */
458
+ blend_mode?: 'normal' | 'multiply' | 'screen' | 'add' | 'overlay' | 'hard-light' | 'soft-light';
459
+ /**
460
+ * Gaussian blur of this element's rendered pixels; the value is the
461
+ * standard deviation (σ) in canvas pixels, CSS `filter: blur()`
462
+ * semantics. 0 = off. Element-local: the blur may bleed past the
463
+ * element's box but never touches other elements. Animatable.
464
+ */
465
+ blur_radius?: number | Keyframe[] | Expr;
466
+ /**
467
+ * Brightness multiplier (CSS `filter: brightness()`): 1 = unchanged,
468
+ * 0 = black, >1 brightens. Element-local, animatable.
469
+ */
470
+ brightness?: number | Keyframe[] | Expr;
471
+ /**
472
+ * Contrast multiplier around mid-gray (CSS `filter: contrast()`):
473
+ * 1 = unchanged, 0 = solid gray, >1 increases. Element-local,
474
+ * animatable.
475
+ */
476
+ contrast?: number | Keyframe[] | Expr;
477
+ /**
478
+ * Saturation multiplier (CSS `filter: saturate()`): 1 = unchanged,
479
+ * 0 = grayscale, >1 oversaturates. Element-local, animatable.
480
+ */
481
+ saturation?: number | Keyframe[] | Expr;
482
+ /**
483
+ * Hue rotation in degrees (CSS `filter: hue-rotate()` — the SVG
484
+ * feColorMatrix hueRotate matrix, normative in §4.6): 0 = unchanged.
485
+ * Element-local, animatable.
486
+ */
487
+ hue_rotate?: number | Keyframe[] | Expr;
488
+ /**
489
+ * Stylize effects, applied IN ARRAY ORDER after the filter fields
490
+ * (blur → brightness → contrast → saturation → hue_rotate →
491
+ * effects[0..n]).
492
+ * Element-local: each effect re-renders only this element's pixels;
493
+ * other elements are never read or altered. Effect params accept
494
+ * keyframes (element-local time) and are NOT addressable via
495
+ * keyframe_animations.
496
+ */
497
+ effects?: Effect[];
498
+ /**
499
+ * PBR surface material (CKP/1.0, §4.8). Opt-in: with no `material` the
500
+ * element renders unlit exactly as before. When present (and the Source
501
+ * declares `lights`), the element's own pixels act as albedo and the
502
+ * runtime shades them — diffuse + view-dependent specular + environment
503
+ * reflection — so highlights/reflections sweep as the camera moves.
504
+ */
505
+ material?: Material;
506
+ animations?: Animation[];
507
+ keyframe_animations?: KeyframeAnimation[];
508
+ [key: string]: unknown;
509
+ }
510
+ export interface VideoElement extends BaseElement {
511
+ type: 'video';
512
+ source: string;
513
+ volume?: number | Keyframe[] | Expr;
514
+ playback_rate?: number | Keyframe[] | Expr;
515
+ trim_start?: number;
516
+ trim_duration?: number;
517
+ loop?: boolean;
518
+ /**
519
+ * Time remapping (§5.3.2): keyframes mapping element-local time
520
+ * (seconds) → MEDIA time (seconds). Speed ramps are steep segments,
521
+ * freeze frames are flat ones, reverse plays from decreasing values.
522
+ * When present it REPLACES the trim_start / trim_duration /
523
+ * playback_rate / loop mapping entirely. The embedded audio follows
524
+ * the warp varispeed-style (§5.3.2): pitch shifts with speed,
525
+ * freezes are silent, reverse plays the sound backwards.
526
+ */
527
+ time_remap?: Keyframe[];
528
+ /** Embedded-audio gain ramps 0→volume over the first N timeline seconds. */
529
+ audio_fade_in?: number;
530
+ /** Embedded-audio gain ramps volume→0 over the last N timeline seconds. */
531
+ audio_fade_out?: number;
532
+ /**
533
+ * How the media fills the element box (CSS object-fit semantics):
534
+ * 'cover' scales to fill and crops overflow (default), 'contain'
535
+ * letterboxes, 'fill' stretches, 'none' renders at natural size
536
+ * cropped to the box.
537
+ */
538
+ fit?: 'cover' | 'contain' | 'fill' | 'none';
539
+ /**
540
+ * Source crop — a normalized sub-rectangle of the media (0..1, origin
541
+ * top-left) selected BEFORE `fit` maps it into the element box. The
542
+ * element box is unchanged; crop only chooses which part of the source
543
+ * fills it. Default `0,0,1,1` (whole source) — omit for no crop. Each
544
+ * component is keyframeable (animate them for a Ken Burns pan/zoom).
545
+ */
546
+ crop_x?: number;
547
+ crop_y?: number;
548
+ crop_width?: number;
549
+ crop_height?: number;
550
+ }
551
+ export interface ImageElement extends BaseElement {
552
+ type: 'image';
553
+ source: string;
554
+ fit?: 'cover' | 'contain' | 'fill' | 'none';
555
+ /**
556
+ * Rounded-corner radius in pixels. Pixels outside the rounded rect
557
+ * are masked out by the renderer (with anti-aliased edges). Clamped
558
+ * by the runtime to half the smaller dimension, so passing a huge
559
+ * value produces a pill / circle. Matches CSS `border-radius`.
560
+ */
561
+ border_radius?: number;
562
+ /**
563
+ * Source crop — a normalized sub-rectangle of the media (0..1, origin
564
+ * top-left) selected BEFORE `fit` maps it into the element box. The
565
+ * element box is unchanged; crop only chooses which part of the source
566
+ * fills it. Default `0,0,1,1` (whole source) — omit for no crop. Each
567
+ * component is keyframeable (animate them for a Ken Burns pan/zoom).
568
+ */
569
+ crop_x?: number;
570
+ crop_y?: number;
571
+ crop_width?: number;
572
+ crop_height?: number;
573
+ }
574
+ /**
575
+ * A styled run within a text element. When `spans` is set on a TextElement
576
+ * the runtime renders each run inline left-to-right, applying any per-span
577
+ * style overrides on top of the element's defaults. A span with text
578
+ * `'\n'` acts as a hard line break.
579
+ *
580
+ * Clipkit extension to the baseline TextElement; allows
581
+ * mid-string emphasis (bold within a sentence, colored highlight on one
582
+ * word, etc.) without authoring N positioned siblings.
583
+ */
584
+ /**
585
+ * Stylized background for a TextSpan — a "highlight band" effect.
586
+ *
587
+ * The fields refine how the background rect is positioned and shaped:
588
+ * - `height_ratio` < 1 makes the band shorter than the line box
589
+ * (e.g. 0.5 for a marker-style underline highlight).
590
+ * - `inset_y_ratio` shifts the band vertically within the line box.
591
+ * - `padding_x` extends the band horizontally past the text glyphs.
592
+ * - `skew_x` shears the band horizontally (parallelogram edges).
593
+ * - `border_radius` rounds the band corners.
594
+ *
595
+ * All optional — minimal usage `{ color }` is equivalent to a flat
596
+ * full-line-box rectangle (same as the older `background_color`).
597
+ */
598
+ export interface TextSpanBackground {
599
+ /** CSS color (hex / rgb / rgba). */
600
+ color: string;
601
+ /**
602
+ * Band height as a fraction of the line-box height. 1.0 = full line
603
+ * (default), 0.5 = half-line marker, etc.
604
+ */
605
+ height_ratio?: number;
606
+ /**
607
+ * Top edge of the band as a fraction of the line-box height, measured
608
+ * from the line-box top. 0 = flush top, 1 - height_ratio = flush
609
+ * bottom. Default 0.
610
+ */
611
+ inset_y_ratio?: number;
612
+ /**
613
+ * Horizontal padding in PIXELS extending past the text glyph bounds
614
+ * on each side. Useful for the "Swipe" effect where the band sticks
615
+ * out a few pixels past the letters.
616
+ */
617
+ padding_x?: number;
618
+ /**
619
+ * Horizontal skew in DEGREES (CSS `skewX(...)`). Positive shears the
620
+ * top to the right; negative to the left.
621
+ */
622
+ skew_x?: number;
623
+ /** Corner radius in pixels. */
624
+ border_radius?: number;
625
+ /** 0..1 opacity. Multiplies with the element's opacity. */
626
+ opacity?: number;
627
+ }
628
+ export interface TextSpan {
629
+ text: string;
630
+ font_weight?: number | string;
631
+ font_style?: 'normal' | 'italic';
632
+ font_family?: string;
633
+ font_size?: number | string;
634
+ fill_color?: string;
635
+ /**
636
+ * Tracking in pixels, added after every character (Chrome's
637
+ * letter-spacing model). Inherits the element's letter_spacing when
638
+ * unset.
639
+ */
640
+ letter_spacing?: number;
641
+ /**
642
+ * Solid color background spanning the full line box. Shortcut for
643
+ * `background: { color }`. Ignored when `background` is set.
644
+ */
645
+ background_color?: string;
646
+ /** Stylized background — overrides `background_color` when present. */
647
+ background?: TextSpanBackground;
648
+ /**
649
+ * When true, the runtime's word-wrap treats this span's text as an
650
+ * atomic unit: it never breaks mid-span. Matches CSS
651
+ * `white-space: nowrap` / `display: inline-block` semantics. Used
652
+ * for highlighted phrases that must stay together (the Swipe band
653
+ * "before you leave" reads wrong if it wraps after "before").
654
+ */
655
+ nowrap?: boolean;
656
+ }
657
+ /**
658
+ * Per-glyph text shadow (CSS `text-shadow` semantics). Each glyph casts
659
+ * its OWN shadow, so it tracks per-letter animation (flip/stagger/3D) and
660
+ * overlapping glyphs — unlike the silhouette `drop_shadow` effect, which
661
+ * shadows the flattened text as one shape. Use an ARRAY for stacked
662
+ * shadows (painted back-to-front; the last entry sits nearest the glyphs).
663
+ */
664
+ export interface TextShadow {
665
+ /** Shadow color (§3.4: hex / rgb() / hsl() / named). */
666
+ color: string;
667
+ /** Offset in px, in the text's local frame (rotates with it). Default 0. */
668
+ offset_x?: number;
669
+ offset_y?: number;
670
+ /** Gaussian softness in px (0 = crisp). Default 0. */
671
+ blur?: number;
672
+ /** Shadow opacity 0..1, multiplies the color's alpha. Default 1. */
673
+ opacity?: number;
674
+ }
675
+ export interface TextElement extends BaseElement {
676
+ type: 'text';
677
+ /** Static text content. Optional when `spans` is provided. */
678
+ text?: string;
679
+ /**
680
+ * Inline-styled runs. When present, takes precedence over `text`. Each
681
+ * span inherits the element's font_family / font_size / fill_color /
682
+ * etc. unless it overrides them.
683
+ */
684
+ spans?: TextSpan[];
685
+ font_family?: string;
686
+ font_size?: number | string;
687
+ /**
688
+ * Lower bound for `font_size: "auto"` fitting. Number = px; unit
689
+ * strings (vmin etc.) resolve against the canvas. Default 8.
690
+ */
691
+ font_size_minimum?: number | string;
692
+ /** Upper bound for `font_size: "auto"`. Default 400. */
693
+ font_size_maximum?: number | string;
694
+ font_weight?: number | string;
695
+ font_style?: 'normal' | 'italic';
696
+ fill_color?: string;
697
+ stroke_color?: string;
698
+ stroke_width?: number;
699
+ /**
700
+ * Case transform applied before layout: 'uppercase', 'lowercase',
701
+ * 'capitalize' (word-initial caps). Default 'none'.
702
+ */
703
+ text_transform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize';
704
+ /**
705
+ * When false, text never soft-wraps — only explicit "\n" breaks
706
+ * lines. Default true (plain text wraps to element.width when set;
707
+ * spans keep their author/importer breaks).
708
+ */
709
+ text_wrap?: boolean;
710
+ text_align?: 'left' | 'center' | 'right';
711
+ vertical_align?: 'top' | 'middle' | 'bottom';
712
+ /**
713
+ * Content insets in pixels: the text box shrinks by 2×padding on the
714
+ * axis and content shifts inward. Numbers or unit strings.
715
+ */
716
+ x_padding?: number | string;
717
+ y_padding?: number | string;
718
+ /**
719
+ * Percentage-based content alignment, overriding text_align /
720
+ * vertical_align when present: "0%" = left/top, "50%" = center,
721
+ * "100%" = right/bottom. Numbers are fractions (0.5 = center).
722
+ */
723
+ x_alignment?: number | string;
724
+ y_alignment?: number | string;
725
+ line_height?: number;
726
+ letter_spacing?: number;
727
+ /**
728
+ * Solid background behind the text — drawn as ONE band PER LINE, each
729
+ * shrink-wrapped to that line's glyphs (not the element box), so
730
+ * centered / ragged multi-line text gets per-line pills. Tracks
731
+ * wrapping and `font_size: "auto"`. For a per-run highlight band use a
732
+ * span `background`; for a drop shadow use a `drop_shadow` effect.
733
+ */
734
+ background_color?: string;
735
+ /** Corner radius (px) for `background_color`. Default 0. */
736
+ background_border_radius?: number;
737
+ /**
738
+ * Padding (px) added around the shrink-wrapped `background_color` on all
739
+ * sides, OR `[x, y]` for separate horizontal / vertical padding. Default
740
+ * 0 (bg hugs the glyphs). A pill usually wants ~16–28 horizontal.
741
+ */
742
+ background_padding?: number | [number, number];
743
+ /**
744
+ * Per-glyph drop shadow (CSS `text-shadow`), or an array for stacked
745
+ * shadows. Distinct from the silhouette `drop_shadow` effect — see
746
+ * TextShadow. Animatable params are not supported (static per shadow).
747
+ */
748
+ text_shadow?: TextShadow | TextShadow[];
749
+ /**
750
+ * Reveal mask. When present, text is rendered to an offscreen canvas
751
+ * and masked before display. linear-wipe sweeps a soft diagonal edge
752
+ * across the text driven by `progress` (0 = hidden, 1 = revealed).
753
+ */
754
+ mask?: TextMask;
755
+ }
756
+ export interface TextMask {
757
+ type: 'linear-wipe';
758
+ /**
759
+ * Wipe direction in degrees. 0 = left→right, -45 = bottom-left→top-right
760
+ * (a common text-fade default). Default -45.
761
+ */
762
+ angle?: number;
763
+ /**
764
+ * Reveal progress, 0..1. Animatable via keyframes. 0 = fully hidden,
765
+ * 1 = fully revealed.
766
+ */
767
+ progress?: number | Keyframe[] | Expr;
768
+ /**
769
+ * Soft-edge width as a fraction of the bounding box diagonal, 0..1.
770
+ * Larger = softer wipe edge. Default 0.3.
771
+ */
772
+ softness?: number;
773
+ }
774
+ /**
775
+ * Drop shadow drawn behind the shape. Follows CSS `box-shadow`
776
+ * semantics for the outer (non-inset) case:
777
+ * - `color` is the base color of the shadow (CSS color string).
778
+ * - `offset_x` / `offset_y` translate the shadow relative to the
779
+ * shape in PIXELS.
780
+ * - `blur` is the falloff distance in pixels: at offset 0 the
781
+ * shadow has full alpha; alpha fades linearly to 0 over `blur`
782
+ * pixels past the shape's edge.
783
+ *
784
+ * Inset shadows and `spread` are not (yet) supported.
785
+ */
786
+ export interface BoxShadow {
787
+ color: string;
788
+ offset_x?: number;
789
+ offset_y?: number;
790
+ blur?: number;
791
+ }
792
+ /**
793
+ * A `shape` draws geometry to the frame in one of two representations,
794
+ * chosen by whether `paths` is present:
795
+ *
796
+ * • PRIMITIVE — `shape: 'rectangle' | 'ellipse'` (+ `border_radius`): a GPU
797
+ * SDF quad. Resolution-independent and cheap; corners come from a shader.
798
+ * • PATH — `paths`: arbitrary vector geometry rasterized via the path engine,
799
+ * with keyframeable `d` morphing, per-sub-path fill/stroke, and stroke
800
+ * trim/draw-on. Resolution is bound by `view_box`.
801
+ *
802
+ * One element, two pixel-generation strategies — the renderer dispatches on
803
+ * `paths`. (Absorbs the former `svg` element; CKP/1.0.)
804
+ */
805
+ export interface ShapeElement extends BaseElement {
806
+ type: 'shape';
807
+ /** Primitive kind. Default 'rectangle'. */
808
+ shape?: 'rectangle' | 'ellipse';
809
+ fill_color?: string;
810
+ /** Gradient fill. When present, overrides fill_color. Up to 4 stops. */
811
+ gradient?: LinearGradient | RadialGradient;
812
+ stroke_color?: string;
813
+ stroke_width?: number;
814
+ border_radius?: number;
815
+ /** Drop shadow rendered before the shape itself. */
816
+ shadow?: BoxShadow;
817
+ /** Vector geometry: one or more sub-paths, painted back-to-front. When
818
+ * present, the primitive fields above are ignored. */
819
+ paths?: PathDef[];
820
+ /** [x, y, width, height] viewBox for `paths`. Default [0, 0, 100, 100]. */
821
+ view_box?: [number, number, number, number];
822
+ /** Linear gradients addressable from `paths` via `fill: "url(#id)"`. */
823
+ gradients?: PathGradient[];
824
+ }
825
+ export interface GradientStop {
826
+ /** Position along the gradient in [0, 1]. */
827
+ offset: number;
828
+ /** Hex color. */
829
+ color: string;
830
+ }
831
+ export interface LinearGradient {
832
+ type: 'linear';
833
+ /**
834
+ * Direction in degrees, following the CSS `linear-gradient()`
835
+ * convention: 0° = to top, measured clockwise — 90° = to right,
836
+ * 180° = to bottom, 270° = to left. Default 180 (to bottom).
837
+ */
838
+ angle?: number;
839
+ /** Color stops. Up to 4 are honored by the v1 runtime. */
840
+ stops: GradientStop[];
841
+ }
842
+ export interface RadialGradient {
843
+ type: 'radial';
844
+ /** Center X as a fraction of the shape's box. Default 0.5 (center). */
845
+ cx?: number;
846
+ /** Center Y as a fraction. Default 0.5. */
847
+ cy?: number;
848
+ /** Outer radius as a fraction of the shape's box. Default 0.5. */
849
+ radius?: number;
850
+ stops: GradientStop[];
851
+ }
852
+ export interface AudioElement extends BaseElement {
853
+ type: 'audio';
854
+ source: string;
855
+ volume?: number | Keyframe[] | Expr;
856
+ trim_start?: number;
857
+ trim_duration?: number;
858
+ loop?: boolean;
859
+ /** Gain ramps 0→volume over the first N timeline seconds. */
860
+ audio_fade_in?: number;
861
+ /** Gain ramps volume→0 over the last N timeline seconds. */
862
+ audio_fade_out?: number;
863
+ }
864
+ /**
865
+ * Group element — a positioned container whose children inherit its
866
+ * transform, opacity, and time window. The fundamental nesting primitive.
867
+ *
868
+ * Semantic rules:
869
+ * - Children's `x`/`y` are coordinates in the group's LOCAL space. The
870
+ * group's anchor sets the local origin.
871
+ * - The group's `rotation`, `scale`, and `opacity` stack with each
872
+ * descendant. (Transforms multiply, opacities multiply.)
873
+ * - A child's `time` is relative to the group's `time`. A child whose
874
+ * time + duration falls outside the group's window is clipped.
875
+ * - Layers on children are LOCAL paint order within the group; siblings
876
+ * of the group (or its ancestor) use their own layers for global
877
+ * paint order. Layer 1 is on top (drawn last).
878
+ */
879
+ /**
880
+ * A mask owned by the group it masks — the masked thing declares its
881
+ * own mask (like CSS mask-image), rather than a sibling element
882
+ * reaching across the timeline. Mask elements render into their own
883
+ * layer in the group's local coordinate space (same as children) and
884
+ * may animate like any elements.
885
+ *
886
+ * Modes: 'alpha' shows content where the mask is opaque;
887
+ * 'alpha-inverted' where it is transparent; 'luma' scales content by
888
+ * the mask's luminance (white = visible, black = hidden);
889
+ * 'luma-inverted' the reverse.
890
+ */
891
+ export interface GroupMask {
892
+ mode: 'alpha' | 'alpha-inverted' | 'luma' | 'luma-inverted';
893
+ elements: Element[];
894
+ }
895
+ export interface GroupElement extends BaseElement {
896
+ type: 'group';
897
+ elements: Element[];
898
+ /**
899
+ * Time remapping for the SUBTREE (§5.8.4): keyframes mapping the
900
+ * group's local time (seconds) → warped local time the children run
901
+ * on. Speed-ramp, freeze, or reverse a whole composed scene: every
902
+ * child `time`, animation, and nested video reads the warped clock.
903
+ * The group's OWN transform/opacity animations stay on real time.
904
+ * Audio inside a remapped subtree plays varispeed (§5.3.2).
905
+ */
906
+ time_remap?: Keyframe[];
907
+ /**
908
+ * When true, children render into an offscreen layer the size of the
909
+ * group's box and anything outside it is clipped (CSS
910
+ * `overflow: hidden`). Requires explicit `width` and `height`.
911
+ * The group's own opacity/rotation/scale apply to the clipped layer
912
+ * as a whole.
913
+ */
914
+ clip?: boolean;
915
+ /**
916
+ * Corner radius in pixels for a clipped group: rounds the clip box so
917
+ * children are masked to a rounded rectangle (a rounded card clipping
918
+ * its content). Only meaningful with `clip: true` (or `mask`); ignored
919
+ * on an unclipped group. Clamped to half the smaller box dimension.
920
+ */
921
+ border_radius?: number;
922
+ /**
923
+ * Mask the group's content through a second layer (see GroupMask).
924
+ * Requires explicit `width` and `height`. Implies clipping to the
925
+ * group's box (both layers are box-sized).
926
+ */
927
+ mask?: GroupMask;
928
+ }
929
+ export interface CaptionWord {
930
+ text: string;
931
+ /** Seconds, relative to the caption element's `time`. */
932
+ start: number;
933
+ /** Seconds, relative to the caption element's `time`. */
934
+ end: number;
935
+ }
936
+ export interface CaptionElement extends BaseElement {
937
+ type: 'caption';
938
+ words: CaptionWord[];
939
+ style?: CaptionStyle;
940
+ /**
941
+ * Windowing — how much of the transcript shows at once. A whole-video caption
942
+ * would otherwise render as one unreadable block; instead the words are split
943
+ * into CHUNKS and only the chunk active at the current time is shown.
944
+ *
945
+ * number — max LETTERS per chunk (a chunk grows word-by-word until adding
946
+ * the next word would exceed this many characters).
947
+ * "auto" — chunk automatically by words (a few words per chunk, also
948
+ * breaking on pauses) — the sensible default for speech.
949
+ * absent — no windowing; the whole transcript shows at once.
950
+ */
951
+ max_length?: number | 'auto';
952
+ font_family?: string;
953
+ font_size?: number | string;
954
+ font_weight?: number | string;
955
+ font_style?: 'normal' | 'italic';
956
+ fill_color?: string;
957
+ stroke_color?: string;
958
+ stroke_width?: number;
959
+ text_align?: 'left' | 'center' | 'right';
960
+ line_height?: number;
961
+ letter_spacing?: number;
962
+ /**
963
+ * Solid background drawn behind the caption phrase, SHRINK-WRAPPED to
964
+ * the laid-out glyph bounds (not the element box). For a per-word
965
+ * background use `highlight_background_color`; for a drop shadow use a
966
+ * `drop_shadow` effect.
967
+ */
968
+ background_color?: string;
969
+ /** Corner radius (px) for `background_color`. Default 0. */
970
+ background_border_radius?: number;
971
+ /**
972
+ * Padding (px) around the shrink-wrapped `background_color`, or `[x, y]`
973
+ * for separate horizontal / vertical padding. Default 0. A caption pill
974
+ * usually wants ~20–32 horizontal.
975
+ */
976
+ background_padding?: number | [number, number];
977
+ /** Per-glyph drop shadow (CSS `text-shadow`), or an array. See TextShadow. */
978
+ text_shadow?: TextShadow | TextShadow[];
979
+ /** Color applied to the currently-active word. Defaults to fill_color. */
980
+ highlight_color?: string;
981
+ /** Background color applied to the currently-active word. */
982
+ highlight_background_color?: string;
983
+ }
984
+ export interface ParticlesElement extends BaseElement {
985
+ type: 'particles';
986
+ /** Particles per second (continuous mode). Default 60. */
987
+ rate?: number;
988
+ /** Lifetime per particle, in seconds. Default 1.5. */
989
+ lifetime?: number;
990
+ /** Initial speed in px/s (mean — actual = velocity × (1 ± 0.3·random)). Default 300. */
991
+ velocity?: number;
992
+ /** Spread cone in degrees. 0 = directional, 360 = omnidirectional. Default 360. */
993
+ spread?: number;
994
+ /** Emission direction in degrees. 0° = right, 90° = down, -90° = up. Default -90. */
995
+ direction?: number;
996
+ /** Gravity in px/s². Positive y = downward. Default 600. */
997
+ gravity?: number;
998
+ /** Particle color. Pass an array of hex strings for per-particle randomization. Default '#ffffff'. */
999
+ color?: string | string[];
1000
+ /** Particle size in pixels. Default 12. */
1001
+ size?: number;
1002
+ /** Random size variation in [0, 1]. 0 = uniform, 1 = anywhere in [0, size]. Default 0.4. */
1003
+ size_variation?: number;
1004
+ /** Particle shape. Default 'square'. */
1005
+ particle_shape?: 'square' | 'circle';
1006
+ /** Initial rotation speed in deg/s (sign randomized per particle). Default 360. */
1007
+ rotation_speed?: number;
1008
+ /** If true, emit `burst_count` particles instantly at element start. Default false. */
1009
+ burst?: boolean;
1010
+ /** Number of particles in burst (only used when burst=true). Default 80. */
1011
+ burst_count?: number;
1012
+ /** Fraction of lifetime [0..1] at which particles start fading out. Default 0.7. */
1013
+ fade_at?: number;
1014
+ /**
1015
+ * Depth velocity in px/s along the emitter plane's normal (+z toward
1016
+ * the viewer, §4.4), CKP/1.0. Per particle: vz = z_velocity +
1017
+ * (random − 0.5) × z_spread; depth = vz × age. Like the `z` field,
1018
+ * invisible without perspective in the chain; paint order is
1019
+ * unchanged (z never sorts, §4.4.3). Default 0.
1020
+ */
1021
+ z_velocity?: number;
1022
+ /** Width of the uniform random vz range in px/s (see z_velocity). Default 0. */
1023
+ z_spread?: number;
1024
+ /**
1025
+ * Convergence mode. When set (non-empty), the particle simulation switches
1026
+ * off ballistic emission — instead each particle interpolates from a
1027
+ * random scattered start position toward one of these target points
1028
+ * (assigned round-robin by index) over its lifetime, producing the
1029
+ * "particles assemble into a logo" effect.
1030
+ *
1031
+ * Points are in canvas pixel coordinates. Typical use: pre-sample
1032
+ * positions along an SVG path with `SVGPathElement.getPointAtLength()`
1033
+ * and pair with `burst: true, burst_count: target_points.length`.
1034
+ */
1035
+ target_points?: [number, number][];
1036
+ /** Easing applied to the convergence position. Default 'ease-out-quart'. */
1037
+ convergence_easing?: EasingFunction;
1038
+ /**
1039
+ * Radius of the random scatter region around the emitter point where
1040
+ * particles start. Default = max(canvas_width, canvas_height).
1041
+ */
1042
+ scatter_radius?: number;
1043
+ }
1044
+ export interface PathGradient {
1045
+ /** Gradient id; reference with fill: "url(#id)" or stroke: "url(#id)". */
1046
+ id: string;
1047
+ type: 'linear';
1048
+ /** Endpoints in viewBox coordinates. */
1049
+ x1: number;
1050
+ y1: number;
1051
+ x2: number;
1052
+ y2: number;
1053
+ stops: GradientStop[];
1054
+ }
1055
+ export interface PathDef {
1056
+ /**
1057
+ * SVG path data ("M x y L x y ..."), or keyframes of d-strings for
1058
+ * PATH MORPHING (§5.6.2): when two keyframe values share an
1059
+ * identical command sequence (same letters, same argument counts,
1060
+ * no arc commands), their numeric arguments interpolate with the
1061
+ * destination keyframe's easing; incompatible pairs SNAP at the
1062
+ * destination keyframe's time.
1063
+ */
1064
+ d: string | Keyframe[];
1065
+ /** Hex color, or "url(#gradient-id)" to reference a linear gradient. */
1066
+ fill?: string;
1067
+ /** Hex color, or "url(#gradient-id)" to reference a linear gradient. */
1068
+ stroke?: string;
1069
+ /** Stroke width in viewBox units. */
1070
+ stroke_width?: number;
1071
+ /**
1072
+ * Fraction of the stroke to draw, 0..1. Sugar for a trim window of
1073
+ * [0, progress] — equivalent to `trim_end` with `trim_start: 0`.
1074
+ * Ignored when any trim_* field is present. Animatable.
1075
+ */
1076
+ stroke_progress?: number | Keyframe[] | Expr;
1077
+ /**
1078
+ * Trim window (§5.6.1): only the stroke between `trim_start` and
1079
+ * `trim_end` (fractions of the path's total length, 0..1) is drawn.
1080
+ * `trim_offset` rotates the window around the path (wrapping — 1 is
1081
+ * a full lap), which animated makes the classic traveling-dash
1082
+ * "snake". All three animatable. Defaults 0 / 1 / 0.
1083
+ */
1084
+ trim_start?: number | Keyframe[] | Expr;
1085
+ trim_end?: number | Keyframe[] | Expr;
1086
+ trim_offset?: number | Keyframe[] | Expr;
1087
+ /**
1088
+ * Path data ("M x y L x y ...") that clips this path's drawing. Only
1089
+ * pixels inside the clip path are visible. Equivalent to SVG mask with
1090
+ * a solid black fill.
1091
+ */
1092
+ clip_path?: string;
1093
+ /** Linecap style. Default "butt". */
1094
+ stroke_linecap?: 'butt' | 'round' | 'square';
1095
+ /** Linejoin style. Default "miter". */
1096
+ stroke_linejoin?: 'miter' | 'round' | 'bevel';
1097
+ /** Per-path opacity, applied to both fill and stroke. 0..1, default 1. */
1098
+ opacity?: number;
1099
+ }
1100
+ export type Element = VideoElement | ImageElement | TextElement | ShapeElement | AudioElement | GroupElement | CaptionElement | ParticlesElement;
1101
+ /**
1102
+ * The current Clipkit Protocol version. Bumped on backward-incompatible
1103
+ * changes to the Source schema. See PROTOCOL.md for the version policy.
1104
+ */
1105
+ export declare const CLIPKIT_PROTOCOL_VERSION = "1.0";
1106
+ export interface FontFace {
1107
+ /** CSS font-family the runtime should register the face as. */
1108
+ family: string;
1109
+ /** CSS font-weight (e.g. 400, 700, "bold"). Defaults to "normal". */
1110
+ weight?: number | string;
1111
+ /** CSS font-style. Defaults to "normal". */
1112
+ style?: 'normal' | 'italic';
1113
+ /**
1114
+ * URL the runtime fetches to load the font. Can be absolute (http(s):),
1115
+ * relative (resolved against the document hosting the Source), or a
1116
+ * data: URI carrying the font bytes inline.
1117
+ */
1118
+ src: string;
1119
+ /**
1120
+ * CSS unicode-range of the face (e.g. "U+0000-00FF, U+0131"). Needed
1121
+ * for subsetted webfonts, which ship one file per script under an
1122
+ * identical family/weight/style — without the range, every subset
1123
+ * competes for every codepoint and the winning file may not contain
1124
+ * the glyphs being rendered.
1125
+ */
1126
+ unicode_range?: string;
1127
+ }
1128
+ /**
1129
+ * Source-level motion blur — exact sub-frame supersampling. The renderer
1130
+ * renders `samples` evenly spaced sub-frame times across a shutter window
1131
+ * centered on each output frame time and averages them (arithmetic mean
1132
+ * per 8-bit channel, single rounding). Deterministic: same Source → same
1133
+ * pixels.
1134
+ *
1135
+ * Sample times for the output frame at time t with frame rate f:
1136
+ * t_k = clamp(t + ((k + 0.5) / samples − 0.5) × shutter / f, 0, duration)
1137
+ *
1138
+ * Applies at export/render time. Interactive previews MAY render the
1139
+ * unblurred scene (single sample) for speed.
1140
+ */
1141
+ export interface MotionBlur {
1142
+ /** Sub-frame samples per output frame. Integer 1–32; default 8. 1 disables blur. */
1143
+ samples?: number;
1144
+ /** Fraction of the frame interval the shutter is open, (0..1]. Default 0.5 (a 180° shutter). */
1145
+ shutter?: number;
1146
+ }
1147
+ /**
1148
+ * Source-level scene camera (CKP/1.0, §4.4). One camera for the whole
1149
+ * composition: a perspective lens (`perspective` + origin, CSS-perspective
1150
+ * semantics — smaller distance = stronger foreshortening) plus an optional
1151
+ * rigid pose (`x`/`y`/`z` position and `x_rotation`/`y_rotation`/
1152
+ * `z_rotation` Euler orientation) that moves the viewpoint through the
1153
+ * scene. The runtime applies `camera = P · V` at the root (§4.4.2). With
1154
+ * the pose at its defaults `V = I` and the camera reduces to the lens
1155
+ * bit-for-bit. Absent camera = identity = exact 2D rendering. All fields
1156
+ * animatable via Keyframe[].
1157
+ *
1158
+ * Elements' `x_rotation` / `y_rotation` render without a camera too
1159
+ * (affine foreshortening, no perspective); `z` offsets are only visible
1160
+ * under a camera.
1161
+ */
1162
+ export interface Camera {
1163
+ /** Focal distance in px (CSS `perspective()`). Must be > 0. Animatable. */
1164
+ perspective: number | Keyframe[] | Expr;
1165
+ /** Projection origin; number (px) or length string. Default "50%" of width. */
1166
+ origin_x?: number | string;
1167
+ /** Projection origin; number (px) or length string. Default "50%" of height. */
1168
+ origin_y?: number | string;
1169
+ /** Eye position offset from the default eye, px, about the origin. +x right. Default 0. Animatable. */
1170
+ x?: number | Keyframe[] | Expr;
1171
+ /** Eye position offset, px. +y down. Default 0. Animatable. */
1172
+ y?: number | Keyframe[] | Expr;
1173
+ /** Eye position along the view axis, px. +z = eye toward the scene (dolly in). Default 0. Animatable. */
1174
+ z?: number | Keyframe[] | Expr;
1175
+ /** Eye pitch in degrees (Euler, applied Rz·Ry·Rx). Default 0. Animatable. */
1176
+ x_rotation?: number | Keyframe[] | Expr;
1177
+ /** Eye yaw in degrees. Default 0. Animatable. */
1178
+ y_rotation?: number | Keyframe[] | Expr;
1179
+ /** Eye roll in degrees. Default 0. Animatable. */
1180
+ z_rotation?: number | Keyframe[] | Expr;
1181
+ /**
1182
+ * Compositing order under this camera (§4.4.3). `'depth'` (default)
1183
+ * paints flat cards back-to-front by camera distance (2.5D occlusion).
1184
+ * `'paint'` forces fixed `layer` order even under the camera.
1185
+ */
1186
+ sort?: 'depth' | 'paint';
1187
+ }
1188
+ /**
1189
+ * PBR material on an element (CKP/1.0, §4.8). The element's own rendered
1190
+ * pixels are the albedo; these fields control how it responds to the
1191
+ * scene `lights` and `environment`. All animatable. Absent ⇒ unlit.
1192
+ */
1193
+ export interface Material {
1194
+ /** Surface roughness 0 (mirror/tight highlight) .. 1 (matte/broad). Default 0.5. */
1195
+ roughness?: number | Keyframe[] | Expr;
1196
+ /** Metalness 0 (dielectric, F0≈0.04) .. 1 (metal — albedo tints reflections). Default 0. */
1197
+ metalness?: number | Keyframe[] | Expr;
1198
+ /** Environment-reflection strength (art dial over the physical term). Default 1. */
1199
+ reflectivity?: number | Keyframe[] | Expr;
1200
+ /** Self-illumination 0..(>1): mixes the element toward its own unlit pixels. Default 0. */
1201
+ emissive?: number | Keyframe[] | Expr;
1202
+ /**
1203
+ * Tangent-space normal map URL (CKP/1.0 Phase 2, §4.8). RGB encodes a
1204
+ * per-texel surface normal (the usual 0.5-centered convention; flat =
1205
+ * #8080ff). Perturbs the face normal across the surface for bumps /
1206
+ * brushed detail, sampled in the element's UV space. Absent ⇒ flat
1207
+ * face normal.
1208
+ */
1209
+ normal_map?: string;
1210
+ /**
1211
+ * Strength of `normal_map` perturbation. 0 = flat (ignore the map),
1212
+ * 1 = as authored, >1 exaggerates. Default 1. Animatable.
1213
+ */
1214
+ normal_scale?: number | Keyframe[] | Expr;
1215
+ }
1216
+ /**
1217
+ * A scene light (CKP/1.0, §4.8). One of:
1218
+ * - ambient: uniform fill.
1219
+ * - directional: a parallel light whose direction is given by `azimuth`
1220
+ * (around the view axis, degrees) and `elevation` (above the screen
1221
+ * plane toward the viewer, degrees).
1222
+ */
1223
+ export type Light = {
1224
+ type: 'ambient';
1225
+ color?: string;
1226
+ intensity?: number | Keyframe[] | Expr;
1227
+ } | {
1228
+ type: 'directional';
1229
+ /** Direction azimuth in degrees (0 = +x, CCW). Default 0. Animatable. */
1230
+ azimuth?: number | Keyframe[] | Expr;
1231
+ /** Direction elevation in degrees above the screen plane. Default 45. Animatable. */
1232
+ elevation?: number | Keyframe[] | Expr;
1233
+ color?: string;
1234
+ intensity?: number | Keyframe[] | Expr;
1235
+ };
1236
+ /**
1237
+ * The scene environment surfaces reflect (CKP/1.0, §4.8). Either a
1238
+ * gradient "sky" or an equirectangular image, sampled along the
1239
+ * reflection vector. Roughness blurs the reflection toward the
1240
+ * environment's average color (so both types share one IBL path).
1241
+ */
1242
+ export type Environment = {
1243
+ type: 'gradient';
1244
+ /** Gradient stops; offset 0 = looking down, 1 = looking up. */
1245
+ stops: GradientStop[];
1246
+ } | {
1247
+ /**
1248
+ * Equirectangular (2:1 lat-long) environment image URL. Reflective
1249
+ * surfaces mirror it along the reflection vector — real photographic
1250
+ * reflections (Phase 3 IBL). Sharp at roughness 0, blurring toward
1251
+ * the image's average color as roughness rises.
1252
+ */
1253
+ type: 'image';
1254
+ src: string;
1255
+ };
1256
+ export interface Source {
1257
+ /**
1258
+ * The Clipkit Protocol version this Source conforms to. SHOULD be
1259
+ * present on documents produced by tooling. Absence is interpreted as
1260
+ * "1.0" for backward compatibility. Runtimes MUST attempt to render
1261
+ * documents declaring a higher patch / minor version and SHOULD warn
1262
+ * about a higher major version. See PROTOCOL.md §11. The 3D transform
1263
+ * fields and `camera` require "1.1".
1264
+ */
1265
+ clipkit_version?: string;
1266
+ output_format?: OutputFormat;
1267
+ width?: number;
1268
+ height?: number;
1269
+ duration?: number | 'auto';
1270
+ frame_rate?: number;
1271
+ background_color?: string;
1272
+ /**
1273
+ * Font faces the runtime must register before rendering. Each entry is
1274
+ * loaded via the FontFace API; the resulting face becomes available
1275
+ * under `family` at the given `weight`/`style`. Without this block,
1276
+ * the runtime depends on the host document to have registered the
1277
+ * fonts itself.
1278
+ */
1279
+ fonts?: FontFace[];
1280
+ /**
1281
+ * Exact sub-frame supersampled motion blur, applied to the whole frame
1282
+ * at export/render time. See the MotionBlur type for the normative
1283
+ * sampling math. Previews may show the unblurred scene.
1284
+ */
1285
+ motion_blur?: MotionBlur;
1286
+ /**
1287
+ * Scene perspective camera (CKP/1.0, §4.4). Absent = exact 2D
1288
+ * (identity projection, zero cost).
1289
+ */
1290
+ camera?: Camera;
1291
+ /**
1292
+ * Scene lights (CKP/1.0, §4.8). Absent ⇒ unlit (today's render). Only
1293
+ * elements that carry a `material` are shaded by these.
1294
+ */
1295
+ lights?: Light[];
1296
+ /**
1297
+ * The environment reflective materials sample (CKP/1.0, §4.8). A
1298
+ * gradient "sky" in Phase 1.
1299
+ */
1300
+ environment?: Environment;
1301
+ /**
1302
+ * Scene bloom (CKP/1.0 Phase 2, §4.8) — a whole-frame post-process:
1303
+ * pixels brighter than `threshold` bleed light into their surroundings
1304
+ * (bright specular highlights, emissive surfaces, bright media). Opt-in;
1305
+ * absent ⇒ no bloom (byte-identical). The amount each region blooms is
1306
+ * driven by its own brightness — these are the global "lens" knobs.
1307
+ */
1308
+ bloom?: Bloom;
1309
+ elements: Element[];
1310
+ }
1311
+ /** Scene bloom parameters (§4.8). All animatable. */
1312
+ export interface Bloom {
1313
+ /** Luma above which a pixel blooms, 0..1. Default 0.75. */
1314
+ threshold?: number | Keyframe[] | Expr;
1315
+ /** Soft knee width above the threshold, 0..1. Default 0.1. */
1316
+ knee?: number | Keyframe[] | Expr;
1317
+ /** Bloom add strength. Default 1. */
1318
+ intensity?: number | Keyframe[] | Expr;
1319
+ /** Blur spread (Gaussian σ) in canvas px. Default 24. */
1320
+ radius?: number | Keyframe[] | Expr;
1321
+ }
1322
+ export interface ParsedValue {
1323
+ value: number;
1324
+ unit: Unit;
1325
+ }
1326
+ //# sourceMappingURL=types.d.ts.map