@vosjs/studio-core 0.19.1 → 0.21.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.
package/dist/index.d.ts CHANGED
@@ -61,7 +61,13 @@ interface ZoomStyleParams {
61
61
  autoZoom: boolean;
62
62
  /** clicks within this many seconds merge into one span (session merge). */
63
63
  clusterGap: number;
64
- /** minimum clicks for a cluster to earn a zoom (Cursorful's ≥2 rule). */
64
+ /**
65
+ * Minimum clicks for a cluster to earn a zoom. Every style is 1: the
66
+ * Cursorful ≥2 rule glide and keynote carried was never in effect on a
67
+ * recorded take (the recorder counted each press twice, so a lone click
68
+ * was a pair on most pages and a stray on the rest), and the lone click on
69
+ * a button is the zoom people miss when it goes.
70
+ */
65
71
  minClusterClicks: number;
66
72
  /** zoom so the clicked element fills ~this fraction of the frame. */
67
73
  targetFill: number;
@@ -1353,6 +1359,18 @@ interface HtmlOverlayClip extends OverlayClipBase {
1353
1359
  * component designed at 452 px lands at 452 px in a 1080p frame.
1354
1360
  */
1355
1361
  width?: number;
1362
+ /**
1363
+ * A LIVE layer: the picture is a function of clip-local time. CSS
1364
+ * `@keyframes` inside it are scrubbed to t (paused, delayed by -t), and
1365
+ * `{{t}}` / `{{data.<name>}}` placeholders in the markup and CSS fill per
1366
+ * frame, so a counter, a progress bar or a typed line come out on the
1367
+ * timeline's clock in the preview and in every export chunk alike. One
1368
+ * rasterize per frame while on screen. Absent = a still, byte-identical
1369
+ * to before the field existed.
1370
+ */
1371
+ live?: boolean;
1372
+ /** Values for a live layer's `{{data.<name>}}` placeholders. */
1373
+ data?: Record<string, string | number | boolean>;
1356
1374
  /** Opacity 0..1. Absent = 1. */
1357
1375
  opacity?: number;
1358
1376
  }
@@ -2011,11 +2029,18 @@ interface SmoothOptions {
2011
2029
  declare function smoothCursor(track: CursorTrack, options?: SmoothOptions): SmoothPoint[];
2012
2030
 
2013
2031
  /**
2014
- * A click cluster whose element FITS the frame at less than this level is
2015
- * not a target: it is a drag (aiming, scrubbing, moving a thing across the
2016
- * canvas) or a frame-sized surface, and a zoom on it says nothing. Five real
2017
- * takes (2026-08-25) each carried 1-4 such clusters, planned at the floor
2018
- * level for 10-25s; every one was dropped by hand. Now they plan nothing.
2032
+ * A press whose element FITS the frame at less than this level is not a
2033
+ * target: it is a drag (aiming, scrubbing, moving a thing across the canvas)
2034
+ * or a frame-sized surface (a canvas, a panel, a modal clicked to focus or
2035
+ * dismiss it), and a zoom on it says nothing. Five real takes (2026-08-25)
2036
+ * each carried 1-4 clusters of these, planned at the floor level for 10-25s;
2037
+ * every one was dropped by hand. Now they plan nothing.
2038
+ *
2039
+ * The rule is PER PRESS, never per cluster: a target cluster's level came
2040
+ * from its LARGEST element, so one press on a panel inside a chain of button
2041
+ * clicks read the whole chain as a drag and threw every zoom away — two real
2042
+ * takes (2026-09-11) lost 5 of 8 and 4 of 4 clicks that way. A surface press
2043
+ * is set aside before clustering; the targets around it keep their zoom.
2019
2044
  */
2020
2045
  declare const DRAG_FIT_LEVEL = 1.15;
2021
2046
  interface PlanOptions {
@@ -2105,6 +2130,12 @@ interface Moment {
2105
2130
  /** Normalized target bounds (element rect union), when the events had one. */
2106
2131
  rect: NormRect | null;
2107
2132
  clicks?: number;
2133
+ /**
2134
+ * A click cluster on a frame-sized element (a canvas, a panel, a drag):
2135
+ * the planner frames nothing on it (DRAG_FIT_LEVEL), so a zoom here is
2136
+ * the agent's call, never a proposal.
2137
+ */
2138
+ surface?: boolean;
2108
2139
  pings?: number;
2109
2140
  /** Motion in the window, 0..1 (fraction of changed pixels), null without frames. */
2110
2141
  activity: number | null;
@@ -3090,7 +3121,7 @@ declare function studioEntry(data: Record<string, unknown>): StudioEntry;
3090
3121
  * import, so the composer is carried twice and `htmlOverlay.test.ts` pins
3091
3122
  * the two byte-for-byte. Change them together.
3092
3123
  */
3093
- declare const HTML_LAYER_SVG_CODE = "(src, faces) => {\n const bleed = Math.max(0, Math.round(src.bleed || 0))\n const boxW = src.box.width + bleed * 2\n const boxH = src.box.height + bleed * 2\n let fontCss = ''\n for (const f of faces || []) fontCss += \"@font-face{font-family:'\" + f.family + \"';src:url(\" + f.dataUri + \") format('woff2');font-weight:\" + f.weight + \";font-style:\" + f.style + \";font-display:block}\"\n const wrapper = '.vos-html-layer{width:' + boxW + 'px;height:' + boxH + 'px;box-sizing:border-box;margin:0;padding:' + bleed + 'px;font-family:-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:geometricPrecision}'\n return '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"' + boxW + '\" height=\"' + boxH + '\" viewBox=\"0 0 ' + boxW + ' ' + boxH + '\">' +\n '<foreignObject x=\"0\" y=\"0\" width=\"' + boxW + '\" height=\"' + boxH + '\">' +\n '<div xmlns=\"http://www.w3.org/1999/xhtml\" class=\"vos-html-layer\">' +\n '<style>' + fontCss + wrapper + (src.css || '') + '</style>' + src.html +\n '</div></foreignObject></svg>'\n}";
3124
+ declare const HTML_LAYER_SVG_CODE = "(src, faces, assets) => {\n const bleed = Math.max(0, Math.round(src.bleed || 0))\n const boxW = src.box.width + bleed * 2\n const boxH = src.box.height + bleed * 2\n let fontCss = ''\n for (const f of faces || []) fontCss += \"@font-face{font-family:'\" + f.family + \"';src:url(\" + f.dataUri + \") format('woff2');font-weight:\" + f.weight + \";font-style:\" + f.style + \";font-display:block}\"\n const t = src.t || 0\n const fmtT = (n) => String(Math.round(n * 1000) / 1000)\n const esc = (v) => String(v).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n const fill = (text) => {\n let out = text\n for (const a of assets || []) out = out.split(a.url).join(a.dataUri)\n if (src.live) {\n out = out.replace(/\\{\\{\\s*t\\s*\\}\\}/g, fmtT(t))\n out = out.replace(/\\{\\{\\s*data\\.([A-Za-z0-9_.-]+)\\s*\\}\\}/g, (m, name) => { const v = src.data ? src.data[name] : undefined; return v == null ? '' : esc(v) })\n }\n return out\n }\n const scrub = src.live ? '.vos-html-layer{--vos-t:' + fmtT(t) + '}.vos-html-layer,.vos-html-layer *{animation-play-state:paused!important;animation-delay:calc(var(--vos-t) * -1s)!important}' : ''\n const wrapper = '.vos-html-layer{width:' + boxW + 'px;height:' + boxH + 'px;box-sizing:border-box;margin:0;padding:' + bleed + 'px;font-family:-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:geometricPrecision}'\n return '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"' + boxW + '\" height=\"' + boxH + '\" viewBox=\"0 0 ' + boxW + ' ' + boxH + '\">' +\n '<foreignObject x=\"0\" y=\"0\" width=\"' + boxW + '\" height=\"' + boxH + '\">' +\n '<div xmlns=\"http://www.w3.org/1999/xhtml\" class=\"vos-html-layer\">' +\n '<style>' + fontCss + wrapper + fill(src.css || '') + scrub + '</style>' + fill(src.html) +\n '</div></foreignObject></svg>'\n}";
3094
3125
 
3095
3126
  /** The design frame an HTML layer's `box` is measured against (1080p). */
3096
3127
  declare const DESIGN_FRAME_WIDTH = 1920;
@@ -3113,6 +3144,14 @@ interface HtmlLayerSource {
3113
3144
  height: number;
3114
3145
  };
3115
3146
  bleed?: number;
3147
+ /**
3148
+ * A LIVE layer (§3.13): the picture is a function of clip-local time.
3149
+ * `t` is the moment being drawn, `data` the clip's own values for the
3150
+ * `{{data.<name>}}` placeholders. Absent = a still.
3151
+ */
3152
+ live?: boolean;
3153
+ t?: number;
3154
+ data?: Record<string, string | number | boolean>;
3116
3155
  }
3117
3156
  interface HtmlLayerInlineFace {
3118
3157
  family: string;
@@ -3120,6 +3159,11 @@ interface HtmlLayerInlineFace {
3120
3159
  style: 'normal' | 'italic';
3121
3160
  dataUri: string;
3122
3161
  }
3162
+ /** An image the source names by URL, fetched and inlined by the page. */
3163
+ interface HtmlLayerInlineAsset {
3164
+ url: string;
3165
+ dataUri: string;
3166
+ }
3123
3167
  declare function cssFamilies(css: string | undefined): string[];
3124
3168
  /**
3125
3169
  * The weights the CSS and the markup ask for: `font-weight` numbers and
@@ -3180,7 +3224,7 @@ declare function htmlLayerWidth(clip: Pick<HtmlOverlayClip, 'width' | 'box' | 'c
3180
3224
  declare function htmlLayerText(html: string): string;
3181
3225
  /** The timeline item's label: the first words, else the kind. */
3182
3226
  declare function htmlLayerLabel(clip: Pick<HtmlOverlayClip, 'html'>): string;
3183
- type HtmlLayerProblemCode = 'empty' | 'unclosed-void' | 'bare-ampersand' | 'unquoted-attr' | 'too-large' | 'foreign-field' | 'shadow-clipped' | 'external-resource' | 'wall-clock' | 'unhosted-family';
3227
+ type HtmlLayerProblemCode = 'empty' | 'unclosed-void' | 'bare-ampersand' | 'unquoted-attr' | 'too-large' | 'foreign-field' | 'shadow-clipped' | 'external-resource' | 'wall-clock' | 'live-delay' | 'placeholder-still' | 'unhosted-family';
3184
3228
  interface HtmlLayerProblem {
3185
3229
  code: HtmlLayerProblemCode;
3186
3230
  /** A problem stops the layer painting; a warning is a layer that paints wrong. */
@@ -3196,7 +3240,7 @@ declare const HTML_LAYER_FOREIGN_FIELDS: readonly ["key", "radius", "shadow", "b
3196
3240
  * author is told instead, the same answer in the studio, in `vos validate`
3197
3241
  * and in a test.
3198
3242
  */
3199
- declare function htmlLayerProblems(clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'bleed' | 'fonts'>): HtmlLayerProblem[];
3243
+ declare function htmlLayerProblems(clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'bleed' | 'fonts' | 'live'>): HtmlLayerProblem[];
3200
3244
  /**
3201
3245
  * The real XML question, asked of a real parser: the studio hands in
3202
3246
  * `DOMParser`, a test hands in a stub. The CLI does not call this (Node
@@ -3220,9 +3264,15 @@ declare function wellFormedXml(svg: string, parseXml: (text: string) => {
3220
3264
  * resets the two properties an SVG document does not inherit sensibly;
3221
3265
  * everything else is the author's.
3222
3266
  *
3267
+ * Assets the source names by URL are swapped for their bytes. A LIVE layer
3268
+ * is composed for one moment: `{{t}}` and `{{data.<name>}}` fill, and a
3269
+ * scrub block after the author's CSS pauses every animation and delays it
3270
+ * by -t, so the browser lays the frame out at exactly that offset (the
3271
+ * standard way to scrub a CSS animation, here applied per picture).
3272
+ *
3223
3273
  * MIRRORED by `HTML_LAYER_SVG_CODE` in `lower/studioEntry.ts`. Change both.
3224
3274
  */
3225
- declare function htmlLayerSvg(src: HtmlLayerSource, faces?: readonly HtmlLayerInlineFace[]): string;
3275
+ declare function htmlLayerSvg(src: HtmlLayerSource, faces?: readonly HtmlLayerInlineFace[], assets?: readonly HtmlLayerInlineAsset[]): string;
3226
3276
  /**
3227
3277
  * The layer's CACHE KEY: content-addressed over everything the picture is
3228
3278
  * made of (markup, CSS, box, bleed, the resolved faces), so an edit to the
@@ -3231,7 +3281,7 @@ declare function htmlLayerSvg(src: HtmlLayerSource, faces?: readonly HtmlLayerIn
3231
3281
  * scaling or animating the clip does not re-key it: none of those repaint.
3232
3282
  * The key is a handle, never a URL: nothing may fetch it.
3233
3283
  */
3234
- declare function htmlLayerKey(clip: Pick<HtmlOverlayClip, 'id' | 'html' | 'css' | 'box'>, faces: readonly HtmlLayerFace[], bleed: number): string;
3284
+ declare function htmlLayerKey(clip: Pick<HtmlOverlayClip, 'id' | 'html' | 'css' | 'box' | 'live' | 'data'>, faces: readonly HtmlLayerFace[], bleed: number, assets?: readonly string[]): string;
3235
3285
  /** The shape the page is handed for a layer: the source plus resolved faces. */
3236
3286
  interface HtmlLayerPayload {
3237
3287
  markup: string;
@@ -3242,6 +3292,11 @@ interface HtmlLayerPayload {
3242
3292
  };
3243
3293
  bleed: number;
3244
3294
  faces: HtmlLayerFace[];
3295
+ /** Images the source names by URL, for the page to fetch and inline. */
3296
+ assets: string[];
3297
+ /** A live layer: the page composes the picture per frame at clip-local t. */
3298
+ live?: true;
3299
+ data?: Record<string, string | number | boolean>;
3245
3300
  }
3246
3301
  /** Everything the lowering emits for a layer, from the clip alone. */
3247
3302
  declare function htmlLayerPayload(clip: HtmlOverlayClip): {