@vosjs/studio-core 0.29.0 → 0.30.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 +25 -1
- package/dist/index.js +39 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/callout.ts","../src/capture.ts","../src/ingest.ts","../src/lower/motion.ts","../src/anim.ts","../src/media.ts","../src/zoomStyle.ts","../src/docVersion.ts","../src/doc/studioDoc.ts","../src/planner/smoothing.ts","../src/planner/autoZoom.ts","../src/lower/lowerToComposition.ts","../src/layout.ts","../src/stage.ts","../src/overlayText.ts","../src/htmlLayer.ts","../src/text3d.ts","../src/lower/segmentStarts.ts","../src/lower/transitions.ts","../src/lower/audioEnvelope.ts","../src/lower/cursorFollow.ts","../src/lower/pin.ts","../src/lower/cursorIdle.ts","../src/lower/studioEntry.ts","../src/lower/extractClicks.ts","../src/planner/autoTilt.ts","../src/planner/autoSpeed.ts","../src/digest/moments.ts","../src/digest/scenes.ts","../src/digest/framing.ts","../src/digest/style.ts","../src/audioBeds.ts","../src/digest/geometry.ts","../src/digest/build.ts","../src/timeline/rangeActions.ts","../src/lower/lowerStudioDoc.ts","../src/destinations.ts","../src/htmlStarters.ts","../src/lower/audioPlan.ts","../src/look.ts","../src/timeline/lanes.ts","../src/waveform.ts","../src/lower/duckCurve.ts","../src/backdrop.ts","../src/rejected.ts"],"sourcesContent":["/**\n * The studio — shared contracts.\n *\n * These are the seams between the capture extension, the editor, the planner,\n * and the lowering. Branding lives at the app layer;\n * these types are intentionally generic so the core stays extraction-ready.\n */\nimport type { Segment } from '@vosjs/timeline'\nimport type { ZoomStyleName, ZoomStyleParams } from './zoomStyle'\nimport type { SpeedParams } from './planner/autoSpeed'\n\nexport type { Segment }\n\n/**\n * A kept span. `hold` is the LEGACY spelling of a trailing freeze (a\n * freeze on the segment's last frame for these output seconds): it is\n * read on every path and migrated into `doc.freeze` on read\n * (`migrateMotion`), so nothing new writes it.\n */\nexport type DocSegment = Segment & {\n /** The media this clip plays (`doc.media[].id`); absent = the primary, `doc.source`. */\n media?: string\n /**\n * How this clip meets the clips beside it: `enter` is its arrival at the\n * boundary with the clip BEFORE it, `exit` its leaving at the boundary\n * with the clip AFTER it (`slide` | `fade` | `scale` | `none`; a slide\n * names its `side`, a step its `seconds`). When an exit and the next\n * clip's enter meet at one boundary they run together, the outgoing\n * frozen on its last frame sliding away while the incoming plays: a\n * push. The first clip's enter and the last clip's exit are the CARD's\n * own (`frame.anim`) and are ignored here. Absent = a hard cut.\n */\n anim?: Anim\n /** @deprecated migrated on read into a `FreezeSpan` at this segment's `out`. */\n hold?: number\n}\n\n/** A single input event captured in the page, relative to the recording's t0. */\nexport interface CursorEvent {\n /** ms since t0 (recording start). */\n t: number\n /** viewport CSS px. */\n x: number\n y: number\n /**\n * Screen-coordinate CSS px (MouseEvent.screenX/Y) — the mapping anchor for\n * window/monitor captures, where the viewport is only part of the frame.\n * Carrying both spaces per event also gives an exact per-event viewport→screen\n * offset (sx−x, sy−y) for transforming element rects. Absent on old tracks.\n */\n sx?: number\n sy?: number\n /**\n * `key` is a typing-ACTIVITY ping (throttled): when and where typing is\n * happening, never what is typed — the payload must not carry key identity\n * or input contents at any layer. Position/rect follow the `focus`\n * convention: the focused editable element's center + bounds.\n */\n type: 'move' | 'down' | 'up' | 'scroll' | 'focus' | 'key'\n /** pointer button for down/up (0=left). */\n button?: 0 | 1 | 2\n /** target element bounds at event time — enables element-aware auto-zoom. */\n rect?: Rect\n}\n\nexport type CursorTrack = CursorEvent[]\n\nexport interface Rect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** Recording metadata needed to map captured pixels ↔ cursor coords ↔ time. */\nexport interface RecordingMeta {\n /** device pixel ratio at capture. */\n dpr: number\n /** page/browser zoom at capture. */\n zoom: number\n /** wall-clock origin (Date.now at first frame). */\n t0: number\n durationMs: number\n /** captured pixel dimensions. */\n width: number\n height: number\n fps: number\n /**\n * The recording's OWN file carries an audio track — unmute preview, mux into\n * export. After the AT split that track is SYSTEM/tab audio only (it rides\n * the same stream as the video, sample-aligned by construction); on takes\n * recorded before the split it is the legacy record-time mic+system mix.\n */\n hasAudio?: boolean\n /**\n * A separately-recorded microphone sidecar exists. The mic never\n * enters the video file — it records through its own audio-only\n * MediaRecorder so the studio can gain/mute/duck it independently.\n */\n hasMic?: boolean\n /**\n * Recorder start skew: wall-clock ms between the main recorder's start and\n * the mic/cam sidecar recorders' starts (positive = sidecar started later).\n * Lets the consume path trim/pad a sidecar head instead of assuming t0\n * equality. Absent on takes without the matching sidecar.\n */\n micT0DeltaMs?: number\n camT0DeltaMs?: number\n /**\n * Encoded frame dimensions in device px, from the capture track's settings.\n * `width`/`height` are the CSS-px viewport (the CursorEvent coordinate space);\n * these are the actual video pixels — same aspect when capture is constrained\n * to the tab size, but keep both spaces so mapping never assumes it.\n */\n captureWidth?: number\n captureHeight?: number\n /**\n * The tab viewport changed size mid-take. Capture resolution is fixed for the\n * whole take, so Chrome letterboxes the resized content — surface a notice.\n */\n resizedDuringTake?: boolean\n /**\n * Page URL/title at record start (seeds the browser-bar mock's address pill).\n * Query/hash are stripped at capture for privacy.\n */\n pageUrl?: string\n pageTitle?: string\n /**\n * What the frame contains. Absent = 'tab' (back-compat). Non-tab surfaces use\n * CursorEvent.sx/sy + the geometry rects below to map cursor → capture px\n * (see normalizeCaptureSpace); tab-only studio features (browser-bar mock,\n * letterbox notice) are gated off for them.\n */\n captureSurface?: 'tab' | 'window' | 'monitor'\n /**\n * Target-tab browser-window bounds at record start, screen-coord CSS px\n * (window.screenX/Y + outerWidth/Height). Anchor for 'window' captures.\n */\n windowRect?: Rect\n /**\n * FULL bounds of the display hosting the target window at record start,\n * screen-coord CSS px (chrome.system.display bounds — the true origin,\n * including the macOS menu bar; page availLeft/Top only as a fallback).\n * Anchor for 'monitor' captures; wrong-display shares surface as low\n * coverage and fall back to no auto-zoom.\n */\n screenRect?: Rect\n /**\n * The target window moved or resized during a 'window' take — the single\n * windowRect anchor can't map the whole track, so the studio drops the\n * cursor rather than rendering it at stale positions.\n */\n windowMovedDuringTake?: boolean\n /**\n * Viewport CSS-px size (innerWidth/Height) of the recorded tab at record\n * start. On 'window' takes this + windowRect + the cursor events' screen\n * coords derive the viewport crop that removes the real browser chrome from\n * the footage (deriveViewportCrop) so the synthetic browser bar applies.\n */\n viewport?: { w: number; h: number }\n /**\n * The tab viewport changed size mid-take on a display take (resize, devtools\n * dock, zoom) — the static viewport crop can't map the whole take, so crop\n * derivation fails closed.\n */\n viewportChangedDuringTake?: boolean\n /**\n * Fraction of a 'window' take during which the target tab's browser window\n * was the FOCUSED window (chrome.windows.onFocusChanged, pause-gated).\n * The wrong-window tell that geometry can't provide: cursor events come from\n * the recorded tab and its window geometry is self-consistent, so sharing a\n * DIFFERENT window (Finder, another app — even one with identical bounds)\n * still maps events \"in frame\". But driving that other window means focusing\n * it — a low fraction ⇒ the footage isn't the browser window, so cursor\n * effects and the viewport crop must fail closed (WINDOW_FOCUS_MIN).\n */\n windowFocusedFrac?: number\n /** Recorder OS (chrome.runtime.getPlatformInfo) — seeds the browser-bar style. */\n platform?: 'mac' | 'windows' | 'linux'\n /**\n * Which recorder produced the artifact. CLI takes synthesize the cursor\n * track from automation (exact coords, fresh rects, coverage 1 by\n * construction) and encode WebM; absent means the extension.\n */\n producer?: 'extension' | 'cli'\n /**\n * The step timeline: when each actions.json step ran, in SOURCE\n * seconds. This is what makes a cut re-anchorable across re-records — a\n * span anchored to a step re-times to wherever that step landed in the\n * new recording (`vos plan --reuse`). CLI takes only; a human recording\n * has no script and carries none.\n */\n steps?: StepSpan[]\n /**\n * The recorder did not land where it was asked: a sign-in, an identity\n * provider, a 401/403, or a redirect to somewhere else. Present only when\n * the take was recorded ANYWAY, so whoever reads it later (a digest, an\n * agent cutting the footage) knows the picture may be the wrong page.\n * `asked`/`landed` are origin + path, never a query or hash.\n */\n wall?: {\n kind: 'status' | 'idp' | 'signin' | 'redirect'\n asked: string\n landed: string\n }\n /**\n * Sensitive-looking text the recorder saw IN the frame: an email address,\n * something shaped like a key, a card number. The KIND and the place, never\n * the string. Each is listed once, at the first step it was visible\n * (`step` -1 = as the page opened); `seen` counts the scans that saw it.\n * `rect` is viewport CSS px. Absent on a recorder that does not look.\n */\n exposures?: {\n step: number\n kind: 'email' | 'key' | 'card' | 'card-tail'\n selector: string\n rect: { x: number; y: number; w: number; h: number }\n seen: number\n }[]\n /**\n * What the script asked the recorder to hide before the camera rolled, and\n * how many elements each selector reached. `hits: 0` is a mask that hid\n * nothing, so whatever it was for may be showing.\n */\n masks?: { selector: string; as: 'blur' | 'text'; hits: number }[]\n}\n\n/**\n * A span's tie to an actions.json step: metadata for `vos plan\n * --reuse`, which re-times the span onto a NEW recording of the same script\n * by resolving the step in the new `meta.steps`. NEVER read by lowering —\n * seconds stay the wire truth (`in`/`out` are always authoritative), so a\n * human recording with no steps renders identically with or without one.\n */\nexport interface StepAnchor {\n /** The step: its `id` from actions.json when it has one, else its index. */\n step: string | number\n /** Which edge of the step the span's `in` is measured from. Default 'start'. */\n at?: 'start' | 'end'\n /** Seconds from that edge to the span's `in` (negative = before it). */\n offset?: number\n}\n\n/** The lanes whose planners propose spans, and whose proposals can be rejected. */\nexport type RejectedLane = 'zoom' | 'tilt' | 'speed'\n\n/**\n * A planner proposal the human or the agent deleted, kept so no re-plan\n * proposes it again: the lane and the SOURCE extent of the deleted `auto`\n * span (plus its step anchor when it had one). Every auto-merge drops a\n * fresh proposal that lands on a rejected extent of the same lane;\n * `plan --reuse` re-times these the way it re-times a manual span.\n */\nexport interface RejectedSpan {\n /** `r{n}`, stable for the differ and the history. */\n id: string\n lane: RejectedLane\n /** The media the deleted span sat on; absent = the primary. */\n media?: string\n /** SOURCE seconds, the deleted span's extent. */\n in: number\n out: number\n anchor?: StepAnchor\n /** Why, in the deleter's words. Optional. */\n note?: string\n}\n\n/** One executed actions.json step's extent in the recording. */\nexport interface StepSpan {\n /** index into actions.steps at record time. */\n step: number\n /** the step's own id from actions.json, when it names one — an id lets a\n * step move or be reordered without breaking anchors (absent = the index\n * is the identity). */\n id?: string\n do: string\n selector?: string\n /** SOURCE seconds the gesture occupied, [tStart, tEnd]. */\n tStart: number\n tEnd: number\n /** the selector never became visible — the gesture did not run. */\n skipped?: boolean\n /**\n * The page's URL changed while the step ran: a click that navigated, or\n * the `wait` that let a navigation land. The planner proposes a\n * transition at the page change.\n */\n navigated?: boolean\n /**\n * The selector's element bounds at the gesture, in the cursor track's\n * space (the recorded viewport's CSS px, `meta.width` × `meta.height`) —\n * what a pinned layer names (`overlays[].pin.step`). Absent on a step\n * without a selector, and on takes recorded before the recorder kept it\n * (a pin then reads the presses inside the step's window).\n */\n rect?: Rect\n}\n\n/** Everything the capture extension hands off to the studio. */\nexport interface RecordingArtifact {\n /** OPFS key / object URL for the recorded video. */\n videoKey: string\n cursor: CursorTrack\n /** object URL for the separately-recorded mic sidecar (the mic/system split). */\n audioKey?: string\n /** object URL for a separately-recorded webcam track (drawn as an editable bubble). */\n camKey?: string\n meta: RecordingMeta\n}\n\n/**\n * Per-span transition speed — how fast the camera/bubble/card moves\n * into and out of a span's state, as NAMED steps (the category convention:\n * Screen Studio's speed words, Descript's one knob — never a curve editor).\n * Multipliers on the lane's own ramp constants, so 'smooth' (absent) is\n * byte-identical to the pre-feature motion and each lane keeps its feel.\n * 'instant' is a hard cut: the ramp collapses to the track emitter's 1ms\n * collision nudge.\n */\nexport type TransitionSpeed = 'instant' | 'fast' | 'smooth' | 'slow'\n\nexport const TRANSITION_SPEED_MULT: Record<TransitionSpeed, number> = {\n instant: 0,\n fast: 0.5,\n smooth: 1,\n slow: 1.6,\n}\n\n/** A span's ramp multiplier (absent = 'smooth' = 1, the exact legacy motion). */\nexport function transitionMult(t: TransitionSpeed | undefined): number {\n return t !== undefined && t in TRANSITION_SPEED_MULT\n ? TRANSITION_SPEED_MULT[t]\n : 1\n}\n\n/**\n * A speed-change region over a SOURCE-time span (seconds). Footage-anchored\n * like zoom keyframes — it follows its content through trims/splits, and a\n * span whose footage is fully cut away simply has no effect (and comes back\n * if the trim is undone). Non-overlapping (the lane clamps). The lowering\n * intersects spans with `segments` via @vosjs/timeline `splitBySpeed` into\n * rated segments; playback, export, and lane display all evaluate those.\n */\nexport interface SpeedSpan {\n /** Stable identity for selection/editing in the timeline UI. */\n id: string\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /** Playback rate (> 0): 2 = twice as fast, 0.5 = half speed. */\n rate: number\n /**\n * The auto-zoom wand contract: 'auto' = planner suggestion\n * (planAutoSpeed — typing/scroll/idle), replaced by a re-plan; 'manual' =\n * user/agent work, always preserved. Absent = manual (spans predating the contract).\n */\n source?: 'auto' | 'manual'\n}\n\n/**\n * A FREEZE: the footage frozen on one frame for `seconds` of output. A\n * SOURCE moment (`at`), footage-anchored like a speed span so it follows\n * its frame through trims and splits (a freeze whose frame is cut away has\n * no effect, and comes back if the trim is undone), plus output seconds.\n * The retime primitive beside `SpeedSpan`, on the same lane: a speed span\n * is a source RANGE with a rate, a freeze a source POINT with a length,\n * and a rate of zero is deliberately not how it is spelled (the pointer-\n * true gesture maps divide by the rate). Lowered as a rated piece whose\n * hair of source time plays for the seconds, so every consumer of the\n * rated list (mapTime, the zoom remap, the audio splice, the duration)\n * inherits it from one seam. The still every still-taking surface takes\n * is where the LAST freeze begins (`docRestTime`).\n */\nexport interface FreezeSpan {\n /** Stable identity for selection/editing (`f{n}`). */\n id: string\n /** The media the frozen frame is on; absent = the primary. */\n media?: string\n /** SOURCE seconds: the frame that freezes is the one just before `at`. */\n at: number\n /** OUTPUT seconds the frame holds. */\n seconds: number\n /** Re-record tie to an actions.json step; `at` stays the truth. */\n anchor?: StepAnchor\n /** The template that placed it (the clips' `from`), so a re-apply replaces it and a loop drops it. */\n from?: string\n}\n\n/** Freeze length bounds, output seconds. */\nexport const FREEZE_SECONDS_MIN = 0.25\nexport const FREEZE_SECONDS_MAX = 30\n/** A new freeze's length, output seconds. */\nexport const FREEZE_DEFAULT_SECONDS = 2\n\n/** Clamp + quantize a freeze's seconds for storage (3 decimals). */\nexport function clampFreezeSeconds(seconds: number): number {\n const s = Math.min(FREEZE_SECONDS_MAX, Math.max(FREEZE_SECONDS_MIN, seconds))\n return Math.round(s * 1000) / 1000\n}\n\n/**\n * Speed-rate bounds. 16 is also Chromium's HTMLMediaElement.playbackRate\n * ceiling, so preview (native playback) and export (offline resample) can\n * honor the same range.\n */\nexport const SPEED_RATE_MIN = 0.1\nexport const SPEED_RATE_MAX = 16\n\n/**\n * Minimum speed-span length in OUTPUT seconds (the lane converts through the\n * span's own rate: a 2× span may not shrink below 0.5s of source). A source\n * floor shrank with the rate — 0.1s of source at 5× was 20ms of screen, a\n * sliver nobody could grab again.\n */\nexport const SPEED_SPAN_MIN = 0.25\n\n/** Clamp + quantize a speed rate for storage (2 decimals, like \"1.75×\"). */\nexport function clampSpeedRate(rate: number): number {\n const r = Math.min(SPEED_RATE_MAX, Math.max(SPEED_RATE_MIN, rate))\n return Math.round(r * 100) / 100\n}\n\n/**\n * A zoom region over a SOURCE-time span (seconds) — one adjustable clip on the\n * zoom lane. Footage-anchored like SpeedSpan/CamStyle.window: it follows its\n * content through trims/splits (a span whose footage is fully cut away renders\n * nothing, and comes back if the trim is undone; a partially-cut span keeps its\n * kept extent). Non-overlapping (the lane clamps). The camera ramps in around\n * `in`, holds `[level, cx, cy]` until `out`, then ramps back to 1× — or pans\n * straight to the next span when the gap is short (see `zoomTrackFromDoc`).\n */\nexport interface ZoomSpan {\n /** Stable identity for selection/editing (`z{n}` planner, `u{n}` user). */\n id: string\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /** zoom level (1 = no zoom), ZOOM_LEVEL_MIN..ZOOM_LEVEL_MAX, 2 decimals. */\n level: number\n /** focus point in normalized [0..1] video-frame coords. */\n cx: number\n cy: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the default ramp ease. */\n ease?: string\n /**\n * Transition speed for THIS span's ramps (in, out, and the pan arriving\n * here from a chained neighbor). Absent = 'smooth', the camera style's\n * stock motion; 'instant' is a hard cut.\n */\n transition?: TransitionSpeed\n /**\n * 'auto' = the camera follows the cursor through the span (dead-zone\n * recenter, baked deterministically at lowering — see followFocusEvents);\n * absent/'manual' = the fixed cx/cy focus.\n */\n focusMode?: 'manual' | 'auto'\n /**\n * 'auto' = planner suggestion — regenerate replaces these freely, never\n * 'manual' ones. Any edit gesture promotes the span to 'manual' (OpenScreen's\n * contract: suggestions are disposable, user work is sacred).\n */\n source?: 'auto' | 'manual'\n}\n\n/** Zoom-level preset chips (OpenScreen-style picker). */\nexport const ZOOM_LEVELS = [1.25, 1.5, 1.8, 2.2, 3.5, 5] as const\nexport const ZOOM_LEVEL_MIN = 1\nexport const ZOOM_LEVEL_MAX = 5\n/** Default level for new/user-created zooms. */\nexport const DEFAULT_ZOOM_LEVEL = 1.8\n/**\n * Minimum zoom-span length in OUTPUT seconds (the lane clamps resizes,\n * converting through the rate in force so the floor is what the eye sees).\n */\nexport const ZOOM_SPAN_MIN = 0.3\n\n/** Clamp + quantize a zoom level for storage (2 decimals, like \"1.8×\"). */\nexport function clampZoomLevel(level: number): number {\n const l = Math.min(ZOOM_LEVEL_MAX, Math.max(ZOOM_LEVEL_MIN, level))\n return Math.round(l * 100) / 100\n}\n\n/**\n * A tilt region over a SOURCE-time span (seconds) — one adjustable clip on the\n * tilt lane. Footage-anchored\n * like ZoomSpan/SpeedSpan: it follows its content through trims/splits and\n * speed changes (a span whose footage is fully cut away renders nothing, and\n * comes back if the trim is undone). Non-overlapping (the lane clamps). While\n * active the card leans to this pose; between spans it returns to the RESTING\n * FLAT rest pose (there is no static card tilt) — expanded at lowering into an\n * OUTPUT-time [rx, ry] degree keyframe track (see tiltTrackFromDoc).\n */\nexport interface TiltSpan {\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n /** Stable identity for selection/editing (`t{n}` planner, `u{n}` user). */\n id: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /**\n * Pose in DEGREES (the CardTilt convention): rx leans the card back/forward,\n * ry swings it left/right. Gentle values read best (±5..18°).\n */\n rx: number\n ry: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the house tilt ease. */\n ease?: string\n /**\n * Transition speed for this span's ramps. Absent = 'smooth' (the stock\n * tilt motion); 'instant' snaps the card to the pose.\n */\n transition?: TransitionSpeed\n /**\n * 'auto' = Dynamic-tilt wand suggestion — regenerate replaces these freely,\n * never 'manual' ones. Any edit gesture promotes the span to 'manual' (the\n * auto-zoom wand contract).\n */\n source?: 'auto' | 'manual'\n}\n\n/** Hard tilt bound in degrees (schema/lint); UI sliders stay within ±20. */\nexport const TILT_DEG_MAX = 45\n/** UI slider bound — matches the Card panel's static (rest) tilt sliders. */\nexport const TILT_UI_DEG_MAX = 20\n/**\n * Minimum tilt-span length in OUTPUT seconds (the lane clamps resizes). Bigger\n * than ZOOM_SPAN_MIN because tilt ramps are longer — a pose that can't settle\n * isn't a pose.\n */\nexport const TILT_SPAN_MIN = 0.8\n/** Default pose for user-created spans: a medium three-quarter \"showcase\" lean. */\nexport const DEFAULT_TILT_POSE = { rx: 6, ry: -9 }\n\n/** Clamp + quantize a tilt angle for storage (1 decimal, degrees). */\nexport function clampTiltDeg(deg: number): number {\n const d = Math.min(TILT_DEG_MAX, Math.max(-TILT_DEG_MAX, deg))\n return Math.round(d * 10) / 10\n}\n\n/**\n * Dynamic-tilt wand intensity ladder (the category convention — FocuSee ships\n * Subtle/Default/Strong): the max degrees planAutoTilt will lean per axis.\n */\nexport type TiltStyleName = 'off' | 'subtle' | 'medium' | 'strong'\nexport const TILT_INTENSITY_MAX: Record<\n Exclude<TiltStyleName, 'off'>,\n number\n> = {\n subtle: 5,\n medium: 9,\n strong: 14,\n}\n\nexport interface CursorStyle {\n /**\n * Draw the cursor dot. Off still keeps the track: auto-zoom cursor-follow\n * and click effects are independent of whether the dot is painted. Absent\n * reads as visible (pre-toggle docs).\n */\n visible: boolean\n /** 0..1 smoothing strength (lerp factor; higher = smoother/laggier). */\n smoothing: number\n /** rendered cursor size in px. */\n size: number\n /**\n * What the pointer is drawn as. `'arrow'` is an OS-style pointer (a\n * black arrow with a white edge, its tip on the recorded point), the\n * default for new takes: it is the pointer the viewer's own screen wears,\n * so the frame reads as a screen and not as a screen recorder's overlay.\n * `'default'`, `'dot'` and `'ring'` all draw the white dot every earlier\n * take was cut with. Click effects bloom under the tip either way.\n */\n style: 'default' | 'dot' | 'ring' | 'arrow'\n hideWhenIdle: boolean\n clickFx: ClickFxStyle\n}\n\n/**\n * Click-effect styling. Clicks are extracted\n * at lowering (OUTPUT-anchored — see extractClicks) and drawn by ON_FRAME as a\n * pure function of t; every field here is a live SET_DATA edit.\n */\nexport interface ClickFxStyle {\n /** ring drawn at the click point ('highlight' glows the clicked element's rect). */\n style: 'none' | 'ripple' | 'pulse' | 'highlight'\n /** cursor press dip on real down→up spans — independent of ring style. */\n press: boolean\n intensity: 'subtle' | 'medium' | 'strong'\n /** resolved hex for rings/glow; 'auto' = neutral white-over-dark-rim. */\n color: string | 'auto'\n}\n\n/**\n * Named intensity levels → resolved multipliers (size/alpha `k`, duration\n * `dur`), baked into ctx.data at lowering so ON_FRAME needs no registry —\n * the same pattern as MINIMAL_BAR_THEMES resolving to concrete colors.\n */\nexport const CLICK_FX_INTENSITY: Record<\n ClickFxStyle['intensity'],\n { k: number; dur: number }\n> = {\n subtle: { k: 0.7, dur: 0.9 },\n medium: { k: 1, dur: 1 },\n strong: { k: 1.35, dur: 1.1 },\n}\n\n/** Webcam bubble overlay — an editable layer composited over the frame. */\nexport interface CamStyle {\n visible: boolean\n /**\n * Free placement: the bubble CENTER as frame fractions (the overlay\n * and zoom cx/cy convention, so positions survive aspect switches). When\n * present they WIN over `position`; clearing them snaps back to the corner.\n */\n x?: number\n y?: number\n /** corner the bubble is anchored to when x/y are absent. */\n position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n /** bubble diameter as a fraction of frame height (0..1). */\n size: number\n shape: 'circle' | 'rounded'\n /**\n * Corner radius in design px for the 'rounded' shape (absent = 18, the\n * house look; a circle ignores it). Scales with the canvas like every\n * frame-owned control.\n */\n radius?: number\n /**\n * Ring stroke over the bubble edge. Absent = the house ring (3px white at\n * 0.9 alpha — the pre-existing paint); `width: 0` = no ring.\n */\n border?: { width: number; color: string }\n /** Bubble shadow. Absent = 'soft', the pre-existing paint. */\n shadow?: 'none' | 'soft' | 'strong'\n /** mirror horizontally (selfie view). */\n mirror: boolean\n /**\n * Show the bubble only during this SOURCE-time span (trimmed on the cam\n * timeline lane; anchored to footage like zoom keyframes). Absent = always.\n */\n window?: Segment\n}\n\n/**\n * A cam pose region over a SOURCE-time span (seconds) — one adjustable clip on\n * the cam-move lane (MO track: animated cam layouts, the Screen Studio\n * signature). Footage-anchored like ZoomSpan/TiltSpan: it follows its content\n * through trims/splits and speed changes (a span whose footage is fully cut\n * away renders nothing, and comes back if the trim is undone). Non-overlapping\n * (the lane clamps). While active the bubble holds this pose; outside spans it\n * rests at the doc's cam style (doc.cam IS the rest pose); spans close together\n * in output time morph pose-to-pose without returning to rest. Absent pose\n * fields inherit the rest pose, so a span may move without resizing. Expanded\n * at lowering into an OUTPUT-time [x, y, size] fraction keyframe track\n * (camTrackFromDoc) — pure f(t), no springs.\n */\nexport interface CamPoseSpan {\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n /** Stable identity for selection/editing (`m{n}` user-created). */\n id: string\n in: number\n out: number\n /**\n * Bubble CENTER as frame fractions [0..1] (the cam.x/y and zoom cx/cy\n * convention — aspect-stable). Absent = the rest pose's center.\n */\n x?: number\n y?: number\n /** Bubble diameter as a fraction of frame height. Absent = the rest size. */\n size?: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the house cam ease. */\n ease?: string\n /**\n * Transition speed for this span's morphs. Absent = 'smooth' (~0.65s);\n * 'instant' jump-cuts the bubble to its pose — the Screen Studio layout-cut.\n */\n transition?: TransitionSpeed\n /**\n * Reserved for a future auto planner (the wand contract: 'auto' spans are\n * disposable suggestions). Every studio gesture writes 'manual'.\n */\n source?: 'auto' | 'manual'\n}\n\n/**\n * Minimum cam-move span length in OUTPUT seconds (the lane clamps resizes).\n * Between zoom's 0.3 and tilt's 0.8: a bubble move settles faster than a card\n * pose but still needs its ~0.65s ramp to read as a move, not a jump.\n */\nexport const CAM_SPAN_MIN = 0.5\n/** Bubble-size band for pose spans (fractions of frame height; UI + lint). */\nexport const CAM_SIZE_MIN = 0.08\nexport const CAM_SIZE_MAX = 0.6\n\n/** Clamp + quantize a pose size for storage (3 decimals, frame fraction). */\nexport function clampCamSize(size: number): number {\n const v = Math.min(CAM_SIZE_MAX, Math.max(CAM_SIZE_MIN, size))\n return Math.round(v * 1000) / 1000\n}\n\n/** Clamp + quantize a pose center coordinate for storage (3 decimals, [0..1]). */\nexport function clampCamFrac(v: number): number {\n const c = Math.min(1, Math.max(0, v))\n return Math.round(c * 1000) / 1000\n}\n\n/**\n * Default pose for user-created cam-move spans: front-and-center, large —\n * the \"talk to camera\" moment that is the feature's reason to exist (the\n * DEFAULT_TILT_POSE philosophy: a new span shows a visible, editable move,\n * never a no-op).\n */\nexport const DEFAULT_CAM_POSE = { x: 0.5, y: 0.55, size: 0.45 }\n\n/**\n * Mock browser chrome drawn as a strip above the video inside the frame card.\n * Drawn by the compositor (never captured) — the editor-frame pattern every\n * shot/recorder tool uses. All values travel in `ctx.data.frame` (live T2 edits).\n */\nexport interface BrowserBarStyle {\n kind:\n | 'none'\n | 'mac-light'\n | 'mac-dark'\n | 'windows-light'\n | 'windows-dark'\n | 'minimal'\n /** address-pill text (editable; seeded from the recorded page's URL). */\n url: string\n showUrl: boolean\n /** traffic lights (mac) / window buttons (windows). */\n showControls: boolean\n /** bar height in design px (1080-based, same space as padding/radius). */\n height: number\n /**\n * Minimal-bar color theme — RESOLVED colors (not a palette id) so ON_FRAME\n * needs no registry lookup (interpreter rule: everything in ctx.data is\n * self-contained). Absent = the built-in graphite look. Pick from\n * MINIMAL_BAR_THEMES in the inspector.\n */\n theme?: MinimalBarTheme\n}\n\n/** Resolved minimal-bar colors. `light` flips the hairline to dark-on-light. */\nexport interface MinimalBarTheme {\n id: string\n bar: string\n pill: string\n text: string\n light?: boolean\n}\n\n/**\n * Curated minimal-bar palette (Cursorful-style 12 swatches: 6 dark, 6 light).\n * The first entry matches the built-in default (theme absent).\n */\nexport const MINIMAL_BAR_THEMES: MinimalBarTheme[] = [\n { id: 'graphite', bar: '#141417', pill: '#26262b', text: '#9a9aa1' },\n { id: 'charcoal', bar: '#27272a', pill: '#3a3a3f', text: '#b0b0b6' },\n { id: 'ink', bar: '#0f172a', pill: '#1e293b', text: '#94a3b8' },\n { id: 'slate', bar: '#1e293b', pill: '#334155', text: '#a8b6c8' },\n { id: 'navy', bar: '#172033', pill: '#232e47', text: '#8fa0bd' },\n { id: 'steel', bar: '#2f3542', pill: '#414a5c', text: '#aab3c5' },\n { id: 'snow', bar: '#f8fafc', pill: '#ffffff', text: '#5f6368', light: true },\n {\n id: 'white',\n bar: '#ffffff',\n pill: '#f1f3f4',\n text: '#5f6368',\n light: true,\n },\n { id: 'mist', bar: '#e8ecf1', pill: '#f8fafc', text: '#566172', light: true },\n {\n id: 'lavender',\n bar: '#e7e9f8',\n pill: '#f6f7fe',\n text: '#5b5f79',\n light: true,\n },\n { id: 'sky', bar: '#dbe6f7', pill: '#f2f7ff', text: '#4d6079', light: true },\n {\n id: 'blush',\n bar: '#f7ecec',\n pill: '#fdf7f7',\n text: '#77595c',\n light: true,\n },\n]\n\n/**\n * A music/SFX clip. OUTPUT-anchored (`start` is final-cut seconds): music and\n * effects are authored against the cut that remains — unlike zoom keyframes /\n * cam window, they do NOT follow footage through trims. The mic track stays\n * source-anchored via the export's segment splice.\n */\nexport interface AudioClip {\n id: string\n /** blob URL (or asset URL) of the audio file. */\n key: string\n /** display name (file name or library track title). */\n name: string\n /** placement on the OUTPUT timeline, seconds. */\n start: number\n /** kept span within the source file, seconds (trim). */\n in: number\n out: number\n /** full source-file length, seconds — the trim ceiling (set on add). */\n duration: number\n /** linear gain 0..1. */\n gain: number\n /** fade durations, seconds. */\n fadeIn: number\n fadeOut: number\n /** loop the [in,out) span to fill `loopLen` output seconds. */\n loop?: boolean\n /** placed output length when looping (≥ span; defaults to the span). */\n loopLen?: number\n /** duck this clip under the mic while speech is detected. */\n duck?: boolean\n /** The template that placed this clip (see the overlay clip's `from`). */\n from?: string\n}\n\n/** Effective placed length of a clip on the output timeline, seconds. */\nexport function clipLength(\n clip: Pick<AudioClip, 'in' | 'out' | 'loop' | 'loopLen'>,\n): number {\n const span = Math.max(0, clip.out - clip.in)\n return clip.loop ? Math.max(span, clip.loopLen ?? span) : span\n}\n\n/**\n * Media layer drawn over the CSS `background` and under the card\n * The flagship option is a vos rendered\n * to a seamless loop (`vosId` provenance kept for re-bakes + a future live\n * tier). Video time is OUTPUT-anchored modulo the loop (`bgT = t % duration`)\n * — trims/speed never retime ambience. Fail-open: while the media loads (or if\n * it can't), the CSS background underneath still paints — never a black frame.\n */\nexport interface BackgroundMedia {\n kind: 'video' | 'image'\n /**\n * Source URL — blob URL (session), /api/assets/{id}/file (saved vos),\n * https://assets.vos.so/... (pre-baked official), or take-dir relative path\n * (CLI). Rides the same resolution plumbing as source.videoKey.\n */\n key: string\n /** Loop length in seconds (video; the bake duration). */\n duration?: number\n /** Provenance: the vos this media was rendered from. */\n vosId?: string\n versionId?: string\n /** Poster/thumbnail URL (picker display + reduced-motion; not drawn by the layer). */\n poster?: string\n /** Black scrim over the media, 0..1 — the one legibility dial. */\n dim: number\n /** Blur radius in design px — softens the media behind the card. */\n blur?: number\n}\n\nexport interface FrameStyle {\n /** CSS background (gradient/color): always painted — the media underlay/fallback. */\n background: string\n /** Optional media layer (vos loop / image) drawn over the CSS background. */\n backgroundMedia?: BackgroundMedia | null\n padding: number\n radius: number\n /**\n * Ambient shadow strength 0..1: the wide, soft layer under the card\n * (blur 60, offset 24 design px). The look presets set it with\n * `shadowContact`; every existing doc keeps its single layer.\n */\n shadow: number\n /**\n * Contact shadow strength 0..1: a second, tight layer (blur 10, offset 3\n * design px) drawn over the ambient one. It is what makes a light card sit\n * on a light ground, where a wide soft shadow alone reads as haze. Absent\n * = 0 (no second layer; byte-identical to before the field existed).\n */\n shadowContact?: number\n /**\n * Shadow colour as a `#rrggbb` hex; the strengths are its alpha. Absent =\n * black. A dark look lifts it toward its accent so the card glows instead\n * of sinking.\n */\n shadowColor?: string\n /**\n * Per-side card placement as FRACTIONS of the frame (left/right of its\n * width, top/bottom of its height), each overriding `padding` on its side\n * when present. A negative side BLEEDS the card past the frame edge (the\n * feature-clip grammar: headroom above, the window running off the\n * bottom). Absent sides keep `padding`, so every existing doc is untouched.\n * Under contain the card centres inside the inset area; under cover the\n * inset area IS the card.\n */\n inset?: { top?: number; right?: number; bottom?: number; left?: number }\n /** stroke around the card, 0..1 alpha (0 = off). The switch AND the opacity. */\n border: number\n /**\n * Stroke width in design px (scales with the canvas, like radius), drawn\n * OUTWARD from the card's edge (a CSS outline) so it never covers footage.\n * Absent = FRAME_BORDER_WIDTH_DEFAULT, the hairline every take shipped with.\n */\n borderWidth?: number\n /**\n * Stroke colour, any CSS colour string. Absent = FRAME_BORDER_COLOR_DEFAULT.\n * `border` is the alpha it is drawn at, so an opaque colour is correct here.\n */\n borderColor?: string\n /**\n * How footage meets an off-ratio frame. 'contain' (default) fits the\n * whole card inside the padded area, letterboxing onto the background;\n * 'cover' makes the padded area the card and cover-fills it with footage,\n * cropped around `focus` — what a 440x280 store tile or a 2.5:1 marquee\n * demands (\"fill the region\"). Absent = contain; every existing doc is\n * byte-identical.\n */\n fit?: 'contain' | 'cover'\n /**\n * Cover-crop anchor, normalized video-frame fractions (the zoom cx/cy\n * convention): which point of the footage stays visible when `fit:'cover'`\n * crops. Absent = center. Ignored under contain.\n */\n focus?: { cx: number; cy: number }\n /**\n * The zoom camera's model. `'card'` (absent; every existing document) is\n * the magnifier: the card scales about the focus point, which stays where\n * it is on screen, and the lowering clamps the focus so the zoomed card\n * always covers the canvas, which means a target near a card edge can\n * never sit near the frame's centre. `'stage'` is a camera: the card\n * scales AND slides so the focus lands at the frame's centre (blended in\n * over the first CAMERA_CENTRE_RAMP of level, so level 1 is the identity),\n * the focus is never clamped, and past the card's edge the frame shows\n * the ground, radius and shadow, the way a filmed card reads. New takes\n * open on `'stage'` (BASE_FRAME_STYLE); the layout helpers, the digest's\n * framing window and the studio's aiming rect all read this field.\n */\n camera?: 'card' | 'stage'\n aspectRatio: string\n browserBar: BrowserBarStyle\n /**\n * Background parallax 0..1: the background media counter-pans subtly\n * as the zoom camera moves (depth cue). 0/absent = static.\n */\n parallax?: number\n /**\n * How the card ENTERS at t = 0 (the feature-clip grammar: no clip opens\n * on a static, centred, flat window). `tilt-in` swings in from a\n * perspective pose while the card settles up and in; `pull-out` opens\n * zoomed in and pulls out; `rise` settles up and in flat. Lowered into\n * the tilt or zoom track's head and a card-pose track, so the card's only\n * rotation is still the tilt track. Absent = none, byte-identical.\n * @deprecated The card's `anim.enter`; migrated on read.\n */\n entrance?: FrameEntrance\n /**\n * How the card ENTERS at t = 0 and LEAVES when its footage ends (`recede`\n * settles it smaller and dimmer behind whatever plays after it; `fade`\n * takes it out). Past its footage the card holds its last frame at the\n * exit's end pose. Absent = neither, byte-identical.\n */\n anim?: Anim\n /**\n * Under `fit:'cover'`, follow the zoom track's focus with the crop\n * (`'camera'`), so a 9:16 cut of a 16:9 take keeps the affordance in\n * frame instead of the centre. Absent = `focus` (or the centre).\n */\n focusFollow?: 'camera'\n}\n\nexport interface FrameEntrance {\n kind: 'tilt-in' | 'pull-out' | 'rise' | 'none'\n /** Seconds to settle (default 1.2, clamped 0.2..3). */\n seconds?: number\n}\n\n/**\n * The end card: the last frame holds for `seconds` while the card recedes\n * and the words rise over it (the house title, caption and label presets,\n * so they lower like any overlay). The clip's last frame is its poster.\n * @deprecated A card `anim.exit` plus clips placed after the footage\n * (`from: 'endcard'`); migrated on read.\n */\nexport interface EndCard {\n seconds?: number\n headline?: string\n sub?: string\n wordmark?: string\n /**\n * The brand's MARK as an image (a take-dir path or a URL the render page\n * can load), with its aspect (width over height), drawn above the\n * wordmark; a wide mark (a stylised wordmark asset) stands alone.\n */\n mark?: { key: string; aspect: number }\n /**\n * The words' colour (any CSS colour). The house presets are white for\n * footage; over a light plate the brand's ink is what reads. Absent =\n * the presets' own.\n */\n ink?: string\n}\n\n/**\n * Overlay clips (compositor v2: \"elements-shaped data\"). Screen-\n * space clips drawn on the OVERLAY layer (above the card, never tilts, outside\n * the zoom transform). **OUTPUT-anchored** — trims/speed never retime a title.\n * The first slice ships `kind: 'text'`; image/video kinds are the next slice and extend this\n * union without changing the anchoring or transform model.\n */\nexport type OverlayKind = 'text' | 'image' | 'video' | 'html'\n\n/** Named house text styles — resolved to concrete font/size/color at lowering. */\nexport type TextOverlayPreset = 'title' | 'caption' | 'label'\n\n/** Enter/exit transition presets — pure f(t), evaluated in ON_FRAME. */\nexport type OverlayTransition = 'none' | 'fade' | 'rise'\n\n/**\n * THE animation vocabulary, one for every visual primitive. Which kinds a\n * thing accepts is its own (`anim.ts` holds the tables): the card enters by\n * `tilt-in`, `pull-out`, `rise`, `fade` or `slide` and leaves by `recede` or\n * `fade`; words enter by the block transitions or per unit (`pop`, `blur`,\n * `typewriter`, or a `fade`/`rise` that names its unit); an image or a\n * video clip enters and leaves by `fade` or `rise`; a prop idles by `spin`\n * or `float`; a FOOTAGE clip meets the clip beside it by `slide`, `fade` or\n * `scale` (a transition at the boundary). `none` is an explicit nothing;\n * absent is the house default.\n */\nexport type AnimKind =\n | 'none'\n | 'fade'\n | 'rise'\n | 'pop'\n | 'blur'\n | 'typewriter'\n | 'tilt-in'\n | 'pull-out'\n | 'recede'\n | 'slide'\n | 'scale'\n\n/**\n * The side a slide comes from (an enter) or leaves to (an exit), as seen on\n * screen: `right` enters from the right edge, `left` leaves off the left.\n */\nexport type AnimSide = 'left' | 'right' | 'up' | 'down'\n\n/** What a thing does while it stays. */\nexport type IdleKind = 'spin' | 'float'\n\n/**\n * One step of an animation: the kind, its seconds, and for words the\n * per-unit grammar. A bare kind is the same step with the house timing.\n */\nexport interface AnimStep {\n kind: AnimKind\n /** Seconds the step takes (the house length when absent). */\n seconds?: number\n /** Words only: the unit the step animates by (`block` = the whole clip). */\n unit?: TextFxUnit\n /** Words only: the order units animate in. */\n direction?: TextFxDirection\n /** Words only: seconds between one unit's start and the next. */\n stagger?: number\n /**\n * A slide's side: where an enter comes FROM, where an exit goes TO.\n * Absent: an enter comes from the right, an exit leaves to the left (the\n * page turns forward).\n */\n side?: AnimSide\n}\n\nexport interface Anim {\n enter?: AnimKind | AnimStep\n exit?: AnimKind | AnimStep\n idle?: IdleKind | null\n}\n\n/** Text animation vocabulary — entrance presets evaluated per unit. */\nexport type TextFxKind = 'fade' | 'rise' | 'pop' | 'blur' | 'typewriter'\nexport type TextFxUnit = 'block' | 'line' | 'word' | 'char'\nexport type TextFxDirection = 'forward' | 'reverse' | 'center'\n\n/**\n * Text entrance animation. When present it OWNS the entrance — the\n * clip's `enter` string is ignored (a spec with `unit: 'block'` is the\n * superset of the legacy presets); `exit` stays clip-level. Segmentation is\n * baked at lowering (deterministic doc-derived data), per-unit progress is\n * evaluated in ON_FRAME — pure f(t), so scrub/seek/chunk cold-seeks agree.\n */\nexport interface TextFxSpec {\n fx: TextFxKind\n /** What animates as one thing (default 'block' — the whole text). */\n unit?: TextFxUnit\n /** Unit start order (default 'forward'; 'center' ripples outward). */\n direction?: TextFxDirection\n /**\n * Seconds between unit starts. Defaults: typewriter 0.05, other kinds\n * 0.06 when unit ≠ block, else 0. Clamped at lowering so the whole\n * entrance fits the clip.\n */\n stagger?: number\n /** Per-unit seconds (default OVERLAY_TRANSITION_DUR); typewriter ignores it. */\n duration?: number\n}\n\n/**\n * A pose keyframe on an overlay/object clip (element motion).\n * `at` is CLIP-LOCAL OUTPUT seconds (0 = the clip's start), so poses ride\n * along when the clip moves. Values interpolate across the gap between poses\n * (ease-into per pose, the KeyframeTrack convention); a hold is two identical\n * poses. The clip's base transform is the value before the first pose, and\n * absent fields inherit it — a pose may move without resizing. Baked at\n * lowering into a clip-local keyframe track, sampled in ON_FRAME as pure\n * f(t): scrub, export and chunked server renders agree by construction.\n */\nexport interface MotionPose {\n /** Clip-local OUTPUT seconds. */\n at: number\n /** Anchor center as frame fractions (the transform.x/y convention). */\n x?: number\n y?: number\n /** Scale multiplier (the transform.scale convention). */\n scale?: number\n /** Degrees (the transform.rotation convention). */\n rotation?: number\n /** Opacity MULTIPLIER 0..1 on the clip's own alpha (default 1). */\n opacity?: number\n /** Arrival ease (@vosjs/timeline EASINGS name). Absent = the house motion ease. */\n ease?: string\n}\n\n/** Default pose-to-pose ease: a symmetric in-out (continuous motion between\n * poses, not a settle — the CapCut/keyframe convention). */\nexport const MOTION_EASE = 'power2.inOut'\n\nexport interface OverlayTransform {\n /**\n * Anchor CENTER as FRACTIONS of the output frame [0..1] (the zoom cx/cy\n * convention): 0.5/0.5 = frame center at ANY aspect ratio — positions\n * survive aspect switches (design px did not: the space's width changes\n * with the aspect, pushing clips off-frame).\n */\n x: number\n y: number\n /** Uniform scale multiplier on the preset size. */\n scale: number\n /** Rotation in degrees (screen-space, about the anchor). */\n rotation: number\n}\n\n/** Which side of its referent a pinned layer sits on. */\nexport type PinSide = 'auto' | 'right' | 'left' | 'below' | 'above'\n\n/** The standing mark a pinned layer leaves on its referent. */\nexport type PinMark = 'none' | 'ring' | 'underline'\n\n/**\n * A layer's REFERENT: the thing on the page it is about. A pinned layer is\n * placed beside that rect and follows it as the camera moves it on screen,\n * so the statement and its subject stay bound (proximity), optionally with\n * a hairline between them (a connector) and a standing mark on the subject.\n * One of `step`, `press` or `rect` names it. Resolved at lowering into the\n * clip's motion track through the frame's camera; `transform.x/y` stay the\n * author's fallback for a pin that cannot resolve. Not the re-record\n * `anchor` (a time tie the lowering never reads).\n */\nexport interface OverlayPin {\n /** A recorder step (`meta.steps[].id`, else its index) whose element\n * rect is the referent. */\n step?: string | number\n /** The press nearest this SOURCE second (a human recording has no\n * script): the referent is that click's element rect. */\n press?: number\n /** The referent itself, in normalised video fractions [0..1] (the zoom\n * cx/cy convention — never pixels). */\n rect?: Rect\n /** Absent = 'auto': the first of right, left, below, above whose box\n * fits inside the frame through the layer's life, else the roomiest. */\n side?: PinSide\n /** Design px between the referent and the layer's visible box (24). */\n gap?: number\n /** A standing mark on the referent for the layer's life (absent = none). */\n mark?: PinMark\n /** A hairline from the layer's near edge to the referent. */\n leader?: boolean\n /** The mark's and the leader's ink (CSS colour; absent = white over a\n * dark rim, legible on any ground). */\n color?: string\n}\n\ninterface OverlayClipBase {\n /** Stable identity for selection/editing in the timeline UI. */\n id: string\n /** OUTPUT-time span (seconds). */\n start: number\n duration: number\n transform: OverlayTransform\n /**\n * The layer's referent (see OverlayPin). A pinned layer's place is\n * computed from the pin; `motion` poses keep their scale, rotation and\n * opacity, and their x/y are ignored.\n */\n pin?: OverlayPin\n /**\n * How the clip enters and leaves (see Anim). Absent = the house motion:\n * a rise in, a fade out.\n */\n anim?: Anim\n /**\n * The template that placed this clip (a vos id, or a legacy marker),\n * provenance the lowering never reads: a loop drops a template's clips,\n * a re-apply replaces one template's and leaves the maker's.\n */\n from?: string\n /**\n * @deprecated `anim.enter` / `anim.exit`; migrated on read.\n */\n enter?: OverlayTransition\n exit?: OverlayTransition\n /**\n * Pose keyframes (see MotionPose): the clip's transform animated\n * over clip-local time, rendered as diamonds on the clip. Optional: absent\n * lowers byte-identically (no track in data).\n */\n motion?: MotionPose[]\n}\n\nexport interface TextOverlayClip extends OverlayClipBase {\n kind: 'text'\n /** Text content; '\\n' breaks lines. */\n text: string\n preset: TextOverlayPreset\n /** Font size override in design px (preset default when absent). */\n size?: number\n /** CSS color override (preset default when absent). */\n color?: string\n /**\n * Font family override — a catalog family name (GET /api/fonts). Unknown\n * names fail open: used verbatim with the preset stack as fallback.\n */\n family?: string\n /** Weight override — snapped to the nearest weight the catalog hosts. */\n weight?: number\n /** Synthesized oblique (no italic files are hosted). */\n italic?: boolean\n /** Multi-line alignment within the block (default center). */\n align?: 'left' | 'center' | 'right'\n /** Letter spacing in design px at the resolved size (default 0). */\n letterSpacing?: number\n /** Line height multiplier (default OVERLAY_LINE_HEIGHT). */\n lineHeight?: number\n /**\n * Legibility shadow strength 0..1 (the preset's when absent; 0 = none).\n * Words on a plate ground carry none: the shadow exists for words over\n * footage.\n */\n shadow?: number\n /** Text outline, drawn under the fill. */\n stroke?: TextOverlayStroke\n /** Background pill behind the text block (absent = none). */\n box?: TextOverlayBox\n /**\n * @deprecated A per-unit `anim.enter` step; migrated on read.\n */\n fx?: TextFxSpec\n /**\n * Wrap width as a FRACTION of the frame width [0.1..1] (the transform.x\n * convention — aspect-stable). Absent = no wrapping (lines break only on\n * explicit \\n). Wrapping is greedy over word tokens at measured widths; a\n * single token wider than the budget gets its own line (no intra-word\n * breaks). Tokens keep their trailing whitespace, so fx unit sequences\n * are IDENTICAL wrapped or not — entrances regroup, never recount.\n */\n maxWidth?: number\n}\n\nexport interface TextOverlayStroke {\n /** CSS stroke color. */\n color: string\n /** Stroke width in design px at the resolved size. */\n width: number\n}\n\n/**\n * Text background pill. Paddings and radius are EMs of the resolved font\n * size, so the pill scales with the text through size overrides, transform\n * scale and output resolution alike.\n */\nexport interface TextOverlayBox {\n /** CSS color of the pill. */\n color: string\n /** Extra opacity multiplier on top of the clip's fade alpha (default 1). */\n opacity?: number\n /** Horizontal padding in EMs (default 0.6). */\n paddingX?: number\n /** Vertical padding in EMs (default 0.35). */\n paddingY?: number\n /** Corner radius in EMs (default 0.25); clamped to half the pill height. */\n radius?: number\n}\n\n/**\n * Image/video overlay (V1b) — a media card on the overlay layer. `key` rides\n * the same resolution plumbing as source.videoKey / backgroundMedia.key\n * (blob URL in-session, /api/assets URL saved, take-dir path in CLI takes).\n * Sized by `width` (fraction of the FRAME width, aspect from the media) ×\n * transform.scale. Video time is clip-local (t − start), muted (soundtracks\n * belong to doc.audio), looping optional.\n */\nexport interface MediaOverlayClip extends OverlayClipBase {\n kind: 'image' | 'video'\n /**\n * The picture: an asset URL, a take-dir file, or a DOCUMENT MEDIA by\n * reference, `media:<id>` (`media:` alone is the primary), which brings\n * the media's facts with it (its cursor track, its recorded page) and\n * follows the media's kind. The lowering resolves the reference to the\n * media's own key; the document keeps the reference.\n */\n key: string\n /**\n * The layer as a CARD: drawn by the card painter on its own plane above\n * the primary card (the layered shadow, the media clipped to its corners,\n * a browser bar, the border, a lean, and for a document media its cursor\n * dot and click rings). Absent, the layer is the flat picture it always\n * was, byte-identically.\n */\n frame?: LayerCard\n /**\n * The card shadow. Absent = 'soft' — the baked look every\n * doc predating the field renders, so absence lowers byte-identically. 'strong' is the\n * hero float, 'none' the flat cutout.\n */\n shadow?: 'none' | 'soft' | 'strong'\n /**\n * An outline stroke drawn over the clipped media edge.\n * Absent = none. `width` in design px (scales with the canvas like\n * radius); any CSS color.\n */\n border?: { width: number; color: string }\n /** Base width as a fraction of the frame width [0..1]. Absent = 0.35. */\n width?: number\n /** Corner radius in design px. Absent = 12 (the house card radius). */\n radius?: number\n /** Opacity 0..1. Absent = 1. */\n opacity?: number\n /** Video only: loop while the clip is active. Absent = hold the last frame. */\n loop?: boolean\n}\n\n/**\n * A face an HTML layer sets type in that the CATALOG does not host, named by\n * URL. A catalog family needs no entry here: the lowering finds it from the\n * family the CSS names (`htmlLayerFaces`). The bytes are never stored: an SVG\n * loaded through `<img>` may fetch nothing, so the page fetches the face by\n * ordinary `fetch` at setup and inlines it into the picture.\n */\nexport interface HtmlOverlayFont {\n family: string\n url: string\n weight?: number\n style?: 'normal' | 'italic'\n}\n\n/**\n * A layer authored as DOM: markup and CSS, laid out and painted by the\n * browser itself, composited over the footage like any other picture.\n *\n * WHY IT IS SOURCE AND NOT A PICTURE. A product video points at a piece of\n * UI, and the camera is the only pointing device a recording has: a zoom\n * magnifies recorded pixels, so the thing the video is about becomes the\n * blurriest thing on screen. A layer rasterized FROM SOURCE at the size each\n * frame asks for has no such ceiling.\n *\n * WHY THE SOURCE CANNOT BE BAKED INTO A KEY. An SVG carrying a\n * `<foreignObject>` is origin-clean ONLY as a `data:` URI (served from a URL\n * it taints the canvas even same-origin, and a tainted frame cannot be\n * uploaded as a WebGL texture, so every layer disappears), and a `data:` URI\n * in the document costs tens of kilobytes per face per layer. Keeping the\n * SOURCE here and building the picture in the page is small in the\n * document, inline at draw time, and vector at any export size.\n *\n * It draws its OWN chrome: shadow, radius, border and any bar are the CSS's,\n * so the compositor adds none (`shadow`, `radius`, `border`, `frame` are\n * refused by the lint). It rides every other layer gesture unchanged: the\n * timing, the fractional transform, `anim`, `motion`, z-order.\n */\nexport interface HtmlOverlayClip extends OverlayClipBase {\n kind: 'html'\n /** The markup. Well-formed XML: `foreignObject` is XML, not HTML. */\n html: string\n /** The CSS. Scoped by the layer's own wrapper, so plain selectors are fine. */\n css?: string\n /** The design box the markup is laid out in, in 1080p design px. */\n box: { width: number; height: number }\n /**\n * Design px of room around the box for what paints OUTSIDE it: a shadow,\n * a glow, a blur. The SVG viewport clips hard, so without room a shadow is\n * sliced square at the edge. Absent = derived from the CSS at lowering\n * (`htmlLayerBleedFor`); stated, it wins.\n */\n bleed?: number\n /** Faces the CSS names that the catalog does not host, by URL. */\n fonts?: HtmlOverlayFont[]\n /**\n * Base width as a fraction of the FRAME width. Absent = the design size,\n * the picture box (the design box plus its bleed) over 1920, so a\n * component designed at 452 px lands at 452 px in a 1080p frame.\n */\n width?: number\n /**\n * A LIVE layer: the picture is a function of clip-local time. CSS\n * `@keyframes` inside it are scrubbed to t (paused, delayed by -t), and\n * `{{t}}` / `{{data.<name>}}` placeholders in the markup and CSS fill per\n * frame, so a counter, a progress bar or a typed line come out on the\n * timeline's clock in the preview and in every export chunk alike. One\n * rasterize per frame while on screen. Absent = a still, byte-identical\n * to before the field existed.\n */\n live?: boolean\n /** Values for a live layer's `{{data.<name>}}` placeholders. */\n data?: Record<string, string | number | boolean>\n /** Opacity 0..1. Absent = 1. */\n opacity?: number\n}\n\nexport type OverlayClip = TextOverlayClip | MediaOverlayClip | HtmlOverlayClip\n\n/**\n * Does this layer's picture come from a KEY? True for an asset, a take-dir\n * file or a document media; false for words and for an HTML layer, which\n * carry their own content.\n *\n * A predicate rather than `kind !== 'text'`, because that test silently\n * became wrong the moment a second contentful kind existed, and the way it\n * goes wrong is that a re-host tries to re-host something that has no key.\n */\nexport function isKeyedOverlay(c: OverlayClip): c is MediaOverlayClip {\n return c.kind === 'image' || c.kind === 'video'\n}\n\n/**\n * A layer's own card. The bar is ALLOWED on any card and opens off; its\n * address defaults to the media's recorded page when the layer shows a\n * document media. The lean is the tilt convention, in degrees.\n */\nexport interface LayerCard {\n browserBar?: Partial<BrowserBarStyle>\n lean?: { rx: number; ry: number }\n /** The layered shadow's strength; absent = LAYER_CARD_SHADOW. */\n shadow?: number\n shadowContact?: number\n shadowColor?: string\n /** Draw the media's cursor and clicks inside the card; absent = yes when it has a track. */\n cursor?: boolean\n}\nexport const LAYER_CARD_SHADOW = 0.32\n/** The prefix of a media overlay key that names a document media. */\nexport const MEDIA_KEY_PREFIX = 'media:'\n\n/**\n * Ceiling on `transform.scale` for text overlays, shared by the canvas box\n * and the panel so the two can never disagree about where growth stops (a\n * 64px title at 8× is a 512px hero word — past that it is a poster, not a\n * caption). The floor is 0.1 in both places.\n */\nexport const OVERLAY_SCALE_MAX = 8\nexport const OVERLAY_MEDIA_DEFAULT_WIDTH = 0.35\nexport const OVERLAY_MEDIA_DEFAULT_RADIUS = 12\n\n/**\n * World-space object clips (compositor v2). These shapes are\n * DRAFTED AS THE FUTURE ENGINE SPEC: field names, asset-ref shape, and transform\n * convention carry to `objects?: ObjectConfig[]` upstream unchanged — today they\n * run interpreter-side (ON_FRAME reconciles meshes from ctx.data; live\n * SET_DATA add/remove), and a later engine release swaps the construction site into the engine.\n *\n * Conventions (agent-facing units match the rest of the doc):\n * - position x/y = FRACTIONS of the frame [0..1] (the overlay/zoom\n * convention), z = world units TOWARD the camera from the card plane\n * (0 = on the card's depth; 0.5 floats clearly in front).\n * - scale = fraction of the FRAME HEIGHT the object's unit size occupies.\n * - span (OUTPUT seconds) gates visibility with soft edge fades; absent =\n * the whole timeline.\n */\nexport type ObjectPrimitiveShape = 'cube' | 'sphere' | 'torus' | 'knot'\n\n/**\n * 3D-text material presets — fleet-audited: everything single-sided,\n * no `dispersion`, transmission only single-sided (the documented\n * SwiftShader constraints). Resolved to plain material params at lowering.\n */\nexport type Text3dMaterial = 'standard' | 'metal' | 'glass' | 'neon'\n\nexport type ObjectAsset =\n /** Curated primitive props — fleet-safe, no asset fetch. */\n | { kind: 'primitive'; shape: ObjectPrimitiveShape; color?: string }\n /** GLB by key — accepted in the schema for forward compat with the engine spec; loads in a later slice. */\n | { kind: 'gltf'; key: string }\n /**\n * Extruded 3D text from a hosted typeface JSON. `typeface` is a\n * catalog slug or family name (GET the list from the typeface catalog;\n * unknown names fall back to the house face). `depth` is the extrusion as\n * a fraction of the glyph height (default 0.25); `bevel` defaults on.\n */\n | {\n kind: 'text3d'\n text: string\n typeface?: string\n material?: Text3dMaterial\n color?: string\n depth?: number\n bevel?: boolean\n }\n\n/** Curated motion presets — pure f(t), deterministic. */\nexport type ObjectAnimation = 'spin' | 'float'\n\n/**\n * A pose keyframe on a 3D object clip (the MotionPose model over\n * transform3d). `at` is CLIP-LOCAL OUTPUT seconds from the clip's span start\n * (0 when the clip has no span). Absent fields inherit the base transform3d;\n * `spin`/`float` presets compose ADDITIVELY on top of the sampled pose.\n */\nexport interface MotionPose3D {\n at: number\n /** Frame fractions (the transform3d.x/y convention). */\n x?: number\n y?: number\n /** World units toward the camera from the card plane. */\n z?: number\n /** Euler degrees. */\n rx?: number\n ry?: number\n rz?: number\n /** Fraction of the frame height. */\n scale?: number\n /** Arrival ease (@vosjs/timeline EASINGS name). Absent = the house motion ease. */\n ease?: string\n}\n\nexport interface ObjectClip {\n id: string\n asset: ObjectAsset\n /** OUTPUT-time visibility span; absent = always. */\n span?: { start: number; duration: number }\n transform3d: {\n x: number\n y: number\n /** World units toward the camera from the card plane. */\n z: number\n /** Euler degrees. */\n rx: number\n ry: number\n rz: number\n /** Fraction of the frame height. */\n scale: number\n }\n /** What the prop does while it stays (`anim.idle`), and how it comes and goes. */\n anim?: Anim\n /** The template that placed this prop (see the overlay clip's `from`). */\n from?: string\n /** @deprecated `anim.idle`; migrated on read. */\n animation?: ObjectAnimation | null\n /** Pose keyframes (see MotionPose3D). Absent lowers byte-identically. */\n motion?: MotionPose3D[]\n}\n\nexport const OBJECT_DEFAULT_SCALE = 0.18\n\n/** Enter/exit transition length in seconds (pure f(t) in ON_FRAME). */\nexport const OVERLAY_TRANSITION_DUR = 0.35\n/** Line height multiplier for multi-line text overlays. */\nexport const OVERLAY_LINE_HEIGHT = 1.25\nexport const OVERLAY_MIN_DURATION = 0.2\n\n/**\n * The editable project state. An app-level convention that *lowers to* a vos\n * Composition (it is NOT vos core). Fully serializable.\n */\nexport interface ProjectDoc {\n source: {\n videoKey: string\n cursor: CursorTrack\n meta: RecordingMeta\n /** object URL for a separately-recorded webcam track, if the take had a camera. */\n camKey?: string\n /**\n * object URL for the separately-recorded microphone sidecar (AT split).\n * When present the recording's own audio track (hasAudio) is SYSTEM/tab\n * audio and micGain governs this sidecar; absent on legacy takes, where\n * the recording's track is the old record-time mix.\n */\n micKey?: string\n /**\n * Decode strategy for the recording (vos VideoElement.frameSource):\n * 'webcodecs' (frame-accurate, MP4), 'html5' (robust, any format), 'auto'.\n * Defaults to 'auto' in lowering. Dev uploads use 'html5'; the B2 recorder\n * (known-good MP4) uses 'webcodecs'.\n */\n frameSource?: 'auto' | 'webcodecs' | 'html5'\n /**\n * 'image' = videoKey points at a still (screenshot) shown for the doc's\n * whole duration — the full editing stack (frame, browser bar, zoom, export)\n * applies unchanged. Defaults to 'video'.\n */\n sourceKind?: 'video' | 'image'\n /**\n * drawImage source rect (capture px) for window takes: the viewport's rect\n * inside the captured frame, derived once at doc build (normalizeCaptureSpace)\n * — crops the real browser chrome out of the footage so the synthetic\n * browser bar applies. When set, cursor track + meta dims are already in\n * crop space. Absent = draw the full frame.\n */\n crop?: Rect\n /**\n * The derived viewport crop + the full capture dims it was cut from — kept\n * even while the \"Original\" frame mode shows the uncropped window, so the\n * crop can be re-applied losslessly (docToCropSpace/docToFullSpace remap\n * cursor/zoom/meta between the two spaces; the footage itself always holds\n * the full frame). Present ⟺ crop derivation succeeded at ingest.\n */\n chromeCrop?: { rect: Rect; frameW: number; frameH: number }\n }\n /**\n * Kept SOURCE-time spans (@vosjs/timeline `Segment`s); the output timeline is\n * their concatenation — trim/split/cut are all segment edits. Canonical form\n * is one full-source segment; an empty list is tolerated and means \"untrimmed\".\n */\n segments: DocSegment[]\n /**\n * The OTHER media this take carries (concat, a layer's source): each the\n * `source` shape with an `id`, named by a segment's or a span's `media`.\n * The primary stays `source` (the sequence, the camera and the cut are\n * its). Absent = one media; a document with none lowers byte-identically.\n */\n media?: Media[]\n /**\n * The frame that stands for this document (OUTPUT seconds): the cover the\n * shelf shows, the still a kit leads with. Absent, `docStillTime` derives\n * it: the document's own last freeze (a poster's rest), else the hero\n * moment after the opening has entered. A template never sets it.\n */\n still?: number\n /**\n * Speed-change spans (SOURCE time, footage-anchored — see SpeedSpan).\n * Optional for backward compatibility with persisted docs; absent = all 1×.\n */\n speed?: SpeedSpan[]\n /**\n * Freezes (SOURCE moments, footage-anchored — see FreezeSpan), the\n * other retime primitive. Absent = none; a legacy segment `hold` migrates\n * here on read.\n */\n freeze?: FreezeSpan[]\n /** Zoom regions (SOURCE time, footage-anchored, non-overlapping — see ZoomSpan). */\n zoom: ZoomSpan[]\n /**\n * Camera style — one named strategy preset driving BOTH the auto-zoom\n * planner and the camera motion (ramps/eases/pans/follow; see zoomStyle.ts).\n * Absent = DEFAULT_ZOOM_STYLE.\n */\n zoomStyle?: ZoomStyleName\n /**\n * Per-doc overrides on top of the named style — the \"Custom\" seam for\n * agents/doc.json (the studio shows Custom while any override is present;\n * picking a named style clears them). Span edits do NOT set this: the style\n * describes camera dynamics, spans are content.\n */\n zoomParams?: Partial<ZoomStyleParams>\n /**\n * Per-doc overrides for the auto-speed planner: idle/typing/scroll\n * thresholds and rates. Absent = DEFAULT_SPEED_PARAMS.\n */\n speedParams?: Partial<SpeedParams>\n /**\n * Tilt regions (SOURCE time, footage-anchored, non-overlapping — see\n * TiltSpan). Optional: absent lowers byte-identically (no tiltTrack in data).\n */\n tilt?: TiltSpan[]\n /**\n * Dynamic-tilt wand intensity (planAutoTilt — the auto-zoom wand contract:\n * regenerate replaces only `source:'auto'` spans). 'off'/absent = the wand\n * is off; manual tilt spans work either way.\n */\n tiltStyle?: TiltStyleName\n /**\n * Deleted planner proposals (SOURCE time, one lane each — see\n * RejectedSpan): a re-plan never proposes a span that lands on one. Absent\n * = nothing rejected; lowers byte-identically (the renderer never reads it).\n */\n rejected?: RejectedSpan[]\n /** music/SFX clips on the output timeline (see AudioClip anchoring note). */\n audio: AudioClip[]\n /**\n * Master gain for the VOICE, 0..1. Absent = 1. With a mic sidecar\n * (source.micKey) this governs the sidecar; on legacy takes it governs the\n * recording's own (mixed) track.\n */\n micGain?: number\n /**\n * Master gain for the recording's own SYSTEM/tab audio track, 0..1. Absent\n * = 1. Only meaningful on split takes (source.micKey present) — legacy\n * takes have one track and one fader (micGain).\n */\n systemGain?: number\n cursor: CursorStyle\n cam: CamStyle\n /**\n * Cam pose regions (SOURCE time, footage-anchored, non-overlapping — see\n * CamPoseSpan). The bubble morphs to a span's pose and back to the rest\n * pose (doc.cam). Optional: absent lowers byte-identically (no camTrack\n * in data). Only renders when the take has a cam track (source.camKey).\n */\n camMotion?: CamPoseSpan[]\n frame: FrameStyle\n /**\n * Card presentation (tilt / entrance) — compositor v2. Optional: absent\n * lowers byte-identically to a pre-v2 doc and renders pixel-identically.\n */\n /**\n * The end card the clip closes on: a hold on the last frame, the card\n * receding, the words rising. Absent = the clip ends where the footage\n * ends.\n */\n endCard?: EndCard\n /**\n * Screen-space overlay clips (text; later image/video) — compositor v2.\n * OUTPUT-anchored spans on the overlay layer. Optional: absent lowers\n * byte-identically to a doc predating overlays.\n */\n overlays?: OverlayClip[]\n /**\n * World-space object clips (interpreter-side; the drafted engine spec).\n * Optional: absent lowers byte-identically.\n */\n objects?: ObjectClip[]\n export: {\n resolution: ExportResolution\n fps: 30 | 60\n format: 'mp4'\n }\n}\n\n/** Export quality presets — each names the SHORT edge of the output (see resolveExportSize). */\nexport type ExportResolution = '720p' | '1080p' | '2k' | '4k'\n\nexport const EXPORT_SHORT_EDGE: Record<ExportResolution, number> = {\n '720p': 720,\n '1080p': 1080,\n '2k': 1440,\n '4k': 2160,\n}\n\n/** Presets in ascending quality order (picker order; recommendedExportResolution walks it). */\nexport const EXPORT_RESOLUTION_OPTIONS: ExportResolution[] = [\n '720p',\n '1080p',\n '2k',\n '4k',\n]\n\n/**\n * Default ON (ripple + press, medium, neutral): click emphasis is the point of\n * the product, and the wrong-window/coverage gates already drop the cursor\n * track (and with it every click) on takes where effects would misfire.\n */\nexport const DEFAULT_CLICK_FX: ClickFxStyle = {\n style: 'ripple',\n press: true,\n intensity: 'medium',\n color: 'auto',\n}\n\nexport const DEFAULT_CURSOR_STYLE: CursorStyle = {\n visible: true,\n smoothing: 0.15,\n size: 24,\n // The dot every earlier take was cut with; a NEW take opens on the\n // arrow (projectFromArtifact sets it), so a document that stores\n // 'default' keeps its dot.\n style: 'default',\n hideWhenIdle: true,\n clickFx: DEFAULT_CLICK_FX,\n}\n\nexport const DEFAULT_CAM_STYLE: CamStyle = {\n visible: true,\n position: 'bottom-left',\n size: 0.25,\n shape: 'circle',\n mirror: true,\n}\n\nexport const DEFAULT_BROWSER_BAR: BrowserBarStyle = {\n kind: 'none',\n url: '',\n showUrl: true,\n showControls: true,\n height: 44,\n}\n\n/**\n * The frame with no backdrop: the card, its chrome and a flat ground. A\n * host that opens new takes on a backdrop builds its own opening frame\n * with `withBackdrop` (backdrop.ts) and hands it to `projectFromArtifact`,\n * so the pick never lives in the document model.\n */\nexport const BASE_FRAME_STYLE: FrameStyle = {\n // Brand default: signal red → amber (sunset warmth; deliberately not AI-purple).\n background: 'linear-gradient(135deg, #ff5148, #ffb03a)',\n padding: 48,\n radius: 12,\n shadow: 0.4,\n border: 0,\n // The stage camera: a new take's zoom centres its target and shows the\n // ground past the card's edge. A document without the field keeps the\n // clamped magnifier it was cut with (see FrameStyle.camera).\n camera: 'stage',\n // 'native' = the recording's own aspect ratio (meta.width/height). See ASPECT_RATIOS.\n aspectRatio: 'native',\n browserBar: DEFAULT_BROWSER_BAR,\n}\n\n/**\n * The frame a NEW take opens on when the host names none: the bare frame.\n * Every ingest path spreads the frame it is handed (or this one), so a\n * host's pick reaches the extension handoff, the in-page recorder, a\n * dropped file and `vos record` through one argument; a doc that already\n * carries a frame keeps it.\n */\nexport const DEFAULT_FRAME_STYLE: FrameStyle = BASE_FRAME_STYLE\n\n/** Border alpha applied when the Frame-border toggle turns on. */\nexport const FRAME_BORDER_DEFAULT = 0.35\n\n/**\n * The border a doc that names no width/colour is drawn with: the hairline\n * white stroke that was hard-coded in ON_FRAME before the two knobs existed,\n * so every take made before them renders byte-identically after.\n */\nexport const FRAME_BORDER_WIDTH_DEFAULT = 1.5\nexport const FRAME_BORDER_COLOR_DEFAULT = '#ffffff'\n\n/**\n * How far past the card's rounded clip the footage and the browser-bar fill\n * are drawn, in device pixels, so the clip is the ONE edge the eye sees.\n * A clipped drawImage whose destination rect lands exactly on the clip path\n * anti-aliases that edge twice wherever it falls on a fractional device\n * pixel (the studio canvas is the fitted box at device pixel ratio, and any\n * zoom rescales everything about its focus), leaving a partially covered\n * seam pixel along every straight edge that the arc, drawn by the clip\n * alone, never has. One pixel covers the seam and changes the footage scale\n * by less than a part in a thousand. Read by ON_FRAME; the layout tests\n * expect the destination rect to be the mirror's grown by this much.\n */\nexport const CARD_EDGE_OVERDRAW = 1\n\n/**\n * The realistic browser-bar kind matching the recorder's OS — seeds Default\n * mode so the synthetic chrome looks native to where the take was recorded\n * (light variants: browsers default light). Windows/Linux get the windows\n * chrome; when the platform is unknown — a direct upload with no browser\n * information — we default to macOS.\n */\nexport function platformBarKind(\n platform: RecordingMeta['platform'],\n): BrowserBarStyle['kind'] {\n return platform === 'windows' || platform === 'linux'\n ? 'windows-light'\n : 'mac-light'\n}\n\n/**\n * Address-pill display text for a recorded page URL: hostname (www. stripped)\n * plus a non-root path. Empty for non-http(s) or unparsable URLs.\n */\nexport function pageDisplayUrl(pageUrl: string | undefined): string {\n if (!pageUrl) return ''\n try {\n const u = new URL(pageUrl)\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return ''\n const host = u.hostname.replace(/^www\\./, '')\n return u.pathname && u.pathname !== '/' ? host + u.pathname : host\n } catch {\n return ''\n }\n}\n\n/** Output aspect-ratio presets (id used as FrameStyle.aspectRatio). Ordered for the picker. */\nexport interface AspectRatioOption {\n id: string\n label: string\n}\n\nexport const ASPECT_RATIOS: AspectRatioOption[] = [\n { id: 'native', label: 'Native' },\n { id: '21:9', label: '21:9' },\n { id: '16:9', label: '16:9' },\n { id: '16:10', label: '16:10' },\n { id: '3:2', label: '3:2' },\n { id: '4:3', label: '4:3' },\n { id: '1:1', label: '1:1' },\n { id: '3:4', label: '3:4' },\n { id: '2:3', label: '2:3' },\n { id: '10:16', label: '10:16' },\n { id: '9:16', label: '9:16' },\n]\n\n/** Numeric width/height ratio for an aspect-ratio id; 'native' resolves from the source meta. */\nexport function aspectRatioValue(\n id: string,\n meta: { width: number; height: number },\n): number {\n const nativeRatio = (meta.width || 16) / (meta.height || 9)\n if (!id || id === 'native') return nativeRatio\n const [w, h] = id.split(':').map(Number)\n return w > 0 && h > 0 ? w / h : nativeRatio\n}\n\n/**\n * Resolve the export pixel dimensions from the chosen aspect ratio + quality. The quality\n * (`export.resolution`) is the SHORT edge (720/1080/1440/2160), so 16:9 @ 1080p→1920×1080,\n * 9:16 @ 4k→2160×3840, 1:1 @ 1080p→1080×1080. Dimensions are rounded to even numbers\n * (H.264 requires it). Unknown values (hand-edited doc.json) fall back to 1080p.\n */\nexport function resolveExportSize(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'export'>,\n resolution: ExportResolution = doc.export.resolution,\n): { width: number; height: number } {\n return exportSizeFor(\n aspectRatioValue(doc.frame.aspectRatio, doc.source.meta),\n resolution,\n )\n}\n\n/**\n * The quality-preset → pixels math, free of any document. Every product's\n * export UI resolves its dimensions through this one function (the shared\n * ExportDialog included), so a preset name means the same thing everywhere:\n * before it existed the web app carried three private resolution tables that\n * disagreed about what \"2K\" was.\n */\nexport function exportSizeFor(\n ratio: number,\n resolution: ExportResolution,\n): { width: number; height: number } {\n // Widened index: hand-edited doc.json can carry values outside the union.\n const short =\n (EXPORT_SHORT_EDGE as Record<string, number | undefined>)[resolution] ??\n 1080\n const even = (n: number) => {\n const r = Math.round(n)\n return r % 2 ? r + 1 : r\n }\n const safe = ratio > 0 && Number.isFinite(ratio) ? ratio : 16 / 9\n return safe >= 1\n ? { width: even(short * safe), height: even(short) }\n : { width: even(short), height: even(short / safe) }\n}\n\n/**\n * One of a take's OTHER media (`doc.media`): the `source` shape (a media\n * plus the facts only a capture has) with the `id` a segment or a span\n * names. The primary media has no id; it is `doc.source`.\n */\n/**\n * The CARD-owned frame fields, what a media may carry of its own: its\n * placement and size (`inset`, per-side fractions of the frame), the bar,\n * the corner, the shadow, the border, the cover fit and its focus. The\n * frame-wide fields (the aspect, the padding, the ground, the backdrop, the\n * card's animation) stay the take's: one frame, many cards.\n */\nexport const MEDIA_FRAME_KEYS = [\n 'browserBar',\n 'radius',\n 'shadow',\n 'shadowContact',\n 'shadowColor',\n 'inset',\n 'border',\n 'borderWidth',\n 'borderColor',\n 'fit',\n 'focus',\n 'focusFollow',\n] as const\nexport type MediaFrameKey = (typeof MEDIA_FRAME_KEYS)[number]\n/** What a media says about its own card; absent fields fall to the take's frame. */\nexport type MediaFrame = Partial<\n Omit<Pick<FrameStyle, MediaFrameKey>, 'browserBar'>\n> & { browserBar?: Partial<BrowserBarStyle> }\nexport type Media = ProjectDoc['source'] & { id: string; frame?: MediaFrame }\n","/**\n * The callout grammar — three shapes a viewer learns once, composed from the\n * product's own register so the card is unmistakably the video's and still\n * the product's.\n *\n * The rule set (the distinguishability principle: share the product's hue\n * and type, contrast in value, elevation, scale and place):\n * - VALUE INVERSION: a light app gets a dark card in the product's hue, a\n * dark app a light one; the accent is kept for the kicker and the dot.\n * - ELEVATION: a real shadow plus a hairline of light on the edge.\n * - SCALE: the title is set for the render, CALLOUT_TYPE_RATIO times the\n * app's body as it appears on screen at the layer's start (the camera\n * scales the app, so the caller passes that scale), never the app's own\n * size.\n * - MOTION: a rise in, a fade out.\n * - PLACE is the pin's (lower/pin.ts), not the card's.\n *\n * Pure: hex in, markup and CSS out. The starters are this grammar in the\n * house register; `vos callout` is it in the take's BRAND.md.\n */\nimport type { HtmlOverlayClip, OverlayPin } from './types'\n\nexport type CalloutShape = 'tag' | 'note' | 'code'\nexport const CALLOUT_SHAPES: readonly CalloutShape[] = ['tag', 'note', 'code']\n\n/** The title's size over the app's body size as seen on screen. */\nexport const CALLOUT_TYPE_RATIO = 1.35\n/** The app's body size when nothing says otherwise (CSS px at the viewport). */\nexport const CALLOUT_DEFAULT_BODY_PX = 14\n\n/** The product's register: what a card shares with the app. */\nexport interface CalloutRegister {\n /** The app's ground (its page background), a hex. Decides the inversion. */\n ground: string\n /** The product's accent, a hex: the kicker, the dot, the bar. */\n accent: string\n /** The words face: a hosted catalog family or a system stack. */\n face?: string\n /** The code face. */\n mono?: string\n /** The app's body type size in CSS px at the recorded viewport. */\n body?: number\n}\n\nexport interface CalloutWords {\n /** A short label above the title, set in the accent (a tag is only this). */\n kicker?: string\n title?: string\n /** One or two lines under the title. */\n body?: string\n /** The payload, kept as typed (lines break on \\n). */\n code?: string\n}\n\n/** The resolved look: colours and sizes in design px. */\nexport interface CalloutLook {\n light: boolean\n cardGround: string\n cardInk: string\n cardMuted: string\n hairline: string\n accent: string\n shadow: string\n face: string\n mono: string\n kickerPx: number\n titlePx: number\n bodyPx: number\n codePx: number\n radius: number\n padX: number\n padY: number\n}\n\nexport const HOUSE_REGISTER: CalloutRegister = {\n ground: '#ffffff',\n accent: '#7c3aed',\n}\n\nconst DEFAULT_FACE = \"'Inter',-apple-system,system-ui,sans-serif\"\nconst DEFAULT_MONO = \"'JetBrains Mono',ui-monospace,monospace\"\n\n/**\n * Resolve a register into a look. `scale` is design px per CSS px of the\n * app as it appears at the layer's start (the card's footage scale times\n * the camera's level); 1 when unknown.\n */\nexport function calloutLook(register: CalloutRegister, scale = 1): CalloutLook {\n const ground = hexToRgb(register.ground) ?? [255, 255, 255]\n const accent = hexToRgb(register.accent) ?? [124, 58, 237]\n const light = relativeLuminance(ground) > 0.4\n const [h, s] = rgbToHsl(accent)\n // The card takes the product's hue at the opposite value: near-black or\n // near-white, tinted just enough to belong to the app.\n const cardGround = light\n ? hslToHex(h, Math.min(0.35, s * 0.6), 0.07)\n : hslToHex(h, Math.min(0.25, s * 0.35), 0.97)\n const bodyPx = register.body ?? CALLOUT_DEFAULT_BODY_PX\n const titlePx = Math.max(20, Math.round(CALLOUT_TYPE_RATIO * bodyPx * scale))\n return {\n light,\n cardGround,\n cardInk: light ? '#fafafa' : '#101012',\n cardMuted: light ? 'rgba(255,255,255,0.64)' : 'rgba(16,16,18,0.6)',\n hairline: light ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.1)',\n accent: rgbToHex(accent),\n shadow: '0 24px 60px rgba(0,0,0,0.35), 0 2px 6px rgba(0,0,0,0.16)',\n face: register.face ? quoteFamily(register.face) : DEFAULT_FACE,\n mono: register.mono ? quoteFamily(register.mono) : DEFAULT_MONO,\n kickerPx: Math.max(11, Math.round(titlePx * 0.55)),\n titlePx,\n bodyPx: Math.max(15, Math.round(titlePx * 0.75)),\n codePx: Math.max(14, Math.round(titlePx * 0.68)),\n radius: 16,\n padX: 24,\n padY: 20,\n }\n}\n\nexport interface CalloutBox {\n width: number\n height: number\n}\n\n/**\n * The design box a shape needs for its words: one line for a tag, the\n * kicker, title and wrapped body for a note, the lines for code. Widths\n * estimate glyphs at 0.56 em for words and 0.62 em for mono.\n */\nexport function calloutBox(\n shape: CalloutShape,\n look: CalloutLook,\n words: CalloutWords,\n): CalloutBox {\n const em = (px: number, chars: number, adv: number) =>\n Math.ceil(px * adv * chars)\n if (shape === 'tag') {\n const text = words.kicker ?? words.title ?? ''\n const w = em(look.kickerPx * 1.1, text.length, 0.62) + 14 + 18 * 2\n return {\n width: clamp(w, 120, 720),\n height: Math.max(36, Math.round(look.kickerPx * 1.1 * 2.6)),\n }\n }\n if (shape === 'code') {\n const lines = (words.code ?? '').split('\\n')\n const longest = Math.max(1, ...lines.map((l) => l.length))\n const lineH = Math.round(look.codePx * 1.7)\n const w = em(look.codePx, longest, 0.62) + look.padX * 2\n const h =\n lines.length * lineH +\n look.padY * 2 +\n (words.kicker ? look.kickerPx * 2 : 0)\n return { width: clamp(w, 360, 820), height: clamp(h, 72, 640) }\n }\n const title = words.title ?? ''\n const body = words.body ?? ''\n const titleW = em(look.titlePx, title.length, 0.56)\n const width = clamp(Math.max(titleW, 320) + look.padX * 2, 360, 640)\n const inner = width - look.padX * 2\n const bodyLines = body\n ? Math.max(1, Math.ceil(em(look.bodyPx, body.length, 0.54) / inner))\n : 0\n const titleLines = Math.max(1, Math.ceil(titleW / inner))\n const height =\n look.padY * 2 +\n (words.kicker ? Math.round(look.kickerPx * 1.6) : 0) +\n titleLines * Math.round(look.titlePx * 1.25) +\n (bodyLines ? 8 + bodyLines * Math.round(look.bodyPx * 1.5) : 0)\n return { width, height: clamp(height, 64, 480) }\n}\n\n/** The markup and CSS for a shape in a look, sized to a box. */\nexport function calloutSource(\n shape: CalloutShape,\n look: CalloutLook,\n words: CalloutWords,\n box: CalloutBox,\n): { html: string; css: string } {\n const base =\n `box-sizing:border-box;width:${box.width}px;height:${box.height}px;` +\n `background:${look.cardGround};color:${look.cardInk};` +\n `border:1px solid ${look.hairline};border-radius:${look.radius}px;` +\n `box-shadow:${look.shadow};-webkit-font-smoothing:antialiased;`\n if (shape === 'tag') {\n const text = esc(words.kicker ?? words.title ?? '')\n return {\n html: `<div class=\"tag\"><span class=\"dot\"></span><span class=\"t\">${text}</span></div>`,\n css:\n `.tag{font-family:${look.face};display:flex;align-items:center;gap:10px;${base}border-radius:999px;padding:0 18px;background:${look.accent};color:#fff;border-color:rgba(255,255,255,0.18);font-size:${Math.round(look.kickerPx * 1.1)}px;font-weight:600;letter-spacing:0.02em;text-transform:uppercase;white-space:nowrap}` +\n `.dot{width:8px;height:8px;border-radius:99px;background:#fff;opacity:0.9;flex:none}`,\n }\n }\n if (shape === 'code') {\n const lines = (words.code ?? '')\n .split('\\n')\n .map((l) =>\n /^\\s*(\\/\\/|#|\\$)/.test(l)\n ? `<span class=\"mut\">${esc(l)}</span>`\n : esc(l),\n )\n .join('\\n')\n const kicker = words.kicker\n ? `<div class=\"k\">${esc(words.kicker)}</div>`\n : ''\n return {\n html: `<div class=\"code\">${kicker}<pre class=\"p\">${lines}</pre></div>`,\n css:\n `.code{font-family:${look.mono};${base}padding:${look.padY}px ${look.padX}px;font-size:${look.codePx}px;line-height:1.7;letter-spacing:-0.01em}` +\n `.k{font-family:${look.face};font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin:0 0 ${Math.round(look.kickerPx * 0.9)}px}` +\n `.p{margin:0;white-space:pre;font:inherit}` +\n `.mut{color:${look.cardMuted}}`,\n }\n }\n const kicker = words.kicker\n ? `<div class=\"k\"><span class=\"dot\"></span>${esc(words.kicker)}</div>`\n : ''\n const body = words.body ? `<div class=\"b\">${esc(words.body)}</div>` : ''\n return {\n html: `<div class=\"note\">${kicker}<div class=\"t\">${esc(words.title ?? '')}</div>${body}</div>`,\n css:\n `.note{font-family:${look.face};${base}padding:${look.padY}px ${look.padX}px;display:flex;flex-direction:column;justify-content:center}` +\n `.k{display:flex;align-items:center;gap:8px;font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin-bottom:${Math.round(look.kickerPx * 0.6)}px}` +\n `.dot{width:7px;height:7px;border-radius:99px;background:${look.accent};box-shadow:0 0 10px ${look.accent}}` +\n `.t{font-size:${look.titlePx}px;font-weight:600;line-height:1.25;letter-spacing:-0.01em}` +\n `.b{margin-top:8px;font-size:${look.bodyPx}px;line-height:1.5;color:${look.cardMuted}}`,\n }\n}\n\nexport interface CalloutClipOptions {\n id: string\n /** OUTPUT seconds. */\n start: number\n duration: number\n /** Design px per CSS px of the app on screen at `start` (1 = unknown). */\n scale?: number\n pin?: OverlayPin\n /** The fallback place when the pin cannot resolve (frame fractions). */\n at?: { x: number; y: number }\n}\n\n/** A callout as an html overlay clip: the grammar applied to a register. */\nexport function calloutClip(\n shape: CalloutShape,\n register: CalloutRegister,\n words: CalloutWords,\n opts: CalloutClipOptions,\n): HtmlOverlayClip {\n const look = calloutLook(register, opts.scale ?? 1)\n const box = calloutBox(shape, look, words)\n const { html, css } = calloutSource(shape, look, words, box)\n return {\n id: opts.id,\n kind: 'html',\n start: opts.start,\n duration: opts.duration,\n html,\n css,\n box,\n transform: {\n x: opts.at?.x ?? 0.5,\n y: opts.at?.y ?? 0.5,\n scale: 1,\n rotation: 0,\n },\n anim: { enter: 'rise', exit: 'fade' },\n ...(opts.pin ? { pin: opts.pin } : {}),\n }\n}\n\n/**\n * The card's ground as its CSS declares it (the root rule's background),\n * for the picture check that compares it with the footage it covers. Null\n * when the CSS names none in a form a hex can be read from.\n */\nexport function calloutGroundOf(css: string): string | null {\n const m =\n /background(?:-color)?\\s*:\\s*(#[0-9a-fA-F]{3,8}|rgba?\\([^)]*\\))/.exec(css)\n if (!m) return null\n const rgb = parseCssColour(m[1])\n return rgb ? rgbToHex(rgb) : null\n}\n\n// --- colour ---------------------------------------------------------------\n\nexport type Rgb = [number, number, number]\n\nexport function hexToRgb(hex: string): Rgb | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim())\n if (!m) return null\n let h = m[1]\n if (h.length === 3)\n h = h\n .split('')\n .map((c) => c + c)\n .join('')\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ]\n}\n\nexport function rgbToHex([r, g, b]: Rgb): string {\n const c = (v: number) =>\n Math.round(clamp(v, 0, 255))\n .toString(16)\n .padStart(2, '0')\n return `#${c(r)}${c(g)}${c(b)}`\n}\n\nexport function parseCssColour(s: string): Rgb | null {\n const hex = hexToRgb(s)\n if (hex) return hex\n const m = /rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/.exec(s)\n if (!m) return null\n return [Number(m[1]), Number(m[2]), Number(m[3])]\n}\n\nexport function relativeLuminance([r, g, b]: Rgb): number {\n const lin = (v: number) => {\n const c = v / 255\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4\n }\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)\n}\n\nexport function rgbToHsl([r, g, b]: Rgb): [number, number, number] {\n const rr = r / 255\n const gg = g / 255\n const bb = b / 255\n const max = Math.max(rr, gg, bb)\n const min = Math.min(rr, gg, bb)\n const l = (max + min) / 2\n if (max === min) return [0, 0, l]\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n let h = 0\n if (max === rr) h = (gg - bb) / d + (gg < bb ? 6 : 0)\n else if (max === gg) h = (bb - rr) / d + 2\n else h = (rr - gg) / d + 4\n return [h / 6, s, l]\n}\n\nexport function hslToHex(h: number, s: number, l: number): string {\n const f = (n: number) => {\n const k = (n + h * 12) % 12\n const a = s * Math.min(l, 1 - l)\n return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))\n }\n return rgbToHex([f(0) * 255, f(8) * 255, f(4) * 255])\n}\n\n/** CIE76 colour difference between two sRGB colours. */\nexport function deltaE(a: Rgb, b: Rgb): number {\n const la = rgbToLab(a)\n const lb = rgbToLab(b)\n return Math.hypot(la[0] - lb[0], la[1] - lb[1], la[2] - lb[2])\n}\n\nexport function rgbToLab([r, g, b]: Rgb): [number, number, number] {\n const lin = (v: number) => {\n const c = v / 255\n return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4\n }\n const R = lin(r)\n const G = lin(g)\n const B = lin(b)\n const x = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047\n const y = R * 0.2126 + G * 0.7152 + B * 0.0722\n const z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 1.08883\n const f = (t: number) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116)\n return [116 * f(y) - 16, 500 * (f(x) - f(y)), 200 * (f(y) - f(z))]\n}\n\nfunction quoteFamily(family: string): string {\n const first = family\n .split(',')[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, '')\n const rest = family.includes(',')\n ? family.slice(family.indexOf(',') + 1).trim()\n : /mono/i.test(first)\n ? 'ui-monospace,monospace'\n : '-apple-system,system-ui,sans-serif'\n return /^[a-z-]+$/i.test(first) &&\n /^(ui-|system-ui|sans-serif|serif|monospace)/.test(first)\n ? `${first},${rest}`\n : `'${first}',${rest}`\n}\n\nfunction esc(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\n","/**\n * Capture-space normalization — the single seam that makes window/monitor\n * recordings look like tab recordings to everything downstream.\n *\n * Tab captures record the viewport, so CursorEvent.x/y (viewport CSS px) IS the\n * cursor space and meta.width/height describes it. For window/monitor captures\n * the viewport is only part of the frame, so events are mapped into capture\n * pixels here — once, at doc-build time — using each event's screen coords\n * (sx/sy) and the geometry sampled at record start. After normalization the\n * planner, lowering, and composition consume the doc unchanged.\n *\n * Window takes additionally get a VIEWPORT CROP when the geometry is clean\n * (deriveViewportCrop): the real browser\n * chrome is cut out of the footage at the drawImage seam, the cursor/meta are\n * rewritten into crop space, and the synthetic browser bar becomes available\n * exactly as on tab takes. Fail-closed: a wrong crop (chrome sliver, cut page\n * edge) reads far worse than no crop, so any geometry doubt → no crop.\n */\nimport type {\n CursorEvent,\n CursorTrack,\n ProjectDoc,\n RecordingMeta,\n Rect,\n} from './types'\n\nexport interface CaptureNormalization {\n cursor: CursorTrack\n meta: RecordingMeta\n /**\n * Fraction of mapped events that landed inside the captured frame (1 for tab\n * captures). Low coverage means the user shared a different window/display\n * than the one hosting the recorded tab — the app should skip auto-zoom and\n * cursor overlay rather than render them at wrong positions.\n */\n coverage: number\n /**\n * Window takes with clean geometry: the viewport's rect inside the capture\n * frame (capture px) — the drawImage source crop that removes the real\n * browser chrome. When present, the returned cursor/meta are already in\n * crop space. Absent = render the full frame.\n */\n crop?: Rect\n}\n\n/** Plausible top-chrome height (tab strip + toolbar), CSS px. Outside → geometry is lying. */\nconst CHROME_TOP_MIN = 20\nconst CHROME_TOP_MAX = 220\n/** Event-offset vs window-geometry agreement tolerance, CSS px. */\nconst CROP_TOLERANCE = 16\n/** Minimum agreeing events before the event-derived viewport origin is trusted. */\nconst CROP_MIN_EVENTS = 3\n\n/**\n * Derive the viewport crop for a window take: where the page viewport sits\n * inside the captured window frame, in capture px.\n *\n * Two independent estimators cross-check each other:\n * 1. event-derived (primary): each event's own viewport→screen offset\n * (sx − x, sy − y) — exact wherever the chrome actually is, but needs events;\n * 2. window-derived: windowRect vs meta.viewport under the chrome-on-top\n * assumption (side insets split evenly) — no events needed, but wrong for\n * docked devtools / exotic decorations.\n *\n * Only offsets agreeing with (2) are kept — this simultaneously validates the\n * chrome-on-top assumption AND rejects cross-origin-iframe events, whose\n * offsets are the IFRAME's origin, not the top viewport's. Returns null\n * (no crop) on any doubt — see the fail-closed matrix in the analysis doc.\n */\nexport function deriveViewportCrop(\n cursor: CursorTrack,\n meta: RecordingMeta,\n): Rect | null {\n if ((meta.captureSurface ?? 'tab') !== 'window') return null\n const win = meta.windowRect\n const vp = meta.viewport\n const capW = meta.captureWidth ?? 0\n const capH = meta.captureHeight ?? 0\n if (\n !win ||\n !vp ||\n win.w <= 0 ||\n win.h <= 0 ||\n vp.w <= 0 ||\n vp.h <= 0 ||\n capW <= 0 ||\n capH <= 0\n )\n return null\n // Any mid-take geometry drift invalidates the single static crop.\n if (\n meta.windowMovedDuringTake ||\n meta.viewportChangedDuringTake ||\n meta.resizedDuringTake\n )\n return null\n // Browser window unfocused for most of the take ⇒ the user was driving a\n // different window — the SHARED surface is probably not this browser window,\n // and the crop would cut unrelated pixels (see windowFocusedFrac).\n if ((meta.windowFocusedFrac ?? 1) < WINDOW_FOCUS_MIN) return null\n // Page zoom breaks CSS px == DIPs for event x/y — deferred (a later fold-in).\n if (Math.abs((meta.zoom || 1) - 1) > 0.001) return null\n\n const scaleX = capW / win.w\n const scaleY = capH / win.h\n // A clean window capture scales both axes identically; disagreement means the\n // captured surface isn't this window (or the rect is stale).\n if (Math.abs(scaleX / scaleY - 1) > 0.05) return null\n\n // Window-derived estimate (chrome on top, side insets split evenly).\n const estX = win.x + (win.w - vp.w) / 2\n const estY = win.y + (win.h - vp.h)\n\n const xs: number[] = []\n const ys: number[] = []\n for (const e of cursor) {\n if (e.sx === undefined || e.sy === undefined) continue\n const ox = e.sx - e.x\n const oy = e.sy - e.y\n if (\n Math.abs(ox - estX) <= CROP_TOLERANCE &&\n Math.abs(oy - estY) <= CROP_TOLERANCE\n ) {\n xs.push(ox)\n ys.push(oy)\n }\n }\n if (xs.length < CROP_MIN_EVENTS) return null\n const vx = median(xs)\n const vy = median(ys)\n\n const topChrome = vy - win.y\n if (topChrome < CHROME_TOP_MIN || topChrome > CHROME_TOP_MAX) return null\n\n const crop: Rect = {\n x: Math.round((vx - win.x) * scaleX),\n y: Math.round((vy - win.y) * scaleY),\n w: Math.round(vp.w * scaleX),\n h: Math.round(vp.h * scaleY),\n }\n // Must sit inside the capture (rounding slack only) and be most of it.\n if (\n crop.x < -2 ||\n crop.y < 0 ||\n crop.x + crop.w > capW + 2 ||\n crop.y + crop.h > capH + 2\n )\n return null\n crop.x = Math.max(0, crop.x)\n crop.w = Math.min(crop.w, capW - crop.x)\n crop.h = Math.min(crop.h, capH - crop.y)\n if (crop.w * crop.h < 0.5 * capW * capH) return null\n return crop\n}\n\n/**\n * Map a cursor track into the capture's pixel space. Identity for tab captures\n * (or when geometry is missing). For window/monitor captures the returned meta\n * has width/height set to the capture pixel dimensions (the new cursor space)\n * and dpr/zoom reset to 1 — cursor space and video pixels now coincide. When a\n * window take yields a viewport crop, cursor space is the CROPPED frame and\n * meta dims are the crop dims (downstream layout/planner/zoom need no crop\n * awareness — only drawImage reads the rect).\n */\nexport function normalizeCaptureSpace(\n cursor: CursorTrack,\n meta: RecordingMeta,\n): CaptureNormalization {\n const surface = meta.captureSurface ?? 'tab'\n if (surface === 'tab') return { cursor, meta, coverage: 1 }\n const anchor = surface === 'window' ? meta.windowRect : meta.screenRect\n const capW = meta.captureWidth ?? 0\n const capH = meta.captureHeight ?? 0\n if (!anchor || anchor.w <= 0 || anchor.h <= 0 || capW <= 0 || capH <= 0) {\n // A display take we CANNOT map (missing geometry or capture dims — e.g. a\n // recorder that read track settings after the source ended). Never pass the\n // raw viewport-space track through as if it were frame space: the cursor\n // would render at unrelated positions. Report coverage 0 so the app drops\n // the track with its normal notice.\n return { cursor, meta, coverage: 0 }\n }\n\n // Independent axis scales: outerHeight vs captured height can disagree by a\n // title-bar's worth of chrome, so a single uniform scale would drift.\n const scaleX = capW / anchor.w\n const scaleY = capH / anchor.h\n\n const crop = deriveViewportCrop(cursor, meta) ?? undefined\n const cropX = crop?.x ?? 0\n const cropY = crop?.y ?? 0\n const frameW = crop?.w ?? capW\n const frameH = crop?.h ?? capH\n\n let inFrame = 0\n const mapped: CursorEvent[] = []\n for (const e of cursor) {\n if (e.sx === undefined || e.sy === undefined) continue // unmappable (old capture) — drop\n const x = (e.sx - anchor.x) * scaleX - cropX\n const y = (e.sy - anchor.y) * scaleY - cropY\n if (x >= 0 && x <= frameW && y >= 0 && y <= frameH) inFrame++\n // Element rects are viewport-relative; this event's own viewport→screen\n // offset transforms them without any window-geometry guesswork.\n let rect: Rect | undefined\n if (e.rect) {\n const dx = e.sx - e.x\n const dy = e.sy - e.y\n rect = {\n x: (e.rect.x + dx - anchor.x) * scaleX - cropX,\n y: (e.rect.y + dy - anchor.y) * scaleY - cropY,\n w: e.rect.w * scaleX,\n h: e.rect.h * scaleY,\n }\n }\n mapped.push({ ...e, x, y, rect })\n }\n\n return {\n cursor: mapped,\n meta: {\n ...meta,\n width: frameW,\n height: frameH,\n ...(crop ? { captureWidth: frameW, captureHeight: frameH } : {}),\n dpr: 1,\n zoom: 1,\n },\n coverage: mapped.length ? inFrame / mapped.length : 0,\n crop,\n }\n}\n\n/** Coverage below this → treat the cursor track as unusable (skip auto-zoom + overlay). */\nexport const CAPTURE_COVERAGE_MIN = 0.5\n\n/**\n * Window takes whose browser window was focused for less than this fraction of\n * the take are treated as wrong-window shares: cursor track dropped, no\n * viewport crop (see RecordingMeta.windowFocusedFrac).\n */\nexport const WINDOW_FOCUS_MIN = 0.5\n\n/**\n * Crop-space ↔ full-space doc remaps — the \"Original\" frame mode (show the\n * user's real browser chrome on a cropped window take). The footage always\n * holds the full frame, so the toggle is a LOSSLESS coordinate remap of the\n * doc (cursor events/rects, zoom focus points, meta dims) driven by the\n * chromeCrop record kept from ingest — one undoable patch-store edit, program\n * string untouched (everything involved lowers into ctx.data). Time-based\n * state (segments, speed, audio, cam window) is space-independent.\n *\n * Both helpers MUTATE a draft doc (call inside the store's edit()) and are\n * no-ops when the doc is already in the requested space or has no chromeCrop.\n */\n\n/** Remap a crop-space doc to full-capture space (show the original chrome). */\nexport function docToFullSpace(d: ProjectDoc): void {\n const cc = d.source.chromeCrop\n if (!cc || !d.source.crop) return\n const { rect, frameW, frameH } = cc\n d.source.crop = undefined\n d.source.cursor = d.source.cursor.map((e) => ({\n ...e,\n x: e.x + rect.x,\n y: e.y + rect.y,\n rect: e.rect\n ? { ...e.rect, x: e.rect.x + rect.x, y: e.rect.y + rect.y }\n : undefined,\n }))\n d.source.meta = {\n ...d.source.meta,\n width: frameW,\n height: frameH,\n captureWidth: frameW,\n captureHeight: frameH,\n }\n d.zoom = d.zoom.map((z) => ({\n ...z,\n cx: (rect.x + z.cx * rect.w) / frameW,\n cy: (rect.y + z.cy * rect.h) / frameH,\n }))\n}\n\n/** Remap a full-space doc back into crop space (hide the chrome again). */\nexport function docToCropSpace(d: ProjectDoc): void {\n const cc = d.source.chromeCrop\n if (!cc || d.source.crop) return\n const { rect, frameW, frameH } = cc\n d.source.crop = { ...rect }\n d.source.cursor = d.source.cursor.map((e) => ({\n ...e,\n x: e.x - rect.x,\n y: e.y - rect.y,\n rect: e.rect\n ? { ...e.rect, x: e.rect.x - rect.x, y: e.rect.y - rect.y }\n : undefined,\n }))\n d.source.meta = {\n ...d.source.meta,\n width: rect.w,\n height: rect.h,\n captureWidth: rect.w,\n captureHeight: rect.h,\n }\n // A focus aimed at chrome pixels clamps to the crop edge (the lowering's\n // clampFocus refines against the real card layout).\n d.zoom = d.zoom.map((z) => ({\n ...z,\n cx: clamp01((z.cx * frameW - rect.x) / rect.w),\n cy: clamp01((z.cy * frameH - rect.y) / rect.h),\n }))\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\n\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b)\n const mid = Math.floor(sorted.length / 2)\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2\n}\n","/**\n * Artifact → ProjectDoc ingest — the seam every recorder (extension, CLI)\n * funnels through. Moved from the web app so non-browser producers (the vos\n * CLI) run the exact same ingest as the studio.\n */\nimport {\n DEFAULT_CAM_STYLE,\n DEFAULT_CURSOR_STYLE,\n DEFAULT_FRAME_STYLE,\n pageDisplayUrl,\n platformBarKind,\n} from './types'\nimport {\n CAPTURE_COVERAGE_MIN,\n WINDOW_FOCUS_MIN,\n normalizeCaptureSpace,\n} from './capture'\nimport type { FrameStyle, ProjectDoc, RecordingArtifact } from './types'\n\nexport interface IngestOptions {\n /**\n * The frame the take opens on: a host's house backdrop on the bare frame,\n * say (`withBackdrop`). Its browser bar is still derived from the footage\n * below. Absent, `DEFAULT_FRAME_STYLE`.\n */\n frame?: FrameStyle\n}\n\n/** Build a ProjectDoc from a RecordingArtifact handed off by a recorder. */\nexport function projectFromArtifact(\n artifact: RecordingArtifact,\n videoUrl: string,\n opts: IngestOptions = {},\n): { doc: ProjectDoc; videoUrl: string } {\n const baseFrame = opts.frame ?? DEFAULT_FRAME_STYLE\n // Window/monitor takes: map cursor events into capture px (normalizeCaptureSpace);\n // low coverage means the user shared a surface other than the one hosting the\n // recorded tab (or the mapping anchors are unusable) — drop the track rather\n // than draw the cursor at wrong positions. A window that moved mid-take\n // invalidates its anchor the same way.\n const surface = artifact.meta.captureSurface ?? 'tab'\n const norm = normalizeCaptureSpace(artifact.cursor, artifact.meta)\n const { cursor, meta } = norm\n // Entire-screen takes: the cursor is only trackable INSIDE browser pages —\n // the moment the pointer leaves the browser (other apps, desktop, dock) the\n // track goes blind while the video keeps showing the real cursor, so the\n // synthetic cursor/zoom would be wrong for exactly the footage people record\n // screens for. Policy: no cursor effects for monitor takes (the record page\n // says so up front).\n // Wrong-window tell: cursor events come from the recorded tab, so their\n // geometry is self-consistent even when a DIFFERENT window was shared —\n // coverage can't catch it. A browser window that was unfocused for most of\n // the take means the user was driving another window (the one they shared).\n const unfocused =\n surface === 'window' &&\n (artifact.meta.windowFocusedFrac ?? 1) < WINDOW_FOCUS_MIN\n const coverage =\n surface === 'monitor' || artifact.meta.windowMovedDuringTake || unfocused\n ? 0\n : norm.coverage\n if (artifact.cursor.length > 0 && coverage < CAPTURE_COVERAGE_MIN) {\n console.warn(\n '[voila/studio] dropping cursor track —',\n surface === 'monitor'\n ? 'entire-screen takes have no cursor effects (untrackable outside browser pages)'\n : artifact.meta.windowMovedDuringTake\n ? 'window moved/resized during the take'\n : unfocused\n ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take — a different window was likely shared`\n : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`,\n )\n }\n const doc: ProjectDoc = {\n source: {\n videoKey: videoUrl,\n cursor: coverage >= CAPTURE_COVERAGE_MIN ? cursor : [],\n meta,\n camKey: artifact.camKey,\n micKey: artifact.audioKey,\n // Tab takes are known-good MP4 → frame-accurate WebCodecs. Display takes\n // encode WebM (surface resizes break MP4) → robust HTMLVideoElement path.\n // CLI takes encode WebM too — same robust path.\n frameSource:\n surface === 'tab' && artifact.meta.producer !== 'cli'\n ? 'webcodecs'\n : 'html5',\n // Window takes with clean geometry: crop the real browser chrome out of the\n // footage (cursor/meta are already in crop space — see normalizeCaptureSpace).\n crop: norm.crop,\n // Keep the derivation + full capture dims so the \"Original\" frame mode can\n // remap the doc between crop/full space losslessly (docToFullSpace).\n chromeCrop: norm.crop\n ? {\n rect: norm.crop,\n frameW: artifact.meta.captureWidth ?? artifact.meta.width,\n frameH: artifact.meta.captureHeight ?? artifact.meta.height,\n }\n : undefined,\n },\n segments: [{ in: 0, out: artifact.meta.durationMs / 1000 }], // canonical full-source span\n zoom: [], // planner runs once the doc is loaded (editor) or planned (CLI)\n audio: [],\n // A new take wears the OS pointer; DEFAULT_CURSOR_STYLE keeps the dot\n // so a document that stores it renders as it was cut.\n cursor: { ...DEFAULT_CURSOR_STYLE, style: 'arrow' },\n cam: { ...DEFAULT_CAM_STYLE },\n frame: {\n ...baseFrame,\n browserBar: {\n ...baseFrame.browserBar,\n // Chrome-free footage (tab takes, cropped window takes) opens with the\n // OS-matched realistic frame — the Screen-Studio first render; Hidden is\n // one click away. Footage that still contains real chrome gets none.\n kind:\n surface === 'tab' || norm.crop\n ? platformBarKind(artifact.meta.platform)\n : 'none',\n // Pre-fill the address pill from the recorded page.\n url: pageDisplayUrl(artifact.meta.pageUrl),\n },\n },\n // 60 fps: the footage duplicates frames, the camera does not, and a\n // 0.5 s glide at 30 fps is fifteen visible steps.\n export: { resolution: '1080p', fps: 60, format: 'mp4' },\n }\n return { doc, videoUrl }\n}\n","/**\n * How the card MOVES and how a cut ENDS, as data the lowering already\n * understands: the card's `anim.enter` writes the head of the tilt or zoom\n * track and a card-pose track (scale, rise, opacity); its `anim.exit`\n * writes that track's tail; a FREEZE (`doc.freeze`, a source moment plus\n * output seconds; a legacy segment `hold` is one at the segment's end) is a\n * rated piece whose hair of source time plays for its seconds; the card is\n * on screen exactly while its clip (the footage, freezes included) runs and\n * gone after it, like every layer; and the output lasts until the last\n * visual clip ends, so an \"end card\" is a freeze of the last frame, a card\n * exit over it and clips placed over the freeze. The two older spellings\n * (`frame.entrance`, `doc.endCard`) migrate here, on read, into exactly\n * that. Pure; ON_FRAME reads the tracks.\n */\nimport { sortKeyframes } from '@vosjs/timeline'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type {\n Anim,\n AnimStep,\n DocSegment,\n EndCard,\n FrameStyle,\n FreezeSpan,\n MediaOverlayClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n TextOverlayClip,\n} from '../types'\nimport { resolveExportSize } from '../types'\nimport { animStep, enterOf, exitOf, lastLayerEnd } from '../anim'\nimport { splitBySpeed, totalDuration } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\n\n/**\n * THE REST of a take: the one output time its still is taken at, where\n * the LAST freeze begins — the composed frame before nothing moves (a\n * poster ends on one). Null when the take has no freeze on kept footage.\n * One convention for every still-taking surface (an export, a thumbnail,\n * a kit), so they agree. Reads a legacy segment `hold` as the freeze it\n * migrates to, and a raw hosted payload (no `segments`, no `source`) as\n * what it has. A legacy end card yields null: its words rise over a\n * receding card, which is footage under a fading title, not a poster.\n */\n/** The freezes the document owns: the ones no template laid (no `from`). */\nexport function ownFreezes(doc: ProjectDoc): FreezeSpan[] {\n return docFreezes(doc).filter((f) => !f.from)\n}\n\n/** The still a document with nothing to say gets: the old default. */\nexport const STILL_DEFAULT = 0.5\n/** A short beat after the opening has entered, so the hero frame is settled. */\nexport const HERO_BEAT = 0.25\n/** A clip starting within this many output seconds is part of the opening. */\nexport const OPENING_WINDOW = 3\n/** The house length of a clip's block enter, for clips that name none. */\nexport const OPENING_ENTER_SECONDS = 0.6\n\nconst r3 = (v: number) => Math.round(v * 1000) / 1000\n\n/**\n * The output moment that STANDS for a document: its cover on the shelf,\n * the still a kit leads with. One ladder, one derivation, read by the\n * fleet thumbnail, the stills export and the kit alike; a template can\n * never move it, because everything a template lays is stamped `from`.\n *\n * 1. `doc.still`, the author's own choice, clamped to the output.\n * 2. The last freeze the document OWNS (no `from`), where it begins: a\n * poster's rest, or a moment the author froze on purpose.\n * 3. The hero: the first moment at which the card and every clip present\n * at the cold open have finished entering, plus a beat, never past the\n * footage. Read from `anim` seconds, so it is the same everywhere.\n * 4. The old default, for a document with none of the above.\n */\nexport function docStillTime(doc: ProjectDoc): number {\n const segs = docSegmentsOf(doc)\n const rated = withFreezes(\n splitBySpeed(segs, doc.speed ?? []),\n docFreezes(doc),\n )\n const footage = totalDuration(rated)\n const end = outputEnd(doc, footage)\n const clampOut = (v: number) => Math.max(0, Math.min(end, v))\n if (typeof doc.still === 'number' && Number.isFinite(doc.still))\n return r3(clampOut(doc.still))\n if (!doc.endCard) {\n const own = ownFreezes(doc)\n if (own.length) {\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n own,\n )\n let rest: number | null = null\n for (const m of marks.values())\n if (rest === null || m.t > rest) rest = m.t\n if (rest !== null && rest >= 0) return r3(clampOut(rest))\n }\n }\n if (!(footage > 0)) return r3(clampOut(STILL_DEFAULT))\n let hero = entranceSeconds(cardEnter(doc.frame))\n for (const clip of doc.overlays ?? []) {\n if (clip.start > OPENING_WINDOW) continue\n const step = enterOf(clip.anim)\n const seconds =\n !step || step.kind === 'none'\n ? 0\n : Math.max(0, step.seconds ?? OPENING_ENTER_SECONDS)\n hero = Math.max(hero, clip.start + seconds)\n }\n hero = Math.max(STILL_DEFAULT, hero + HERO_BEAT)\n // Never past the footage: the card is what a cover shows.\n hero = Math.min(hero, Math.max(0, footage - 1 / 30))\n return r3(clampOut(hero))\n}\n\n/**\n * Where the LAST freeze of any origin begins, or null with none: the\n * poster's rest as first spelled. Prefer `docStillTime`, which ignores a\n * template's freezes and always answers.\n */\nexport function docRestTime(doc: ProjectDoc): number | null {\n if (doc.endCard) return null\n const freezes = docFreezes(doc)\n if (!freezes.length) return null\n const segs = docSegmentsOf(doc)\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n freezes,\n )\n let rest: number | null = null\n for (const m of marks.values()) if (rest === null || m.t > rest) rest = m.t\n if (rest === null || !(rest >= 0)) return null\n return Math.round(rest * 1000) / 1000\n}\n\n/** The doc's kept spans in explicit form; a raw payload with none is untrimmed. */\nfunction docSegmentsOf(doc: ProjectDoc): DocSegment[] {\n const segs = Array.isArray(doc.segments) ? doc.segments : []\n if (segs.length) return segs\n const ms = doc.source?.meta?.durationMs\n return [{ in: 0, out: typeof ms === 'number' ? ms / 1000 : 0 }]\n}\n\n/**\n * Every freeze the document carries, the spelled ones and the legacy\n * segment holds (as a freeze at that segment's `out`, id `h{i}`), sorted\n * by source moment. The one read every consumer takes, so an unmigrated\n * document freezes exactly where a migrated one does.\n */\nexport function docFreezes(\n doc: Pick<ProjectDoc, 'freeze' | 'segments'>,\n): FreezeSpan[] {\n const own = Array.isArray(doc.freeze) ? doc.freeze : []\n const legacy = legacyHoldFreezes(\n Array.isArray(doc.segments) ? doc.segments : [],\n )\n const all = [...own, ...legacy].filter(\n (f) =>\n typeof f.at === 'number' &&\n Number.isFinite(f.at) &&\n typeof f.seconds === 'number' &&\n f.seconds > 0,\n )\n return all.sort((a, b) => a.at - b.at)\n}\n\n/** A legacy segment `hold` as the freeze it migrates to. */\nfunction legacyHoldFreezes(segments: readonly DocSegment[]): FreezeSpan[] {\n const out: FreezeSpan[] = []\n segments.forEach((s, i) => {\n const hold = s.hold\n if (typeof hold === 'number' && hold > 0)\n out.push({ id: `h${i}`, at: s.out, seconds: hold })\n })\n return out\n}\n\n/** The card entrance's default length, seconds. */\nexport const ENTRANCE_SECONDS = 1.2\n/** The legacy end card's default length, seconds. */\nexport const END_CARD_SECONDS = 2.5\n/** How long the card takes to recede (the `recede` exit's house length). */\nexport const END_CARD_RECEDE = 0.7\n/** The `fade` exit's house length. */\nexport const CARD_FADE_SECONDS = 0.35\n/** The tilt-in's opening pose, degrees: top edge away, left edge toward. */\nexport const TILT_IN_POSE: [number, number] = [-9, 14]\n/** The pull-out's opening zoom level. */\nexport const PULL_OUT_LEVEL = 1.32\n/** The legacy marker a migrated end card's clips carry as `from`. */\nexport const END_CARD_FROM = 'endcard'\n\nconst HOLD_SOURCE_SPAN = 0.002\n\n/** A step with a kind and optional seconds: the card's, or a legacy entrance. */\ntype Step = { kind: string; seconds?: number } | null | undefined\n\n/** The card's enter step, from its `anim` (a migrated doc carries nothing else). */\nexport function cardEnter(frame: Pick<FrameStyle, 'anim'>): AnimStep | null {\n return enterOf(frame.anim)\n}\n\n/** The card's exit step, from its `anim`. */\nexport function cardExit(frame: Pick<FrameStyle, 'anim'>): AnimStep | null {\n return exitOf(frame.anim)\n}\n\nexport function entranceSeconds(e: Step): number {\n if (!e || e.kind === 'none') return 0\n return Math.max(0.2, Math.min(3, e.seconds ?? ENTRANCE_SECONDS))\n}\n\n/** The exit's length: its own seconds, else the house length of its kind. */\nexport function exitSeconds(e: Step): number {\n if (!e || e.kind === 'none') return 0\n const house = e.kind === 'fade' ? CARD_FADE_SECONDS : END_CARD_RECEDE\n return Math.max(0.1, Math.min(8, e.seconds ?? house))\n}\n\n/** A freeze's place on the output timeline (its start and its seconds). */\nexport interface FreezeExtent {\n t: number\n duration: number\n}\n\n/**\n * The rated pieces with every freeze placed: at a freeze's source moment\n * the piece holding it splits, and a freeze piece (a hair of source time\n * just before the moment, rated to play for the freeze's seconds) goes in\n * between; a freeze at a piece's end goes after it, one at the very start\n * before it. A freeze whose moment is on no kept footage is not placed\n * (it follows its frame, like a span). Every consumer of the rated list\n * (mapTime, the zoom remap, the audio splice, the duration) inherits the\n * freezes from this one seam. `marks` is where each placed freeze landed\n * in OUTPUT time, for the lane and the rest.\n */\nexport function placeFreezes(\n rated: readonly (Segment & { media?: string })[],\n freezes: readonly FreezeSpan[],\n): { pieces: Segment[]; marks: Map<string, FreezeExtent> } {\n const marks = new Map<string, FreezeExtent>()\n const placed = new Set<string>()\n const pieces: Segment[] = []\n let acc = 0\n const freezePiece = (f: FreezeSpan, media: string | undefined): void => {\n const at = Math.max(HOLD_SOURCE_SPAN, f.at)\n marks.set(f.id, { t: acc, duration: f.seconds })\n pieces.push({\n in: at - HOLD_SOURCE_SPAN,\n out: at,\n rate: HOLD_SOURCE_SPAN / f.seconds,\n ...(media !== undefined ? { media } : {}),\n } as Segment)\n placed.add(f.id)\n acc += f.seconds\n }\n const push = (p: Segment): void => {\n pieces.push(p)\n acc += Math.max(0, p.out - p.in) / (p.rate ?? 1)\n }\n for (let i = 0; i < rated.length; i++) {\n let piece = rated[i]\n const next = rated[i + 1]\n // The freezes on THIS piece's media whose moment it holds, in order: a\n // freeze inside it splits it; one at its very start goes before it (the\n // first frame, or a boundary the previous piece deferred); one exactly\n // at its end goes after it, unless the next piece continues the same\n // footage (a speed edge), which is the same frame either way — take the\n // later place so the freeze sits where the moment is.\n const continues =\n !!next &&\n sameMedia(next.media, piece.media) &&\n Math.abs(next.in - piece.out) < 1e-9 &&\n next.out > piece.out\n const mine = freezes\n .filter(\n (f) =>\n f.seconds > 0 &&\n !placed.has(f.id) &&\n sameMedia(f.media, piece.media) &&\n f.at >= piece.in - 1e-9 &&\n (continues ? f.at < piece.out - 1e-9 : f.at <= piece.out + 1e-9),\n )\n .sort((a, b) => a.at - b.at)\n for (const f of mine) {\n if (f.at <= piece.in + 1e-9) {\n freezePiece(f, piece.media)\n } else if (f.at < piece.out - 1e-9) {\n push({ ...piece, out: f.at })\n freezePiece(f, piece.media)\n piece = { ...piece, in: f.at }\n } else {\n const media = piece.media\n push(piece)\n piece = null as unknown as Segment\n freezePiece(f, media)\n }\n }\n if (piece) push(piece)\n }\n return { pieces, marks }\n}\n\n/** The rated segments with every freeze placed (see `placeFreezes`). */\nexport function withFreezes(\n rated: readonly Segment[],\n freezes: readonly FreezeSpan[],\n): Segment[] {\n return placeFreezes(rated, freezes).pieces\n}\n\n/** Where each freeze lands on the output timeline (unplaced ones absent). */\nexport function freezeOutputExtents(\n rated: readonly Segment[],\n freezes: readonly FreezeSpan[],\n): Map<string, FreezeExtent> {\n return placeFreezes(rated, freezes).marks\n}\n\n/**\n * The rated segments with every legacy segment `hold` placed as the freeze\n * it migrates to.\n * @deprecated Read `docFreezes` and place them with `withFreezes`.\n */\nexport function withHolds(\n docSegments: readonly DocSegment[],\n rated: Segment[],\n): Segment[] {\n return withFreezes(rated, legacyHoldFreezes(docSegments))\n}\n\n/** Keyframes the card's entrance prepends to the tilt track, or none. */\nexport function entranceTiltKeyframes(e: Step): Keyframe<number[]>[] {\n const s = entranceSeconds(e)\n if (!s || e?.kind !== 'tilt-in') return []\n return [\n { t: 0, value: [...TILT_IN_POSE], ease: 'none' },\n { t: s, value: [0, 0], ease: 'power3.out' },\n ]\n}\n\n/**\n * Keyframes the card's entrance prepends to the zoom track ([level, cx,\n * cy]): the pull-out's own head, or, for the other kinds, a REST head,\n * because the camera must not zoom into a card that is still arriving (a\n * planner's cold-open span would otherwise fight the entrance). None for\n * none.\n */\nexport function entranceZoomKeyframes(e: Step): Keyframe<number[]>[] {\n const s = entranceSeconds(e)\n if (!s) return []\n if (e?.kind === 'pull-out')\n return [\n { t: 0, value: [PULL_OUT_LEVEL, 0.5, 0.42], ease: 'none' },\n { t: s, value: [1, 0.5, 0.5], ease: 'power3.out' },\n ]\n return [\n { t: 0, value: [1, 0.5, 0.5], ease: 'none' },\n { t: s, value: [1, 0.5, 0.5], ease: 'none' },\n ]\n}\n\n/**\n * Prepend an entrance's keyframes to a track: keyframes the entrance\n * would overlap (earlier than its end plus a beat) give way, so a span\n * that starts at the cold open loses its head to the entrance rather\n * than fighting it. An empty entrance returns the track untouched.\n */\nexport function prependEntrance(\n track: KeyframeTrack<number[]> | undefined,\n head: Keyframe<number[]>[],\n): KeyframeTrack<number[]> | undefined {\n if (!head.length) return track\n const end = head[head.length - 1].t\n const rest = (track?.keyframes ?? []).filter((k) => k.t > end + 0.1)\n return { keyframes: sortKeyframes([...head, ...rest]) }\n}\n\n/** The pose a card exit ENDS on, gone: [scale, dy, opacity]. */\nfunction exitPose(e: Step): number[] {\n return e?.kind === 'fade' ? [1, 0, 0] : [0.9, -0.02, 0]\n}\n\n/** A recede's midway pose: stepped back and still mostly there; the fade comes last. */\nconst RECEDE_MID = [0.91, -0.018, 0.8]\n\n/**\n * The card-pose track [scale, dy, opacity] in OUTPUT seconds: the entrance\n * settles the card in from a slightly smaller, lower, transparent pose;\n * the exit takes it out as its footage ends. The exit runs FROM the\n * footage's end when something plays after it (the card recedes under the\n * words), and ENDS at the footage's end when nothing does (the card leaves\n * as the cut closes). Undefined when the card carries neither, so a doc\n * without them lowers byte-identically.\n */\nexport function cardPoseTrack(\n enter: Step,\n exit: Step,\n footageEnd: number,\n outputEnd: number = footageEnd,\n): KeyframeTrack<number[]> | undefined {\n const keyframes: Keyframe<number[]>[] = []\n const s = entranceSeconds(enter)\n if (s) {\n // The first frame is what a feed shows: the card is VISIBLE at t = 0\n // (the references open in perspective, never on nothing), so the pose\n // settles from a smaller, lower, softened card, not from a blank.\n // A slide moves the plane, not the pose (the transitions table carries\n // it), so its track holds the rest.\n const from =\n enter?.kind === 'rise'\n ? [0.96, 0.08, 0.7]\n : enter?.kind === 'pull-out' ||\n enter?.kind === 'fade' ||\n enter?.kind === 'slide'\n ? enter.kind === 'fade'\n ? [1, 0, 0]\n : [1, 0, 1]\n : [0.94, 0.05, 0.7]\n keyframes.push({ t: 0, value: from, ease: 'none' })\n keyframes.push({ t: s, value: [1, 0, 1], ease: 'power3.out' })\n }\n // The exit plays over the last seconds of the card's clip and ends gone\n // at the clip's end (the footage, freezes included), the contract every\n // clip's exit keeps. A recede steps back first and fades last.\n const x = exitSeconds(exit)\n let last = [1, 0, 1]\n if (x > 0 && footageEnd > 0) {\n const t0 = round3(Math.max(s, footageEnd - x))\n const t1 = round3(Math.max(t0 + 0.05, footageEnd))\n last = exitPose(exit)\n keyframes.push({ t: t0, value: [1, 0, 1], ease: 'none' })\n if (exit?.kind !== 'fade') {\n keyframes.push({\n t: round3(t0 + (t1 - t0) * 0.45),\n value: [...RECEDE_MID],\n ease: 'power2.out',\n })\n keyframes.push({ t: t1, value: last, ease: 'power1.in' })\n } else {\n keyframes.push({ t: t1, value: last, ease: 'power2.out' })\n }\n }\n // Past its clip the card is gone: a cut to nothing, so clips placed\n // after the footage play over the ground alone. A document that ends\n // on its footage never reaches this frame, so it carries no track.\n if (outputEnd > footageEnd + 1e-6 && footageEnd > 0) {\n if (!keyframes.some((k) => Math.abs(k.t - footageEnd) < 1e-9))\n keyframes.push({ t: footageEnd, value: [...last], ease: 'none' })\n keyframes.push({\n t: round3(footageEnd + 0.001),\n value: [last[0], last[1], 0],\n ease: 'none',\n })\n }\n if (!keyframes.length) return undefined\n return { keyframes: sortKeyframes(keyframes) }\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n\n/**\n * The clips a legacy end card places after the footage: the house title,\n * caption and label presets over the receding card, the mark above the\n * wordmark, every one stamped `from: 'endcard'` so a loop drops them and\n * a re-apply replaces them. Pure; the words are the card's.\n */\nexport function endCardClips(\n card: EndCard,\n doc: Pick<ProjectDoc, 'export' | 'frame' | 'source'>,\n endStart: number,\n): OverlayClip[] {\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n const anim: Anim = { enter: 'rise', exit: 'none' }\n const clips: OverlayClip[] = []\n const text = (\n id: string,\n body: string,\n preset: TextOverlayClip['preset'],\n y: number,\n delay: number,\n ): TextOverlayClip => ({\n id,\n kind: 'text',\n text: body,\n preset,\n start: endStart + delay,\n duration: Math.max(0.3, seconds - delay),\n transform: { x: 0.5, y, scale: 1, rotation: 0 },\n anim,\n from: END_CARD_FROM,\n align: 'center',\n ...(card.ink ? { color: card.ink } : {}),\n shadow: 0,\n })\n if (card.headline?.trim())\n clips.push(text('endcard-title', card.headline.trim(), 'title', 0.44, 0.35))\n if (card.sub?.trim())\n clips.push(text('endcard-sub', card.sub.trim(), 'caption', 0.57, 0.5))\n // The mark: a square-ish one sits above the wordmark at 1.3 times the\n // label's cap height; a wide one (a stylised wordmark asset) is the\n // wordmark. Width is a fraction of the FRAME width, so it needs the\n // frame's aspect; height is design px over 1080.\n const mark = card.mark\n const wide = !!mark && mark.aspect > 2.2\n if (mark && mark.key) {\n const size = resolveExportSize(doc as ProjectDoc)\n const designW = (1080 * size.width) / Math.max(1, size.height)\n const hPx = wide ? 44 : 30\n const clip: MediaOverlayClip = {\n id: 'endcard-markimg',\n kind: 'image',\n key: mark.key,\n width: Math.min(0.9, (hPx * Math.max(0.2, mark.aspect)) / designW),\n radius: 0,\n shadow: 'none',\n start: endStart + 0.65,\n duration: Math.max(0.3, seconds - 0.65),\n transform: { x: 0.5, y: wide ? 0.88 : 0.835, scale: 1, rotation: 0 },\n anim,\n from: END_CARD_FROM,\n }\n clips.push(clip)\n }\n if (card.wordmark?.trim() && !wide)\n clips.push(\n text(\n 'endcard-mark',\n card.wordmark.trim(),\n 'label',\n mark && mark.key ? 0.895 : 0.88,\n 0.65,\n ),\n )\n return clips\n}\n\n/**\n * A doc with its legacy end card expanded the OLD way: the last segment\n * holds for the card's seconds and the words ride over the frozen footage.\n * Kept for a caller that still wants the hold shape; the lowering migrates\n * instead (clips after the footage, a card exit, no hold), which paints the\n * same picture without a scenario field.\n * @deprecated Use `migrateMotion`.\n */\nexport function expandEndCard(\n doc: ProjectDoc,\n outputDuration: number,\n): { doc: ProjectDoc; endStart: number | null; seconds: number } {\n const card = doc.endCard\n if (!card || !doc.segments.length) return { doc, endStart: null, seconds: 0 }\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n const segments = doc.segments.map((s, i) =>\n i === doc.segments.length - 1\n ? { ...s, hold: ((s as { hold?: number }).hold ?? 0) + seconds }\n : s,\n )\n const endStart = outputDuration\n const overlays: OverlayClip[] = [\n ...(doc.overlays ?? []),\n ...endCardClips(card, doc, endStart),\n ]\n return { doc: { ...doc, segments, overlays }, endStart, seconds }\n}\n\nconst hasLegacyClipMotion = (o: OverlayClip): boolean =>\n o.enter !== undefined ||\n o.exit !== undefined ||\n (o.kind === 'text' && o.fx !== undefined)\n\n/** The one `anim` a legacy clip's spellings describe. */\nfunction migrateClipAnim(o: OverlayClip): Anim {\n const anim: Anim = { ...(o.anim ?? {}) }\n if (o.kind === 'text' && o.fx) {\n const fx = o.fx\n anim.enter = {\n kind: fx.fx,\n unit: fx.unit ?? 'block',\n ...(fx.direction !== undefined ? { direction: fx.direction } : {}),\n ...(fx.stagger !== undefined ? { stagger: fx.stagger } : {}),\n ...(fx.duration !== undefined ? { seconds: fx.duration } : {}),\n }\n } else if (o.enter !== undefined && anim.enter === undefined) {\n anim.enter = o.enter\n }\n if (o.exit !== undefined && anim.exit === undefined) anim.exit = o.exit\n return anim\n}\n\n/**\n * A document in the one vocabulary: `frame.entrance` becomes the card's\n * `anim.enter`; a clip's `enter`, `exit` and `fx` become its `anim`; a\n * prop's `animation` becomes its `anim.idle`; and the end card becomes a\n * card `anim.exit` of `recede` plus its clips placed after the footage\n * (no hold: the output now lasts until the last clip, and the card holds\n * its last frame at the exit's end pose, which is the picture the end card\n * painted). Returns the SAME object when there is nothing to migrate, so a\n * document already in the vocabulary lowers byte-identically and identity\n * checks hold.\n */\nexport function migrateMotion(doc: ProjectDoc): ProjectDoc {\n // Read over UNTRUSTED shapes too (a hosted payload on its way in): a\n // document with no frame or no segments migrates what it has.\n const frame: FrameStyle = doc.frame ?? ({} as FrameStyle)\n const segments = Array.isArray(doc.segments) ? doc.segments : []\n const overlays = Array.isArray(doc.overlays) ? doc.overlays : []\n const objects = Array.isArray(doc.objects) ? doc.objects : []\n const heldSegments = segments.some((s) => s.hold !== undefined)\n const legacy =\n frame.entrance !== undefined ||\n doc.endCard !== undefined ||\n heldSegments ||\n overlays.some(hasLegacyClipMotion) ||\n objects.some((o) => o.animation !== undefined)\n if (!legacy) return doc\n\n const out: ProjectDoc = { ...doc }\n if (heldSegments) {\n // A segment's hold is a freeze at its end: spelled once, on the lane\n // every retime lives on, and the segment loses the field.\n const holds = legacyHoldFreezes(segments)\n const own = Array.isArray(doc.freeze) ? doc.freeze : []\n const taken = new Set(own.map((f) => f.id))\n let n = own.length\n const migrated = holds.map((h) => {\n let id = `f${n++}`\n while (taken.has(id)) id = `f${n++}`\n taken.add(id)\n return { id, at: h.at, seconds: h.seconds }\n })\n out.freeze = [...own, ...migrated].sort((a, b) => a.at - b.at)\n out.segments = segments.map((s) => {\n if (s.hold === undefined) return s\n const next = { ...s }\n delete next.hold\n return next\n })\n }\n const nextFrame: FrameStyle = { ...frame }\n if (frame.entrance !== undefined) {\n const e = frame.entrance\n if (e && e.kind !== 'none') {\n nextFrame.anim = {\n ...(frame.anim ?? {}),\n enter:\n e.seconds !== undefined\n ? { kind: e.kind, seconds: e.seconds }\n : e.kind,\n }\n }\n delete nextFrame.entrance\n }\n\n let nextOverlays: OverlayClip[] = overlays.map((o) => {\n if (!hasLegacyClipMotion(o)) return o\n const next = { ...o, anim: migrateClipAnim(o) } as OverlayClip\n delete next.enter\n delete next.exit\n if (next.kind === 'text') delete (next as TextOverlayClip).fx\n return next\n })\n\n if (doc.endCard !== undefined) {\n const card = doc.endCard\n if (card && segments.length) {\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n // The card stays under the words as a FREEZE of its last frame (the\n // card is gone past its clip), receding over those seconds; the\n // words play over the freeze.\n const segsNow = (out.segments ?? segments) as DocSegment[]\n const lastSeg = segsNow.at(-1)!\n const freezesNow = docFreezes({ segments: segsNow, freeze: out.freeze })\n const rated = withFreezes(\n splitBySpeed(segsNow, doc.speed ?? []),\n freezesNow,\n )\n const endStart = totalDuration(rated)\n const taken = new Set(freezesNow.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n out.freeze = [\n ...freezesNow,\n { id: `f${n}`, at: lastSeg.out, seconds, from: END_CARD_FROM },\n ].sort((a, b) => a.at - b.at)\n nextOverlays = [...nextOverlays, ...endCardClips(card, doc, endStart)]\n nextFrame.anim = {\n ...(nextFrame.anim ?? {}),\n exit: { kind: 'recede', seconds },\n }\n }\n delete out.endCard\n }\n\n const nextObjects: ObjectClip[] = objects.map((o) => {\n if (o.animation === undefined) return o\n const next: ObjectClip = {\n ...o,\n anim: {\n ...(o.anim ?? {}),\n ...(o.anim?.idle === undefined ? { idle: o.animation ?? null } : {}),\n },\n }\n delete next.animation\n return next\n })\n\n if (doc.frame !== undefined || nextFrame.anim !== undefined)\n out.frame = nextFrame\n if (doc.overlays !== undefined || nextOverlays.length)\n out.overlays = nextOverlays\n if (doc.objects !== undefined) out.objects = nextObjects\n return out\n}\n\n/**\n * Where the OUTPUT ends: the footage's end (its rated segments, freezes\n * included) or the last visual clip's end, whichever is later. Past its\n * footage the card is gone and the clips play over the ground. A document\n * that ends on its footage answers the footage's end, byte-identically to\n * before the rule.\n */\nexport function outputEnd(\n doc: Pick<ProjectDoc, 'overlays' | 'objects'>,\n footageEnd: number,\n): number {\n return Math.max(footageEnd, lastLayerEnd(doc))\n}\n\n/** Re-export for callers that read the step shape through this module. */\nexport { animStep }\n","/**\n * ONE animation vocabulary for every visual primitive: the card, a text,\n * image or video clip, a prop. `anim.enter` and `anim.exit` are how a thing\n * arrives and leaves; `anim.idle` is what it does while it stays. A kind\n * alone is the house motion; a step object adds its seconds and, for words,\n * the per-unit grammar (unit, direction, stagger). Pure helpers over the\n * type; the lowering bakes them into the same ON_FRAME data the older\n * spellings produced (`enter`, `exit`, `fx`, `anim`), so a migrated document\n * paints the same picture.\n */\nimport type {\n Anim,\n AnimKind,\n AnimSide,\n AnimStep,\n IdleKind,\n ObjectClip,\n OverlayClip,\n TextFxSpec,\n TextOverlayClip,\n} from './types'\n\n/** What the CARD can do. */\nexport const CARD_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'fade',\n 'rise',\n 'tilt-in',\n 'pull-out',\n 'slide',\n]\nexport const CARD_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'recede']\n/**\n * What a FOOTAGE clip can do at a boundary with the clip beside it: the\n * incoming slides, fades or scales in while the outgoing, frozen on its\n * last frame, does the same out. `none` (or absent) is a hard cut.\n */\nexport const SEGMENT_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'slide',\n 'fade',\n 'scale',\n]\nexport const SEGMENT_EXIT_KINDS: readonly AnimKind[] = [\n 'none',\n 'slide',\n 'fade',\n 'scale',\n]\n/** A transition's house length, seconds. */\nexport const TRANSITION_SECONDS = 0.6\n/** A transition's bounds, seconds (the lint's and the lowering's). */\nexport const TRANSITION_SECONDS_MIN = 0.15\nexport const TRANSITION_SECONDS_MAX = 2\n\n/**\n * A transition step's seconds: its own, else the house length, clamped.\n * Zero for none or absent.\n */\nexport function transitionSeconds(step: AnimStep | null | undefined): number {\n if (!step || step.kind === 'none') return 0\n return Math.max(\n TRANSITION_SECONDS_MIN,\n Math.min(TRANSITION_SECONDS_MAX, step.seconds ?? TRANSITION_SECONDS),\n )\n}\n\n/**\n * A slide's side: where an enter comes from, where an exit goes to. The\n * default turns the page forward: in from the right, out to the left.\n */\nexport function slideSide(\n step: AnimStep | null | undefined,\n which: 'enter' | 'exit',\n): AnimSide {\n return step?.side ?? (which === 'enter' ? 'right' : 'left')\n}\n/** What WORDS can do (the per-unit kinds are text-only). */\nexport const TEXT_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'fade',\n 'rise',\n 'pop',\n 'blur',\n 'typewriter',\n]\nexport const TEXT_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\n/** What an image or a video clip can do. */\nexport const MEDIA_ENTER_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\nexport const MEDIA_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\n/** What a prop does while it stays. */\nexport const IDLE_KINDS: readonly IdleKind[] = ['spin', 'float']\n\n/** The kinds that animate PER UNIT (word by word, letter by letter). */\nconst UNIT_KINDS: ReadonlySet<AnimKind> = new Set(['pop', 'blur', 'typewriter'])\n\n/**\n * A step as an object: a bare kind becomes `{ kind }`; absent stays null so\n * a caller can tell \"the house default\" from \"none\".\n */\nexport function animStep(\n step: AnimKind | AnimStep | null | undefined,\n): AnimStep | null {\n if (step == null) return null\n if (typeof step === 'string') return { kind: step }\n return step\n}\n\n/** The enter step of a thing, or null when it carries none. */\nexport function enterOf(anim: Anim | null | undefined): AnimStep | null {\n return animStep(anim?.enter)\n}\n\n/** The exit step of a thing, or null when it carries none. */\nexport function exitOf(anim: Anim | null | undefined): AnimStep | null {\n return animStep(anim?.exit)\n}\n\n/**\n * Whether a text clip's enter step is a PER-UNIT animation (the baked `fx`\n * payload ON_FRAME reads) rather than the block transition: a per-unit\n * kind, or a fade/rise that names its unit, stagger or seconds. A bare\n * `'rise'` stays the block transition, which is what a migrated `enter`\n * was; a migrated `fx` always names its unit, so it stays per-unit.\n */\nexport function isUnitStep(step: AnimStep | null): boolean {\n if (!step) return false\n if (UNIT_KINDS.has(step.kind)) return true\n if (step.kind !== 'fade' && step.kind !== 'rise') return false\n return (\n step.unit !== undefined ||\n step.stagger !== undefined ||\n step.seconds !== undefined ||\n step.direction !== undefined\n )\n}\n\n/**\n * The text-fx spec a clip's enter step describes, in the shape the fx\n * normalizer reads, or null when the clip enters by the block transition.\n */\nexport function overlayFxSpec(clip: TextOverlayClip): TextFxSpec | null {\n const step = enterOf(clip.anim)\n if (!isUnitStep(step) || !step || step.kind === 'none') return null\n const kind = step.kind\n const fx: TextFxSpec['fx'] =\n kind === 'fade' ||\n kind === 'rise' ||\n kind === 'pop' ||\n kind === 'blur' ||\n kind === 'typewriter'\n ? kind\n : 'rise'\n return {\n fx,\n ...(step.unit !== undefined ? { unit: step.unit } : {}),\n ...(step.direction !== undefined ? { direction: step.direction } : {}),\n ...(step.stagger !== undefined ? { stagger: step.stagger } : {}),\n ...(step.seconds !== undefined ? { duration: step.seconds } : {}),\n }\n}\n\nconst BLOCK_KINDS: ReadonlySet<string> = new Set(['none', 'fade', 'rise'])\n\n/**\n * The block transition ON_FRAME reads as `enter`: the step's kind when it\n * is one of the three transitions, the house `rise` otherwise (a per-unit\n * step bakes `fx` beside it, and ON_FRAME ignores `enter` while `fx` is\n * set, exactly as before).\n */\nexport function enterKey(clip: OverlayClip): 'none' | 'fade' | 'rise' {\n const step = enterOf(clip.anim)\n if (!step) return 'rise'\n return BLOCK_KINDS.has(step.kind)\n ? (step.kind as 'none' | 'fade' | 'rise')\n : 'rise'\n}\n\n/** The block transition ON_FRAME reads as `exit`. */\nexport function exitKey(clip: OverlayClip): 'none' | 'fade' | 'rise' {\n const step = exitOf(clip.anim)\n if (!step) return 'fade'\n return BLOCK_KINDS.has(step.kind)\n ? (step.kind as 'none' | 'fade' | 'rise')\n : 'fade'\n}\n\n/** A prop's idle motion as ON_FRAME reads it (`anim`). */\nexport function idleKey(clip: ObjectClip): IdleKind | null {\n const idle = clip.anim?.idle\n return idle === 'spin' || idle === 'float' ? idle : null\n}\n\n/**\n * Where the last VISUAL layer ends, in output seconds: a text, image or\n * video clip's end, a prop's span end. The output lasts at least this\n * long, so an end card is nothing but clips placed after the footage.\n * Audio never extends the output (a sound after the last picture is not a\n * thing to watch); a bed is trimmed to the output instead. A prop with no\n * span is always on and extends nothing.\n */\nexport function lastLayerEnd(\n doc: Pick<\n Partial<{ overlays: OverlayClip[]; objects: ObjectClip[] }>,\n 'overlays' | 'objects'\n >,\n): number {\n let end = 0\n for (const o of doc.overlays ?? []) end = Math.max(end, o.start + o.duration)\n for (const o of doc.objects ?? [])\n if (o.span) end = Math.max(end, o.span.start + o.span.duration)\n return Number.isFinite(end) && end > 0 ? end : 0\n}\n","/**\n * Many media in one take, one rule: a recording is media plus the FACTS\n * only a capture has, and `doc.source` is the PRIMARY media (the subject:\n * the sequence, the camera and the cut belong to it). `doc.media` holds\n * the others, each the same shape with an `id`; a footage clip (a segment)\n * and every source-anchored span (zoom, tilt, speed, freeze, cam move, a\n * rejected proposal) name the media they belong to by that id, and an\n * absent `media` is the primary. Nothing else moves: `mapTime` still\n * answers source seconds, and the piece under the playhead says whose.\n */\nimport type { Segment } from '@vosjs/timeline'\nimport { MEDIA_FRAME_KEYS, MEDIA_KEY_PREFIX, pageDisplayUrl } from './types'\nimport type { FrameStyle, Media, MediaFrameKey, ProjectDoc } from './types'\n\n/** The primary media's key: the absent `media` on a clip or a span. */\nexport const PRIMARY_MEDIA = ''\n\n/** The key a `media` field carries, the primary's when absent. */\nexport const mediaKey = (id: string | undefined | null): string =>\n id ?? PRIMARY_MEDIA\n\n/** Do two `media` fields name the same media (absent = the primary)? */\nexport const sameMedia = (\n a: string | undefined | null,\n b: string | undefined | null,\n): boolean => mediaKey(a) === mediaKey(b)\n\n/** A piece of the rated list, carrying the media it plays. */\nexport type MediaSegment = Segment & { media?: string }\n\n/**\n * Every media the document carries, the primary first (its key is the\n * empty string), then `doc.media` in order.\n */\nexport function docMediaList(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n): { id: string; source: ProjectDoc['source'] }[] {\n return [\n { id: PRIMARY_MEDIA, source: doc.source },\n ...(doc.media ?? []).map((m) => ({ id: m.id, source: m })),\n ]\n}\n\n/** The media a key names, the primary for the empty key, null for a stranger. */\nexport function mediaSource(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n id: string | undefined | null,\n): ProjectDoc['source'] | null {\n const key = mediaKey(id)\n if (key === PRIMARY_MEDIA) return doc.source\n return (doc.media ?? []).find((m) => m.id === key) ?? null\n}\n\n/** A media's source length in seconds (its capture meta). */\nexport function mediaDuration(\n source: Pick<ProjectDoc['source'], 'meta'>,\n): number {\n return (source.meta.durationMs || 0) / 1000\n}\n\n/**\n * The media the output plays at `t`: the rated piece under it, the last\n * piece past the footage's end (the frame the card froze on), the primary\n * for an empty list.\n */\nexport function mediaAtOutput(\n rated: readonly MediaSegment[],\n t: number,\n): string {\n let acc = 0\n let last: MediaSegment | null = null\n for (const p of rated) {\n const len = Math.max(0, p.out - p.in) / (p.rate && p.rate > 0 ? p.rate : 1)\n if (t < acc + len) return mediaKey(p.media)\n acc += len\n last = p\n }\n return last ? mediaKey(last.media) : PRIMARY_MEDIA\n}\n\n/**\n * The card a media wears: the take's frame with the media's own card\n * fields over it. Facts first: a media with a recorded page names it in\n * the bar, and one with no page (an upload) wears no bar, unless its own\n * `frame.browserBar` says otherwise. The primary wears the take's frame.\n */\nexport function mediaFrame(\n doc: Pick<ProjectDoc, 'source' | 'media' | 'frame'>,\n id: string | undefined | null,\n): FrameStyle {\n const key = mediaKey(id)\n if (key === PRIMARY_MEDIA) return doc.frame\n const m = (doc.media ?? []).find((x) => x.id === key)\n if (!m) return doc.frame\n const over = m.frame ?? {}\n const out: FrameStyle = { ...doc.frame }\n for (const k of MEDIA_FRAME_KEYS) {\n if (k === 'browserBar') continue\n const v = over[k]\n if (v !== undefined) (out as Record<MediaFrameKey, unknown>)[k] = v\n }\n const bar = { ...doc.frame.browserBar }\n const page = pageDisplayUrl(m.meta.pageUrl)\n if (page) bar.url = page\n if (!m.meta.pageUrl) bar.kind = 'none'\n out.browserBar = { ...bar, ...(over.browserBar ?? {}) }\n return out\n}\n\n/** The card-owned fields of a frame, alone (what a media's card is made of). */\nexport function cardFields(frame: FrameStyle): Pick<FrameStyle, MediaFrameKey> {\n const out: Partial<Pick<FrameStyle, MediaFrameKey>> = {}\n for (const k of MEDIA_FRAME_KEYS) {\n const v = frame[k]\n if (v !== undefined) (out as Record<MediaFrameKey, unknown>)[k] = v\n }\n return out as Pick<FrameStyle, MediaFrameKey>\n}\n\n/** The overlay key that shows a document media (`''` = the primary). */\nexport const mediaRef = (id: string): string => MEDIA_KEY_PREFIX + id\n\n/** The media id an overlay key names, `''` for the primary, null for a plain key. */\nexport function mediaRefId(key: string | undefined | null): string | null {\n if (typeof key !== 'string' || !key.startsWith(MEDIA_KEY_PREFIX)) return null\n return key.slice(MEDIA_KEY_PREFIX.length)\n}\n\n/** The document media a layer's key shows, or null for a plain key or a stranger. */\nexport function layerMedia(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n key: string | undefined | null,\n): ProjectDoc['source'] | null {\n const id = mediaRefId(key)\n return id === null ? null : mediaSource(doc, id)\n}\n\n/** The next free media id (`m1`, `m2`, …). */\nexport function nextMediaId(doc: Pick<ProjectDoc, 'media'>): string {\n const taken = new Set((doc.media ?? []).map((m) => m.id))\n let n = 1\n while (taken.has(`m${n}`)) n++\n return `m${n}`\n}\n\n/** A document's media as `Media` rows (the primary is not one: it has no id). */\nexport type MediaRow = Media\n","/**\n * Zoom/pan camera styles — named strategy\n * presets covering the whole auto-zoom pipeline: how the planner turns clicks\n * into spans (cluster vs session merging, level clamps, follow default) AND\n * how the lowering animates the camera (ramp durations, eases, connected-pan\n * gap, dead-zone follow tuning, and the tilt track's tempo). One name → one\n * coherent feel.\n *\n * The presets were grounded (2026-07) in a measured comparison of the four\n * shipping strategies (frame-by-frame optical-flow tracking of real exports +\n * source audits of Recordly/OpenScreen + Cursorful bundle/behavior research),\n * and re-founded (2026-09) on the stage camera at 60 fps, where every style\n * is timed against the default's traced arrival and obeys two structural\n * rules (see `pumpFreeChainGap` and the lowering's ramp fitting):\n *\n * - Cursorful (\"glide\"): ONE modest zoom (~1.5×) per activity session —\n * clicks within a rolling window keep the zoom alive — and the camera\n * TRAVELS by panning between focus points while zoomed. Its arrival was\n * traced at 60 fps and fitted by sum of differences (see the entry).\n * - Screen Studio \"Focused\" / Recordly (\"focus\"): a zoom block per click\n * cluster, spring-settled ramps, direct pans across short gaps, no cursor\n * follow. The measured OpenScreen/Recordly ramp (their bezier filtered\n * through the spring) fits css-bezier(0.28, 0.03, 0.09, 1) — that curve IS\n * the family feel; it now runs at the default's tempo instead of 1.4 s.\n * - Screen Studio \"Smooth\" (\"cinema\"): the long-HOLD style, slower and more\n * fluid — for content that is watched, not read.\n * - \"snappy\": the studio's original fast cycles, with the defect fixed — the\n * raw css-bezier(0.16, 1, 0.3, 1) arrival had an initial velocity 6.25×\n * the ramp average (the \"jump cut\" complaint); competitors always filter\n * that curve through a spring. This preset keeps the pace but caps the\n * onset.\n * - \"cut\": Screen Studio's \"instant zoom\" option — hard cut-in, no glide.\n *\n * `keynote` and `drift` (2026-08) were another row plus a tilt intensity the\n * panel already exposes as its own control; they are retired names that\n * resolve to `glide` + medium and `cinema` + subtle (RETIRED_ZOOM_STYLES;\n * migrated on read by docVersion). What they had right survives in every\n * style: the tilt track moves at the style's own zoom tempo, so a chosen\n * lean lands WITH its zoom.\n *\n * Style changes are live SET_DATA (the zoom track is data); regenerating\n * auto spans on a style switch re-plans with the style's planner params while\n * preserving user-touched ('manual') spans, per the wand contract.\n */\n\nimport type { TiltStyleName } from './types'\n\nexport type ZoomStyleName =\n | 'glide'\n | 'focus'\n | 'cinema'\n | 'snappy'\n | 'cut'\n | 'none'\n\n/**\n * Retired style names and what they meant: a live style plus the tilt\n * intensity the name carried. `resolveZoomStyle` honours the name at runtime\n * (a hand-edited doc.json never falls silently to the default) and\n * `migrateHostedDoc` rewrites a stored document onto the pair.\n */\nexport const RETIRED_ZOOM_STYLES: Record<\n string,\n { style: ZoomStyleName; tilt: TiltStyleName }\n> = {\n keynote: { style: 'glide', tilt: 'medium' },\n drift: { style: 'cinema', tilt: 'subtle' },\n}\n\n/** The live style a name resolves to, retired names included; null = unknown. */\nexport function liveZoomStyleName(\n name: string | undefined,\n): ZoomStyleName | null {\n if (!name) return null\n if (name in ZOOM_STYLES) return name as ZoomStyleName\n return RETIRED_ZOOM_STYLES[name]?.style ?? null\n}\n\n/**\n * The tilt half of a camera style: the Dynamic-tilt intensity the style\n * ships with (every live style ships 'off' — intensity is the Dynamic tilt\n * dial's, never a name's) plus the tilt track's motion, which is the\n * style's OWN zoom tempo so a lean lands with its zoom. A style pick stamps\n * `doc.tiltStyle` with `intensity` and re-plans auto tilt spans alongside\n * the auto zooms — one name, one coherent camera sentence (zoom AND lean).\n */\nexport interface TiltPersonality {\n /** Dynamic-tilt intensity this style ships with ('off' = flat card). */\n intensity: TiltStyleName\n /** tilt ramp overrides (seconds); absent = the TILT_RAMP_* constants. */\n rampIn?: number\n rampOut?: number\n /** output-time gap ≤ this → swing pose-to-pose (absent = TILT_CHAIN_GAP). */\n chainGap?: number\n /** connected-swing duration (absent = TILT_PAN). */\n pan?: number\n}\n\nexport interface ZoomStyleParams {\n // ── planner (planAutoZoom) ───────────────────────────────────────────────\n /** false = the planner emits nothing (the 'none' style — manual zooms only). */\n autoZoom: boolean\n /** clicks within this many seconds merge into one span (session merge). */\n clusterGap: number\n /**\n * Minimum clicks for a cluster to earn a zoom. Every style is 1: the\n * Cursorful ≥2 rule glide once carried was never in effect on a recorded\n * take (the recorder counted each press twice, so a lone click was a pair\n * on most pages and a stray on the rest), and the lone click on a button\n * is the zoom people miss when it goes.\n */\n minClusterClicks: number\n /** zoom so the clicked element fills ~this fraction of the frame. */\n targetFill: number\n minLevel: number\n maxLevel: number\n /**\n * The level a DRAG is followed at (a press that travels before its\n * release: a slider thumb, a scrubber, a thing moved across a canvas).\n * The camera pans WITH the pointer for the whole press (a path follow,\n * not a dead zone), so the level stays mild: the thumb and what it drives\n * both stay in the window. By rule minLevel + 0.2.\n */\n dragLevel: number\n /** span lead-in before the first click / hold after the last (seconds). */\n lead: number\n hold: number\n /** planner emits spans with focusMode 'auto' (cursor-follow camera). */\n followByDefault: boolean\n // ── typing (`key` activity pings → typing-session spans) ─────────────\n /** false = typing sessions plan no spans (clicks/dwells still do). */\n typingZoom: boolean\n /** max silence between pings before the typing session ends (seconds). */\n typingGap: number\n /** hold after the last keystroke — the read-what-you-typed beat (seconds). */\n typingHold: number\n /**\n * Level FLOOR for typing spans, above the style's minLevel: a wide field\n * (URL bar, dialog search input) fit-clamps to this instead — typing is the\n * moment being narrated, so it reads a notch punchier than a wide click.\n * By rule minLevel + 0.1.\n */\n typingMinLevel: number\n // ── camera (zoomTrackFromDoc) ────────────────────────────────────────────\n /** zoom-in ramp duration; arrival lands rampInOverlap into the span. */\n rampIn: number\n rampInOverlap: number\n rampOut: number\n /**\n * output-time gap ≤ this → pan straight to the next span (no zoom-out).\n * Never below `pumpFreeChainGap`: a gap just past it would zoom out and\n * straight back in (the pump), which is arithmetic, not taste.\n */\n chainGap: number\n /** connected-pan duration. */\n pan: number\n /** arrival ease (zoom-in AND zoom-out). */\n ease: string\n /** connected-pan + follow-recenter ease. */\n panEase: string\n // ── cursor follow (followFocusEvents) ────────────────────────────────────\n /** recenter when the cursor exits this central fraction of the crop. */\n followSafeRatio: number\n /** seconds the camera takes to glide to a recentered focus. */\n followRecenter: number\n /**\n * Recenter targets the cursor this many seconds AHEAD of the exit moment\n * (Cursorful's look-ahead: the camera leads the pointer instead of chasing\n * a stale position). Sampled from the real track — still deterministic.\n */\n followLookahead: number\n // ── tilt (planAutoTilt + tiltTrackFromDoc) ───────────────────────────────\n /** The style's tilt personality — see TiltPersonality. */\n tilt: TiltPersonality\n}\n\n/**\n * The shortest rest between a zoom-out and the next zoom-in that reads as\n * a rest rather than a pump (seconds).\n */\nexport const PUMP_REST = 1.0\n\n/**\n * The chain gap below which a style PUMPS: two spans whose gap lies in\n * (chainGap, chainGap + rampOut + rampIn − overlap + PUMP_REST] zoom out and\n * straight back in with no rest between. Every ZOOM_STYLES entry clears\n * this floor by construction (a test pins it) and `resolveZoomStyle`\n * raises a `zoomParams` override that does not.\n */\nexport function pumpFreeChainGap(\n p: Pick<ZoomStyleParams, 'rampIn' | 'rampInOverlap' | 'rampOut'>,\n): number {\n return p.rampOut + (p.rampIn - p.rampInOverlap) + PUMP_REST\n}\n\n// Cursorful strategy — one steady zoom per activity session, travel by pans.\n// The camera's timing was measured against a Cursorful take of the same\n// steps: a zoom-in of ~0.55 s on a heavy ease-out (most of the move in\n// the first quarter second, a long settle), a zoom-out of ~0.45 s, and\n// holds of four to five seconds per beat. The old 1.1 s ramp started\n// 0.75 s BEFORE the click and landed 0.35 s after it, so the frame was\n// moving more of the time than it was resting.\nconst glide: ZoomStyleParams = {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 1,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n dragLevel: 1.5,\n lead: 0.35,\n hold: 1.2,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 0.6,\n rampInOverlap: 0.2,\n rampOut: 0.5,\n chainGap: 3.0,\n pan: 0.55,\n // The reference's corner trace at 60 fps: a quarter of the move by a\n // fifth of the time, two thirds by half, nine tenths by three\n // quarters, over ~0.6 s. Fitted against those three points,\n // (0.36, 0, 0.4, 1) is the closest cubic (15 / 64 / 93 %; half the\n // move at ~245 ms and 90 % at ~425 ms at 0.6 s): a gentle start and a\n // long settle. CSS ease (0.25, 0.1, 0.25, 1) reached the midpoint\n // 70 ms earlier and read a touch fast; (0.16, 1, 0.3, 1) put half the\n // move in the first sixty milliseconds and read as a twitch.\n ease: 'css-bezier(0.36, 0, 0.4, 1)',\n panEase: 'css-bezier(0.36, 0, 0.4, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.55,\n followLookahead: 0.4,\n tilt: {\n intensity: 'off',\n rampIn: 0.6,\n rampOut: 0.5,\n chainGap: 3.0,\n pan: 0.55,\n },\n}\n\nexport const ZOOM_STYLES: Record<ZoomStyleName, ZoomStyleParams> = {\n glide,\n // Screen Studio \"Focused\" / Recordly — a block per click, framed and\n // released, no travel. The spring-settled family ease (near-linear onset,\n // long settle) at the default's tempo: 0.8 s in, 0.65 s out, and a beat\n // of 1.5 s after the click so the block is read before it goes. At its\n // 2026-07 timing (1.4 s in, 1.0 s out around a 1.35 s span) a lone click\n // spent 74% of its beat moving and every dwell or sped-up click was\n // shorter than the ramp, which the lowering compressed into a cut.\n focus: {\n autoZoom: true,\n clusterGap: 1.5,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.0,\n dragLevel: 1.6,\n lead: 0.35,\n hold: 1.5,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.0,\n typingHold: 1.3,\n typingMinLevel: 1.5,\n rampIn: 0.8,\n rampInOverlap: 0.25,\n rampOut: 0.65,\n chainGap: 2.4,\n pan: 0.7,\n ease: 'css-bezier(0.28, 0.03, 0.09, 1)',\n panEase: 'css-bezier(0.26, 0.08, 0.2, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.55,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.8,\n rampOut: 0.65,\n chainGap: 2.4,\n pan: 0.7,\n },\n },\n // Screen Studio \"Smooth\" — the long-HOLD style: 1.2 s in, 1.0 s out,\n // two-second holds, chains across 3.5 s so a slow clip travels rather\n // than pumps, and the default's level range, because a watched clip\n // wants the frame, not the magnifier. Its ramps still exceed a dwell or\n // a sped-up click; the lowering fits them to the room.\n cinema: {\n autoZoom: true,\n clusterGap: 2.4,\n minClusterClicks: 1,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n dragLevel: 1.5,\n lead: 0.6,\n hold: 2.0,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.8,\n typingMinLevel: 1.4,\n rampIn: 1.2,\n rampInOverlap: 0.4,\n rampOut: 1.0,\n chainGap: 3.5,\n pan: 1.1,\n ease: 'css-bezier(0.33, 0, 0.15, 1)',\n panEase: 'css-bezier(0.33, 0, 0.22, 1)',\n followSafeRatio: 0.6,\n followRecenter: 0.9,\n followLookahead: 0.5,\n tilt: {\n intensity: 'off',\n rampIn: 1.2,\n rampOut: 1.0,\n chainGap: 3.5,\n pan: 1.1,\n },\n },\n // The studio's original pace with the instant-velocity onset defect fixed:\n // 0.4 s cycles on the onset-capped ease, the 2.5× ceiling of the 2026-07\n // comparison's defect two brought to 2.2, a chain gap past its pump band.\n snappy: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.2,\n dragLevel: 1.6,\n lead: 0.25,\n hold: 1.2,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.6,\n typingHold: 0.9,\n typingMinLevel: 1.5,\n rampIn: 0.4,\n rampInOverlap: 0.15,\n rampOut: 0.4,\n chainGap: 2.0,\n pan: 0.5,\n ease: 'css-bezier(0.3, 0.55, 0.2, 1)',\n panEase: 'css-bezier(0.25, 0.1, 0.25, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.45,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.4,\n rampOut: 0.4,\n chainGap: 2.0,\n pan: 0.5,\n },\n },\n // Screen Studio's \"instant zoom\" — a cut-in (eight frames at 60 fps),\n // tutorial tempo. Untouched in motion (the one style that never pumped\n // and never left a ramp unsettled); the ceiling comes down with the\n // others and the hold gains the beat a cut-in needs to be read.\n cut: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.0,\n dragLevel: 1.6,\n lead: 0.2,\n hold: 1.2,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.2,\n typingHold: 0.6,\n typingMinLevel: 1.5,\n rampIn: 0.14,\n rampInOverlap: 0.07,\n rampOut: 0.14,\n chainGap: 1.3,\n pan: 0.35,\n ease: 'css-bezier(0.2, 0, 0.4, 1)',\n panEase: 'css-bezier(0.2, 0, 0.4, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.35,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.3,\n rampOut: 0.3,\n chainGap: 1.3,\n pan: 0.35,\n },\n },\n // Auto-zoom off (every competitor ships this switch). Camera params still\n // apply to MANUAL spans — they move the way the default camera moves\n // TODAY (spelled as a spread, never a copy that fossilises).\n none: { ...glide, autoZoom: false },\n}\n\n/** The default camera style for new projects (the Cursorful-family strategy). */\nexport const DEFAULT_ZOOM_STYLE: ZoomStyleName = 'glide'\n\n/**\n * Resolve a style name (+ optional per-doc overrides, `doc.zoomParams`) into a\n * full parameter bundle. Overrides are the \"Custom\" seam: agents/doc.json can\n * tune individual params on top of a named preset; the studio shows Custom\n * while any override is present. A retired name resolves to its live style;\n * an unknown name from a hand-edited doc.json falls back to the default.\n * The chain gap never rests below its pump-free floor, whatever an override\n * asked for.\n */\nexport function resolveZoomStyle(\n name?: string,\n overrides?: Partial<ZoomStyleParams>,\n): ZoomStyleParams {\n const live = liveZoomStyleName(name) ?? DEFAULT_ZOOM_STYLE\n const params = { ...ZOOM_STYLES[live], ...overrides }\n const floor = pumpFreeChainGap(params)\n if (params.chainGap < floor) params.chainGap = floor\n return params\n}\n\n/** Picker order + copy for the studio's Camera style control. */\nexport const ZOOM_STYLE_OPTIONS: {\n name: ZoomStyleName\n label: string\n hint: string\n}[] = [\n {\n name: 'glide',\n label: 'Glide',\n hint: 'One steady zoom that travels between clicks',\n },\n {\n name: 'focus',\n label: 'Focus',\n hint: 'A zoom per click, framed and released',\n },\n {\n name: 'cinema',\n label: 'Cinema',\n hint: 'Slow moves and long holds, for content that is watched',\n },\n { name: 'snappy', label: 'Snappy', hint: 'Quick, energetic zoom cycles' },\n { name: 'cut', label: 'Cut', hint: 'Instant zooms, no glide' },\n {\n name: 'none',\n label: 'None',\n hint: 'No automatic zooms, add your own on the timeline',\n },\n]\n","/**\n * Hosted-doc schema versioning: the scoped reversal of \"ProjectDocs\n * are never persisted\" is hosted versions only, and every persisted doc is\n * stamped `docSchemaVersion` from day one so the migration obligation the\n * old rule avoided stays bounded to one seam — migrate-on-read, here.\n *\n * Local studio sessions still never persist docs; CLI take dirs carry\n * doc.json under `schema/doc.schema.json` (which tolerates the stamp via\n * additionalProperties). Both hydration paths (studio handback, `vos\n * pull`) run through migrateHostedDoc before trusting a hosted doc.\n */\nimport { migrateMotion } from './lower/motion'\nimport { RETIRED_ZOOM_STYLES } from './zoomStyle'\nimport type { ProjectDoc } from './types'\n\n/**\n * 5 = the six-style era: the retired camera styles `keynote` and `drift`\n * are read into their live style plus the tilt intensity the name carried\n * (`glide` + medium, `cinema` + subtle; an explicit `tiltStyle` wins). The\n * spans are untouched: a migration never re-plans.\n * 4 = the freeze era: a segment's `hold` is read into `doc.freeze` (a\n * freeze at that segment's end), the retime primitive beside speed spans.\n * 3 = the one-vocabulary era: a recording document's `frame.entrance`,\n * `endCard`, clip `enter`/`exit`/`fx` and prop `animation` are read into\n * `anim` (and the end card into clips after the footage plus a card exit).\n * 2 = the document FAMILY era (a recording document, or a program\n * document); a v1 doc IS a recording document, so 1 → 2 was a stamp, and\n * 0 → 1 was a stamp too.\n */\nexport const DOC_SCHEMA_VERSION = 5\n\n/**\n * Upgrade a hosted doc.json payload to the current schema version.\n * Unstamped docs are v0 — the pre-stamp era. v0 → v2 were stamps; v2 → v3\n * rewrites a recording document's motion spellings into the vocabulary,\n * and v3 → v4 its segment holds into freezes (one rewrite, `migrateMotion`,\n * idempotent, so both steps are one call); v4 → v5 rewrites a retired\n * camera style name (`migrateZoomStyle`); a program document is left as\n * it was (its layers migrate the same way when they carry the old\n * spellings). A real shape change chains its step here.\n */\nexport function migrateHostedDoc(\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const version =\n typeof raw.docSchemaVersion === 'number' ? raw.docSchemaVersion : 0\n if (version >= DOC_SCHEMA_VERSION) return raw\n let doc = raw\n if (version < 4 && doc.source && typeof doc.source === 'object') {\n doc = migrateMotion(doc as unknown as ProjectDoc) as unknown as Record<\n string,\n unknown\n >\n }\n if (version < 5) doc = migrateZoomStyle(doc)\n return { ...doc, docSchemaVersion: DOC_SCHEMA_VERSION }\n}\n\n/**\n * A retired camera style name becomes its live style plus the tilt\n * intensity the name carried, unless the document already says how far it\n * leans. Idempotent; a document on a live name is returned as it was.\n */\nexport function migrateZoomStyle(\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const name = doc.zoomStyle\n if (typeof name !== 'string') return doc\n const retired = RETIRED_ZOOM_STYLES[name]\n if (!retired) return doc\n return {\n ...doc,\n zoomStyle: retired.style,\n tiltStyle: doc.tiltStyle ?? retired.tilt,\n }\n}\n","import type {\n AudioClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n SpeedSpan,\n} from '../types'\n\n/**\n * The studio document family.\n *\n * A document is an ANCHOR plus the layers every anchor shares. `ProjectDoc`\n * is the recording-anchored member, field for field what it always was (its\n * wire, `doc.json`, does not move). `ProgramAnchorDoc` is the program-anchored\n * member: its anchor IS the user's config — the execution IR, untouched\n * — plus the tween-timing overlay that used to live only in a\n * hook. The shared layers are optional on it until the shared modules activate them.\n *\n * Discriminated on `source`: every recording doc carries one, no program doc\n * may.\n */\n\n/** One entry of the tween-timing overlay — `@vosjs/tween`'s `TweenEdit`, structurally. */\nexport interface ProgramTweenEdit {\n index: number\n startTime?: number\n duration?: number\n ease?: string\n to?: Record<string, number>\n from?: Record<string, number>\n}\n\nexport interface ProgramAnchorDoc {\n program: {\n /** THE user's config, as authored (functions as strings). Never composed here. */\n config: Record<string, unknown>\n /** Retimes over the config's recorded tweens, by spec index. */\n tweenEdits?: Record<number, ProgramTweenEdit>\n /** The anchor's own length when the config's is a placeholder. */\n duration?: number\n }\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n /** Required, like the recording's: the audio module and its lane read it without a guard. Minted `[]`. */\n audio: AudioClip[]\n /** Retime spans over the ANCHOR's clock: the recording's type, `in`/`out` in program seconds. */\n speed?: SpeedSpan[]\n export?: ProjectDoc['export']\n}\n\nexport type StudioDoc = ProjectDoc | ProgramAnchorDoc\n\nexport type AnchorKind = 'recording' | 'program'\n\nexport const anchorKindOf = (doc: StudioDoc): AnchorKind =>\n 'source' in doc ? 'recording' : 'program'\n\nexport const isRecordingDoc = (doc: StudioDoc): doc is ProjectDoc =>\n 'source' in doc\n\nexport const isProgramDoc = (doc: StudioDoc): doc is ProgramAnchorDoc =>\n !('source' in doc)\n\n/** A program anchor's own length in seconds: `program.duration`, else the config's. */\nexport function programDuration(doc: ProgramAnchorDoc): number {\n const own = doc.program.duration\n if (typeof own === 'number' && own > 0) return own\n const cfg = doc.program.config.duration\n return typeof cfg === 'number' && cfg > 0 ? cfg : 0\n}\n\n/**\n * The anchor's SOURCE length: the footage's for a recording, the\n * program's own for a program. Speed spans, segments and every source-time\n * floor measure against it.\n */\nexport function anchorSourceDuration(doc: StudioDoc): number {\n return isRecordingDoc(doc)\n ? doc.source.meta.durationMs / 1000\n : programDuration(doc)\n}\n","/**\n * Cursor smoothing.\n *\n * Raw pointer samples are jittery and irregularly spaced. We resample to a fixed\n * cadence and apply an exponential lerp — counterintuitively, linear smoothing\n * beats ease-in-out for cursors (easing stutters between samples). Pure and\n * deterministic: same input → same output.\n */\nimport type { CursorTrack } from '../types'\n\nexport interface SmoothPoint {\n /** seconds. */\n t: number\n x: number\n y: number\n}\n\nexport interface SmoothOptions {\n /** lerp factor per step, 0..1 (higher = smoother/laggier). Default 0.15. */\n factor?: number\n /** resample cadence in fps. Default 60. */\n fps?: number\n /**\n * Pull the smoothed path onto each click's true position around the click\n * instant: click effects anchor at the\n * click point, so the (laggy) smoothed cursor must arrive on time or the\n * ring blooms away from the dot. The pull feeds back into the lerp state,\n * so the path continues from the click point afterwards. Deterministic.\n */\n clickSnap?: boolean\n}\n\n/** Snap window: the pull ramps in over this many seconds before the click… */\nconst SNAP_BEFORE = 0.12\n/** …and holds through this many seconds after it (covers the press dip). */\nconst SNAP_AFTER = 0.18\n/** Per-step pull gain at full envelope (60 fps steps → arrives by click time). */\nconst SNAP_GAIN = 0.5\n\n/** Linear interpolation of raw (move) samples at an arbitrary time. */\nfunction sampleAt(points: SmoothPoint[], t: number): { x: number; y: number } {\n if (points.length === 0) return { x: 0, y: 0 }\n if (t <= points[0].t) return { x: points[0].x, y: points[0].y }\n const last = points[points.length - 1]\n if (t >= last.t) return { x: last.x, y: last.y }\n // linear scan is fine for studio-length tracks; binary search if needed later\n for (let i = 1; i < points.length; i++) {\n if (points[i].t >= t) {\n const a = points[i - 1]\n const b = points[i]\n const f = (t - a.t) / (b.t - a.t || 1)\n return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f }\n }\n }\n return { x: last.x, y: last.y }\n}\n\n/**\n * Produce a smoothed, fixed-cadence cursor path (in seconds) from a raw track.\n * Only `move`/`down`/`up` events carry positions; others are ignored here —\n * `scroll` re-emits a stale point, and `focus`/`key` synthesize element\n * centers the cursor never visited (letting those in would teleport the dot).\n */\nexport function smoothCursor(\n track: CursorTrack,\n options: SmoothOptions = {},\n): SmoothPoint[] {\n const factor = clamp(options.factor ?? 0.15, 0.01, 1)\n const fps = options.fps ?? 60\n const raw: SmoothPoint[] = track\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n if (raw.length === 0) return []\n\n const clicks: SmoothPoint[] = options.clickSnap\n ? track\n .filter((e) => e.type === 'down')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n : []\n\n const start = raw[0].t\n const end = raw[raw.length - 1].t\n const step = 1 / fps\n const out: SmoothPoint[] = []\n let cx = raw[0].x\n let cy = raw[0].y\n let ci = 0\n for (let t = start; t <= end + 1e-9; t += step) {\n const target = sampleAt(raw, t)\n cx += (target.x - cx) * factor\n cy += (target.y - cy) * factor\n if (clicks.length) {\n // smoothstep envelope rising into the click, held through SNAP_AFTER\n while (ci < clicks.length - 1 && t > clicks[ci].t + SNAP_AFTER) ci++\n const ck = clicks[ci]\n const u = clamp(\n (t - (ck.t - SNAP_BEFORE)) / SNAP_BEFORE,\n 0,\n t <= ck.t + SNAP_AFTER ? 1 : 0,\n )\n const w = u * u * (3 - 2 * u) * SNAP_GAIN\n if (w > 0) {\n cx += (ck.x - cx) * w\n cy += (ck.y - cy) * w\n }\n }\n out.push({ t, x: cx, y: cy })\n }\n return out\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\n","/**\n * Element-aware auto-zoom planner — the differentiator.\n *\n * Because we capture inside the browser, a click is not a guessed pixel point\n * but a known element (`rect`). We frame that element's bounding box, merge\n * clustered clicks into one sustained zoom span, and pick an adaptive level\n * that fits the element (unlike fixed-level recorders). Clicks are the PRIMARY\n * signal (Recordly's stance); TYPING SESSIONS are their peer (nobody\n * ships this): `key` activity pings group into sessions that frame the field\n * being typed into, absorb the click that focused it (the camera commits as\n * the field is clicked), and hold until typing stops; DWELLS augment last\n * (OpenScreen's stance) only where no other span exists — the cursor parking\n * on the thing being narrated is worth framing even without a click. Pure &\n * deterministic: same track → same spans. The editor edits this *output*, not\n * the planner — every span is tagged `source: 'auto'`, so a regenerate can\n * replace planner suggestions while leaving user-touched ('manual') spans\n * alone. Ids by origin: `z{n}` clicks, `k{n}` typing, `d{n}` dwells.\n */\nimport { clampZoomLevel } from '../types'\nimport { resolveZoomStyle } from '../zoomStyle'\nimport type { CursorTrack, Rect, ZoomSpan } from '../types'\nimport type { ZoomStyleName, ZoomStyleParams } from '../zoomStyle'\n\n/** Normalized step distance that ends a dwell run (OpenScreen's 0.02). */\nconst DWELL_MOVE_FRAC = 0.02\n/** A run qualifies as a dwell when it lasts this long (seconds). */\nconst DWELL_MIN = 0.45\nconst DWELL_MAX = 2.6\n/** Min gap between accepted dwell centers (longest dwell wins). */\nconst DWELL_SPACING = 1.8\n/**\n * A press whose element FITS the frame at less than this level is not a\n * target: it is a drag (aiming, scrubbing, moving a thing across the canvas)\n * or a frame-sized surface (a canvas, a panel, a modal clicked to focus or\n * dismiss it), and a zoom on it says nothing. Five real takes (2026-08-25)\n * each carried 1-4 clusters of these, planned at the floor level for 10-25s;\n * every one was dropped by hand. Now they plan nothing.\n *\n * The rule is PER PRESS, never per cluster: a target cluster's level came\n * from its LARGEST element, so one press on a panel inside a chain of button\n * clicks read the whole chain as a drag and threw every zoom away — two real\n * takes (2026-09-11) lost 5 of 8 and 4 of 4 clicks that way. A surface press\n * is set aside before clustering; the targets around it keep their zoom.\n */\nexport const DRAG_FIT_LEVEL = 1.15\n/**\n * Two `down`s this close in time and space are ONE press seen twice: a\n * recorder listening to pointer AND mouse events records each click as a\n * pair 0-1 ms apart (the extension did for a year). Counting the echo made\n * every lone click a two-click cluster and doubled the digest's counts.\n */\nconst PRESS_ECHO_MS = 4\nconst PRESS_ECHO_PX = 2\n/**\n * A press that TRAVELS before its release is a drag (a slider thumb, a\n * scrubber, a thing moved across a canvas): the pointer's path is the\n * content, so the camera follows it for the whole press at the style's\n * dragLevel instead of framing the press as a click. Travel is the farthest\n * the pointer gets from the press point, as a fraction of the frame width;\n * a press held still (a long click) stays a click.\n */\nexport const DRAG_MIN_TRAVEL_FRAC = 0.015\nexport const DRAG_MIN_S = 0.2\n\n/** A press that travelled before its release, in seconds and frame fractions. */\nexport interface Drag {\n /** the press and the release, seconds */\n t0: number\n t1: number\n /** the press point, normalized */\n nx: number\n ny: number\n /** the pointer's farthest excursion from the press point, frame widths */\n travel: number\n}\n\n/**\n * The drags in a cursor track: each `down` paired with the next `up`, kept\n * when the pointer travelled ≥ DRAG_MIN_TRAVEL_FRAC between them over\n * ≥ DRAG_MIN_S. Shared by the planner (a drag plans a follow span, and its\n * press is not a click) and the lowering's cursor follow (a drag inside any\n * follow span is tracked as a path).\n */\nexport function dragsFromTrack(\n track: CursorTrack,\n width: number,\n height: number,\n): Drag[] {\n if (!(width > 0) || !(height > 0)) return []\n const downs = dedupePresses(track.filter((e) => e.type === 'down'))\n const out: Drag[] = []\n for (const d of downs) {\n const up = track.find((e) => e.type === 'up' && e.t > d.t)\n if (!up) continue\n const t0 = d.t / 1000\n const t1 = up.t / 1000\n if (t1 - t0 < DRAG_MIN_S) continue\n let travel = 0\n for (const e of track) {\n if (e.t < d.t) continue\n if (e.t > up.t) break\n if (e.type !== 'move' && e.type !== 'up') continue\n const dist = Math.hypot(e.x - d.x, e.y - d.y) / width\n if (dist > travel) travel = dist\n }\n if (travel < DRAG_MIN_TRAVEL_FRAC) continue\n out.push({\n t0,\n t1,\n nx: clamp01(d.x / width),\n ny: clamp01(d.y / height),\n travel,\n })\n }\n return out\n}\n\n// ── typing sessions ────────────────────────────────────────────────────\n/** A session needs at least this many `key` pings (a lone Enter never zooms). */\nconst TYPING_MIN_PINGS = 2\n/** …spanning at least this long (seconds) — sub-half-second typing is a blip. */\nconst TYPING_MIN_DUR = 0.5\n/**\n * A `down` this close before the first ping, on the same field, is absorbed:\n * the span enters at the click so the camera commits as the field is clicked,\n * not after the fact (the anticipatory beat).\n */\nconst TYPING_CLICK_ABSORB = 1.5\n/**\n * Normalized field-center distance that means \"a different field\" — splits a\n * session (form-filling becomes a field-to-field pan chain via the lowering's\n * chainGap) and gates click absorption / same-field span merging.\n */\nconst TYPING_REFOCUS_FRAC = 0.08\n\nexport interface PlanOptions {\n /** captured frame size (for normalizing rects → [0..1] focus points). */\n width: number\n height: number\n /**\n * Camera style whose planner params seed every default below (zoomStyle.ts).\n * Explicit options still win. Absent = DEFAULT_ZOOM_STYLE.\n */\n style?: ZoomStyleName\n /** per-doc overrides on top of the style (doc.zoomParams — the Custom seam). */\n params?: Partial<ZoomStyleParams>\n /** target: zoom so the element fills ~this fraction of the frame. */\n targetFill?: number\n /** clamp zoom level. */\n minLevel?: number\n maxLevel?: number\n /** clicks within this many seconds merge into one zoom (session merge). */\n clusterGap?: number\n /** span lead-in before the first click + hold after the last. */\n lead?: number\n hold?: number\n /** minimum clicks for a cluster to earn a zoom (Cursorful's ≥2 rule). */\n minClusterClicks?: number\n /** the level a drag is followed at (see ZoomStyleParams.dragLevel). */\n dragLevel?: number\n /** emit spans with focusMode 'auto' (cursor-follow camera). */\n followByDefault?: boolean\n /** typing sessions plan spans. */\n typingZoom?: boolean\n /** max silence between `key` pings before the typing session ends. */\n typingGap?: number\n /** hold after the last keystroke (the read-what-you-typed beat). */\n typingHold?: number\n /** level floor for typing spans (a wide field still reads punchier). */\n typingMinLevel?: number\n}\n\n/** One press (or ping) in seconds, with the target element when known. */\nexport interface Click {\n t: number // seconds\n rect?: Rect\n x: number\n y: number\n}\n\n/**\n * The planner's first two passes, exported so the take DIGEST lists\n * the same click clusters and typing sessions the planner zooms on — one\n * grouping, never a second implementation that drifts. Typing sessions come\n * FIRST: a session may ABSORB the click that focused its field, and that\n * click must then not seed a click cluster — the two passes never compete\n * for the same press.\n */\nexport function groupTrack(\n track: CursorTrack,\n opts: {\n width: number\n height: number\n clusterGap: number\n typingGap: number\n typingZoom: boolean\n /** the style's fill target — what decides a press is on a SURFACE. */\n targetFill: number\n },\n): { sessions: TypingSession[]; clusters: Click[][]; surfaces: Click[][] } {\n const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts\n const clicks: Click[] = dedupePresses(\n track.filter((e) => e.type === 'down'),\n ).map((e) => ({ t: e.t / 1000, rect: e.rect, x: e.x, y: e.y }))\n\n const sessions = typingZoom\n ? typingSessions(track, width, height, typingGap)\n : []\n const absorbed = new Set<Click>()\n for (const s of sessions) {\n let best: Click | null = null\n for (const c of clicks) {\n if (absorbed.has(c)) continue\n if (c.t >= s.first || s.first - c.t > TYPING_CLICK_ABSORB) continue\n const [nx, ny] = fieldCenter(c, width, height)\n if (Math.hypot(nx - s.cx, ny - s.cy) > TYPING_REFOCUS_FRAC) continue\n if (!best || c.t > best.t) best = c\n }\n if (best) {\n absorbed.add(best)\n // Anticipatory entry: the span opens on the click into the field, so the\n // camera is already moving when the first character appears.\n s.start = best.t\n s.events = [best, ...s.events]\n }\n }\n\n // Surface presses (DRAG_FIT_LEVEL) are set aside BEFORE clustering, each\n // press on its own: the targets around one keep their chain. A press that\n // a typing session absorbed is its field, never a surface.\n const targets: Click[] = []\n const surfaces: Click[] = []\n for (const c of clicks) {\n if (absorbed.has(c)) continue\n if (isSurfacePress(c, width, height, targetFill)) surfaces.push(c)\n else targets.push(c)\n }\n // Merge clicks that are close in time into one sustained zoom.\n const chain = (list: Click[]): Click[][] => {\n const clusters: Click[][] = []\n for (const c of list) {\n const last = clusters.at(-1) // Click[] | undefined\n const prev = last?.at(-1)\n if (last && prev && c.t - prev.t <= clusterGap) last.push(c)\n else clusters.push([c])\n }\n return clusters\n }\n return { sessions, clusters: chain(targets), surfaces: chain(surfaces) }\n}\n\n/** Drop a `down` that echoes the previous one (PRESS_ECHO_MS/PX). */\nexport function dedupePresses(downs: CursorTrack): CursorTrack {\n const kept: CursorTrack = []\n for (const e of downs) {\n const prev = kept.at(-1)\n if (\n prev &&\n e.t - prev.t <= PRESS_ECHO_MS &&\n Math.abs(e.x - prev.x) <= PRESS_ECHO_PX &&\n Math.abs(e.y - prev.y) <= PRESS_ECHO_PX\n ) {\n continue\n }\n kept.push(e)\n }\n return kept\n}\n\n/**\n * The level at which the element fills `targetFill` of the frame — the\n * planner's zoom level before clamping, and the drag test's measure. Null\n * without a usable rect (a point press zooms at the ceiling).\n */\nexport function fitLevel(\n rect: Rect | undefined,\n width: number,\n height: number,\n targetFill: number,\n): number | null {\n if (!rect || rect.w <= 0 || rect.h <= 0) return null\n return Math.min((width * targetFill) / rect.w, (height * targetFill) / rect.h)\n}\n\n/** A press on a frame-sized element (a drag, a canvas, a panel): no target. */\nexport function isSurfacePress(\n c: Click,\n width: number,\n height: number,\n targetFill: number,\n): boolean {\n const fit = fitLevel(c.rect, width, height, targetFill)\n return fit !== null && fit < DRAG_FIT_LEVEL\n}\n\nexport function planAutoZoom(\n track: CursorTrack,\n options: PlanOptions,\n): ZoomSpan[] {\n const style = resolveZoomStyle(options.style, options.params)\n // The 'none' style (or an autoZoom:false override): manual zooms only.\n if (!style.autoZoom) return []\n const {\n width,\n height,\n targetFill = style.targetFill,\n minLevel = style.minLevel,\n maxLevel = style.maxLevel,\n clusterGap = style.clusterGap,\n lead = style.lead,\n hold = style.hold,\n minClusterClicks = style.minClusterClicks,\n dragLevel = style.dragLevel,\n followByDefault = style.followByDefault,\n typingZoom = style.typingZoom,\n typingGap = style.typingGap,\n typingHold = style.typingHold,\n typingMinLevel = style.typingMinLevel,\n } = options\n\n const grouped = groupTrack(track, {\n width,\n height,\n clusterGap,\n typingGap,\n typingZoom,\n targetFill,\n })\n const { sessions } = grouped\n\n // A drag's press is the drag's, never a click: it leaves its cluster (a\n // target's or a surface's) before the clusters plan anything.\n const drags = dragsFromTrack(track, width, height)\n const dragPress = new Set(drags.map((d) => round(d.t0)))\n const withoutDrags = (list: Click[][]): Click[][] =>\n list\n .map((cl) => cl.filter((c) => !dragPress.has(round(c.t))))\n .filter((cl) => cl.length > 0)\n const clusters = withoutDrags(grouped.clusters)\n const surfaces = withoutDrags(grouped.surfaces)\n\n interface Working {\n in: number\n out: number\n cx: number\n cy: number\n level: number\n dead?: boolean\n }\n\n // One follow span per drag, at the drag level, from the press to the\n // release: the lowering's cursor follow tracks the pointer's path for\n // the whole press.\n const dragSpans: ZoomSpan[] = drags.map((d, i) => ({\n id: `g${i}`,\n in: round(Math.max(0, d.t0 - lead)),\n out: round(d.t1 + hold),\n level: clampZoomLevel(Math.min(Math.max(dragLevel, 1.1), maxLevel)),\n cx: round(d.nx),\n cy: round(d.ny),\n focusMode: 'auto' as const,\n source: 'auto',\n }))\n\n // Lone clicks below the cluster minimum earn no zoom (the Cursorful rule:\n // one stray click isn't worth a camera move — dwells may still cover it).\n const eligible = clusters.filter((c) => c.length >= minClusterClicks)\n const clickSpans: Working[] = []\n // Surface clusters (drags, canvas and panel presses) plan no zoom but still\n // RESERVE their window: the cursor was working there, and the pauses\n // between drags are not dwells.\n const dragReserved: ZoomSpan[] = surfaces.map((cluster, i) => {\n const f = clusterFocus(cluster, width, height)\n return {\n id: `drag${i}`,\n in: Math.max(0, cluster[0].t - lead),\n out: cluster[cluster.length - 1].t + hold,\n level: 1,\n cx: f.cx,\n cy: f.cy,\n }\n })\n for (const cluster of eligible) {\n const first = cluster[0]\n const last = cluster[cluster.length - 1]\n // focus point + level from the element rect when present, else the point\n const f = focusFor(cluster, width, height, targetFill, minLevel, maxLevel)\n clickSpans.push({\n in: Math.max(0, first.t - lead),\n out: last.t + hold,\n cx: f.cx,\n cy: f.cy,\n level: f.level,\n })\n }\n\n // Typing spans clamp to their own floor: the field is the moment being\n // narrated, so a wide input still reads a notch punchier than a wide click.\n const typingFloor = Math.min(Math.max(minLevel, typingMinLevel), maxLevel)\n let typing: Working[] = sessions.map((s) => {\n const f = focusFor(\n s.events,\n width,\n height,\n targetFill,\n typingFloor,\n maxLevel,\n )\n return {\n in: Math.max(0, s.start - lead),\n out: s.last + typingHold,\n cx: f.cx,\n cy: f.cy,\n level: f.level,\n }\n })\n\n // Resolve typing↔click overlaps. Same field → MERGE (union extents, the\n // typing focus wins: the field is the payload). Different field → the click\n // beat keeps its span and the typing span CEDES the overlap — the camera\n // moves off the field for the click and the chainGap pan carries the travel.\n for (const t of typing) {\n for (const z of clickSpans) {\n if (z.dead || t.dead) continue\n if (t.in >= z.out || t.out <= z.in) continue\n if (Math.hypot(t.cx - z.cx, t.cy - z.cy) <= TYPING_REFOCUS_FRAC) {\n t.in = Math.min(t.in, z.in)\n t.out = Math.max(t.out, z.out)\n z.dead = true\n } else if (z.in <= t.in && z.out >= t.out) {\n t.dead = true\n } else if (z.in > t.in) {\n t.out = z.in\n } else {\n t.in = z.out\n }\n }\n }\n // A field switch splits sessions faster than lead+hold shrink — trim the\n // earlier span to the later one's entry (adjacent spans chain into a pan).\n typing = typing.filter((t) => !t.dead).sort((a, b) => a.in - b.in)\n for (let i = 0; i + 1 < typing.length; i++) {\n if (typing[i].out > typing[i + 1].in) typing[i].out = typing[i + 1].in\n }\n typing = typing.filter((t) => t.out - t.in >= 0.3)\n\n // Deterministic ids (pure planner: same track → same spans, same ids),\n // re-numbered in TIME order per origin so ids stay stable under replans.\n const zSpans: ZoomSpan[] = clickSpans\n .filter((s) => !s.dead)\n .sort((a, b) => a.in - b.in)\n .map((s, i) => ({\n id: `z${i}`,\n in: round(s.in),\n out: round(s.out),\n level: clampZoomLevel(s.level),\n cx: round(s.cx),\n cy: round(s.cy),\n // Follow styles ride the cursor through the span (entry + dead-zone\n // recenters baked at lowering); typing/dwell spans stay fixed-focus.\n ...(followByDefault ? { focusMode: 'auto' as const } : {}),\n source: 'auto',\n }))\n const kSpans: ZoomSpan[] = typing.map((s, i) => ({\n id: `k${i}`,\n in: round(s.in),\n out: round(s.out),\n level: clampZoomLevel(s.level),\n cx: round(s.cx),\n cy: round(s.cy),\n // Never focusMode:'auto': the dot is parked (and fading) while typing —\n // the FIELD is the anchor, and a follow would be a no-op at best.\n source: 'auto',\n }))\n const spans = [...zSpans, ...kSpans, ...dragSpans].sort((a, b) => a.in - b.in)\n\n // Dwell augmentation: sustained cursor rests no click/typing/drag span\n // covers.\n const dwells = dwellSpans(track, width, height, maxLevel, [\n ...spans,\n ...dragReserved,\n ])\n return [...spans, ...dwells].sort((a, b) => a.in - b.in)\n}\n\nexport interface TypingSession {\n /** pings (+ the absorbed focusing click) — fed to focusFor unchanged. */\n events: Click[]\n first: number\n last: number\n /** span anchor: the absorbed click's time, else the first ping's. */\n start: number\n /** normalized field center of the FIRST ping — the session's identity. */\n cx: number\n cy: number\n}\n\n/**\n * Group `key` pings into typing sessions: a ping joins the current session\n * while the silence stays ≤ typingGap AND it is still the same field (a ping\n * whose field center moved > TYPING_REFOCUS_FRAC starts a new session — that\n * split is what turns form-filling into a field-to-field pan chain). Sessions\n * below TYPING_MIN_PINGS/TYPING_MIN_DUR are noise (a lone Enter, a shortcut\n * chord) and plan nothing.\n */\nexport function typingSessions(\n track: CursorTrack,\n width: number,\n height: number,\n typingGap: number,\n): TypingSession[] {\n const pings: Click[] = track\n .filter((e) => e.type === 'key')\n .map((e) => ({ t: e.t / 1000, rect: e.rect, x: e.x, y: e.y }))\n const all: TypingSession[] = []\n let cur: TypingSession | null = null\n for (const p of pings) {\n const [nx, ny] = fieldCenter(p, width, height)\n if (\n cur &&\n p.t - cur.last <= typingGap &&\n Math.hypot(nx - cur.cx, ny - cur.cy) <= TYPING_REFOCUS_FRAC\n ) {\n cur.events.push(p)\n cur.last = p.t\n } else {\n cur = { events: [p], first: p.t, last: p.t, start: p.t, cx: nx, cy: ny }\n all.push(cur)\n }\n }\n return all.filter(\n (s) =>\n s.events.length >= TYPING_MIN_PINGS && s.last - s.first >= TYPING_MIN_DUR,\n )\n}\n\n/** Normalized center of the event's element rect (or its point). */\nexport function fieldCenter(\n c: Click,\n width: number,\n height: number,\n): [number, number] {\n const px = c.rect ? c.rect.x + c.rect.w / 2 : c.x\n const py = c.rect ? c.rect.y + c.rect.h / 2 : c.y\n return [clamp01(px / width), clamp01(py / height)]\n}\n\n/**\n * Dwell detection over move samples. One capture subtlety drives the shape:\n * the recorder is event-driven with a distance gate, so a PARKED cursor emits\n * NO samples — stillness is the time gap between a run's last sample and the\n * sample that finally breaks the distance threshold. A run therefore extends\n * to its breaking sample's time (or the track end). Candidates are ranked by\n * duration (longest wins), deduped by DWELL_SPACING between centers, and any\n * span that would overlap an existing (click) span is dropped.\n */\nexport function dwellSpans(\n track: CursorTrack,\n width: number,\n height: number,\n maxLevel: number,\n reserved: ZoomSpan[],\n): ZoomSpan[] {\n const moves = track\n .filter((e) => e.type === 'move')\n .map((e) => ({ t: e.t / 1000, nx: e.x / width, ny: e.y / height }))\n if (moves.length < 2) return []\n const trackEnd = track[track.length - 1].t / 1000\n\n interface Candidate {\n center: number\n cx: number\n cy: number\n strength: number\n }\n const candidates: Candidate[] = []\n let start = 0\n for (let i = 1; i <= moves.length; i++) {\n const breaks =\n i === moves.length ||\n Math.hypot(moves[i].nx - moves[i - 1].nx, moves[i].ny - moves[i - 1].ny) >\n DWELL_MOVE_FRAC\n if (!breaks) continue\n const endT = i < moves.length ? moves[i].t : trackEnd\n const dur = endT - moves[start].t\n // The run that starts at the track's FIRST sample is the cursor before\n // it has done anything: on a CLI take it is parked at the frame's\n // corner until the first step, and six of eight real takes opened on a\n // point zoom at the ceiling onto that corner. Not a moment.\n const opening = start === 0\n if (!opening && dur >= DWELL_MIN && dur <= DWELL_MAX) {\n const run = moves.slice(start, i)\n candidates.push({\n center: (moves[start].t + endT) / 2,\n cx: run.reduce((s, p) => s + p.nx, 0) / run.length,\n cy: run.reduce((s, p) => s + p.ny, 0) / run.length,\n strength: dur,\n })\n }\n start = i\n }\n\n // Longest dwell wins; enforce center spacing; drop-on-overlap vs everything\n // already accepted (click spans + earlier dwells — adjacency is fine).\n const sorted = [...candidates].sort((a, b) => b.strength - a.strength)\n const sourceDuration = trackEnd\n const len = Math.max(1, sourceDuration * 0.05)\n const taken: ZoomSpan[] = [...reserved]\n const accepted: ZoomSpan[] = []\n const centers: number[] = []\n for (const c of sorted) {\n if (centers.some((t) => Math.abs(t - c.center) < DWELL_SPACING)) continue\n const spanIn = Math.max(\n 0,\n Math.min(c.center - len / 2, sourceDuration - len),\n )\n const spanOut = Math.min(sourceDuration, spanIn + len)\n if (spanOut - spanIn < 0.3) continue\n if (taken.some((z) => spanIn < z.out && spanOut > z.in)) continue\n const span: ZoomSpan = {\n id: `d${accepted.length}`,\n in: round(spanIn),\n out: round(spanOut),\n // No element rect on moves → point zoom at the planner ceiling.\n level: clampZoomLevel(maxLevel),\n cx: round(clamp01(c.cx)),\n cy: round(clamp01(c.cy)),\n source: 'auto',\n }\n accepted.push(span)\n taken.push(span)\n centers.push(c.center)\n }\n // Deterministic ids in TIME order (rank order depends on durations, which\n // would make ids unstable under small edits) — re-id after sorting.\n return accepted\n .sort((a, b) => a.in - b.in)\n .map((z, i) => ({ ...z, id: `d${i}` }))\n}\n\nfunction focusFor(\n cluster: Click[],\n width: number,\n height: number,\n targetFill: number,\n minLevel: number,\n maxLevel: number,\n): { cx: number; cy: number; level: number } {\n // Average the rect centers (or points) in the cluster.\n let sx = 0\n let sy = 0\n let maxW = 0\n let maxH = 0\n for (const c of cluster) {\n if (c.rect) {\n sx += c.rect.x + c.rect.w / 2\n sy += c.rect.y + c.rect.h / 2\n maxW = Math.max(maxW, c.rect.w)\n maxH = Math.max(maxH, c.rect.h)\n } else {\n sx += c.x\n sy += c.y\n }\n }\n const n = cluster.length\n const cx = clamp01(sx / n / width)\n const cy = clamp01(sy / n / height)\n\n // Level: zoom so the LARGEST element fills ~targetFill of the frame\n // (element-aware). No rect → the max level (point zoom). Every member\n // cleared DRAG_FIT_LEVEL on its own (groupTrack), so the level is a\n // target's, never a surface's.\n const level =\n fitLevel({ x: 0, y: 0, w: maxW, h: maxH }, width, height, targetFill) ??\n maxLevel\n return { cx, cy, level: clamp(level, minLevel, maxLevel) }\n}\n\n/**\n * Normalized focus + union rect of a click cluster or typing session (the\n * digest's per-moment `focus`/`rect`, in the doc's [0..1] units) — the same\n * averaging `focusFor` zooms on, minus the level.\n */\nexport function clusterFocus(\n events: readonly Click[],\n width: number,\n height: number,\n): { cx: number; cy: number; rect: Rect | null } {\n let sx = 0\n let sy = 0\n let x0 = Infinity\n let y0 = Infinity\n let x1 = -Infinity\n let y1 = -Infinity\n let rects = 0\n for (const c of events) {\n if (c.rect) {\n sx += c.rect.x + c.rect.w / 2\n sy += c.rect.y + c.rect.h / 2\n x0 = Math.min(x0, c.rect.x)\n y0 = Math.min(y0, c.rect.y)\n x1 = Math.max(x1, c.rect.x + c.rect.w)\n y1 = Math.max(y1, c.rect.y + c.rect.h)\n rects++\n } else {\n sx += c.x\n sy += c.y\n }\n }\n const n = Math.max(1, events.length)\n const rect =\n rects > 0\n ? {\n x: clamp01(x0 / width),\n y: clamp01(y0 / height),\n w: clamp01((x1 - x0) / width),\n h: clamp01((y1 - y0) / height),\n }\n : null\n return { cx: clamp01(sx / n / width), cy: clamp01(sy / n / height), rect }\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\nfunction clamp01(v: number): number {\n return clamp(v, 0, 1)\n}\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * lowerToComposition — the IR bridge.\n *\n * Turns the app-level `ProjectDoc` into a vos `VosConfigJson` (+ a `data` value).\n * The editor's \"opinion\" lowers into vos core's function-string IR; vos core never\n * sees `ProjectDoc`.\n *\n * The studio's output is 2D compositing (gradient background, the video inset with\n * padding + rounded corners + shadow, zoom/pan, a cursor on top). Compositor v2\n * draws it as a **three-layer mesh stack** under one perspective camera —\n * a background quad (CSS fill + vos loop), the CARD (its own 2D canvas, on a\n * plane that can tilt), and an overlay quad (cam bubble + future overlays). Each\n * layer is a 2D canvas that `onFrame` paints from `ctx.data` (deterministic — a\n * pure function of ctx.time); at tilt = 0 the card fills the frustum and renders\n * pixel-identically to the pre-v2 fullscreen quad. See stage.ts for the geometry.\n *\n * THE INTERPRETER PATTERN: all four\n * function strings are module-level CONSTANTS — the program is a fixed\n * interpreter and the entire editable state travels in `ctx.data`. The compiled\n * program string is therefore a structural hash: every edit is a live SET_DATA\n * (T2), a trim is SET_DATA + SET_DURATION (T2.5, via the `vosCarrier` opt-in),\n * and the program only changes when this module changes. Even `duration` is\n * data: the config bakes a placeholder and the carrier timeline reads\n * `ctx.data.duration` (delivered with LOAD / deps.data).\n *\n * Time model: `t` (ctx.time) is OUTPUT-timeline seconds; the source moment on\n * screen is `srcT = mapTime(segments, t)` (@vosjs/timeline, inlined into the\n * program via `timelineRuntimeCode` so host and sandbox evaluate identically).\n * Cursor samples stay SOURCE-anchored and are read at `srcT`; zoom spans are\n * source-anchored in the doc and expanded to an OUTPUT-time keyframe track here\n * (ramps must run in output time so they never straddle a cut).\n *\n * NOTE: uses an HTMLVideoElement for the source (robust, any format). Frame-accurate\n * WebCodecs export is a later layer; this nails the *look* + makes every control work.\n */\nimport {\n EASINGS,\n lerpArray,\n sample,\n segmentRate,\n sortKeyframes,\n sourceToTimeline,\n splitBySpeed,\n totalDuration,\n} from '@vosjs/timeline'\nimport { timelineRuntimeCode } from '@vosjs/timeline/bundle'\nimport {\n CAMERA_CENTRE_RAMP,\n camBubbleRect,\n cameraModel,\n clampFocus,\n docCardLayout,\n} from '../layout'\nimport { smoothCursor } from '../planner/smoothing'\nimport {\n BACKGROUND_Z,\n CAMERA_FAR,\n CAMERA_NEAR,\n CARD_FOV,\n CARD_OVERSCAN_MAX,\n CARD_OVERSCAN_STEP,\n CARD_Z,\n OVERLAY_Z,\n} from '../stage'\nimport {\n OVERLAY_FONT_FACES,\n overlayFaceFor,\n overlayFontFaces,\n overlayLines,\n resolveOverlayBox,\n resolveOverlayFx,\n resolveOverlayStyle,\n} from '../overlayText'\nimport { resolveText3dAsset } from '../text3d'\nimport {\n CARD_EDGE_OVERDRAW,\n CLICK_FX_INTENSITY,\n FRAME_BORDER_COLOR_DEFAULT,\n FRAME_BORDER_WIDTH_DEFAULT,\n LAYER_CARD_SHADOW,\n MOTION_EASE,\n OBJECT_DEFAULT_SCALE,\n OVERLAY_LINE_HEIGHT,\n OVERLAY_MEDIA_DEFAULT_RADIUS,\n OVERLAY_MEDIA_DEFAULT_WIDTH,\n OVERLAY_MIN_DURATION,\n clampCamSize,\n clampTiltDeg,\n clampZoomLevel,\n clipLength,\n pageDisplayUrl,\n transitionMult,\n TILT_SPAN_MIN,\n ZOOM_SPAN_MIN,\n} from '../types'\nimport {\n GHOST_RENDER_ORDER,\n GHOST_Z_LIFT,\n TRANSITION_SCALE_STEP,\n TRANSITION_TRAVEL,\n docTransitions,\n restSpansThroughTransitions,\n} from './transitions'\nimport { DEFAULT_ZOOM_STYLE, ZOOM_STYLES, resolveZoomStyle } from '../zoomStyle'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport { clipEnvelope } from './audioEnvelope'\nimport { followFocusEvents } from './cursorFollow'\nimport { pinPlacement, pinReferent } from './pin'\nimport { cursorIdleFade } from './cursorIdle'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\nimport {\n cardEnter,\n cardExit,\n cardPoseTrack,\n docFreezes,\n entranceTiltKeyframes,\n entranceZoomKeyframes,\n migrateMotion,\n outputEnd,\n prependEntrance,\n withFreezes,\n} from './motion'\nimport { enterKey, exitKey, idleKey } from '../anim'\nimport {\n CLICK_FX_PRE,\n CLICK_HIGHLIGHT_FADE,\n CLICK_PULSE_DUR,\n CLICK_RIPPLE_DUR,\n extractClicks,\n hexToRgbTriplet,\n} from './extractClicks'\nimport { cardFields, layerMedia, mediaFrame, sameMedia } from '../media'\nimport { htmlLayerPayload } from '../htmlLayer'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { TimelineEdit } from '@vosjs/shared/timelineEdits'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type { ZoomStyleParams } from '../zoomStyle'\nimport type { CamBubbleRect, CameraModel, CardLayout } from '../layout'\nimport type { FollowEvent } from './cursorFollow'\nimport type { PinCandidate, PinPlacement } from './pin'\nimport type {\n AudioClip,\n CamPoseSpan,\n CamStyle,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n TiltSpan,\n ZoomSpan,\n} from '../types'\n\nexport interface LoweredComposition {\n /** The composed config: the anchor's program plus the studio stack entry. */\n config: Record<string, unknown>\n /** The MAIN program's ctx.data. */\n data: Record<string, unknown>\n /** Each stack entry's own ctx.data, by entry id (`deps.stack` / `SET_DATA { target }`). */\n stack: Record<string, Record<string, unknown>>\n /**\n * A program anchor's tween-timing overlay: delivered to the player\n * LIVE (`SET_TWEEN_EDITS`, bridge protocol 8) so a retime never changes\n * the program string. Absent on a recording, and on a stored (baked)\n * program config.\n */\n tweenEdits?: readonly TimelineEdit[]\n /** Output duration in seconds (drives the carrier + SET_DURATION). */\n duration: number\n}\n\n/**\n * Zoom transition shape — deterministic pure keyframes evaluated by TL.sample\n * (no stateful springs, seek stays a pure function of t). The zoom-in ramp\n * starts BEFORE the span and lands rampInOverlap into it (the camera arrives\n * just after the moment it frames); the zoom-out starts at the span's end.\n * All timing/ease constants come from the doc's zoom STYLE (zoomStyle.ts —\n * named strategy presets grounded in the measured competitor comparison).\n * Eases are `css-bezier(…)` curves\n * (@vosjs/timeline >=0.4.0 — parsed identically by host + runtime bundle).\n *\n * Legacy constant names = the DEFAULT style's values, kept for scripts/tests\n * that reason about \"the default ramp\" symbolically.\n */\nexport const ZOOM_RAMP_IN = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampIn\nexport const ZOOM_RAMP_IN_OVERLAP =\n ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampInOverlap\nexport const ZOOM_RAMP_OUT = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampOut\n/** Output-time gap ≤ this → pan straight to the next span (no zoom-out). */\nexport const ZOOM_CHAIN_GAP = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].chainGap\n/** Connected-zoom pan duration (compressed into short gaps). */\nexport const ZOOM_PAN = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].pan\nexport const ZOOM_EASE = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].ease\nexport const ZOOM_PAN_EASE = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].panEase\n\n/**\n * Tilt transition shape (tilt spans): the FALLBACK for a bare\n * tiltTrackFromDoc call. The lowering hands the track the camera style's\n * own tilt motion (`ZoomStyleParams.tilt`, the style's zoom tempo), so a\n * lean lands WITH its zoom in every style — the one thing the retired\n * keynote style had right, generalized. The eases are the same measured\n * css-bezier family the default zoom style uses. Unlike zoom, the pose is\n * SETTLED at span.in (the ramp starts TILT_RAMP_IN before, with no\n * overlap-into-span): tilt frames a moment, it doesn't chase content.\n */\nexport const TILT_RAMP_IN = 0.9\nexport const TILT_RAMP_OUT = 0.8\n/** Output-time gap ≤ this → swing straight to the next pose (no flatten between). */\nexport const TILT_CHAIN_GAP = 1.35\n/** Connected-tilt swing duration (compressed into short gaps). */\nexport const TILT_PAN = 0.9\nexport const TILT_EASE = ZOOM_EASE\nexport const TILT_PAN_EASE = ZOOM_PAN_EASE\n\n/**\n * Cam-move transition shape (animated cam layouts). Own constants,\n * deliberately NOT the zoom style's or tilt's (switching another\n * subsystem's personality must never silently change the bubble's feel). The\n * premium band for an in-video layout morph is 0.6–1.0s (Descript's Smart\n * Transition defaults to 0.8s); the bubble is small chrome, so it sits at the\n * fast end. Like tilt, the pose is SETTLED at span.in (the ramp starts before,\n * no overlap-into-span): a cam move frames what follows.\n */\nexport const CAM_RAMP_IN = 0.65\nexport const CAM_RAMP_OUT = 0.65\n/** Output-time gap ≤ this → morph straight to the next pose (no return to rest). */\nexport const CAM_CHAIN_GAP = 1.2\n/** Connected-move morph duration (compressed into short gaps). */\nexport const CAM_PAN = 0.7\nexport const CAM_EASE = ZOOM_EASE\nexport const CAM_PAN_EASE = ZOOM_PAN_EASE\n\n/**\n * The config `duration` placeholder. The REAL duration lives in `ctx.data`\n * (the carrier timeline reads it), so trims never change the program string.\n */\nconst PROGRAM_DURATION = 1\n\n/**\n * The doc's kept spans with speed spans applied — the OUTPUT-time truth every\n * downstream consumer evaluates (mapTime in ON_FRAME, duration, zoom remap,\n * the export's audio splice). An empty segment list means \"untrimmed\", so\n * speed spans still apply over one synthesized full-source segment.\n */\nexport function ratedSegments(doc: StudioDoc): Segment[] {\n // A program is ONE source span, its own length: its speed spans\n // rate it exactly as a recording's rate the footage.\n const segs = isRecordingDoc(doc)\n ? doc.segments.length\n ? doc.segments\n : [{ in: 0, out: doc.source.meta.durationMs / 1000 }]\n : [{ in: 0, out: programDuration(doc) }]\n // A freeze is a rated piece placed at its source moment, so every reader\n // of this list inherits it (a legacy segment hold is one at its end).\n // Every span is its media's: a segment on another media is rated by that\n // media's speed spans, and the pieces keep the media they play.\n const freezes = isRecordingDoc(doc) ? docFreezes(doc) : []\n const speeds = doc.speed ?? []\n const pieces = segs.flatMap((seg) =>\n splitBySpeed(\n [seg],\n speeds.filter((sp) =>\n sameMedia(sp.media, (seg as { media?: string }).media),\n ),\n ),\n )\n return withFreezes(pieces, freezes)\n}\n\nfunction durationSec(doc: ProjectDoc, rated: Segment[]): number {\n const trimmed = totalDuration(rated)\n return trimmed > 0 ? trimmed : doc.source.meta.durationMs / 1000\n}\n\n/**\n * Map a SOURCE-time span onto the output timeline through the RATED segment\n * list: the output extent of its KEPT footage (a partially-cut span snaps its\n * edges into kept footage; a fully-cut span returns null — it follows its\n * footage, like every source-anchored feature). Rate-aware: output positions\n * accumulate each piece's (out − in) / rate.\n */\nexport function spanOutputExtent(\n segments: readonly (Segment & { media?: string })[],\n sIn: number,\n sOut: number,\n media?: string,\n): { start: number; end: number } | null {\n let acc = 0\n let start: number | null = null\n let end: number | null = null\n for (const p of segments) {\n const rate = segmentRate(p)\n const len = Math.max(0, p.out - p.in) / rate\n // Source seconds are one media's: a piece of another media is output\n // time this span never covers, however its numbers overlap.\n if (!sameMedia(p.media, media)) {\n acc += len\n continue\n }\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) {\n if (start === null) start = acc + (ovIn - p.in) / rate\n end = acc + (ovOut - p.in) / rate\n }\n acc += len\n }\n return start !== null && end !== null && end > start ? { start, end } : null\n}\n\n/** A span's arrival ease, validated against the shared ease set. */\nfunction spanEase(\n ease: string | undefined,\n fallback: string,\n): NonNullable<Keyframe['ease']> {\n return (ease && ease in EASINGS ? ease : fallback) as NonNullable<\n Keyframe['ease']\n >\n}\n\n/**\n * The shortest ramp the tracks ever emit (seconds): the cut style's own\n * arrival, eight frames at 60 fps. A ramp that collides with the keyframe\n * before it shrinks to the room it has, never below this, instead of the\n * emitter's 1 ms nudge, which turned every ramp longer than its span (a\n * dwell of a second under a 1.8 s cinema ramp, a click under a 3× speed\n * span) into a hard cut inside a style sold as smooth.\n */\nexport const RAMP_FLOOR = 0.14\n\n/**\n * Where a ramp lands. Unclamped (the emitter accepted the wanted start),\n * the landing is exactly start + duration, float-identical to the old\n * expression so a fitting track stays byte-identical. Clamped (the ramp\n * would have begun before t = 0, or before the previous keyframe), the\n * ramp keeps its full duration when the span has the room (a clip that\n * opens mid-zoom-in reads as the camera already moving, the way it\n * always did), lands at the span's end when it does not, and never lands\n * before its designed point (`startWanted + duration`, rampInOverlap into\n * the span) or less than RAMP_FLOOR after the actual start. The hold pin\n * and the exit take over from there.\n */\nfunction rampLanding(\n start: number,\n startWanted: number,\n duration: number,\n spanEnd: number,\n): number {\n if (Math.abs(start - startWanted) < 1e-9) return start + duration\n const landWanted = startWanted + duration\n const landFull = Math.min(start + duration, spanEnd)\n return Math.max(start + RAMP_FLOOR, landWanted, landFull)\n}\n\n/**\n * Monotonic keyframe emitter shared by every span→track expansion (zoom,\n * tilt): clamps into strictly-increasing time, skips exact no-op repeats,\n * nudges 1ms on time collisions. Extracted so the tracks can never drift on\n * these rules — zoom's emitted keyframes are byte-identical to the previous\n * in-closure version (verify-zoom-spans pins it).\n */\nfunction trackEmitter(): {\n keyframes: Keyframe<number[]>[]\n push: (\n t: number,\n value: number[],\n ease: NonNullable<Keyframe['ease']>,\n ) => number\n} {\n const keyframes: Keyframe<number[]>[] = []\n const push = (\n t: number,\n value: number[],\n ease: NonNullable<Keyframe['ease']>,\n ): number => {\n const prev = keyframes.at(-1)\n let tt = Math.max(0, t)\n if (prev) {\n if (tt <= prev.t + 1e-6 && sameVec(prev.value, value)) return prev.t\n if (tt <= prev.t + 1e-6) tt = prev.t + 0.001\n }\n keyframes.push({ t: round(tt), value: value.map(round), ease })\n return tt\n }\n return { keyframes, push }\n}\n\n/**\n * Expand the doc's source-anchored zoom spans into a standard @vosjs/timeline\n * keyframe track in OUTPUT time (values are [level, cx, cy] vectors):\n *\n * rest ──ramp-in──▶ [level,cx,cy] ──hold──▶ span end ──ramp-out──▶ rest\n *\n * with one twist: when the output gap to the NEXT span is ≤ ZOOM_CHAIN_GAP,\n * the camera never returns to rest — it pans straight to the next span's\n * state over ZOOM_PAN and holds it through the gap (OpenScreen's connected\n * zooms: the camera glides from focus to focus). Transitions run in output\n * time, so they never straddle a cut; keyframe times are strictly increasing\n * (dense spans compress rather than reorder).\n */\n/** A span enriched by the lowering with baked cursor-follow recenters. */\nexport interface LoweredZoomSpan extends ZoomSpan {\n followEvents?: FollowEvent[]\n}\n\nexport function zoomTrackFromDoc(\n zoom: LoweredZoomSpan[],\n segments: Segment[],\n style: ZoomStyleParams = ZOOM_STYLES[DEFAULT_ZOOM_STYLE],\n): KeyframeTrack<number[]> {\n const panEase = style.panEase as NonNullable<Keyframe['ease']>\n const mapped = [...zoom]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n // Monotonic emit: clamp into strictly-increasing time (skip exact no-ops).\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n // [level, cx, cy, centring]: the fourth component is the stage camera's\n // pull of the focus toward the frame's centre, 0 at rest and 1 at the\n // apex, interpolated by the SAME ease as the level so the card's slide\n // and its scale are one motion. Keyed to the level over a fixed band it\n // was a jump: any ease reaches level 1.3 in its first frames, so the\n // whole slide landed at once and the corner drifted back out as the\n // scale caught up (the twitch measured on the tweakcn take).\n const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1]\n // The camera's current state within the span — advanced by follow recenters.\n let cur = entry\n // Per-span transition speed: a multiplier on the style's ramps.\n // ×1 (absent/'smooth') is float-exact, so legacy tracks stay byte-identical.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; scale in place around this span's focus\n // (level 1 renders identically for any focus, so the rest focus is free).\n const startWanted = tIn - (style.rampIn - style.rampInOverlap) * m\n const start = push(startWanted, [1, z.cx, z.cy, 0], 'none')\n push(\n rampLanding(start, startWanted, style.rampIn * m, tOut),\n entry,\n spanEase(z.ease, style.ease),\n )\n }\n\n // Cursor-follow recenters (focusMode 'auto', baked by the lowering): hold\n // at the current focus, glide to the recentered one over the style's\n // recenter duration.\n let inPath = false\n for (const e of z.followEvents ?? []) {\n const eOut = sourceToTimeline(segments, e.t)\n if (eOut === null || eOut <= tIn || eOut >= tOut) continue\n // A recenter with less than RAMP_FLOOR of room after the arrival or\n // before the exit would compress into a jump (the focus freezes for\n // the zoom-out anyway); it is dropped, and the next one glides.\n if (eOut - tIn < RAMP_FLOOR || tOut - eOut < RAMP_FLOOR) continue\n const next = [cur[0], e.cx, e.cy, 1]\n if (e.path) {\n // A path sample: the camera is HERE at this time. The first sample\n // of a run is reached by a glide of the recenter length from the\n // held focus (a hold keyframe pins the start), every later one by\n // a linear segment, so the pan reads as the pointer's own motion.\n if (!inPath) {\n push(Math.max(tIn, eOut - style.followRecenter), cur, 'none')\n push(eOut, next, panEase)\n inPath = true\n } else {\n push(eOut, next, 'linear')\n }\n } else {\n inPath = false\n push(eOut, cur, 'none')\n push(Math.min(eOut + style.followRecenter, tOut), next, panEase)\n }\n cur = next\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, cur, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= style.chainGap) {\n // Connected zooms: pan straight to the next state. Adjacent spans still\n // get a real pan by letting it land up to rampInOverlap into the next.\n // The pan is the NEXT span's arrival, so its transition speed governs.\n const mNext = transitionMult(next.z.transition)\n const nextValue = [clampZoomLevel(next.z.level), next.z.cx, next.z.cy, 1]\n push(\n Math.max(\n tOut + RAMP_FLOOR,\n Math.min(\n tOut + style.pan * mNext,\n next.tIn + style.rampInOverlap * mNext,\n ),\n ),\n nextValue,\n panEase,\n )\n chained = true\n } else {\n // Focus FREEZES for the zoom-out (Recordly's rule): the camera pulls\n // back from wherever the follow left it, no parting pan.\n push(\n tOut + style.rampOut * m,\n [1, cur[1], cur[2], 0],\n spanEase(z.ease, style.ease),\n )\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\nfunction sameVec(a: number[], b: number[]): boolean {\n return a.length === b.length && a.every((v, i) => Math.abs(v - b[i]) < 1e-6)\n}\n\n/**\n * Expand the doc's source-anchored tilt spans into an OUTPUT-time keyframe\n * track of [rx, ry] DEGREES (ON_FRAME converts to radians at the mesh):\n *\n * flat ──ramp-in──▶ [rx,ry] ──hold──▶ span end ──ramp-out──▶ flat\n *\n * Rest is FLAT: there is no static card pose to return to (decided\n * 2026-08-03 — a lean is a moment on the timeline), which makes this the\n * exact analog of zoom's level-1 rest. Deliberate\n * differences from zoomTrackFromDoc: the pose is SETTLED at\n * span.in (ramp starts TILT_RAMP_IN before, no overlap-into-span), there are\n * no follow events, and ramps are fixed constants rather than the zoom\n * style's. Spans ≤ TILT_CHAIN_GAP apart in output time swing pose-to-pose\n * without flattening between (the connected-zoom rule). Transitions run in\n * output time so they never straddle a cut; keyframe times are strictly\n * increasing (dense spans compress rather than reorder).\n */\nexport function tiltTrackFromDoc(\n tilt: TiltSpan[],\n segments: Segment[],\n // Motion overrides from the camera style's tilt personality —\n // absent fields fall back to the TILT_* constants, so a bare call keeps\n // the house motion and a style like 'drift' can slow its leans down.\n motion: {\n rampIn?: number\n rampOut?: number\n chainGap?: number\n pan?: number\n } = {},\n): KeyframeTrack<number[]> {\n const rampInDur = motion.rampIn ?? TILT_RAMP_IN\n const rampOutDur = motion.rampOut ?? TILT_RAMP_OUT\n const chainGap = motion.chainGap ?? TILT_CHAIN_GAP\n const panDur = motion.pan ?? TILT_PAN\n const panEase = TILT_PAN_EASE as NonNullable<Keyframe['ease']>\n const rest = [0, 0]\n const mapped = [...tilt]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n const pose = [clampTiltDeg(z.rx), clampTiltDeg(z.ry)]\n // Per-span transition speed — ×1 when absent, float-exact.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; arrive settled exactly at the span start.\n const startWanted = tIn - rampInDur * m\n const start = push(startWanted, rest, 'none')\n push(\n rampLanding(start, startWanted, rampInDur * m, tOut),\n pose,\n spanEase(z.ease, TILT_EASE),\n )\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, pose, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= chainGap) {\n // Connected tilts: swing straight to the next pose, landing by its\n // start — the next span's arrival, so its transition speed governs.\n const nextPose = [clampTiltDeg(next.z.rx), clampTiltDeg(next.z.ry)]\n push(\n Math.max(\n tOut + RAMP_FLOOR,\n Math.min(tOut + panDur * transitionMult(next.z.transition), next.tIn),\n ),\n nextPose,\n panEase,\n )\n chained = true\n } else {\n push(tOut + rampOutDur * m, rest, spanEase(z.ease, TILT_EASE))\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/**\n * The bubble's rest pose as [x, y, size] frame fractions, resolved through the\n * SAME oracle the picking layer uses (camBubbleRect) so the corner math can\n * never fork a third way (draw / pick / lowering). Fractions are resolution-\n * stable at a fixed aspect: the margin (24·s) and diameter (size·H) both\n * scale with s = H/1080, so the fraction depends only on the aspect ratio.\n */\nexport function camRestPose(cam: CamStyle, W: number, H = 1080): number[] {\n const r = camBubbleRect(cam, W, H)\n return [(r.x + r.size / 2) / W, (r.y + r.size / 2) / H, r.size / H]\n}\n\n/**\n * Expand the doc's source-anchored cam pose spans into an OUTPUT-time keyframe\n * track of [x, y, size] frame fractions (the third consumer of the\n * span→track seam):\n *\n * rest ──ramp-in──▶ [x,y,size] ──hold──▶ span end ──ramp-out──▶ rest\n *\n * Rest is the doc's cam style resolved to fractions (camRestPose) — doc.cam IS\n * the rest pose, exactly as tilt's rest is flat. The pose is SETTLED at\n * span.in (ramp starts CAM_RAMP_IN before): a cam move frames what follows.\n * Spans ≤ CAM_CHAIN_GAP apart in output time morph pose-to-pose without\n * returning to rest (the connected-zoom rule). Absent pose fields inherit the\n * rest pose. Transitions run in output time so they never straddle a cut.\n */\nexport function camTrackFromDoc(\n cam: CamStyle,\n spans: CamPoseSpan[],\n segments: Segment[],\n W: number,\n H = 1080,\n): KeyframeTrack<number[]> {\n const rest = camRestPose(cam, W, H)\n const poseOf = (z: CamPoseSpan): number[] => [\n z.x != null ? Math.min(1, Math.max(0, z.x)) : rest[0],\n z.y != null ? Math.min(1, Math.max(0, z.y)) : rest[1],\n z.size != null ? clampCamSize(z.size) : rest[2],\n ]\n const panEase = CAM_PAN_EASE as NonNullable<Keyframe['ease']>\n const mapped = [...spans]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n const pose = poseOf(z)\n // Per-span transition speed — 'instant' is the layout jump-cut.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; arrive settled exactly at the span start.\n const start = push(tIn - CAM_RAMP_IN * m, rest, 'none')\n push(start + CAM_RAMP_IN * m, pose, spanEase(z.ease, CAM_EASE))\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, pose, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= CAM_CHAIN_GAP) {\n // Connected moves: morph straight to the next pose, landing by its\n // start — the next span's arrival, so its transition speed governs.\n push(\n Math.min(tOut + CAM_PAN * transitionMult(next.z.transition), next.tIn),\n poseOf(next.z),\n panEase,\n )\n chained = true\n } else {\n push(tOut + CAM_RAMP_OUT * m, rest, spanEase(z.ease, CAM_EASE))\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/**\n * Pose fractions → the bubble square, mirroring ON_FRAME's pose branch (the\n * 40px floor and the rounded radius ride s, exactly like the static path).\n */\nexport function camRectFromPose(\n pose: readonly number[],\n cam: CamStyle,\n W: number,\n H = 1080,\n): CamBubbleRect {\n const s = H / 1080\n const size = Math.max(40, pose[2] * H)\n return {\n x: pose[0] * W - size / 2,\n y: pose[1] * H - size / 2,\n size,\n radius: cam.shape === 'rounded' ? (cam.radius ?? 18) * s : size / 2,\n }\n}\n\n/**\n * The bubble rect at OUTPUT time t — the time-aware picking oracle.\n * With no motion spans it is exactly camBubbleRect at the doc's design layout;\n * with spans it samples the SAME track the lowering ships, so picking can\n * never drift from the paint (camDraw.test.ts pins both paths). Design space\n * is docCardLayout's (H = 1080, W from the output aspect).\n */\nexport function camBubbleRectAt(doc: ProjectDoc, t: number): CamBubbleRect {\n const { W, H } = docCardLayout(doc)\n if (!doc.camMotion || !doc.camMotion.length) {\n return camBubbleRect(doc.cam, W, H)\n }\n const track = camTrackFromDoc(\n doc.cam,\n doc.camMotion,\n ratedSegments(doc),\n W,\n H,\n )\n if (!track.keyframes.length) return camBubbleRect(doc.cam, W, H)\n return camRectFromPose(sample(track, t, lerpArray), doc.cam, W, H)\n}\n\n/** A resolved pose keyframe: clip-local time + full value vector. */\nexport interface MotionKey {\n at: number\n value: number[]\n ease?: string\n}\n\n/**\n * Bake resolved pose keyframes into a CLIP-LOCAL keyframe track.\n * The base vector holds until the first pose (a leading keyframe at 0 pins\n * it), values interpolate across each gap (ease-into per pose), and the last\n * pose holds to the clip's end (sample clamps). Same emitter, same\n * interpolator, same purity as the zoom/tilt/cam tracks.\n */\nexport function motionTrack(\n base: readonly number[],\n keys: MotionKey[],\n dur: number,\n): KeyframeTrack<number[]> {\n const sorted = keys\n .filter((k) => Number.isFinite(k.at))\n .sort((a, b) => a.at - b.at)\n const first = sorted.at(0)\n if (!first) return { keyframes: [] }\n const { keyframes, push } = trackEmitter()\n if (first.at > 0.001) push(0, [...base], 'none')\n for (const k of sorted) {\n push(\n Math.min(Math.max(0, k.at), dur),\n k.value,\n spanEase(k.ease, MOTION_EASE),\n )\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/** An overlay clip's base vector: [x, y, scale, rotation, opacityMul]. */\nexport function overlayMotionBase(o: OverlayClip): number[] {\n return [\n o.transform.x,\n o.transform.y,\n o.transform.scale || 1,\n o.transform.rotation || 0,\n 1,\n ]\n}\n\nfunction overlayMotionKeys(o: OverlayClip, base: readonly number[]) {\n return (o.motion ?? []).map((p) => ({\n at: p.at,\n ease: p.ease,\n value: [\n p.x ?? base[0],\n p.y ?? base[1],\n p.scale ?? base[2],\n p.rotation ?? base[3],\n p.opacity ?? base[4],\n ],\n }))\n}\n\n/**\n * Effective [x, y, scale, rotation, opacityMul] of an overlay clip at\n * CLIP-LOCAL time t — the host-side mirror of ON_FRAME's sampling (the\n * picking layer substitutes it into the clip's transform so hit rects track\n * the animated element). Null = the clip has no motion.\n */\nexport function overlayMotionPoseAt(\n o: OverlayClip,\n t: number,\n): number[] | null {\n if (!o.motion || !o.motion.length) return null\n const base = overlayMotionBase(o)\n const track = motionTrack(\n base,\n overlayMotionKeys(o, base),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n if (!track.keyframes.length) return null\n return [...sample(track, t, lerpArray)]\n}\n\n/** An object clip's base vector: [x, y, z, rx, ry, rz, scale]. */\nexport function objectMotionBase(o: ObjectClip): number[] {\n const t = o.transform3d\n return [t.x, t.y, t.z, t.rx, t.ry, t.rz, t.scale || OBJECT_DEFAULT_SCALE]\n}\n\nfunction objectMotionKeys(o: ObjectClip, base: readonly number[]) {\n return (o.motion ?? []).map((p) => ({\n at: p.at,\n ease: p.ease,\n value: [\n p.x ?? base[0],\n p.y ?? base[1],\n p.z ?? base[2],\n p.rx ?? base[3],\n p.ry ?? base[4],\n p.rz ?? base[5],\n p.scale ?? base[6],\n ],\n }))\n}\n\n/**\n * Effective [x, y, z, rx, ry, rz, scale] of an object clip at CLIP-LOCAL\n * time t (from the span start; 0 when span-less over `clipDur`). Null = the\n * clip has no motion. The 3D mirror of overlayMotionPoseAt.\n */\nexport function objectMotionPoseAt(\n o: ObjectClip,\n t: number,\n clipDur: number,\n): number[] | null {\n if (!o.motion || !o.motion.length) return null\n const base = objectMotionBase(o)\n const track = motionTrack(\n base,\n objectMotionKeys(o, base),\n o.span?.duration ?? clipDur,\n )\n if (!track.keyframes.length) return null\n return [...sample(track, t, lerpArray)]\n}\n\n// The paused-frame steps: ONE source, inlined into SETUP (where the frame-prep\n// hook runs it) and into ON_FRAME (where syncVid runs it), so a capture\n// harness and the paint can never disagree on a target. `stepPaused` asks a\n// WebCodecs provider (capture pages) by PTS at decode speed, else seeks the\n// element and puts its 'seeked' promise (250ms fallback) on pendingDecodes for\n// the export loop to await. `stepBg` is the background loop's seek, COALESCED:\n// a scrub moves t every frame and re-assigning currentTime ABORTS the in-flight\n// seek — on a remote (assets.vos.so) source that keeps the element mid-seek for\n// the whole drag, so no frame ever decodes and the background pops in seconds\n// late — so a seek is issued only when none is in flight; the frame after\n// 'seeked' fires corrects toward the latest target, so seeks run serially and\n// converge on the release point. Both read `ns` from the enclosing scope.\nexport const FRAME_STEP_SRC = `\n function stepPaused(vid, srcT) {\n if (!vid.paused) vid.pause()\n // A WebCodecs provider (capture pages) services the frame by PTS at\n // decode speed — no element seek, no 250ms settle cap. The element stays\n // paused as the dimension source and fallback.\n var wcp = vid.__voilaWc\n if (wcp) {\n var wcT = Math.min(srcT, wcp.duration || srcT)\n if (wcp.req !== wcT) {\n var wdp = wcp.seek(wcT)\n if (ns.pendingDecodes) {\n ns.pendingDecodes.add(wdp)\n wdp.finally(function () { ns.pendingDecodes.delete(wdp) })\n }\n }\n return\n }\n var target = Math.min(srcT, vid.duration || srcT)\n if (vid.readyState >= 1 && Math.abs(vid.currentTime - target) > 0.02) {\n if (ns.pendingDecodes) {\n var dp = new Promise(function (resolve) {\n var done = function () { vid.removeEventListener('seeked', done); resolve() }\n vid.addEventListener('seeked', done)\n setTimeout(done, 250) // fallback so a missed 'seeked' can't hang the export\n })\n ns.pendingDecodes.add(dp)\n dp.finally(function () { ns.pendingDecodes.delete(dp) })\n }\n vid.currentTime = target\n }\n }\n function stepBg(bgEl, bgT) {\n if (!bgEl.paused) bgEl.pause()\n var bgTarget = Math.min(bgT, bgEl.duration || bgT)\n if (bgEl.readyState >= 1 && !bgEl.seeking && Math.abs(bgEl.currentTime - bgTarget) > 0.02) {\n if (ns.pendingDecodes) {\n var bgDp = new Promise(function (resolve) {\n var bgDone = function () { bgEl.removeEventListener('seeked', bgDone); resolve() }\n bgEl.addEventListener('seeked', bgDone)\n setTimeout(bgDone, 250) // fallback so a missed 'seeked' can't hang the export\n })\n ns.pendingDecodes.add(bgDp)\n bgDp.finally(function () { ns.pendingDecodes.delete(bgDp) })\n }\n bgEl.currentTime = bgTarget\n }\n }\n // A GHOST element: a second element on the same bytes, held on ONE frame\n // (a transition's outgoing snapshot), so one recording can be both cards\n // at a boundary. Cached by source. Its readiness rides pendingDecodes,\n // so an export's first paint inside a window waits for it, and a frame\n // asked before it could seek is sought the moment it can.\n function ghostEl(el) {\n var gc = ns.ghostCache || (ns.ghostCache = new Map())\n var src = el.currentSrc || el.src\n var g = gc.get(src)\n if (g) return g\n g = document.createElement('video')\n g.crossOrigin = 'anonymous'\n g.muted = true\n g.playsInline = true\n g.preload = 'auto'\n var gp = new Promise(function (resolve) {\n var fired = false\n var done = function () {\n if (fired) return\n fired = true\n g.removeEventListener('loadeddata', done)\n if (g.__voilaWant != null && g.readyState >= 1) {\n var want = g.__voilaWant\n g.__voilaWant = null\n var seeked = function () { g.removeEventListener('seeked', seeked); resolve() }\n g.addEventListener('seeked', seeked)\n setTimeout(seeked, 250)\n g.currentTime = Math.min(want, g.duration || want)\n } else resolve()\n }\n g.addEventListener('loadeddata', done)\n setTimeout(done, 2000)\n })\n if (ns.pendingDecodes) {\n ns.pendingDecodes.add(gp)\n gp.finally(function () { ns.pendingDecodes.delete(gp) })\n }\n g.src = src\n g.load()\n gc.set(src, g)\n return g\n }\n function stepGhost(g, at) {\n if (g.readyState >= 1) { g.__voilaWant = null; stepPaused(g, at); return }\n g.__voilaWant = at\n }`\n\n// Load the recording as an HTMLVideoElement (any container/codec the browser plays).\n// Warm-swap asset reuse: cache the decoded <video> by src on window.__vos__ so a\n// program swap reuses the already-decoded element instead of reloading it — no\n// flash. The cached element deliberately survives cleanup (it is not appended to the\n// scene/DOM and content has no dispose), so it persists across warm LOADs.\n//\n// The @vosjs/timeline runtime IIFE is inlined first: it defines\n// globalThis.__vosTimeline (sample/mapTime/easings) with EXACTLY the code the host\n// evaluates, so keyframes/segments in ctx.data render identically on both sides.\nconst SETUP = `async (ctx) => {\n ;${timelineRuntimeCode}\n const ns = (window.__vos__ = window.__vos__ || {})\n // Paused/decode machinery (the engine's video-renderer contract). The studio has no element\n // renderers, so set it up here: the player bridge toggles isPaused on play/pause, and the\n // deterministic export loop awaits pendingDecodes via waitForVideosReady before capturing.\n if (ns.isPaused === undefined) ns.isPaused = true\n if (!ns.setGlobalPaused) ns.setGlobalPaused = (p) => { ns.isPaused = p }\n ns.pendingDecodes = ns.pendingDecodes || new Set()\n if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {\n if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])\n }\n ${FRAME_STEP_SRC}\n const cache = ns.videoCache || (ns.videoCache = new Map())\n // Server capture pages opt into BLOB-backed elements (data.videoFetchMode,\n // merged in by the render queue — never stored in a doc or config): a\n // detached, paused, network-backed video gets SUSPENDED by Chrome within\n // seconds (readyState drops to 0) and every later seek pays a ranged\n // re-fetch — the background-media rationale, applied to the\n // recording an export chunk seeks a hundred-plus times. Size-capped (a\n // page has a memory budget) and FAIL-OPEN: any fetch trouble degrades to\n // the plain network element, never a dead LOAD.\n const BLOB_FETCH_MAX = 400 * 1024 * 1024\n const toBlobUrl = async (src) => {\n const resp = await fetch(src)\n if (!resp.ok) throw new Error('[voila] blob fetch HTTP ' + resp.status)\n const len = Number(resp.headers.get('content-length') || 0)\n if (len > BLOB_FETCH_MAX) {\n try { if (resp.body) await resp.body.cancel() } catch (e) { void e }\n throw new Error('[voila] blob fetch over size cap')\n }\n const fetched = await resp.blob()\n // Keep the Blob itself: the WebCodecs provider demuxes the SAME\n // bytes (BlobSource) instead of paying a second network read.\n ;(ns.videoBlobs || (ns.videoBlobs = new Map())).set(src, fetched)\n return URL.createObjectURL(fetched)\n }\n const load = async (src, muted) => {\n let v = cache.get(src)\n if (v) return v\n let url = src\n if (ctx.data.videoFetchMode === 'blob') {\n try { url = await toBlobUrl(src) }\n catch (e) { console.warn('[voila] blob fetch failed, using network src', e) }\n }\n v = document.createElement('video')\n v.src = url\n v.crossOrigin = 'anonymous'\n v.muted = muted\n v.playsInline = true\n v.preload = 'auto'\n await new Promise((res, rej) => {\n v.oncanplay = () => res()\n // The MediaError rides along: code 4 is an unreadable/unsupported source\n // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network\n // stall. A bare \"failed to load\" gave the fleet log nothing to act on.\n v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))\n v.load()\n })\n cache.set(src, v)\n return v\n }\n // Shots: the \"video\" is a still image — drawImage accepts it directly and the\n // whole compositor (frame, browser bar, zoom) applies unchanged.\n const loadImage = (src) => {\n const hit = cache.get(src)\n if (hit) return Promise.resolve(hit)\n return new Promise((res, rej) => {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.onload = () => { cache.set(src, img); res(img) }\n img.onerror = () => rej(new Error('[voila] image failed to load'))\n img.src = src\n })\n }\n // Capture pages only: a WebCodecs sequential frame provider for the\n // screen recording. Element seeks cost up to 250ms/frame under the settle\n // cap and currentTime is audio-clock-backed (not frame-accurate by spec);\n // sink-driven sequential decode delivers frames at decode speed and BY\n // PTS. The pull queue feeds canvasesAtTimestamps so the capture walk's\n // monotonic timestamps keep mediabunny's decode-each-packet-once fast\n // path. FAIL-OPEN at every step: a null provider leaves the element path\n // exactly as it was; preview/scrub never sets the flag.\n const makeWcProvider = async (src) => {\n if (!window.VideoDecoder) return null\n const MB = await import('https://esm.sh/mediabunny@1.55.7?target=es2022')\n const wcBlob = ns.videoBlobs && ns.videoBlobs.get(src)\n const input = new MB.Input({\n formats: MB.ALL_FORMATS,\n source: wcBlob ? new MB.BlobSource(wcBlob) : new MB.UrlSource(src),\n })\n try {\n const track = await input.getPrimaryVideoTrack()\n if (!track || !(await track.canDecode())) { input.dispose(); return null }\n // VideoSampleSink, NOT CanvasSink — CanvasSink converts EVERY\n // decoded source frame to a canvas, and that per-source-frame paint\n // lost 41% on the SwiftShader fleet (job 4f1e99ab), multiplied by\n // rate-N spans (N source frames decoded per output frame). Samples\n // skipped in rated spans now CLOSE unconverted; only the DISPLAYED\n // frame draws, straight into the card via sample.draw (crop-capable).\n //\n // Sequential walk: drive samples() (pre-decoding, each packet decoded\n // once) and advance to the frame CONTAINING each requested timestamp.\n // NOT samplesAtTimestamps — its pipeline prefetches the timestamp\n // iterable ahead of yielding frames, which deadlocks a demand-driven\n // feed. The iterator starts AT THE FIRST REQUESTED TIMESTAMP and\n // RE-SEEKS on any jump beyond WC_JUMP — samples(t) begins at the\n // preceding keyframe, which is chunk cold-seek semantics; starting at\n // 0 and grinding forward decoded the whole source prefix before a\n // mid-timeline chunk's first frame (job 639dd24c: startup grew\n // linearly with chunk index).\n const sink = new MB.VideoSampleSink(track)\n const WC_JUMP = 3\n let iter = null\n let cur = null\n const wcClose = (s) => { if (s) { try { s.close() } catch (e) { void e } } }\n const advanceTo = async (t) => {\n if (\n !iter ||\n (cur && t < cur.timestamp) ||\n (cur && t > cur.timestamp + cur.duration + WC_JUMP)\n ) {\n // Dispose the old walk so its in-flight pre-decoded samples close\n // (an abandoned iterator leaks VideoSamples — pool stall risk).\n if (iter && iter.return) { try { void iter.return() } catch (e) { void e } }\n wcClose(cur)\n iter = sink.samples(t)\n cur = null\n }\n for (;;) {\n if (cur && cur.timestamp + cur.duration > t) return\n const nx = await iter.next()\n if (nx.done || !nx.value) return\n wcClose(cur) // skipped (rated spans) or superseded — never converted\n cur = nx.value\n }\n }\n let chain = Promise.resolve()\n const provider = {\n req: -1,\n width: track.displayWidth,\n height: track.displayHeight,\n duration: await input.computeDuration(),\n seek(t) {\n provider.req = t\n chain = chain\n .then(() => advanceTo(t))\n .catch((e) => { console.warn('[voila] webcodecs frame failed', e) })\n return chain\n },\n // Draw the CURRENT frame into the card. Returns false (element path\n // draws instead) until the first seek resolves or after any failure.\n draw(c2, crp2, dx2, dy2, dw2, dh2) {\n if (!cur) return false\n if (crp2) cur.draw(c2, crp2.x, crp2.y, crp2.w, crp2.h, dx2, dy2, dw2, dh2)\n else cur.draw(c2, dx2, dy2, dw2, dh2)\n return true\n },\n }\n return provider\n } catch (e) {\n input.dispose()\n throw e\n }\n }\n // Mic recordings carry audio → unmute the screen video so it plays back during native\n // playback. (Export pulls audio from the source blob separately; the seek path is silent.)\n const video = ctx.data.isImage\n ? await loadImage(ctx.data.videoSrc)\n : await load(ctx.data.videoSrc, !ctx.data.hasAudio)\n if (!ctx.data.isImage && ctx.data.videoDecodeMode === 'webcodecs') {\n try {\n const wcProvider = await makeWcProvider(ctx.data.videoSrc)\n // The provider's canvases must be drop-in for the element at the draw\n // sites (crop rects are capture-pixel space) — dimension mismatch\n // (rotation metadata, anamorphic) keeps the element path.\n if (\n wcProvider &&\n (!video.videoWidth ||\n (wcProvider.width === video.videoWidth &&\n wcProvider.height === video.videoHeight))\n ) {\n video.__voilaWc = wcProvider\n } else if (wcProvider) {\n console.warn('[voila] webcodecs dims mismatch — element path keeps the frame')\n }\n } catch (e) {\n console.warn('[voila] webcodecs provider unavailable, seeks stay html5', e)\n }\n }\n // The webcam is a separate recording (video-only) drawn as an editable bubble overlay.\n const cam = ctx.data.camSrc ? await load(ctx.data.camSrc, true) : null\n // The mic is a separate AUDIO sidecar (AT split): an off-DOM element driven by\n // the same sync as the screen video (source-anchored, so element time == the\n // video's). Unmuted — syncVid's autoplay net re-mutes on policy rejection.\n const mic = ctx.data.micSrc ? await load(ctx.data.micSrc, false) : null\n // The take's OTHER media (concat): one element each, decoded the way the\n // primary is; ON_FRAME drives the one under the playhead and pauses the\n // rest. Absent = one media, byte-identical.\n const mediaEls = {}\n for (const m of ctx.data.media || []) {\n try {\n const el = m.isImage ? await loadImage(m.src) : await load(m.src, !m.hasAudio)\n if (!m.isImage && ctx.data.videoDecodeMode === 'webcodecs') {\n try {\n const wp = await makeWcProvider(m.src)\n if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__voilaWc = wp\n } catch (e) {\n console.warn('[voila] webcodecs provider unavailable for a media, seeks stay html5', e)\n }\n }\n mediaEls[m.id] = el\n } catch (e) {\n console.warn('[voila] a media failed to load', m.id, e)\n }\n }\n // Background media (frame.backgroundMedia): warm-load so the first paint is\n // complete. FAIL-OPEN — a bad key degrades to the CSS fill underneath, never\n // a dead LOAD (unlike the recording, which is the comp's reason to exist).\n const bgm = ctx.data.frame && ctx.data.frame.backgroundMedia\n if (bgm && bgm.key) {\n try {\n if (bgm.kind === 'image') await loadImage(bgm.key)\n else (await load(bgm.key, true)).loop = true\n } catch (e) { console.warn('[voila] background media failed to load', e) }\n }\n // Frame prep: a capture harness calls every ns.framePrep hook right after\n // seeking the timeline and before the first paint, then awaits\n // waitForVideosReady. The footage for the frame is requested HERE, so the\n // paint that follows draws the right frame the first time. Without the\n // hook the request could only be issued from inside ON_FRAME (the only\n // place that knew the source time), so every take frame painted twice:\n // once with the previous footage, then again after the decode. The steps\n // are the SAME functions ON_FRAME's syncVid runs (one source, inlined in\n // both scopes), so the paint finds its target already met and registers\n // nothing. Keyed by program so a warm LOAD re-registers instead of\n // chaining; playback never calls it; a harness without it keeps the\n // two-phase loop and stays correct.\n ns.framePrep = ns.framePrep || new Map()\n ns.framePrep.set('voila.card', (t) => {\n if (ns.isPaused === false) return\n const d = ctx.data\n const TL = globalThis.__vosTimeline\n const srcT = TL.mapTime(d.segments || [], t)\n // The media under the playhead (concat): step its element; the\n // primary's sidecars belong to the primary's moments only.\n let am = ''\n {\n let acc = 0\n const sg = d.segments || []\n for (let i = 0; i < sg.length; i++) {\n const s0 = sg[i]\n const ln = Math.max(0, s0.out - s0.in) / (s0.rate || 1)\n if (t < acc + ln || i === sg.length - 1) { am = s0.media || ''; break }\n acc += ln\n }\n }\n const amEl = am ? mediaEls[am] : null\n if (amEl) {\n if (amEl.play) stepPaused(amEl, srcT)\n } else {\n if (video.play) stepPaused(video, srcT)\n if (cam) stepPaused(cam, srcT)\n if (mic) stepPaused(mic, srcT)\n }\n // A boundary's outgoing snapshot: the ghost element on the clip's last\n // frame, stepped like the footage, and warmed a second ahead.\n const trs = d.transitions || []\n for (let i = 0; i < trs.length; i++) {\n const tr = trs[i]\n if (!tr.out || t < tr.t - 1 || t >= tr.t + tr.d) continue\n const live = tr.out.media ? mediaEls[tr.out.media] : video\n if (live && live.play) stepGhost(ghostEl(live), tr.out.at)\n }\n const bg2 = d.frame && d.frame.backgroundMedia\n if (bg2 && bg2.key && bg2.kind !== 'image') {\n const bgEl2 = cache.get(bg2.key)\n if (bgEl2 && bgEl2.play) {\n const bgDur2 = bg2.duration || bgEl2.duration || 0\n stepBg(bgEl2, bgDur2 > 0 ? t % bgDur2 : 0)\n }\n }\n })\n return { video: video, cam: cam, mic: mic, media: mediaEls }\n}`\n\n// Compositor v2 — a three-layer mesh stack under ONE perspective camera.\n// Each layer is a plane\n// perpendicular to the camera axis, centered on it, sized to exactly FILL the\n// frustum at its depth (stage.ts planeSizeAtDepth), so it projects to the whole\n// viewport regardless of depth:\n//\n// overlay quad (z ${OVERLAY_Z}) screen-space cam bubble + overlays\n// card mesh (z ${CARD_Z}) world-space the card painting — TILTS\n// background (z ${BACKGROUND_Z}) screen-space CSS fill + vos loop\n//\n// Painter's order is renderOrder (0/1/2) with depthTest off, so depth is free —\n// the card's tilt never z-fights. At tilt = 0 the card plane fills the viewport\n// exactly like the pre-v2 ortho quad ⇒ pixel-identical. LinearFilter/no-mipmaps\n// matches the old quad (1:1 texel↔pixel at tilt 0); mipmaps + anisotropy switch\n// on only when the card tilts (ON_FRAME), where minification would shimmer.\nconst CREATE_CONTENT = `(ctx, setupData) => {\n const THREE = ctx.THREE\n const gl = ctx.renderer && ctx.renderer.domElement\n const res = ctx.resolution\n const W0 = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || 1280))\n const H0 = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || 720))\n const aspect = W0 / H0\n // Own the perspective camera's aspect (ON_FRAME keeps it in sync on resize),\n // so the frustum-filling planes never distort regardless of what the engine\n // initialised. Camera sits at the origin looking down −z; the planes are in\n // front at negative z.\n const cam = ctx.camera\n if (cam && cam.isPerspectiveCamera) { cam.aspect = aspect; cam.updateProjectionMatrix() }\n const planeH = (z) => 2 * Math.abs(z) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n const makeLayer = (z, order) => {\n const canvas = document.createElement('canvas')\n canvas.width = W0\n canvas.height = H0\n const c2d = canvas.getContext('2d')\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.generateMipmaps = false\n const h = planeH(z)\n const mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(h * aspect, h),\n // transparent so each layer alpha-composites over the one behind (the card\n // padding, the overlay's blank area). depthTest off + renderOrder = the\n // painter's order that keeps a tilted card a single coherent layer.\n new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })\n )\n mesh.position.set(0, 0, z)\n mesh.frustumCulled = false\n mesh.renderOrder = order\n ctx.scene.add(mesh)\n return { canvas: canvas, c2d: c2d, texture: texture, mesh: mesh }\n }\n const bg = makeLayer(${BACKGROUND_Z}, 0)\n const card = makeLayer(${CARD_Z}, 1)\n const ov = makeLayer(${OVERLAY_Z}, 2)\n return {\n objects: [bg.mesh, card.mesh, ov.mesh],\n refs: {\n bg: bg, card: card, ov: ov,\n // flat aliases = the card layer, so stub-context tests (which build only\n // { c2d, canvas, texture }) drive the card and let bg/overlay fall back.\n canvas: card.canvas, c2d: card.c2d, texture: card.texture,\n video: setupData.video, cam: setupData.cam, mic: setupData.mic,\n media: setupData.media,\n },\n }\n}`\n\n// Pure duration carrier (the interpreter pattern): the timeline exists only to\n// define duration and drive ctx.time — per-frame state derives from\n// ctx.time + ctx.data in onFrame. Duration comes from ctx.data so trims are\n// data edits; vosCarrier opts into the engine's setDuration (T2.5) capability.\nconst CREATE_TIMELINE = `(ctx, content, duration) => {\n const tl = ctx.gsap.timeline()\n tl.to({}, { duration: (ctx.data && ctx.data.duration) || duration || 1, ease: 'none' })\n tl.data = { vosCarrier: true }\n return tl\n}`\n\n// The compositor. Deterministic: a pure function of ctx.time + ctx.data.\nconst ON_FRAME = `(ctx, content, dt) => {\n var r = content.refs\n // Three layers (compositor v2). Stub-context tests build only the flat\n // { c2d, canvas, texture } — those fall back to the card layer, so bg/overlay\n // draw to the SAME c2d and the merged call log stays in draw order.\n var card = r.card || r, bg = r.bg || r, ov = r.ov || r\n var c = card.c2d, cv = card.canvas, video = r.video\n if (!c || !video) return\n var bgC = bg.c2d || c, ovC = ov.c2d || c\n var res = ctx.resolution\n // Track the LIVE renderer canvas size (resize-aware); ctx.resolution is stale.\n var gl = ctx.renderer && ctx.renderer.domElement\n var W = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || cv.width))\n var H = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || cv.height))\n // Resizing a backing canvas requires disposing its CanvasTexture (THREE keeps\n // the GPU texture allocated at the original dims and re-uploads the new canvas\n // against stale dims → stretch/duplicate; dispose() forces a full realloc) and\n // rebuilding each frustum-filling plane at the new aspect + syncing the camera.\n // Card OVERSCAN: the card's pose (the tilt track, the card-pose track)\n // lets the frame see MORE plane than the frame is wide on a receding\n // side, and the card's content (zoomed footage, a bled card, the shadow)\n // runs past the frame, so a plane that ends at the frame edge shows its\n // texture's edge, and the backdrop through it, as soon as it leans. The\n // card layer's canvas and plane therefore grow about the frame's centre\n // by the extent the two tracks can reach at the LIVE aspect: each frame\n // corner's viewing ray meets the posed plane and reads back in plane\n // units; the extremes of both tracks (largest lean per axis, smallest\n // scale, largest rise, every sign) bound every interpolated frame. This\n // is the twin of stage.ts cardVisibleExtent/cardOverscanFor (pinned by\n // stage.test.ts + cardTilt.test.ts; change them together). A card that\n // never moves, and the flat stub (no card layer), stay at 1: byte- and\n // pixel-identical. The factor is quantised up in steps so a scrubbed\n // pose grows the canvas a few times, not per tick.\n var d = ctx.data || {}\n var okx = 1, oky = 1\n if (r.card) {\n var okT = d.tiltTrack, okP = d.cardPoseTrack, okKf, oki, okv\n var okRx = 0, okRy = 0, okSc = 1, okDy = 0\n okKf = okT && okT.keyframes || []\n for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okRx = Math.max(okRx, Math.abs(okv[0] || 0)); okRy = Math.max(okRy, Math.abs(okv[1] || 0)) }\n okKf = okP && okP.keyframes || []\n for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okSc = Math.min(okSc, okv[0] == null ? 1 : okv[0]); okDy = Math.max(okDy, Math.abs(okv[1] || 0)) }\n if (okRx || okRy || okSc < 1 || okDy) {\n var okD = ${Math.abs(CARD_Z)}, okMax = ${CARD_OVERSCAN_MAX}, okStep = ${CARD_OVERSCAN_STEP}\n var okHh = okD * Math.tan(${CARD_FOV} * Math.PI / 180 / 2), okHw = okHh * W / H\n // [max |x|, max |y|] of the frame corners on the posed plane, in\n // half-plane units (R = Rx · Ry, three.js Euler XYZ with rz = 0).\n var okE = function (rxd, ryd, sc, dy) {\n var rx = rxd * Math.PI / 180, ry = ryd * Math.PI / 180\n var cx = Math.cos(rx), sx = Math.sin(rx), cy = Math.cos(ry), sy = Math.sin(ry)\n var scl = Math.max(0.05, sc), py = dy * 2 * okHh\n var n0 = sy, n1 = -sx * cy, n2 = cx * cy\n var nPos = n1 * py - n2 * okD\n var ex = 0, ey = 0\n for (var kx = -1; kx <= 1; kx += 2) for (var ky = -1; ky <= 1; ky += 2) {\n var d0 = kx * okHw, d1 = ky * okHh, d2 = -okD\n var nd = n0 * d0 + n1 * d1 + n2 * d2\n var lam = nd === 0 ? -1 : nPos / nd\n if (!(lam > 0)) { ex = okMax; ey = okMax; continue }\n var q0 = lam * d0, q1 = lam * d1 - py, q2 = lam * d2 + okD\n ex = Math.max(ex, Math.abs((cy * q0 + sx * sy * q1 - cx * sy * q2) / scl / okHw))\n ey = Math.max(ey, Math.abs((cx * q1 + sx * q2) / scl / okHh))\n }\n return [Math.min(okMax, ex), Math.min(okMax, ey)]\n }\n for (var okA = -1; okA <= 1; okA += 2) for (var okB = -1; okB <= 1; okB += 2) for (var okC = -1; okC <= 1; okC += 2) {\n var okR = okE(okA * okRx, okB * okRy, okSc, okC * okDy)\n okx = Math.max(okx, okR[0]); oky = Math.max(oky, okR[1])\n }\n var okQ = function (k) { if (!(k > 1)) return 1; var q = Math.ceil(Math.round((k + okStep / 10) * 1000) / (okStep * 1000)) * okStep; return Math.min(okMax, Math.round(q * 1000) / 1000) }\n okx = okQ(okx); oky = okQ(oky)\n }\n }\n // The canvas grows by an EVEN number of pixels on each axis so the frame\n // region sits on whole pixels inside it (a half-pixel base translation\n // would resample every draw).\n var cW = W + 2 * Math.round(W * (okx - 1) / 2), cH = H + 2 * Math.round(H * (oky - 1) / 2)\n if (cv.width !== cW || cv.height !== cH) {\n var THREE = ctx.THREE\n var aspect = W / H\n var cm = ctx.camera\n if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }\n // Each layer's canvas size and its plane's per-axis growth; the card's\n // plane grows by exactly its canvas's ratio, so a texel stays a pixel.\n var lyr = [[bg, ${BACKGROUND_Z}, W, H], [card, ${CARD_Z}, cW, cH], [ov, ${OVERLAY_Z}, W, H]]\n for (var Li = 0; Li < lyr.length; Li++) {\n var Ly = lyr[Li][0], Lz = lyr[Li][1], Lw = lyr[Li][2], Lhp = lyr[Li][3]\n if (Ly.canvas) { Ly.canvas.width = Lw; Ly.canvas.height = Lhp }\n if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()\n if (Ly.mesh && THREE) {\n if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()\n var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect * (Lw / W), Lh * (Lhp / H))\n }\n }\n }\n var frame = d.frame || {}\n var TL = globalThis.__vosTimeline\n // Output-timeline seconds (engine-fed master clock) → source seconds on screen.\n var t = ctx.time || 0\n var srcT = TL.mapTime(d.segments || [], t)\n // The media under the playhead (concat): its element, crop, cursor and\n // space stand in for the primary's below, and every other media's\n // element rests. An absent media list is the primary alone.\n var amId = ''\n { var amAcc = 0, amSg = d.segments || []\n for (var amI = 0; amI < amSg.length; amI++) {\n var amS = amSg[amI], amLn = Math.max(0, amS.out - amS.in) / (amS.rate || 1)\n if (t < amAcc + amLn || amI === amSg.length - 1) { amId = amS.media || ''; break }\n amAcc += amLn\n } }\n var actM = null\n if (amId && d.media) for (var amJ = 0; amJ < d.media.length; amJ++) if (d.media[amJ].id === amId) actM = d.media[amJ]\n if (actM && r.media && r.media[amId]) video = r.media[amId]\n else amId = ''\n // The card under the playhead: another media's own card fields over the\n // take's frame (resolved at lowering), the take's frame for the primary.\n var cfr = amId && actM && actM.frame ? actM.frame : frame\n if (r.media) for (var amK in r.media) { var amE = r.media[amK]; if (amK !== amId && amE && amE.pause && amE.paused === false) amE.pause() }\n if (amId) {\n if (r.video && r.video.pause && r.video.paused === false) r.video.pause()\n if (r.cam && r.cam.pause && r.cam.paused === false) r.cam.pause()\n if (r.mic && r.mic.pause && r.mic.paused === false) r.mic.pause()\n }\n var s = H / 1080 // scale design-px controls to comp px\n\n // Play natively while playing (smooth); seek precisely otherwise (paused, scrubbing,\n // export). isPaused is the source of truth (bridge toggles it; export forces it true);\n // playing => let the video advance, else step to the exact frame. During a seek we\n // register a decode promise so the deterministic export loop can await the exact frame.\n var ns = window.__vos__ || {}\n var playing = ns.isPaused === false\n // Speed spans: the rate of the segment under the playhead. Natural playback\n // mirrors the remap with playbackRate (clamped to the browser's supported\n // range); the paused/seek path needs nothing — mapTime already lands srcT.\n var srcRate = TL.rateAt ? TL.rateAt(d.segments || [], t) : 1\n var playRate = Math.min(16, Math.max(0.0625, srcRate))\n // Where the FOOTAGE ends in output seconds. The output may outlast it\n // (clips placed after the footage), and a hold is a freeze piece whose\n // rate is tiny: in both the element must stay PAUSED on its frame. An\n // ended element's play() rewinds it to zero and the drift guard then\n // seeks it back, which reads as frames jumping back and forth; a hold\n // crawling at the clamped 1/16 speed toward that same end was the same\n // bug in slow motion.\n var footEnd = TL.totalDuration ? TL.totalDuration(d.segments || []) : Infinity\n var frozen = srcRate < 0.01 || t >= footEnd - 1e-3\n // Drive a <video> to the on-screen SOURCE moment: play natively while playing (drift\n // correction covers cut-boundary jumps), else step to the exact frame (registering a\n // decode promise so the deterministic export awaits it). Shared by the screen video\n // and the webcam so both stay frame-accurate and in sync.\n ${FRAME_STEP_SRC}\n function syncVid(vid) {\n try {\n if (playing && !frozen) {\n if (vid.playbackRate !== playRate) {\n vid.playbackRate = playRate\n // Resampled (tape-style) speed, matching the export's offline mix.\n if (vid.preservesPitch !== false) vid.preservesPitch = false\n // Rate switches land LATE on the media clock — worst with an audio\n // track, where the element resyncs on the audio clock (~300ms of\n // source error at 4×, measured) — so a span's END plays the wrong\n // content moment. Resync position at the switch when it has already\n // drifted (an unconditional seek costs more than it fixes when the\n // media clock is tight, e.g. muted video-only playback).\n if (Math.abs(vid.currentTime - srcT) > 0.06) vid.currentTime = srcT\n } else if (Math.abs(vid.currentTime - srcT) > (srcRate !== 1 ? 0.12 : 0.3)) {\n // Tighter leash inside rated spans: at N× a given source drift is N×\n // more visible in content terms; a rare micro-seek there is not.\n vid.currentTime = srcT\n }\n if (vid.paused) {\n var p = vid.play()\n if (p && p.catch) p.catch(function (err) {\n // Autoplay policy: an UNMUTED play() without user activation\n // rejects (the studio tab opens programmatically, so there is no\n // gesture yet). Without this fallback the element never plays and\n // \"playback\" degrades to drift-correction seeks — a silent ~3fps\n // slideshow until the user's first scrub. Muted playback is always\n // allowed: play muted now, unmute below once the host reports a\n // user gesture (d.audioUnlocked; the player iframe carries\n // allow=\"autoplay\" so the top frame's activation counts here).\n if (!vid.muted && err && err.name === 'NotAllowedError') {\n vid.muted = true\n vid.__voilaAutoMuted = true\n var p2 = vid.play()\n if (p2 && p2.catch) p2.catch(function () {})\n }\n })\n } else if (vid.__voilaAutoMuted && d.audioUnlocked) {\n // First user gesture happened — lift the policy fallback. If the\n // browser still objects it pauses the element, and the paused branch\n // above self-heals (re-mute + resume) on the next frame.\n vid.muted = false\n vid.__voilaAutoMuted = false\n }\n } else {\n // The paused step (FRAME_STEP_SRC): the provider by PTS, else the\n // element seek. A capture harness that ran the frame-prep hook has\n // already met this target, so nothing registers and the paint is\n // the one that counts.\n stepPaused(vid, srcT)\n }\n } catch (e) {}\n }\n if (video.play) syncVid(video) // stills (HTMLImageElement) have nothing to sync\n if (r.cam && !amId) syncVid(r.cam)\n if (r.mic && !amId) syncVid(r.mic)\n\n // --- transitions at a boundary (data.transitions, output seconds): the\n // record whose window holds t, else the next one within a second (its\n // snapshot warms ahead, so playback never shows a blank ghost). Locals\n // are tr-prefixed (one var scope).\n var trs = d.transitions || [], trA = null, trW = null\n for (var trI = 0; trI < trs.length; trI++) {\n var trR = trs[trI]\n if (t >= trR.t && t < trR.t + trR.d) { trA = trR; break }\n if (trR.out && trR.t > t && trR.t <= t + 1) { trW = trR; break }\n }\n function trEase(p) { p = p < 0 ? 0 : p > 1 ? 1 : p; var q = 1 - p; return 1 - q * q * q }\n function trSide(side) { return side === 'left' ? [-1, 0] : side === 'right' ? [1, 0] : side === 'up' ? [0, 1] : [0, -1] }\n // A record's outgoing media: its live element, and the GHOST held on the\n // clip's last frame (a second element on the same bytes, so one\n // recording can be both cards: a page change inside a single take). A\n // still needs no ghost.\n function trLiveEl(o) { return o.media ? (r.media ? r.media[o.media] : null) : r.video }\n function trGhostEl(o) {\n var el = trLiveEl(o)\n if (!el) return null\n if (!el.play) return el\n var g = ghostEl(el)\n stepGhost(g, o.at)\n return g\n }\n if (trW) trGhostEl(trW.out)\n var trIn = trA && trA.in ? trA.in : null, trOut = trA && trA.out ? trA.out : null\n var trGEl = trOut ? trGhostEl(trOut) : null\n if (!trGEl) trOut = null\n // The live card's enter and the ghost's exit as plane-fraction offsets\n // (x in plane widths, y in plane heights; +y up), a scale and an opacity.\n var trOx = 0, trOy = 0, trSc = 1, trOp = 1\n if (trIn) {\n var trP = 1 - trEase((t - trA.t) / trIn.d)\n if (trIn.kind === 'slide') { var trV = trSide(trIn.side); trOx = trV[0] * trP * ${TRANSITION_TRAVEL}; trOy = trV[1] * trP * ${TRANSITION_TRAVEL} }\n else if (trIn.kind === 'fade') trOp = 1 - trP\n else { trSc = 1 - ${TRANSITION_SCALE_STEP} * trP; trOp = 1 - trP }\n }\n var trGox = 0, trGoy = 0, trGsc = 1, trGop = 1\n if (trOut) {\n var trQ = trEase((t - trA.t) / trOut.d)\n if (trOut.kind === 'slide') { var trV2 = trSide(trOut.side); trGox = trV2[0] * trQ * ${TRANSITION_TRAVEL}; trGoy = trV2[1] * trQ * ${TRANSITION_TRAVEL} }\n else if (trOut.kind === 'fade') trGop = 1 - trQ\n else { trGsc = 1 - ${TRANSITION_SCALE_STEP} * trQ; trGop = 1 - trQ }\n }\n var trGM = null\n if (trOut && trOut.media && d.media) for (var trJ = 0; trJ < d.media.length; trJ++) if (d.media[trJ].id === trOut.media) trGM = d.media[trJ]\n var trGFr = trOut && trOut.media && trGM && trGM.frame ? trGM.frame : frame\n // The ghost layer: made on first need (most documents never cross a\n // boundary), sized with the card layer, above it and below the stack's\n // card planes. The flat stub paints the ghost on the card's own canvas.\n var gh = null\n if (trOut) {\n if (!r.ghost && ctx.THREE && ctx.scene && card.mesh) {\n var gT = ctx.THREE\n var gCv = document.createElement('canvas')\n gCv.width = cW; gCv.height = cH\n var gTex = new gT.CanvasTexture(gCv)\n gTex.colorSpace = gT.SRGBColorSpace\n gTex.minFilter = gT.LinearFilter; gTex.magFilter = gT.LinearFilter; gTex.generateMipmaps = false\n var gPh = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var gMesh = new gT.Mesh(new gT.PlaneGeometry(gPh * (W / H) * (cW / W), gPh * (cH / H)), new gT.MeshBasicMaterial({ map: gTex, transparent: true, depthTest: false, depthWrite: false }))\n gMesh.position.set(0, 0, ${CARD_Z + GHOST_Z_LIFT})\n gMesh.frustumCulled = false\n gMesh.renderOrder = ${GHOST_RENDER_ORDER}\n ctx.scene.add(gMesh)\n r.ghost = { canvas: gCv, c2d: gCv.getContext('2d'), texture: gTex, mesh: gMesh, w: cW, h: cH }\n }\n gh = r.ghost || { c2d: c, canvas: cv }\n if (r.ghost && (r.ghost.w !== cW || r.ghost.h !== cH)) {\n r.ghost.canvas.width = cW; r.ghost.canvas.height = cH; r.ghost.w = cW; r.ghost.h = cH\n if (r.ghost.texture.dispose) r.ghost.texture.dispose()\n if (r.ghost.mesh.geometry && r.ghost.mesh.geometry.dispose) r.ghost.mesh.geometry.dispose()\n var gPh2 = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n r.ghost.mesh.geometry = new ctx.THREE.PlaneGeometry(gPh2 * (W / H) * (cW / W), gPh2 * (cH / H))\n }\n }\n // The two cursor points a boundary lerps between (frame space), set by\n // the passes below: the ghost's last, the live card's current.\n var trQa = null, trQb = null, trQs = 0, trQw = 0\n // Gain routing (live via SET_DATA). With a mic sidecar (AT split) the\n // recording <video> carries SYSTEM audio — its volume is the system fader —\n // and the sidecar element is the voice (micGain). Legacy takes have one\n // mixed track on the <video>, governed by micGain as before.\n if (video.play && video.volume !== undefined) {\n var vidG = r.mic\n ? (d.sysGain != null ? d.sysGain : 1)\n : (d.micGain != null ? d.micGain : 1)\n if (Math.abs(video.volume - vidG) > 0.001) video.volume = vidG\n }\n if (r.mic && r.mic.volume !== undefined) {\n var micG = d.micGain != null ? d.micGain : 1\n if (Math.abs(r.mic.volume - micG) > 0.001) r.mic.volume = micG\n }\n\n // background — the BACKGROUND layer (screen-space plane, never tilts). Painted\n // to bgC (its own canvas at runtime; the card c2d under stub tests). Redraw +\n // re-upload ONLY when the signature changed or the media layer can paint a\n // FRESH frame (a ready video advances every frame). A static gradient thus\n // uploads once, so the common case steady-states at the card texture ALONE —\n // SwiftShader-fleet perf, compositor v2 risk #1.\n var bgSigM = frame.backgroundMedia\n var bgSig = (frame.background || '') + '|' + (bgSigM && bgSigM.key ? bgSigM.kind + ':' + bgSigM.key + ':' + (bgSigM.dim || 0) + ':' + (bgSigM.blur || 0) + ':' + (frame.parallax || 0) : '') + '|' + W + 'x' + H\n\n // --- background media: a baked vos loop\n // or still, cover-fit over the CSS fill, under the card, OUTSIDE the zoom\n // transform. Video time is OUTPUT-anchored modulo the loop (bgT = t % dur) —\n // pure f(t), so chunk cold-seeks land correctly and trims/speed never retime\n // ambience. Locals bg-prefixed (one var scope). Lazy element acquisition\n // keeps background SWAPS live SET_DATA edits (no LOAD): the element is\n // created + cached on first sight, and until it can paint the CSS fill shows\n // through (fail-open, never black). Acquisition + time-sync run EVERY frame\n // (even when the repaint below is skipped) so scrub seeks land and playback\n // stays locked to the modulo clock.\n var bgm = frame.backgroundMedia\n var bgEl = null, bgIsImg = !!(bgm && bgm.kind === 'image'), bgReady = false\n if (bgm && bgm.key && ns.videoCache) {\n bgEl = ns.videoCache.get(bgm.key)\n if (!bgEl) {\n if (bgIsImg) {\n bgEl = new Image()\n bgEl.crossOrigin = 'anonymous'\n bgEl.src = bgm.key\n } else {\n bgEl = document.createElement('video')\n bgEl.crossOrigin = 'anonymous'\n bgEl.muted = true\n bgEl.playsInline = true\n bgEl.preload = 'auto'\n bgEl.loop = true\n if (bgm.key.indexOf('blob:') === 0 || bgm.key.indexOf('data:') === 0) {\n bgEl.src = bgm.key\n bgEl.load()\n } else {\n // URL-backed loops (assets.vos.so bakes, /api proxies, take-dir\n // keys): fetch to a BLOB first — the render-page pattern. A\n // detached, paused, network-backed video gets SUSPENDED by Chrome\n // within seconds (readyState drops to 0, media resources released),\n // and the next seek then needs a full network reload — that's the\n // \"official-vos background vanishes on scrub and pops in seconds\n // late\" bug. Blob-backed elements seek instantly and never suspend.\n // Fail-open to the direct URL if the fetch dies; baked loops are\n // ≤~200KB so the buffer cost is trivial.\n ;(function (el, url) {\n fetch(url).then(function (r) { return r.ok ? r.blob() : Promise.reject(new Error('' + r.status)) })\n .then(function (b) { el.src = URL.createObjectURL(b); el.load() })\n .catch(function () { el.src = url; el.load() })\n })(bgEl, bgm.key)\n }\n }\n // Cache immediately (readiness gates drawing): the export settle guards\n // scan videoCache, so a still-loading background is waited on, not raced.\n ns.videoCache.set(bgm.key, bgEl)\n }\n if (!bgIsImg && bgEl.play) {\n var bgDur = bgm.duration || bgEl.duration || 0\n var bgT = bgDur > 0 ? t % bgDur : 0\n try {\n if (playing) {\n if (bgEl.playbackRate !== 1) bgEl.playbackRate = 1\n // Free-run on the element's native loop; drift-correct against the\n // modulo clock. Near the wrap the raw delta spans ~bgDur — treat\n // wrap-adjacent as in sync so every loop boundary isn't a seek.\n var bgDrift = Math.abs(bgEl.currentTime - bgT)\n if (bgDur > 0 && !bgEl.seeking && bgDrift > 0.3 && bgDur - bgDrift > 0.3) bgEl.currentTime = bgT\n if (bgEl.paused) { var bgP = bgEl.play(); if (bgP && bgP.catch) bgP.catch(function () {}) }\n } else {\n // The background step (FRAME_STEP_SRC): a coalesced seek, met\n // already when a capture harness ran the frame-prep hook.\n stepBg(bgEl, bgT)\n }\n } catch (e) {}\n }\n bgReady = bgIsImg ? !!(bgEl.complete && bgEl.naturalWidth) : bgEl.readyState >= 2\n }\n\n // Repaint on signature change, or when the media can paint a FRESH frame.\n // A video's readyState drops below HAVE_CURRENT_DATA while a seek is in\n // flight, and a scrub issues a new seek every frame — repainting then would\n // flash the CSS fill through until the drag ends (the background-vanishes-\n // while-scrubbing bug). Skipping the repaint keeps the LAST uploaded frame,\n // matching the card video's retained frame mid-seek (and the cam bubble's\n // sticky-readiness fix below). A sig change still repaints immediately —\n // fail-open to the CSS fill until the new medium decodes.\n var bgDirty = bg.sig !== bgSig || bgReady\n if (bgDirty) {\n bgC.clearRect(0, 0, W, H)\n // A KNOWN ground first: assigning an unpaintable string to fillStyle is a\n // silent no-op in canvas, so the layer would keep whatever colour the last\n // draw happened to leave — a backdrop the document never asked for and\n // nothing on screen explains.\n bgC.fillStyle = '#0b0b0c'\n bgC.fillStyle = (function () {\n var bgcss = frame.background || '#0b0b0c'\n if (typeof bgcss === 'string' && bgcss.indexOf('linear-gradient') === 0) {\n var inner = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))\n var parts = inner.split(',').map(function (x) { return x.trim() })\n var ang = 135, cols = []\n for (var i = 0; i < parts.length; i++) {\n if (parts[i].indexOf('deg') >= 0) ang = parseFloat(parts[i])\n else cols.push(parts[i])\n }\n if (cols.length < 2) cols = [cols[0] || '#000', cols[0] || '#000']\n var rad = (ang - 90) * Math.PI / 180\n var ux = Math.cos(rad), uy = Math.sin(rad)\n var g = bgC.createLinearGradient(W / 2 - ux * W / 2, H / 2 - uy * H / 2, W / 2 + ux * W / 2, H / 2 + uy * H / 2)\n g.addColorStop(0, cols[0]); g.addColorStop(1, cols[cols.length - 1])\n return g\n }\n // Radial: 'radial-gradient([circle|ellipse] [at X% Y%,] A, B)'. Canvas\n // cannot take the string, so it is built here like the linear one, with\n // CSS's own default extent (farthest corner) so the second colour lands\n // exactly where a browser would put it.\n if (typeof bgcss === 'string' && bgcss.indexOf('radial-gradient') === 0) {\n var rin = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))\n var rps = rin.split(',').map(function (x) { return x.trim() })\n var rcx = 0.5, rcy = 0.5, rcols = []\n for (var ri = 0; ri < rps.length; ri++) {\n var rp = rps[ri]\n if (rp.indexOf('circle') === 0 || rp.indexOf('ellipse') === 0 || rp.indexOf('at ') === 0) {\n var rat = /at\\\\s+([\\\\d.]+)%\\\\s+([\\\\d.]+)%/.exec(rp)\n if (rat) { rcx = parseFloat(rat[1]) / 100; rcy = parseFloat(rat[2]) / 100 }\n } else rcols.push(rp)\n }\n if (rcols.length < 2) rcols = [rcols[0] || '#000', rcols[0] || '#000']\n var rpx = rcx * W, rpy = rcy * H\n var rr = Math.max(\n Math.sqrt(rpx * rpx + rpy * rpy),\n Math.sqrt((W - rpx) * (W - rpx) + rpy * rpy),\n Math.sqrt(rpx * rpx + (H - rpy) * (H - rpy)),\n Math.sqrt((W - rpx) * (W - rpx) + (H - rpy) * (H - rpy))\n )\n var rg = bgC.createRadialGradient(rpx, rpy, 0, rpx, rpy, rr)\n rg.addColorStop(0, rcols[0]); rg.addColorStop(1, rcols[rcols.length - 1])\n return rg\n }\n return bgcss\n })()\n bgC.fillRect(0, 0, W, H)\n\n if (bgEl && bgReady) {\n var bgW = (bgIsImg ? bgEl.naturalWidth : bgEl.videoWidth) || 16\n var bgH = (bgIsImg ? bgEl.naturalHeight : bgEl.videoHeight) || 9\n // Parallax: the media counter-pans a touch as the zoom camera moves —\n // a depth cue. Pure f(t): offset from the SAME zoom-track sample the card\n // uses; over-scan the cover fit so the pan never reveals an edge.\n var bgPar = Math.min(1, Math.max(0, frame.parallax || 0))\n var bgOx = 0, bgOy = 0\n if (bgPar > 0 && d.zoomTrack && d.zoomTrack.keyframes && d.zoomTrack.keyframes.length) {\n var bgZ = TL.sample(d.zoomTrack, t, TL.lerpArray)\n var bgAmp = bgPar * (bgZ[0] - 1) * 0.08\n bgOx = -(bgZ[1] - 0.5) * bgAmp * W\n bgOy = -(bgZ[2] - 0.5) * bgAmp * H\n }\n var bgOver = 1 + (bgPar > 0 ? 0.1 : 0)\n var bgS = Math.max(W / bgW, H / bgH) * bgOver // cover-fit (+ parallax slack)\n var bgDw = bgW * bgS, bgDh = bgH * bgS\n // Clamp the pan into the cover slack so edges never show.\n var bgSlackX = (bgDw - W) / 2, bgSlackY = (bgDh - H) / 2\n bgOx = Math.max(-bgSlackX, Math.min(bgSlackX, bgOx))\n bgOy = Math.max(-bgSlackY, Math.min(bgSlackY, bgOy))\n // Blur: softens the media behind the card (design px × s).\n var bgBlur = bgm.blur || 0\n if (bgBlur > 0 && bgC.filter !== undefined) bgC.filter = 'blur(' + bgBlur * s + 'px)'\n try { bgC.drawImage(bgEl, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh) } catch (e) {}\n if (bgBlur > 0 && bgC.filter !== undefined) bgC.filter = 'none'\n var bgDim = bgm.dim || 0\n if (bgDim > 0) {\n bgC.fillStyle = 'rgba(0,0,0,' + Math.min(1, bgDim) + ')'\n bgC.fillRect(0, 0, W, H)\n }\n }\n bg.sig = bgSig\n if (bg.texture) bg.texture.needsUpdate = true\n }\n\n // The card paints in PASSES: the ghost first (the outgoing clip's last\n // frame on its own canvas, while a boundary is crossing), then the live\n // card. A pass swaps the canvas, the element, the media, the source\n // moment and the card fields it reads; the ghost reads the camera as it\n // stood at the boundary and paints no clicks and no dot. The live pass\n // runs last, so everything after the loop reads the live card.\n var lvVideo = video, lvActM = actM, lvAmId = amId, lvSrcT = srcT, lvCfr = cfr, lvC = c, lvCv = cv\n var trPasses = trOut ? [true, false] : [false]\n for (var tpI = 0; tpI < trPasses.length; tpI++) {\n var tpG = trPasses[tpI]\n var tpT = t\n if (tpG) { c = gh.c2d; cv = gh.canvas; video = trGEl; actM = trGM; amId = trOut.media; srcT = trOut.at; cfr = trGFr; tpT = trA.t - 0.001 }\n else { c = lvC; cv = lvCv; video = lvVideo; actM = lvActM; amId = lvAmId; srcT = lvSrcT; cfr = lvCfr }\n // The CARD layer canvas starts transparent each frame — the padding around\n // the contain-fit card shows the background layer through the plane's alpha.\n // Under overscan the canvas is larger than the frame and every frame-space\n // draw below rides a base translation to its centre.\n var okOn = cW !== W || cH !== H\n if (okOn) c.setTransform(1, 0, 0, 1, 0, 0)\n c.clearRect(0, 0, cv.width, cv.height)\n if (okOn) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)\n // Footage is nearly always drawn SMALLER than it was recorded (a 1920\n // recording in a padded 1080 card, or a 440-wide tile): the default\n // bilinear tap aliases and blurs a large downscale; 'high' resamples.\n c.imageSmoothingQuality = 'high'\n\n // video destination rect (contain within the padded area). The optional browser-bar\n // strip is part of the card: it takes barH from the available height and the video\n // sits below it — bar + video share the rounded clip and zoom together.\n var pad = (frame.padding || 0) * s\n // Per-side placement (cfr.inset: fractions of the frame, a negative side\n // bleeds the card past the edge) overrides the symmetric padding on the\n // sides it names; absent sides keep pad, so the old math falls out.\n var ipIns = cfr.inset || {}\n var ipL = ipIns.left == null ? pad : ipIns.left * W\n var ipR = ipIns.right == null ? pad : ipIns.right * W\n var ipT = ipIns.top == null ? pad : ipIns.top * H\n var ipB = ipIns.bottom == null ? pad : ipIns.bottom * H\n var bar = cfr.browserBar || {}\n // Window takes carry a viewport crop (drawImage source rect, capture px) that\n // removes the real browser chrome — the card's source dims are then the CROP\n // dims (meta/cursor were rewritten into crop space at doc build).\n var crp = actM ? (actM.crop || null) : (d.crop || null)\n var vw = crp ? crp.w : (video.videoWidth || video.naturalWidth || 16)\n var vh = crp ? crp.h : (video.videoHeight || video.naturalHeight || 9)\n // Card-chrome scale: everything that belongs to the CARD (browser bar +\n // internals, corner radius, border, card shadow, cursor dot, click effects)\n // scales with the CARD, not the frame. When the frame is NARROWER than the\n // footage the card is width-limited and shrinks by frameAspect/videoAspect;\n // frame-relative sizing (s alone) was calibrated for native aspect and made\n // the chrome read giant on a small card (\"the 9:16 huge browser bar\" bug).\n // At native/wider aspects cf = 1 exactly, so nothing changes. MIRRORED by\n // computeCardLayout — change them together.\n // Cover fit: the CARD rect and the VIDEO rect separate. Under\n // contain (default, byte-identical for every existing doc) the card IS the\n // fitted video; under cover the card is the padded area itself and the\n // footage cover-fills it, cropped around cfr.focus (normalized video\n // fractions, the zoom cx/cy convention; clamped so no gap ever shows).\n // Chrome under cover scales by s alone (cf = 1): the card is as wide as\n // the frame allows, which is the case cf existed to protect against.\n var fitCover = cfr.fit === 'cover'\n var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))\n var s2 = s * cf\n // current zoom — a standard keyframe track in OUTPUT time (hold + arrival pairs\n // expanded by the lowering), sampled with the shared deterministic interpolator.\n // Sampled here, before the rect math, so a cover crop can follow it.\n var lvl = 1, zx = 0.5, zy = 0.5, zc = -1\n var zt = d.zoomTrack\n if (zt && zt.keyframes && zt.keyframes.length) {\n var z = TL.sample(zt, tpT, TL.lerpArray)\n lvl = z[0]; zx = z[1]; zy = z[2]\n // The stage camera's centring rides the track (0 rest, 1 apex, eased\n // with the level); a three-component track (a document lowered before\n // the component existed) falls back to the level band below.\n if (z.length > 3) zc = z[3]\n }\n var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0\n var availW = Math.max(1, W - ipL - ipR), availH = Math.max(1, H - ipT - ipB - barH)\n var sc, dw, dh, dx, dy, cardX, cardY, cardW, cardH\n if (fitCover) {\n sc = Math.max(availW / vw, availH / vh)\n dw = vw * sc; dh = vh * sc\n cardX = ipL; cardY = ipT; cardW = availW; cardH = availH + barH\n var fcv = cfr.focus || {}\n var fcx = fcv.cx == null ? 0.5 : Math.max(0, Math.min(1, fcv.cx))\n var fcy = fcv.cy == null ? 0.5 : Math.max(0, Math.min(1, fcv.cy))\n // focusFollow: the crop keeps the camera's focus in frame (a 9:16 cut).\n if (cfr.focusFollow === 'camera' && zt && zt.keyframes && zt.keyframes.length) { fcx = zx; fcy = zy }\n var vTop = ipT + barH\n dx = Math.min(cardX, Math.max(cardX + availW - dw, cardX + availW / 2 - fcx * dw))\n dy = Math.min(vTop, Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh))\n } else {\n sc = Math.min(availW / vw, availH / vh)\n dw = vw * sc; dh = vh * sc\n // Centred inside the inset area (equal to (W - dw) / 2 when no side is\n // inset, the old formula).\n dx = ipL + (availW - dw) / 2; dy = ipT + barH + (availH - dh) / 2\n cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH\n }\n var radius = (cfr.radius || 0) * s2\n var shadow = cfr.shadow || 0\n // The shadow's colour (a #rrggbb; the strengths are its alpha) and the\n // optional tight CONTACT layer that makes a light card sit on a light\n // ground.\n var shC = cfr.shadowContact || 0\n var shRgb = '0,0,0'\n var shHex = cfr.shadowColor\n if (typeof shHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(shHex)) {\n shRgb = parseInt(shHex.slice(1, 3), 16) + ',' + parseInt(shHex.slice(3, 5), 16) + ',' + parseInt(shHex.slice(5, 7), 16)\n }\n\n function rr(x, y, w, h, rd, cx) {\n var cc = cx || c\n if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }\n else { cc.beginPath(); cc.rect(x, y, w, h) }\n }\n\n c.save()\n // d.zoomSuppressed = editor aiming mode: the host merges it into ctx.data\n // while the focus overlay is up so the full frame renders. Never persisted.\n // ceZs = the zoom transform's device scale, for the two values below that\n // are sized in DEVICE pixels inside a scaled user space.\n var ceZs = 1\n // The camera model (cfr.camera; MIRRORS layout.ts zoomView — change them\n // together). The magnifier scales the card about the focus, which stays\n // where it is on screen; the stage camera also slides the card so the\n // focus lands at the frame's centre, blended in over CAMERA_CENTRE_RAMP\n // of level (a smoothstep, 0 at level 1) so a ramp through the band slides\n // without a kink, and past the card's edge the ground shows. wcx/wcy is\n // the content point at the frame's centre under either model; the two\n // branches are the same map, the magnifier's kept in its old float order\n // so every existing document renders byte-identically.\n var camStage = cfr.camera === 'stage'\n var fx = dx + zx * dw, fy = dy + zy * dh\n var wcx = fx, wcy = fy\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) {\n var ctt = zc\n if (ctt < 0) {\n var ctu = Math.max(0, Math.min(1, (lvl - 1) / ${CAMERA_CENTRE_RAMP}))\n ctt = ctu * ctu * (3 - 2 * ctu)\n }\n wcx = fx + ((W / 2 - fx) / lvl) * (1 - ctt)\n wcy = fy + ((H / 2 - fy) / lvl) * (1 - ctt)\n c.translate(W / 2, H / 2); c.scale(lvl, lvl); c.translate(-wcx, -wcy)\n } else {\n c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)\n }\n ceZs = lvl\n }\n // The card's shadows are cast by a body drawn OFF-CANVAS and brought back\n // by shadowOffsetX alone. Shadow offsets are device space, untouched by\n // the transform, so the body moves by shD / ceZs in user space and the\n // offset is shD; shD clears the canvas by the card's own device size, so\n // no part of the body lands on it at any zoom. Painting the body in place\n // (opaque black under the footage) left a 1px dark seam along every\n // straight card edge at a fractional coordinate: the fill's anti-aliased\n // rim showed through where the clipped footage did not cover it, and the\n // corners, drawn by the clip alone, hid theirs inside the arc.\n var shD = ceZs * (cardW + cardH) + 2 * (W + H)\n var shX = cardX - shD / ceZs\n // The card shadow is LAYERED: four shadows whose blur and offset grow\n // while each stays at a low alpha (shadow is the strength they share):\n // a hairline that defines the edge, two that ground it, and a WIDE,\n // faint ambient bloom (120 design px of blur, dropped well below the\n // card) that is what lets a large card read as lifted off a light\n // ground instead of outlined on it. One wide layer at the full strength\n // was the old paint, and at the default 0.4 it read as a dark pool\n // under the card; three tight layers read as a rim.\n if (shadow > 0) {\n var shL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]\n for (var shI = 0; shI < shL.length; shI++) {\n c.save()\n c.shadowColor = 'rgba(' + shRgb + ',' + +(shadow * shL[shI][2]).toFixed(3) + ')'\n c.shadowBlur = shL[shI][0] * s2; c.shadowOffsetX = shD; c.shadowOffsetY = shL[shI][1] * s2\n c.fillStyle = '#000'\n rr(shX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\n }\n // contact shadow: tight and close, over the ambient layer\n if (shC > 0) {\n c.save()\n c.shadowColor = 'rgba(' + shRgb + ',' + shC + ')'\n c.shadowBlur = 10 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 3 * s2\n c.fillStyle = '#000'\n rr(shX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\n // The footage and the bar fill overdraw the clip by one device pixel\n // (CARD_EDGE_OVERDRAW) so the clip is the only edge: a destination rect\n // that lands exactly on the clip path anti-aliases the edge twice at a\n // fractional coordinate and leaves a half-covered seam pixel.\n var ceOv = ${CARD_EDGE_OVERDRAW} / ceZs\n var ceX = dx - ceOv, ceY = dy - ceOv, ceW = dw + 2 * ceOv, ceH = dh + 2 * ceOv\n // video, then bar, clipped to the card's rounded corners. The bar draws\n // AFTER the footage: under cover the video rect can overflow ABOVE the\n // bar strip (a vertical crop), and bar-first let the footage paint over\n // it (found by eye on the padded marquee check — the stub geometry tests\n // have no z-order). Contain never overlaps, so the order is free there.\n c.save()\n rr(cardX, cardY, cardW, cardH, radius); c.clip()\n try {\n // The provider draws the current sample directly (crop-capable,\n // dimension-checked at attach); false ⇒ element path (pre-first-seek,\n // or the provider never attached).\n var wcp3 = video.__voilaWc\n if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {\n if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)\n else c.drawImage(video, ceX, ceY, ceW, ceH)\n }\n } catch (e) {}\n if (barH > 0) {\n var dark = bar.kind.indexOf('dark') >= 0\n var minimal = bar.kind === 'minimal'\n // minimal-bar theme: resolved colors from ctx.data (MINIMAL_BAR_THEMES);\n // absent = the built-in graphite look\n var thm = (minimal && bar.theme) || null\n c.fillStyle = minimal ? (thm ? thm.bar : '#141417') : dark ? '#2a2a2e' : '#e9e9eb'\n c.fillRect(cardX - ceOv, cardY - ceOv, cardW + 2 * ceOv, barH + ceOv)\n c.fillStyle = dark || (minimal && !(thm && thm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'\n c.fillRect(cardX, cardY + barH - s2, cardW, s2) // hairline above the video\n var midY = cardY + barH / 2\n if (bar.showControls !== false && !minimal) {\n if (bar.kind.indexOf('mac') === 0) {\n var lights = ['#ff5f57', '#febc2e', '#28c840']\n for (var li = 0; li < 3; li++) {\n c.fillStyle = lights[li]\n c.beginPath(); c.arc(cardX + (20 + li * 20) * s2, midY, 6 * s2, 0, Math.PI * 2); c.fill()\n }\n } else {\n c.strokeStyle = dark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'\n c.lineWidth = 1.5 * s2\n var g = 4.5 * s2, gx = cardX + cardW - 22 * s2 // close ✕, then ▢, then — leftward\n c.beginPath()\n c.moveTo(gx - g, midY - g); c.lineTo(gx + g, midY + g)\n c.moveTo(gx + g, midY - g); c.lineTo(gx - g, midY + g)\n c.stroke()\n c.strokeRect(gx - 28 * s2 - g, midY - g, g * 2, g * 2)\n c.beginPath()\n c.moveTo(gx - 56 * s2 - g, midY); c.lineTo(gx - 56 * s2 + g, midY)\n c.stroke()\n }\n }\n if (bar.showUrl !== false && bar.url) {\n var pillW = Math.min(cardW * 0.5, Math.max(200 * s2, cardW * 0.34))\n var pillH = barH - 16 * s2\n var px0 = cardX + (cardW - pillW) / 2, py0 = cardY + 8 * s2\n c.fillStyle = minimal ? (thm ? thm.pill : '#26262b') : dark ? '#1d1d20' : '#ffffff'\n rr(px0, py0, pillW, pillH, pillH / 2); c.fill()\n c.fillStyle = minimal ? (thm ? thm.text : '#9a9aa1') : dark ? '#a1a1a6' : '#5f5f64'\n c.font = 13 * s2 + 'px -apple-system, system-ui, sans-serif'\n c.textAlign = 'center'; c.textBaseline = 'middle'\n var label = String(bar.url)\n var maxTextW = pillW - 28 * s2\n if (c.measureText(label).width > maxTextW) {\n while (label.length > 1 && c.measureText(label + '\\\\u2026').width > maxTextW) label = label.slice(0, -1)\n label += '\\\\u2026'\n }\n c.fillText(label, px0 + pillW / 2, py0 + pillH / 2)\n c.textAlign = 'start'; c.textBaseline = 'alphabetic'\n }\n }\n c.restore()\n // border around the card, drawn OUTWARD like a CSS outline: the path is\n // expanded by half the width so the stroke's inner edge lands on the card's\n // own edge and never covers footage (inset, a 24px width ate 24px of the\n // recording). Outer corner radius grows with the width, the CSS-border rule;\n // a square card stays square. cfr.border is the ALPHA (0 = off) and rides\n // globalAlpha, so borderColor takes any CSS colour notation without this\n // having to parse one. Card chrome, so it scales by s2.\n if (cfr.border) {\n var bdW = (cfr.borderWidth > 0 ? cfr.borderWidth : ${FRAME_BORDER_WIDTH_DEFAULT}) * s2\n var bdH = bdW / 2\n c.save()\n c.globalAlpha = Math.min(1, cfr.border)\n c.strokeStyle = cfr.borderColor || '${FRAME_BORDER_COLOR_DEFAULT}'\n c.lineWidth = bdW\n rr(cardX - bdH, cardY - bdH, cardW + bdW, cardH + bdW, radius > 0 ? radius + bdH : 0)\n c.stroke()\n c.restore()\n }\n // Cover crop: click effects and the cursor dot are video-anchored, so a\n // cropped-out moment must not paint over the padding — clip them to the\n // card. Contain never needs it (the video rect IS the card).\n if (fitCover) { c.save(); rr(cardX, cardY, cardW, cardH, radius); c.clip() }\n // cursor coordinate space + drawn radius (shared by click effects + the dot)\n var space = (actM ? actM.cursorSpace : d.cursorSpace) || { w: vw, h: vh }\n var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5\n // The pointer's shape: an OS-style arrow (tip on the recorded point, a\n // black body with a white edge, the shape of the pointer on the viewer's\n // own screen) or the white dot every earlier take was cut with. One\n // painter for both draw sites (in the card, and the ghost across a\n // boundary); r is the dot's radius, so an arrow stands 2r tall.\n var curArrow = !!(d.cursorStyle && d.cursorStyle.style === 'arrow')\n function drawCur(cc, x, y, r, a, lw) {\n cc.save()\n if (curArrow) {\n var h = 2 * r\n var P = [[0, 0], [0, 0.72], [0.19, 0.55], [0.31, 0.83], [0.43, 0.78], [0.31, 0.5], [0.55, 0.5]]\n cc.beginPath()\n cc.moveTo(x, y)\n for (var pi = 1; pi < P.length; pi++) cc.lineTo(x + P[pi][0] * h, y + P[pi][1] * h)\n cc.closePath()\n cc.lineJoin = 'round'\n cc.fillStyle = 'rgba(0,0,0,' + (0.92 * a) + ')'\n cc.strokeStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'\n cc.lineWidth = Math.max(1, lw * 0.75)\n cc.fill(); cc.stroke()\n } else {\n cc.fillStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'\n cc.strokeStyle = 'rgba(0,0,0,' + (0.4 * a) + ')'\n cc.lineWidth = lw\n cc.beginPath(); cc.arc(x, y, r, 0, Math.PI * 2); cc.fill(); cc.stroke()\n }\n cc.restore()\n }\n\n // click effects — pure f(t): d.clicks are\n // OUTPUT-anchored records baked at lowering (sorted by ot; re-baked on every\n // edit, so they can't go stale), drawn UNDER the cursor and inside the zoom\n // transform so they scale with the camera. Locals are ck-prefixed (one var\n // scope). Effects anchor at the click point, never the smoothed cursor.\n var cks = d.clicks || []\n var ckF = d.clickFx || {}\n var ckPress = 1\n if (cks.length && !tpG) {\n var ckK = ckF.k || 1\n var ckPre = ${CLICK_FX_PRE}\n var ckRD = ${CLICK_RIPPLE_DUR} * (ckF.dur || 1)\n var ckPD = ${CLICK_PULSE_DUR} * (ckF.dur || 1)\n // 'highlight' keeps the ripple window too: clicks whose element rect\n // failed the lowering gates (huge/absent) fall back to a ripple per click\n var ckWin = ckF.style === 'pulse' ? ckPD : ckF.style === 'none' ? 0 : ckRD\n var ckCol = ckF.col || 0\n var ckDip = Math.min(0.3, 0.18 * ckK)\n for (var ci = 0; ci < cks.length; ci++) {\n var ck = cks[ci]\n if (ck.ot - 0.08 > t) break // sorted: nothing later is active yet\n var ckEnd = ck.ot - ckPre + ckWin\n if (ckF.style === 'highlight' && ck.r && ck.up + ${CLICK_HIGHLIGHT_FADE} > ckEnd) ckEnd = ck.up + ${CLICK_HIGHLIGHT_FADE}\n if (ckF.press && ck.up + 0.16 > ckEnd) ckEnd = ck.up + 0.16\n if (t > ckEnd) continue\n // cross-cut guard: the on-screen source moment must still be near the\n // click's source moment, or an effect near a cut would keep painting\n // over the NEXT segment's unrelated footage\n if (Math.abs(srcT - ck.st) > 2) continue\n var ckSp = ck.sp || space\n var ckAx = dx + (ck.x / (ckSp.w || vw)) * dw\n var ckAy = dy + (ck.y / (ckSp.h || vh)) * dh\n // press dip: smoothstep down around the real mousedown (80 ms lead —\n // anticipation is what makes effects feel synced), hold through the real\n // down→up span (drags dip long), easeOutBack rebound with a slight\n // overshoot. Feeds the cursor dot's radius below.\n if (ckF.press) {\n var ckPs = 1\n if (t < ck.ot + 0.05) {\n var ckU = (t - (ck.ot - 0.08)) / 0.13\n if (ckU > 0) {\n if (ckU > 1) ckU = 1\n ckU = ckU * ckU * (3 - 2 * ckU)\n ckPs = 1 - ckDip * ckU\n }\n } else if (t <= ck.up) {\n ckPs = 1 - ckDip\n } else {\n var ckV = (t - ck.up) / 0.16\n if (ckV < 1) {\n var ckW2 = ckV - 1\n ckPs = 1 - ckDip + ckDip * (1 + 4 * ckW2 * ckW2 * ckW2 + 3 * ckW2 * ckW2)\n }\n }\n ckPress = ckPs\n }\n var ckStyle = ckF.style === 'highlight' && !ck.r ? 'ripple' : ckF.style\n if (ckStyle === 'ripple') {\n // expanding ring: cubic-out expansion, cubic fade, thinning stroke\n var ckU2 = (t - (ck.ot - ckPre)) / ckRD\n if (ckU2 >= 0 && ckU2 <= 1) {\n var ckFade = (1 - ckU2) * (1 - ckU2) * (1 - ckU2)\n var ckR = curSize * 0.8 + (1 - ckFade) * 44 * s2 * ckK\n var ckA = Math.min(1, ckFade * 0.65 * ckK)\n var ckLw = Math.max(1.5 * s2, 3 * s2 * (1 - ckU2))\n c.save()\n if (ckCol) {\n c.strokeStyle = 'rgba(' + ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] + ',' + ckA + ')'\n c.lineWidth = ckLw\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n // thin white outline so accent colors read on dark content too\n c.strokeStyle = 'rgba(255,255,255,' + ckA * 0.5 + ')'\n c.lineWidth = Math.max(1, s2)\n c.beginPath(); c.arc(ckAx, ckAy, ckR + ckLw * 0.8, 0, Math.PI * 2); c.stroke()\n } else {\n // auto: the cursor dot's dual-stroke trick — dark rim under a\n // white ring, legible on any content\n c.strokeStyle = 'rgba(0,0,0,' + ckA * 0.35 + ')'\n c.lineWidth = ckLw + 2 * s2\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n c.strokeStyle = 'rgba(255,255,255,' + ckA + ')'\n c.lineWidth = ckLw\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n }\n c.restore()\n }\n } else if (ckStyle === 'pulse') {\n // soft filled orb: parabola opacity (in fast, out soft), cubic-out bloom\n var ckU3 = (t - (ck.ot - ckPre)) / ckPD\n if (ckU3 >= 0 && ckU3 <= 1) {\n var ckE3 = 1 - (1 - ckU3) * (1 - ckU3) * (1 - ckU3)\n var ckR3 = Math.max(1, 34 * s2 * ckK * (0.35 + 0.65 * ckE3))\n var ckA3 = Math.min(1, 4 * ckU3 * (1 - ckU3) * 0.45 * ckK)\n var ckC3 = ckCol ? ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] : '255,255,255'\n var ckG = c.createRadialGradient(ckAx, ckAy, 0, ckAx, ckAy, ckR3)\n ckG.addColorStop(0, 'rgba(' + ckC3 + ',' + ckA3 + ')')\n ckG.addColorStop(1, 'rgba(' + ckC3 + ',0)')\n c.save()\n c.fillStyle = ckG\n c.beginPath(); c.arc(ckAx, ckAy, ckR3, 0, Math.PI * 2); c.fill()\n c.restore()\n }\n } else if (ckStyle === 'highlight') {\n // element glow (unique to DOM capture): rounded-rect around the\n // clicked element's rect — fast smoothstep in, hold while pressed,\n // quadratic fade after release\n var ckIn = (t - (ck.ot - ckPre)) / 0.08\n if (ckIn > 0) {\n if (ckIn > 1) ckIn = 1\n ckIn = ckIn * ckIn * (3 - 2 * ckIn)\n var ckOut = 1\n if (t > ck.up) {\n var ckV2 = (t - ck.up) / ${CLICK_HIGHLIGHT_FADE}\n ckOut = ckV2 >= 1 ? 0 : (1 - ckV2) * (1 - ckV2)\n }\n var ckA4 = Math.min(1, ckIn * ckOut * 0.9 * ckK)\n if (ckA4 > 0.004) {\n var ckRX = dx + (ck.r[0] / (space.w || vw)) * dw\n var ckRY = dy + (ck.r[1] / (space.h || vh)) * dh\n var ckRW = (ck.r[2] / (space.w || vw)) * dw\n var ckRH = (ck.r[3] / (space.h || vh)) * dh\n var ckRad = Math.min(10 * s2, ckRH / 2)\n var ckC4 = ckCol ? ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] : '255,255,255'\n c.save()\n // dark rim under the glowing stroke — legible on light content too\n c.strokeStyle = 'rgba(0,0,0,' + ckA4 * 0.35 + ')'\n c.lineWidth = 4 * s2\n rr(ckRX, ckRY, ckRW, ckRH, ckRad); c.stroke()\n c.shadowColor = 'rgba(' + ckC4 + ',' + ckA4 * 0.4 + ')'\n c.shadowBlur = 12 * s2\n c.strokeStyle = 'rgba(' + ckC4 + ',' + ckA4 + ')'\n c.lineWidth = 2 * s2\n rr(ckRX, ckRY, ckRW, ckRH, ckRad); c.stroke()\n c.restore()\n }\n }\n }\n }\n }\n\n // pinned layers' marks — a standing ring or underline on a layer's\n // REFERENT for the layer's life (d.pins: OUTPUT-anchored records baked at\n // lowering, the referent in normalised video fractions). Painted inside the\n // zoom transform so the mark sits on the element under any level or pose,\n // at a constant on-screen weight (÷ ceZs), with the layer's own entrance\n // and exit. Locals are pn-prefixed (one var scope). Skipped on the ghost\n // pass like the effects.\n var pns = d.pins || []\n if (pns.length && !tpG) {\n for (var pi = 0; pi < pns.length; pi++) {\n var pn = pns[pi]\n if (!pn.mark || pn.mark === 'none') continue\n var pnT = t - pn.start\n if (pnT < 0 || pnT > pn.dur) continue\n var pnA = 1\n if (pnT < 0.35) { var pnU = pnT / 0.35; pnA = 1 - Math.pow(1 - pnU, 3) }\n if (pn.dur - pnT < 0.35) { var pnV = (pn.dur - pnT) / 0.35; pnA = Math.min(pnA, 1 - Math.pow(1 - pnV, 3)) }\n if (pnA <= 0.004) continue\n var pnX = dx + pn.x * dw, pnY = dy + pn.y * dh, pnW = pn.w * dw, pnH = pn.h * dh\n // a carried referent (a drag): moved by where the pointer has taken\n // it at the on-screen source moment, held at the release\n if (pn.carry) {\n var pcC = (actM ? actM.cursor : d.cursor) || []\n var pcT = Math.max(pn.carry.t0, Math.min(pn.carry.t1, srcT))\n var pcx = -1, pcy = -1\n for (var pk = 0; pk < pcC.length; pk++) { if (pcC[pk].t <= pcT) { pcx = pcC[pk].x; pcy = pcC[pk].y } }\n if (pcx >= 0) { pnX += (pcx / (space.w || vw) - pn.carry.x0) * dw; pnY += (pcy / (space.h || vh) - pn.carry.y0) * dh }\n }\n var pnLw = 2 * s2 / ceZs, pnPad = 6 * s2 / ceZs\n var pnCol = pn.color || 'rgba(255,255,255,0.95)'\n c.save()\n c.globalAlpha = pnA\n if (pn.mark === 'underline') {\n // a bar under the referent: dark rim, then the ink\n var pnBy = pnY + pnH + pnPad * 0.5\n c.fillStyle = 'rgba(0,0,0,0.35)'\n c.fillRect(pnX - pnLw, pnBy - pnLw, pnW + pnLw * 2, pnLw * 1.5 + pnLw * 2)\n c.fillStyle = pnCol\n c.fillRect(pnX, pnBy, pnW, pnLw * 1.5)\n } else {\n // a ring around the referent: the cursor's dual stroke, a dark rim\n // under the ink, legible on any content\n c.lineWidth = pnLw + 2 * s2 / ceZs\n c.strokeStyle = 'rgba(0,0,0,0.35)'\n rr(pnX - pnPad, pnY - pnPad, pnW + pnPad * 2, pnH + pnPad * 2, pnPad, c); c.stroke()\n c.lineWidth = pnLw\n c.strokeStyle = pnCol\n rr(pnX - pnPad, pnY - pnPad, pnW + pnPad * 2, pnH + pnPad * 2, pnPad, c); c.stroke()\n }\n c.restore()\n }\n }\n\n // cursor (SOURCE-anchored samples, read at the on-screen source moment).\n // The dot is the only thing cursorStyle.visible hides — the track still drives\n // cursor-follow zoom, and click effects draw above on their own switch.\n // Undefined reads as visible so pre-toggle docs are unchanged.\n var cur = (actM ? actM.cursor : d.cursor) || []\n if (cur.length && !(d.cursorStyle && d.cursorStyle.visible === false)) {\n var px = cur[0].x, py = cur[0].y\n for (var j = 0; j < cur.length; j++) { if (cur[j].t <= srcT) { px = cur[j].x; py = cur[j].y } }\n var ax = dx + (px / (space.w || vw)) * dw\n var ay = dy + (py / (space.h || vh)) * dh\n // Idle fade: a sparse SOURCE-time opacity curve baked by cursorIdleFade.\n // Linear between keys — opacity needs no easing, and the ramps are already\n // shaped by where the keys sit. Absent/empty = the cursor never dwells.\n var cuA = 1, cuK = actM ? actM.cursorFade : d.cursorFade\n if (cuK && cuK.length) {\n if (srcT <= cuK[0].t) cuA = cuK[0].a\n else if (srcT >= cuK[cuK.length - 1].t) cuA = cuK[cuK.length - 1].a\n else {\n for (var cuJ = 1; cuJ < cuK.length; cuJ++) {\n if (cuK[cuJ].t >= srcT) {\n var cuB = cuK[cuJ - 1], cuC = cuK[cuJ]\n cuA = cuB.a + (cuC.a - cuB.a) * ((srcT - cuB.t) / ((cuC.t - cuB.t) || 1))\n break\n }\n }\n }\n }\n // Across a boundary the dot is ONE, drawn in frame space on the\n // overlay layer (the lerp after the passes), never on a card that is\n // moving: each pass leaves its point, mapped out of the zoom.\n if (trOut) {\n var tqX = ax, tqY = ay\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) { tqX = W / 2 + (ax - wcx) * lvl; tqY = H / 2 + (ay - wcy) * lvl }\n else { tqX = fx + (ax - fx) * lvl; tqY = fy + (ay - fy) * lvl }\n }\n if (tpG) trQa = [tqX, tqY]\n else { trQb = [tqX, tqY]; trQs = curSize * ckPress; trQw = s2 }\n } else if (cuA > 0.01) {\n drawCur(c, ax, ay, curSize * ckPress, cuA, 2 * s2)\n }\n }\n if (fitCover) c.restore()\n c.restore()\n if (tpG && gh.texture) gh.texture.needsUpdate = true\n }\n\n // --- OVERLAY layer (screen-space plane, never tilts): the cam bubble, the\n // recording's own footage. Text/image/video overlay CLIPS are the studio\n // stack entry's (studioEntry.ts): they paint on their own layer in\n // ctx.overlayScene, above this one. Painted to ovC (its own canvas at\n // runtime; the card c2d under stubs). Redraw + re-upload only while the\n // bubble is active (a video → every frame), on resize, or once when it turns\n // off (to clear) — so a cam-less take never uploads the overlay after frame 1.\n var camV = amId ? null : r.cam, camS = d.cam || {}\n var camOn = !camS.window || (srcT >= camS.window.in && srcT <= camS.window.out)\n // Readiness is STICKY: readyState drops to HAVE_METADATA while a seek is in\n // flight, so gating each frame on it makes the bubble vanish on every scrub\n // step (the screen video is drawn ungated and just shows its retained frame).\n // Wait only for the FIRST decoded frame, then keep drawing through seeks.\n if (camV && camV.readyState >= 2) r.camHasFrame = true\n var camActiveNow = !!(camV && camOn && camS.visible !== false && r.camHasFrame)\n // A pinned layer's leader is live for the layer's life: the overlay layer\n // repaints while one is up (the referent moves with the camera).\n var pqs = d.pins || [], pqLive = false\n for (var pl = 0; pl < pqs.length; pl++) {\n var pq0 = pqs[pl]\n if (pq0.leader && t >= pq0.start && t <= pq0.start + pq0.dur) { pqLive = true; break }\n }\n var ovSig = W + 'x' + H\n var ovDirty = ov.sig !== ovSig || camActiveNow || ov.active || !!trOut || pqLive\n if (ovDirty) {\n ovC.clearRect(0, 0, W, H)\n // webcam bubble — pinned to the frame corner regardless of card tilt/zoom.\n if (camActiveNow) {\n // Cam pose track: [x, y, size] frame fractions sampled at t — wins\n // over the static pose while spans exist. camPoseOverride is ephemeral\n // editor state (the zoomSuppressed seam): the selected span's settled\n // pose while paused, merged by the host, never persisted.\n var camP = d.camPoseOverride || null\n var camTk = d.camTrack\n if (!camP && camTk && camTk.keyframes && camTk.keyframes.length) camP = TL.sample(camTk, t, TL.lerpArray)\n var diam = Math.max(40, (camP ? camP[2] : (camS.size || 0.25)) * H)\n var mg = 24 * s\n var pos = camS.position || 'bottom-left'\n // Free placement: x/y are the bubble CENTER as frame fractions and\n // win over the corner anchor when present; a sampled pose wins over both.\n var bx = camP ? camP[0] * W - diam / 2 : camS.x != null ? camS.x * W - diam / 2 : pos.indexOf('right') >= 0 ? W - mg - diam : mg\n var by = camP ? camP[1] * H - diam / 2 : camS.y != null ? camS.y * H - diam / 2 : pos.indexOf('top') >= 0 ? mg : H - mg - diam\n // The bubble's look is three knobs with the old paint as every default\n // (decided 2026-08-24: the defaults must stay editable): radius\n // (rounded only, 18), shadow ('soft'), border (3px white at 0.9).\n var rd = camS.shape === 'rounded' ? (camS.radius != null ? camS.radius : 18) * s : diam / 2\n var cw = camV.videoWidth || 16, ch = camV.videoHeight || 9\n var sc2 = Math.max(diam / cw, diam / ch)\n var sw = cw * sc2, sh = ch * sc2\n var sx = bx + (diam - sw) / 2, sy = by + (diam - sh) / 2\n var camShadow = camS.shadow || 'soft'\n if (camShadow !== 'none') {\n ovC.save()\n ovC.shadowColor = camShadow === 'strong' ? 'rgba(0,0,0,0.55)' : 'rgba(0,0,0,0.4)'\n ovC.shadowBlur = (camShadow === 'strong' ? 60 : 30) * s\n ovC.shadowOffsetY = (camShadow === 'strong' ? 20 : 10) * s\n ovC.fillStyle = '#000'\n rr(bx, by, diam, diam, rd, ovC); ovC.fill()\n ovC.restore()\n }\n ovC.save()\n rr(bx, by, diam, diam, rd, ovC); ovC.clip()\n if (camS.mirror) { ovC.translate(bx * 2 + diam, 0); ovC.scale(-1, 1) } // mirror about bubble center\n try { ovC.drawImage(camV, sx, sy, sw, sh) } catch (e) {}\n ovC.restore()\n var camBW = camS.border ? camS.border.width : 3\n if (camBW > 0) {\n ovC.save()\n ovC.strokeStyle = (camS.border && camS.border.color) || 'rgba(255,255,255,0.9)'; ovC.lineWidth = camBW * s\n rr(bx, by, diam, diam, rd, ovC); ovC.stroke()\n ovC.restore()\n }\n }\n // pinned layers' leaders — a hairline from the layer's near edge (the\n // baked tip track, clip-local frame fractions) to the referent's near\n // edge, the referent mapped through the camera the way the boundary dot\n // is, with a dot at the referent. Fades with the layer; rides the rise\n // entrance so the line meets the card as it lands.\n if (pqLive) {\n for (var pj = 0; pj < pqs.length; pj++) {\n var pq = pqs[pj]\n if (!pq.leader) continue\n var pqT = t - pq.start\n if (pqT < 0 || pqT > pq.dur) continue\n var pqA = 1, pqRise = 0\n if (pqT < 0.35) {\n var pqU = 1 - Math.pow(1 - pqT / 0.35, 3)\n pqA = pqU\n if (pq.enter === 'rise') pqRise = (1 - pqU) * 24 * s\n }\n if (pq.dur - pqT < 0.35) { var pqV = (pq.dur - pqT) / 0.35; pqA = Math.min(pqA, 1 - Math.pow(1 - pqV, 3)) }\n if (pqA <= 0.004 || !pq.tip || !pq.tip.keyframes || !pq.tip.keyframes.length) continue\n var pqTip = TL.sample(pq.tip, pqT, TL.lerpArray)\n var pqTx = pqTip[0] * W, pqTy = pqTip[1] * H + pqRise\n // the referent's corners in frame space under the live camera\n var pqAx = dx + pq.x * dw, pqAy = dy + pq.y * dh\n var pqBx = dx + (pq.x + pq.w) * dw, pqBy = dy + (pq.y + pq.h) * dh\n if (pq.carry) {\n var pqcT = Math.max(pq.carry.t0, Math.min(pq.carry.t1, srcT))\n var pqcx = -1, pqcy = -1\n for (var pqk = 0; pqk < cur.length; pqk++) { if (cur[pqk].t <= pqcT) { pqcx = cur[pqk].x; pqcy = cur[pqk].y } }\n if (pqcx >= 0) {\n var pqOx = (pqcx / (space.w || vw) - pq.carry.x0) * dw, pqOy = (pqcy / (space.h || vh) - pq.carry.y0) * dh\n pqAx += pqOx; pqBx += pqOx; pqAy += pqOy; pqBy += pqOy\n }\n }\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) {\n pqAx = W / 2 + (pqAx - wcx) * lvl; pqAy = H / 2 + (pqAy - wcy) * lvl\n pqBx = W / 2 + (pqBx - wcx) * lvl; pqBy = H / 2 + (pqBy - wcy) * lvl\n } else {\n pqAx = fx + (pqAx - fx) * lvl; pqAy = fy + (pqAy - fy) * lvl\n pqBx = fx + (pqBx - fx) * lvl; pqBy = fy + (pqBy - fy) * lvl\n }\n }\n var pqRx = pq.side === 'right' ? pqBx : pq.side === 'left' ? pqAx : (pqAx + pqBx) / 2\n var pqRy = pq.side === 'below' ? pqBy : pq.side === 'above' ? pqAy : (pqAy + pqBy) / 2\n var pqCol = pq.color || 'rgba(255,255,255,0.95)'\n var pqLw = 2 * s\n ovC.save()\n ovC.globalAlpha = pqA\n ovC.lineCap = 'round'\n ovC.strokeStyle = 'rgba(0,0,0,0.35)'; ovC.lineWidth = pqLw + 2 * s\n ovC.beginPath(); ovC.moveTo(pqTx, pqTy); ovC.lineTo(pqRx, pqRy); ovC.stroke()\n ovC.strokeStyle = pqCol; ovC.lineWidth = pqLw\n ovC.beginPath(); ovC.moveTo(pqTx, pqTy); ovC.lineTo(pqRx, pqRy); ovC.stroke()\n ovC.fillStyle = 'rgba(0,0,0,0.35)'\n ovC.beginPath(); ovC.arc(pqRx, pqRy, 5 * s, 0, Math.PI * 2); ovC.fill()\n ovC.fillStyle = pqCol\n ovC.beginPath(); ovC.arc(pqRx, pqRy, 4 * s, 0, Math.PI * 2); ovC.fill()\n ovC.restore()\n }\n }\n // The boundary dot: the outgoing card's last cursor point and the\n // incoming's current one, each carried by its card's move, one dot on\n // the eased lerp between them (the idle fade never fires across a page\n // change). It lands on the incoming's own dot as the window closes.\n if (trOut && (trQa || trQb) && !(d.cursorStyle && d.cursorStyle.visible === false)) {\n var tqA = trQa || trQb, tqB = trQb || trQa\n var tqP = trEase((t - trA.t) / trA.d)\n var tqAx = (tqA[0] - W / 2) * trGsc + W / 2 + trGox * W, tqAy = (tqA[1] - H / 2) * trGsc + H / 2 - trGoy * H\n var tqBx = (tqB[0] - W / 2) * trSc + W / 2 + trOx * W, tqBy = (tqB[1] - H / 2) * trSc + H / 2 - trOy * H\n var tqX2 = tqAx + (tqBx - tqAx) * tqP, tqY2 = tqAy + (tqBy - tqAy) * tqP\n var tqR = trQs || (((d.cursorStyle && d.cursorStyle.size) || 24) * s * 0.5)\n drawCur(ovC, tqX2, tqY2, tqR, 1, 2 * (trQw || s))\n }\n ov.sig = ovSig\n ov.active = camActiveNow || !!trOut || pqLive\n if (ov.texture) ov.texture.needsUpdate = true\n }\n\n // --- card presentation (compositor v2): the card's pose is the TILT\n // TRACK and nothing else (decided 2026-08-03 — a lean is a moment in time,\n // so it lives on the timeline; the static rest pose, entrance, exit, float\n // and glow are gone with the Card panel). No spans ⇒ no track ⇒ identity,\n // which is pixel-identical to the pre-v2 fullscreen quad. Mipmaps and\n // anisotropy switch on when the card actually tilts (minification would\n // otherwise shimmer). d.tiltSuppressed is pure editor ui state (the\n // zoomSuppressed seam): on-canvas edit overlays mirror UNtilted card\n // geometry, so edit views need the flat card.\n if (card.mesh) {\n var rx = 0, ry = 0\n var tk = d.tiltTrack\n if (tk && tk.keyframes && tk.keyframes.length && !d.tiltSuppressed) {\n var tkv = TL.sample(tk, t, TL.lerpArray)\n rx = tkv[0] * Math.PI / 180\n ry = tkv[1] * Math.PI / 180\n }\n card.mesh.rotation.x = rx\n card.mesh.rotation.y = ry\n // The card's pose through an entrance or an end card: [scale, dy,\n // opacity]; absent = the rest pose, exactly as before the track existed.\n // dy is a fraction of the FRAME plane's height (not the geometry's,\n // which the overscan grows).\n var cpk = d.cardPoseTrack\n var cpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var cpv = null\n if (cpk && cpk.keyframes && cpk.keyframes.length) cpv = TL.sample(cpk, t, TL.lerpArray)\n if ((cpv || trIn) && card.mesh.scale && card.mesh.position) {\n // The pose track, then a boundary's enter on top: a slide is an\n // offset in plane widths and heights, a fade an opacity, a scale both.\n var cpS = (cpv ? cpv[0] : 1) * trSc\n var cpO = (cpv ? cpv[2] : 1) * trOp\n card.mesh.scale.x = cpS; card.mesh.scale.y = cpS\n card.mesh.position.x = trOx * cpH * (W / H)\n card.mesh.position.y = ((cpv ? cpv[1] : 0) + trOy) * cpH\n if (card.mesh.material) card.mesh.material.opacity = cpO\n // Gone past its clip: the mesh leaves the scene, not just its paint.\n card.mesh.visible = cpO > 0.001\n } else if (card.mesh.scale && card.mesh.position && ((card.mesh.scale.x !== undefined && card.mesh.scale.x !== 1) || (card.mesh.position.y !== undefined && card.mesh.position.y !== 0) || (card.mesh.position.x !== undefined && card.mesh.position.x !== 0) || card.mesh.visible === false)) {\n card.mesh.scale.x = 1; card.mesh.scale.y = 1\n card.mesh.position.x = 0\n card.mesh.position.y = 0\n if (card.mesh.material) card.mesh.material.opacity = 1\n card.mesh.visible = true\n }\n var tilted = rx * rx + ry * ry > 1e-6\n if (card.texture && card.texture.generateMipmaps !== tilted) {\n var THREE2 = ctx.THREE\n card.texture.generateMipmaps = tilted\n card.texture.minFilter = tilted && THREE2 ? THREE2.LinearMipmapLinearFilter : (THREE2 ? THREE2.LinearFilter : card.texture.minFilter)\n if (tilted && ctx.renderer && ctx.renderer.capabilities && card.texture.anisotropy !== undefined) {\n card.texture.anisotropy = ctx.renderer.capabilities.getMaxAnisotropy ? ctx.renderer.capabilities.getMaxAnisotropy() : 1\n }\n card.texture.needsUpdate = true\n }\n }\n\n // The ghost plane: posed as the card stood at the boundary (its lean, its\n // pose), then moved by its exit; hidden while no boundary is crossing.\n if (r.ghost && r.ghost.mesh) {\n var gm = r.ghost.mesh\n if (trOut && gh === r.ghost) {\n var gtT = trA.t - 0.001\n var grx = 0, gry = 0\n var gtk = d.tiltTrack\n if (gtk && gtk.keyframes && gtk.keyframes.length && !d.tiltSuppressed) { var gtv = TL.sample(gtk, gtT, TL.lerpArray); grx = gtv[0] * Math.PI / 180; gry = gtv[1] * Math.PI / 180 }\n gm.rotation.x = grx; gm.rotation.y = gry\n var gpv = [1, 0, 1]\n var gpk = d.cardPoseTrack\n if (gpk && gpk.keyframes && gpk.keyframes.length) gpv = TL.sample(gpk, gtT, TL.lerpArray)\n var gpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var gS = gpv[0] * trGsc, gO = gpv[2] * trGop\n gm.scale.x = gS; gm.scale.y = gS\n gm.position.x = trGox * gpH * (W / H)\n gm.position.y = (gpv[1] + trGoy) * gpH\n if (gm.material) gm.material.opacity = gO\n gm.visible = gO > 0.001\n } else if (gm.visible !== false) gm.visible = false\n }\n\n // The card layer redraws every frame (dynamic content); bg/overlay uploads are\n // gated inside their blocks (dirty-tracking above).\n if (card.texture) card.texture.needsUpdate = true\n\n // verification hook (no-op unless the harness sets window.__VOILA_DEBUG__).\n // cv = the CARD 2D canvas; bgCv/ovCv are the background/overlay layers (v2).\n if (typeof window !== 'undefined' && window.__VOILA_DEBUG__) {\n window.__voilaDebug = { cv: cv, bgCv: bg.canvas, ovCv: ov.canvas, W: W, H: H, glW: gl && gl.width, glH: gl && gl.height, t: t, canvases: document.querySelectorAll('canvas').length }\n }\n}`\n\n/**\n * The shared layers as the studio entry's data: overlay clips (presets resolved\n * to plain values HERE, ON_FRAME reads no registry), 3D props (numbers resolved\n * HERE), the extra font faces SETUP awaits. Every key is omitted when its layer\n * is absent — data byte parity for docs that never touched it. Both anchors\n * call this with their own output duration.\n */\nexport function studioLayerData(\n layers: {\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n audio?: AudioClip[]\n },\n duration: number,\n /**\n * Pinned layers' placements by clip id (resolvePins): a pinned clip's\n * track is its place beside its referent through the camera, in the\n * same clip-local shape a `motion` pose bakes to. Absent for a program\n * document, which has no referents.\n */\n pins?: ReadonlyMap<string, PinPlacement>,\n): Record<string, unknown> {\n // A media layer resolves against the RECORDING it rides (a program\n // document has no media to show).\n const rec = 'source' in layers ? (layers as unknown as ProjectDoc) : null\n return {\n // Music/SFX clips with their gain envelopes baked (shared truth for the\n // preview scheduler and the export's offline mix). `duckEnv` (the mic-derived\n // duck multiplier curve) is merged in asynchronously by useComposition — it\n // needs a decoded recording, which a sync lowering can't produce.\n audio: (layers.audio ?? []).map((c) => ({\n key: c.key,\n start: round(c.start),\n in: round(c.in),\n out: round(c.out),\n gain: round(c.gain),\n loop: !!c.loop,\n len: round(clipLength(c)),\n duck: !!c.duck,\n env: clipEnvelope(c).map((p) => ({ t: round(p.t), g: round(p.g) })),\n })),\n // Object clips: numbers resolved HERE; shapes are the drafted engine spec.\n ...(layers.objects && layers.objects.length\n ? {\n objects: layers.objects.map((o) => ({\n id: o.id,\n asset:\n o.asset.kind === 'primitive'\n ? {\n kind: 'primitive',\n shape: o.asset.shape,\n color: o.asset.color ?? '#e4e4e7',\n }\n : o.asset.kind === 'text3d'\n ? resolveText3dAsset(o.asset)\n : { kind: 'gltf', key: o.asset.key },\n ...(o.span\n ? {\n span: {\n start: round(o.span.start),\n duration: round(o.span.duration),\n },\n }\n : {}),\n x: round(o.transform3d.x),\n y: round(o.transform3d.y),\n z: round(o.transform3d.z),\n rx: round(o.transform3d.rx),\n ry: round(o.transform3d.ry),\n rz: round(o.transform3d.rz),\n scale: round(o.transform3d.scale || OBJECT_DEFAULT_SCALE),\n anim: idleKey(o),\n // Pose keyframes: clip-local [x,y,z,rx,ry,rz,scale] track,\n // sampled at t − span.start. Omitted when absent — parity.\n ...(() => {\n if (!o.motion || !o.motion.length) return {}\n const mb = objectMotionBase(o)\n const track = motionTrack(\n mb,\n objectMotionKeys(o, mb),\n o.span?.duration ?? duration,\n )\n return track.keyframes.length ? { track } : {}\n })(),\n })),\n }\n : {}),\n // Text overlays: presets resolved to plain values HERE (ON_FRAME reads\n // no registry). Omitted when absent/empty — byte parity for docs without them.\n // Full face list for SETUP's cold-load await (export parity). Baked only\n // when overrides add faces beyond the base three — old-doc data parity;\n // SETUP falls back to the base literal.\n ...(layers.overlays &&\n overlayFontFaces(layers).length > OVERLAY_FONT_FACES.length\n ? { overlayFonts: overlayFontFaces(layers) }\n : {}),\n ...(layers.overlays && layers.overlays.length\n ? {\n overlays: layers.overlays.map((o) => {\n const base = {\n id: o.id,\n kind: o.kind,\n start: round(o.start),\n dur: round(Math.max(OVERLAY_MIN_DURATION, o.duration)),\n x: round(o.transform.x),\n y: round(o.transform.y),\n scale: round(o.transform.scale || 1),\n rot: round(o.transform.rotation || 0),\n enter: enterKey(o),\n exit: exitKey(o),\n // Pose keyframes: a CLIP-LOCAL [x, y, scale, rot, opacity]\n // track, sampled in ON_FRAME at t − start. Omitted when the clip\n // has no motion — data byte parity.\n ...(() => {\n const pinned = pins?.get(o.id)\n if (pinned) return { track: pinned.track }\n if (!o.motion || !o.motion.length) return {}\n const mb = overlayMotionBase(o)\n const track = motionTrack(\n mb,\n overlayMotionKeys(o, mb),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n return track.keyframes.length ? { track } : {}\n })(),\n }\n if (o.kind === 'html') {\n // An HTML layer lowers to an IMAGE clip carrying its SOURCE:\n // setup builds the picture into the cache under a\n // content-addressed key and every paint path downstream is the\n // one a picture already takes. The key is a cache handle, never\n // a URL; nothing must ever fetch it. The compositor adds no\n // chrome (the painter casts a clip's shadow from its BOX, and a\n // layer is mostly transparent bleed, so a 'soft' default came\n // out as a black rectangle): the CSS is the chrome.\n const hl = htmlLayerPayload(o)\n return {\n ...base,\n kind: 'image',\n key: hl.key,\n w: round(hl.width),\n radius: 0,\n shadow: 'none',\n opacity: round(o.opacity ?? 1),\n html: hl.html,\n }\n }\n if (o.kind !== 'text') {\n // Media overlay: sized by frame-width fraction; corners in design\n // px; video time is clip-local (ON_FRAME seeks el to t − start).\n // A `media:<id>` key shows a DOCUMENT media: the reference\n // resolves to the media's own key here, the media's kind wins,\n // and its facts (the cursor track in its space, the clicks,\n // the recorded page) ride the clip for the card painter.\n const lm = rec ? layerMedia(rec, o.key) : null\n const lmImage = lm\n ? lm.sourceKind === 'image'\n : o.kind === 'image'\n const lmCard = o.frame\n ? {\n bar: {\n kind: o.frame.browserBar?.kind ?? 'none',\n url:\n o.frame.browserBar?.url ??\n (lm ? pageDisplayUrl(lm.meta.pageUrl) : ''),\n showUrl: o.frame.browserBar?.showUrl ?? true,\n showControls: o.frame.browserBar?.showControls ?? true,\n height: o.frame.browserBar?.height ?? 44,\n ...(o.frame.browserBar?.theme\n ? { theme: o.frame.browserBar.theme }\n : {}),\n },\n lean: [\n round(o.frame.lean?.rx ?? 0),\n round(o.frame.lean?.ry ?? 0),\n ],\n shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,\n shadowContact: o.frame.shadowContact ?? 0,\n ...(o.frame.shadowColor\n ? { shadowColor: o.frame.shadowColor }\n : {}),\n }\n : null\n const lmFacts =\n lm && o.frame && o.frame.cursor !== false && lm.cursor.length\n ? (() => {\n const lfx = rec!.cursor.clickFx\n const sm = smoothCursor(lm.cursor, {\n factor: rec!.cursor.smoothing,\n clickSnap: lfx.style !== 'none' || lfx.press,\n })\n const clicks: { t: number; x: number; y: number }[] = []\n for (const ev of lm.cursor) {\n if (ev.type === 'down')\n clicks.push({\n t: round(ev.t / 1000),\n x: round(ev.x),\n y: round(ev.y),\n })\n }\n return {\n cur: {\n pts: sm.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n space: { w: lm.meta.width, h: lm.meta.height },\n size: rec!.cursor.size,\n clicks,\n },\n }\n })()\n : {}\n return {\n ...base,\n ...(lm ? { kind: lmImage ? 'image' : 'video' } : {}),\n key: lm ? lm.videoKey : o.key,\n ...(lm && lm.crop ? { crop: lm.crop } : {}),\n ...(lmCard ? { card: lmCard } : {}),\n ...lmFacts,\n w: round(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),\n radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,\n opacity: o.opacity ?? 1,\n loop: !!o.loop,\n // Emitted only when SET, so a doc without the\n // fields lowers byte-identically (ON_FRAME defaults absent\n // shadow to 'soft' — the baked look docs predating the field render).\n ...(o.shadow ? { shadow: o.shadow } : {}),\n ...(o.border && o.border.width > 0\n ? {\n border: {\n width: round(o.border.width),\n color: o.border.color,\n },\n }\n : {}),\n }\n }\n const st = resolveOverlayStyle(o)\n const bx = resolveOverlayBox(o)\n return {\n ...base,\n text: o.text,\n lines: overlayLines(o.text),\n fs: st.size,\n weight: st.weight,\n stack: st.stack,\n color: st.color,\n shadow: st.shadow,\n // Style-v2 fields bake only when non-default (byte parity for\n // older docs); ON_FRAME reads them unconditionally.\n ...(st.fontStyle === 'italic' ? { sty: 'italic' } : {}),\n ...(o.maxWidth ? { mw: round(o.maxWidth) } : {}),\n ...(st.letterSpacing ? { ls: round(st.letterSpacing) } : {}),\n ...(st.lineHeight !== OVERLAY_LINE_HEIGHT\n ? { lh: round(st.lineHeight) }\n : {}),\n ...(st.align !== 'center' ? { align: st.align } : {}),\n ...(st.stroke\n ? { stroke: { c: st.stroke.color, w: round(st.stroke.width) } }\n : {}),\n // Hosted face behind an override: ON_FRAME lazy-loads it so a\n // live family/weight edit paints without a LOAD (SETUP only\n // runs on cold load).\n ...(() => {\n const face = overlayFaceFor(o)\n return face\n ? { face: { f: face.family, w: face.weight, u: face.url } }\n : {}\n })(),\n // Background pill, resolved to design px at fs (absent = none;\n // conditional spread keeps box-less docs' data byte-identical).\n ...(bx\n ? {\n box: {\n c: bx.color,\n o: round(bx.opacity),\n px: round(bx.padX),\n py: round(bx.padY),\n r: round(bx.radius),\n },\n }\n : {}),\n // Entrance animation: segmentation + timing normalized\n // HERE (deterministic doc-derived data) — ON_FRAME interprets\n // per-unit progress as pure f(t). Absent = data byte parity.\n ...(() => {\n const olFx = resolveOverlayFx(\n o,\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n return olFx ? { fx: olFx } : {}\n })(),\n }\n }),\n }\n : {}),\n }\n}\n\nexport function lowerToComposition(input: ProjectDoc): LoweredComposition {\n // One vocabulary first: a legacy entrance, end card or clip spelling is\n // read into `anim` (the end card into clips after the footage plus a\n // card exit), so every track below is laid out against one shape.\n const doc = migrateMotion(input)\n const rated = ratedSegments(doc)\n // The boundaries that move (a clip's enter or exit, the card's slide at\n // the open), in output seconds; the camera rests through each window.\n const transitions = docTransitions(doc)\n // The footage's end, holds included; the output lasts until the last\n // visual clip ends, and past its footage the card holds its last frame.\n const footageEnd = durationSec(doc, rated)\n const duration = outputEnd(doc, footageEnd)\n // clickSnap only when effects are on, so an effects-off doc's path (and its\n // lowered data) stays byte-identical to the pre-click-effects lowering.\n const fx = doc.cursor.clickFx\n const smoothed = smoothCursor(doc.source.cursor, {\n factor: doc.cursor.smoothing,\n clickSnap: fx.style !== 'none' || fx.press,\n })\n // Idle fade (SOURCE-anchored, so trims/cuts/speed inherit it). Skipped when\n // the dot is hidden outright, and empty when nothing dwells long enough —\n // either way no `cursorFade` key is emitted and the data stays as it was.\n const cursorFade =\n doc.cursor.hideWhenIdle !== false && doc.cursor.visible !== false\n ? cursorIdleFade(doc.source.cursor, {\n space: { w: doc.source.meta.width, h: doc.source.meta.height },\n sourceDuration: (doc.source.meta.durationMs || 0) / 1000,\n })\n : []\n // Clamp each span's focus so the zoomed card always covers the canvas —\n // focusBounds is the same function the aiming overlay/inspector uses, so what\n // the editor shows and what renders can't disagree. Auto-focus spans get\n // their entry focus + dead-zone recenters baked from the cursor track\n // (followFocusEvents clamps internally).\n const layout = docCardLayout(doc)\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n // The frame's camera model: the stage camera never clamps (the window may\n // show the ground past the card), the magnifier always does.\n const camera = cameraModel(doc.frame)\n const zoomSpans = loweredZoomSpans(\n doc,\n rated,\n transitions,\n layout,\n camera,\n zoomStyle,\n )\n // The OUTPUT-time zoom track; a pull-out entrance writes its head.\n const zoomTrack = prependEntrance(\n zoomTrackFromDoc(zoomSpans, rated, zoomStyle),\n entranceZoomKeyframes(cardEnter(doc.frame)),\n )\n // Pinned layers: each resolved beside its referent through this camera.\n const pins = resolvePins(doc, layout, camera, zoomTrack)\n const pinsData = pinsDataOf(doc, pins)\n\n const data = {\n videoSrc: doc.source.videoKey,\n isImage: doc.source.sourceKind === 'image',\n // Viewport crop for window takes (drawImage source rect; null = full frame).\n crop: doc.source.crop ?? null,\n camSrc: doc.source.camKey ?? null,\n cam: doc.cam,\n // Cam pose spans: OUTPUT-time [x, y, size] fraction track built at\n // the doc's design layout (fractions are aspect-stable, so the track holds\n // at any render size). Omitted when the doc has no spans or no cam track —\n // byte parity with pre-MO docs.\n ...(doc.camMotion && doc.camMotion.length && doc.source.camKey\n ? {\n camTrack: camTrackFromDoc(doc.cam, doc.camMotion, rated, layout.W),\n }\n : {}),\n // Mic sidecar (AT split) — conditional spread keeps legacy docs' data\n // byte-identical; ON_FRAME reads both keys guarded.\n ...(doc.source.micKey\n ? { micSrc: doc.source.micKey, sysGain: doc.systemGain ?? 1 }\n : {}),\n hasAudio: !!doc.source.meta.hasAudio,\n duration,\n // Rated segments (speed spans pre-intersected): ON_FRAME's mapTime and the\n // export's audio splice read the rate straight off each segment.\n segments: rated.map((seg) => ({\n in: round(seg.in),\n out: round(seg.out),\n ...(seg.rate !== undefined && seg.rate !== 1\n ? { rate: round(seg.rate) }\n : {}),\n ...((seg as { media?: string }).media\n ? { media: (seg as { media?: string }).media }\n : {}),\n })),\n frame: doc.frame,\n micGain: doc.micGain ?? 1,\n cursor: smoothed.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n cursorStyle: doc.cursor,\n cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },\n // The other media (concat): each with its element's source, its crop\n // and its own cursor in its own capture space; absent = one media.\n ...(doc.media && doc.media.length\n ? {\n media: doc.media.map((m) => {\n const sm = smoothCursor(m.cursor, {\n factor: doc.cursor.smoothing,\n clickSnap: fx.style !== 'none' || fx.press,\n })\n const fade =\n doc.cursor.hideWhenIdle !== false && doc.cursor.visible !== false\n ? cursorIdleFade(m.cursor, {\n space: { w: m.meta.width, h: m.meta.height },\n sourceDuration: (m.meta.durationMs || 0) / 1000,\n })\n : []\n return {\n id: m.id,\n src: m.videoKey,\n isImage: m.sourceKind === 'image',\n crop: m.crop ?? null,\n hasAudio: !!m.meta.hasAudio,\n cursor: sm.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n cursorSpace: { w: m.meta.width, h: m.meta.height },\n // The card this media wears (its own placement, bar, corner,\n // shadow, border, fit and focus over the take's), resolved\n // here so ON_FRAME swaps one object and reads no document.\n frame: cardFields(mediaFrame(doc, m.id)),\n ...(fade.length ? { cursorFade: fade } : {}),\n }\n }),\n }\n : {}),\n ...(cursorFade.length ? { cursorFade } : {}),\n // The boundaries that move: ON_FRAME reads them by output time and\n // needs no document. Absent when none moves.\n ...(transitions.length ? { transitions } : {}),\n // Click effects: OUTPUT-anchored click records + resolved styling (named\n // intensities/colors become numbers HERE — ON_FRAME reads no registry).\n ...clickFxData(doc, rated),\n // Rated segments so zoom spans land at their speed-adjusted output times.\n zoomTrack,\n // Pinned layers' marks and leaders: the referent (normalised video\n // fractions) plus the leader's tip track, painted by ON_FRAME beside the\n // camera. Absent when no pinned layer asks for either — byte parity.\n ...(pinsData.length ? { pins: pinsData } : {}),\n // Tilt spans: OUTPUT-time [rx, ry] degree track. The rest pose is\n // FLAT — there is no static card tilt any more — and the motion constants\n // come from the camera style's tilt personality ('drift' slows its\n // leans, 'keynote' matches the zoom ramps). Omitted when the doc has no\n // spans and no tilt-in entrance — byte parity. The entrance is a\n // producer of this track, never a second pose.\n ...(() => {\n const head = entranceTiltKeyframes(cardEnter(doc.frame))\n const tilts = doc.tilt\n ? restSpansThroughTransitions(\n doc.tilt,\n rated,\n transitions,\n TILT_SPAN_MIN,\n )\n : []\n const spans = tilts.length\n ? tiltTrackFromDoc(tilts, rated, zoomStyle.tilt)\n : undefined\n const track = prependEntrance(spans, head)\n return track ? { tiltTrack: track } : {}\n })(),\n // The card's pose through its enter and its exit: [scale, dy, opacity]\n // in output seconds. Absent when the card carries neither.\n ...(() => {\n const track = cardPoseTrack(\n cardEnter(doc.frame),\n cardExit(doc.frame),\n footageEnd,\n duration,\n )\n return track ? { cardPoseTrack: track } : {}\n })(),\n }\n\n // The studio stack entry's OWN ctx.data (E0): the shared layers. The\n // recording anchor lights its props itself (`lights`); a program anchor's\n // entry carries no lights, its scene has its own.\n const entryData: Record<string, unknown> = {\n lights: true,\n ...studioLayerData(doc, duration, pins),\n }\n\n const config: Record<string, unknown> = {\n version: 2,\n // Placeholder — the carrier timeline reads ctx.data.duration, so the program\n // string stays constant across trims (see the interpreter-pattern note above).\n duration: PROGRAM_DURATION,\n // Compositor v2: a perspective camera so the world-space card plane can\n // TILT with real foreshortening. Every layer plane is sized to fill this\n // frustum (stage.ts), so tilt = 0 projects pixel-identically to the pre-v2\n // ortho 'fullscreen' quad. Camera sits at the origin looking down −z.\n camera: {\n preset: 'perspective',\n fov: CARD_FOV,\n near: CAMERA_NEAR,\n far: CAMERA_FAR,\n },\n data,\n setup: SETUP,\n createContent: CREATE_CONTENT,\n createTimeline: CREATE_TIMELINE,\n onFrame: ON_FRAME,\n stack: [studioEntry(entryData)],\n }\n\n return { config, data, stack: { [STUDIO_ENTRY_ID]: entryData }, duration }\n}\n\n/**\n * The zoom spans as the camera reads them: rested through the transition\n * windows, focus clamped under the frame's camera model, an auto-focus\n * span's entry and dead-zone recenters baked from the cursor track.\n */\nfunction loweredZoomSpans(\n doc: ProjectDoc,\n rated: Segment[],\n transitions: ReturnType<typeof docTransitions>,\n layout: CardLayout,\n camera: CameraModel,\n zoomStyle: ZoomStyleParams,\n): LoweredZoomSpan[] {\n const meta = doc.source.meta\n return restSpansThroughTransitions(\n doc.zoom,\n rated,\n transitions,\n ZOOM_SPAN_MIN,\n ).map((z) => {\n if (z.focusMode === 'auto') {\n const f = followFocusEvents(\n z,\n doc.source.cursor,\n { w: meta.width, h: meta.height },\n layout,\n {\n safeRatio: zoomStyle.followSafeRatio,\n recenter: zoomStyle.followRecenter,\n lookahead: zoomStyle.followLookahead,\n camera,\n },\n )\n if (f.entry)\n return { ...z, cx: f.entry.cx, cy: f.entry.cy, followEvents: f.events }\n }\n return {\n ...z,\n ...clampFocus(z.cx, z.cy, clampZoomLevel(z.level), layout, camera),\n }\n })\n}\n\n/**\n * The document's OUTPUT-time zoom track — the camera as ON_FRAME samples\n * it, for a host that needs the camera without lowering everything (a\n * pinned layer's on-canvas rect).\n */\nexport function docZoomTrack(\n input: ProjectDoc,\n): KeyframeTrack<number[]> | undefined {\n const doc = migrateMotion(input)\n const rated = ratedSegments(doc)\n const transitions = docTransitions(doc)\n const layout = docCardLayout(doc)\n const camera = cameraModel(doc.frame)\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const spans = loweredZoomSpans(\n doc,\n rated,\n transitions,\n layout,\n camera,\n zoomStyle,\n )\n return prependEntrance(\n zoomTrackFromDoc(spans, rated, zoomStyle),\n entranceZoomKeyframes(cardEnter(doc.frame)),\n )\n}\n\n/**\n * Every pinned layer's placement, by clip id. A pin that names nothing\n * (an unknown step, a step without a rect or a press) resolves to nothing\n * and the layer keeps its `transform` — the lint says why.\n */\nexport function resolvePins(\n doc: ProjectDoc,\n layout: CardLayout,\n camera: CameraModel,\n zoomTrack: KeyframeTrack<number[]> | undefined,\n): Map<string, PinPlacement> {\n const out = new Map<string, PinPlacement>()\n const rated = ratedSegments(doc)\n const space = { w: doc.source.meta.width, h: doc.source.meta.height }\n for (const o of doc.overlays ?? []) {\n if (!o.pin) continue\n const ref = pinReferent(doc, o.pin)\n if (!ref) continue\n const base = overlayMotionBase(o)\n const motion =\n o.motion && o.motion.length\n ? motionTrack(\n base,\n overlayMotionKeys(o, base),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n : null\n out.set(\n o.id,\n pinPlacement({\n clip: o,\n referent: ref.rect,\n layout,\n camera,\n zoomTrack,\n base,\n motion,\n carry: ref.carry ?? null,\n cursor: doc.source.cursor,\n space,\n rated,\n }),\n )\n }\n return out\n}\n\n/**\n * The steps an unpinned layer could name: every step with an element (a\n * rect, or presses inside its window) whose output window overlaps the\n * layer's, most overlap first. The studio's Pin-to select and the lint's\n * advice read this.\n */\nexport function pinCandidates(\n doc: ProjectDoc,\n clip: { start: number; duration: number },\n): PinCandidate[] {\n const rated = ratedSegments(doc)\n const out: PinCandidate[] = []\n const a0 = clip.start\n const a1 = clip.start + clip.duration\n for (const s of doc.source.meta.steps ?? []) {\n if (s.skipped) continue\n const ref = pinReferent(doc, { step: s.id ?? s.step })\n if (!ref) continue\n const ext = spanOutputExtent(rated, s.tStart, s.tEnd)\n if (!ext) continue\n const overlap = Math.min(a1, ext.end) - Math.max(a0, ext.start)\n if (overlap <= 0) continue\n out.push({\n step: s.id ?? s.step,\n do: s.do,\n ...(s.selector ? { selector: s.selector } : {}),\n output: { start: round(ext.start), end: round(ext.end) },\n overlap: round(overlap),\n })\n }\n return out.sort((x, y) => y.overlap - x.overlap)\n}\n\n/**\n * A pinned layer's effective [x, y, scale, rotation, opacity] at CLIP-LOCAL\n * time t — the host-side mirror the picking layer substitutes into the\n * clip's transform, the way overlayMotionPoseAt does for a `motion` clip.\n * Null when the clip is not pinned or its pin resolves to nothing.\n */\nexport function overlayPinPoseAt(\n doc: ProjectDoc,\n o: OverlayClip,\n t: number,\n): number[] | null {\n if (!o.pin) return null\n const layout = docCardLayout(doc)\n const camera = cameraModel(doc.frame)\n const pins = resolvePins(\n { ...doc, overlays: [o] },\n layout,\n camera,\n docZoomTrack(doc),\n )\n const p = pins.get(o.id)\n if (!p) return null\n return [...sample(p.track, t, lerpArray)]\n}\n\n/** The `pins` slice of ctx.data: one record per pinned layer that asks for a mark or a leader. */\nfunction pinsDataOf(\n doc: ProjectDoc,\n pins: ReadonlyMap<string, PinPlacement>,\n): Record<string, unknown>[] {\n const out: Record<string, unknown>[] = []\n for (const o of doc.overlays ?? []) {\n const p = o.pin\n const placed = p ? pins.get(o.id) : undefined\n if (!p || !placed) continue\n const mark = p.mark ?? 'none'\n if (mark === 'none' && !p.leader) continue\n const ref = pinReferent(doc, p)\n if (!ref) continue\n out.push({\n id: o.id,\n start: round(o.start),\n dur: round(Math.max(OVERLAY_MIN_DURATION, o.duration)),\n x: round4(ref.rect.x),\n y: round4(ref.rect.y),\n w: round4(ref.rect.w),\n h: round4(ref.rect.h),\n side: placed.side,\n mark,\n leader: !!p.leader,\n ...(p.color ? { color: p.color } : {}),\n // A carried referent (a drag): ON_FRAME moves the mark and the\n // leader's end with the pointer through [t0, t1], source seconds.\n ...(ref.carry\n ? {\n carry: {\n t0: round(ref.carry.t0),\n t1: round(ref.carry.t1),\n x0: round4(ref.carry.x0),\n y0: round4(ref.carry.y0),\n },\n }\n : {}),\n enter: enterKey(o),\n tip: placed.tip,\n })\n }\n return out\n}\n\nfunction round4(v: number): number {\n return Math.round(v * 100000) / 100000\n}\n\n/**\n * Click-effect slice of ctx.data: extracted OUTPUT-anchored clicks + the\n * doc's named style resolved to numbers (k/dur multipliers, [r,g,b] color).\n * With effects fully off the click list is skipped so the doc lowers as light\n * as before the feature.\n */\nfunction clickFxData(doc: ProjectDoc, rated: Segment[]) {\n const fx = doc.cursor.clickFx\n const on = fx.style !== 'none' || fx.press\n const meta = doc.source.meta\n const level = CLICK_FX_INTENSITY[fx.intensity]\n return {\n clicks: on\n ? [\n ...extractClicks(doc.source.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: meta.width, h: meta.height },\n }),\n // The other media's clicks map through their own pieces and carry\n // their own capture space, so the ring lands where the press was.\n ...(doc.media ?? []).flatMap((m) =>\n extractClicks(m.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: m.meta.width, h: m.meta.height },\n media: m.id,\n }).map((ck) => ({\n ...ck,\n sp: { w: m.meta.width, h: m.meta.height },\n })),\n ),\n ].sort((a, b) => a.ot - b.ot)\n : [],\n clickFx: {\n style: fx.style,\n press: fx.press,\n k: level.k,\n dur: level.dur,\n col: fx.color === 'auto' ? 0 : (hexToRgbTriplet(fx.color) ?? 0),\n },\n }\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Host-side mirror of ON_FRAME's card layout + the focus-space zoom clamp.\n *\n * ON_FRAME (lowerToComposition) is generated code — it computes the card rect\n * (padding, browser-bar strip, contain-fit) inline each frame. These pure\n * helpers duplicate that math for host consumers: focus clamping at lowering\n * time, and the preview focus-region overlay + the inspector's X/Y %\n * mapping. `layout.test.ts` pins the two implementations together — change\n * them TOGETHER or the overlay drifts from the rendered zoom.\n *\n * Clamping lives in normalized focus space (the OpenScreen trick): one bounds\n * function serves the camera, the overlay rect, and the % inputs, so \"0%\"\n * always means \"camera flush against the card edge\" at any zoom level.\n */\nimport {\n EXPORT_RESOLUTION_OPTIONS,\n aspectRatioValue,\n clampZoomLevel,\n resolveExportSize,\n} from './types'\nimport type {\n CamStyle,\n ExportResolution,\n FrameStyle,\n ProjectDoc,\n} from './types'\nimport { mediaFrame, mediaSource } from './media'\n\nexport interface CardLayout {\n /** canvas size the layout was computed for (comp px). */\n W: number\n H: number\n /** video destination rect, comp px. Contain: the fitted video. Cover\n * the cover-scaled video, positioned by frame.focus — it can\n * overflow the card, which crops it. */\n dx: number\n dy: number\n dw: number\n dh: number\n /** card rect = browser-bar strip + footage area — what the zoom must keep\n * covering. Contain: cardX/cardW equal dx/dw. Cover: the padded area\n * itself, which the video rect overflows. */\n cardX: number\n cardY: number\n cardW: number\n cardH: number\n}\n\n/**\n * Mirror of ON_FRAME's destination-rect math (see the \"video destination rect\"\n * block in lowerToComposition's ON_FRAME string). `video` is the source's\n * pixel size — only its aspect matters (contain-fit scales it).\n */\nexport function computeCardLayout(\n frame: FrameStyle,\n video: { width: number; height: number },\n W: number,\n H: number,\n /**\n * The zoom track's current focus, for a frame whose cover crop FOLLOWS\n * the camera (`focusFollow: 'camera'`); absent = the frame's own focus.\n */\n zoomFocus?: { cx: number; cy: number },\n): CardLayout {\n const s = H / 1080 // scale design-px controls to comp px (same rule as ON_FRAME)\n const pad = (frame.padding || 0) * s\n // Per-side placement (MIRRORS ON_FRAME): frame.inset fractions override\n // the symmetric padding on the sides they name; a negative side bleeds.\n const ins = frame.inset ?? {}\n const padL = ins.left == null ? pad : ins.left * W\n const padR = ins.right == null ? pad : ins.right * W\n const padT = ins.top == null ? pad : ins.top * H\n const padB = ins.bottom == null ? pad : ins.bottom * H\n const bar = frame.browserBar\n const vw = video.width || 16\n const vh = video.height || 9\n // Card-chrome scale (MIRRORS ON_FRAME — change together): card-owned sizes\n // (the browser bar here) shrink with the card when the frame is narrower\n // than the footage; exactly 1 at native/wider aspects, so native layouts are\n // untouched. Under cover the card is as wide as the frame allows, so cf = 1.\n const fitCover = frame.fit === 'cover'\n const cf = fitCover ? 1 : Math.min(1, W / H / (vw / vh))\n const barH = bar.kind !== 'none' ? (bar.height || 44) * s * cf : 0\n const availW = Math.max(1, W - padL - padR)\n const availH = Math.max(1, H - padT - padB - barH)\n if (fitCover) {\n // Cover (MIRRORS ON_FRAME): the padded area is the card; the video\n // cover-fills it, positioned by frame.focus and clamped gap-free.\n const sc = Math.max(availW / vw, availH / vh)\n const dw = vw * sc\n const dh = vh * sc\n const follow =\n frame.focusFollow === 'camera' && zoomFocus ? zoomFocus : null\n const fcx = clamp01(follow ? follow.cx : (frame.focus?.cx ?? 0.5))\n const fcy = clamp01(follow ? follow.cy : (frame.focus?.cy ?? 0.5))\n const vTop = padT + barH\n const dx = Math.min(\n padL,\n Math.max(padL + availW - dw, padL + availW / 2 - fcx * dw),\n )\n const dy = Math.min(\n vTop,\n Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh),\n )\n return {\n W,\n H,\n dx,\n dy,\n dw,\n dh,\n cardX: padL,\n cardY: padT,\n cardW: availW,\n cardH: availH + barH,\n }\n }\n const sc = Math.min(availW / vw, availH / vh)\n const dw = vw * sc\n const dh = vh * sc\n // Centred inside the inset area: (W - dw) / 2 when no side is inset.\n const dx = padL + (availW - dw) / 2\n const dy = padT + barH + (availH - dh) / 2\n return {\n W,\n H,\n dx,\n dy,\n dw,\n dh,\n cardX: dx,\n cardY: dy - barH,\n cardW: dw,\n cardH: dh + barH,\n }\n}\n\n/**\n * The doc's card layout at design size (H = 1080, W from the output aspect).\n * All layout terms scale linearly with the canvas at a fixed aspect, so the\n * normalized focus bounds computed from this layout hold at ANY render size.\n *\n * Viewport-cropped window takes need no special casing here: normalization\n * rewrote meta.captureWidth/Height to the CROP dims, which is exactly what\n * ON_FRAME uses as source dims when `d.crop` is set (crp.w/crp.h) — the\n * golden contract holds through the crop.\n */\nexport function docCardLayout(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'media'>,\n /** The media whose card to lay out; absent or `''` = the primary. */\n media?: string | null,\n): CardLayout {\n const meta = doc.source.meta\n const H = 1080\n const W = Math.max(\n 2,\n Math.round(H * aspectRatioValue(doc.frame.aspectRatio, meta)),\n )\n // A media wears its own card on the take's frame (mediaFrame), and its\n // own footage dimensions size it.\n const src = mediaSource(doc, media) ?? doc.source\n const sm = src.meta\n return computeCardLayout(\n mediaFrame(doc, media),\n {\n width: sm.captureWidth ?? sm.width,\n height: sm.captureHeight ?? sm.height,\n },\n W,\n H,\n )\n}\n\n/**\n * Largest export preset whose footage card still gets ≥1 captured px per output\n * px. The composited chrome (background, bar, cursor, effects) is vector-drawn\n * and real at any size; the footage layer is bounded by capture pixels, so\n * presets above this one upscale the footage (the picker labels them, never\n * hides them). Judged on the width of\n * the contain-fitted card at each preset's output size vs meta.captureWidth\n * (crop-space when a viewport crop applies — ingest rewrote meta to crop dims).\n * Floors at the smallest preset for tiny captures.\n */\nexport function recommendedExportResolution(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'export'>,\n): ExportResolution {\n const meta = doc.source.meta\n const capW = meta.captureWidth ?? meta.width\n const video = { width: capW, height: meta.captureHeight ?? meta.height }\n let best = EXPORT_RESOLUTION_OPTIONS[0]\n for (const r of EXPORT_RESOLUTION_OPTIONS) {\n const { width, height } = resolveExportSize(doc, r)\n // Card size grows linearly with output size at fixed aspect → first fail ends it.\n if (computeCardLayout(doc.frame, video, width, height).dw > capW) break\n best = r\n }\n return best\n}\n\nexport interface CamBubbleRect {\n /** top-left corner of the bubble's square, design px (docCardLayout space). */\n x: number\n y: number\n /** the square's side — the bubble diameter. */\n size: number\n /** corner radius (size/2 for circles, the fixed rounded radius otherwise). */\n radius: number\n}\n\n/**\n * Host-side mirror of ON_FRAME's webcam-bubble geometry (the \"webcam bubble\"\n * block in lowerToComposition) — the picking oracle for the on-canvas cam\n * layer. Same design space as docCardLayout (H = 1080), and like ON_FRAME the\n * bubble is FRAME-owned chrome: everything scales by s = H/1080, never the\n * card-chrome cf. The bubble ignores card tilt/zoom (it paints on the\n * screen-space overlay plane), so this rect is valid under any camera pose.\n * camDraw.test.ts pins this to the painted geometry — change them TOGETHER.\n */\nexport function camBubbleRect(\n cam: CamStyle,\n W: number,\n H = 1080,\n): CamBubbleRect {\n const s = H / 1080\n const size = Math.max(40, (cam.size || 0.25) * H)\n const mg = 24 * s\n // Free placement wins over the corner anchor — mirrors ON_FRAME.\n const x =\n cam.x != null\n ? cam.x * W - size / 2\n : cam.position.includes('right')\n ? W - mg - size\n : mg\n const y =\n cam.y != null\n ? cam.y * H - size / 2\n : cam.position.includes('top')\n ? mg\n : H - mg - size\n return {\n x,\n y,\n size,\n radius: cam.shape === 'rounded' ? (cam.radius ?? 18) * s : size / 2,\n }\n}\n\nexport interface FocusBounds {\n minX: number\n maxX: number\n minY: number\n maxY: number\n}\n\n/**\n * Focus bounds (normalized video coords) so the zoomed CARD covers the whole\n * canvas — the crop never reveals background past a card edge at full zoom.\n *\n * Derivation: ON_FRAME's transform maps content point p → f + (p − f)·L around\n * the focus anchor f (fx = dx + zx·dw), so the visible canvas [0, V] shows\n * content [f − f/L, f + (V − f)/L]. Requiring that window ⊆ the cover range\n * [o, o + c] and solving for the anchor (k = 1 − 1/L):\n *\n * f ≥ o / k and f ≤ (o + c − V/L) / k\n *\n * When the zoomed card is too small to cover the canvas (low level + padding),\n * the bounds cross — collapse to their midpoint (the least-uncovered focus;\n * 0.5 for a centered card, matching OpenScreen's margin collapse).\n */\nexport function focusBounds(\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n): FocusBounds {\n if (level <= 1.001) {\n // Identity transform — focus is irrelevant; pin to center like OpenScreen\n // (margin = min(0.5, ratio/2L) also collapses to [0.5, 0.5] at L = 1).\n return { minX: 0.5, maxX: 0.5, minY: 0.5, maxY: 0.5 }\n }\n if (camera === 'stage') {\n // The stage camera clamps only to the cover band: the card must still\n // cover the central CAMERA_COVER_MIN of the frame at the apex, so a\n // corner target sits near the centre with a strip of ground beside it\n // rather than half a frame of it (a centred corner would put the card's\n // edge at the centre).\n const x = stageAxisBounds(\n layout.dx,\n layout.dw,\n layout.cardX,\n layout.cardW,\n layout.W,\n level,\n )\n const y = stageAxisBounds(\n layout.dy,\n layout.dh,\n layout.cardY,\n layout.cardH,\n layout.H,\n level,\n )\n return { minX: x.min, maxX: x.max, minY: y.min, maxY: y.max }\n }\n const x = axisBounds(\n layout.dx,\n layout.dw,\n layout.cardX,\n layout.cardW,\n layout.W,\n level,\n )\n const y = axisBounds(\n layout.dy,\n layout.dh,\n layout.cardY,\n layout.cardH,\n layout.H,\n level,\n )\n return { minX: x.min, maxX: x.max, minY: y.min, maxY: y.max }\n}\n\n/**\n * One axis of focusBounds. The focus is normalized over the ANCHOR rect (the\n * video: cx/cy ∈ [0,1] of it) while coverage is demanded of the COVER rect\n * (the card — bar included, since bar + video zoom together).\n */\nfunction axisBounds(\n anchorOff: number,\n anchorSize: number,\n coverOff: number,\n coverSize: number,\n viewport: number,\n level: number,\n): { min: number; max: number } {\n const k = 1 - 1 / level\n let lo = (coverOff / k - anchorOff) / anchorSize\n let hi =\n ((coverOff + coverSize - viewport / level) / k - anchorOff) / anchorSize\n if (lo > hi) {\n const mid = (lo + hi) / 2\n lo = mid\n hi = mid\n }\n return { min: clamp01(lo), max: clamp01(hi) }\n}\n\n/**\n * The stage camera's axis bounds. With the focus centred, the frame's\n * central band of half-width `b = CAMERA_COVER_MIN / 2 · viewport` shows\n * content [f − b/L, f + b/L]; requiring that inside the cover range gives\n * f ∈ [o + b/L, o + c − b/L]. Bounds that cross (a level too low for the\n * band) collapse to their midpoint, like the magnifier's.\n */\nfunction stageAxisBounds(\n anchorOff: number,\n anchorSize: number,\n coverOff: number,\n coverSize: number,\n viewport: number,\n level: number,\n): { min: number; max: number } {\n const b = (CAMERA_COVER_MIN / 2) * viewport\n let lo = (coverOff + b / level - anchorOff) / anchorSize\n let hi = (coverOff + coverSize - b / level - anchorOff) / anchorSize\n if (lo > hi) {\n const mid = (lo + hi) / 2\n lo = mid\n hi = mid\n }\n return { min: clamp01(lo), max: clamp01(hi) }\n}\n\n/** Clamp a focus point into the bounds for its zoom level. */\nexport function clampFocus(\n cx: number,\n cy: number,\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n): { cx: number; cy: number } {\n const b = focusBounds(level, layout, camera)\n return {\n cx: Math.min(b.maxX, Math.max(b.minX, cx)),\n cy: Math.min(b.maxY, Math.max(b.minY, cy)),\n }\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\n\n/**\n * The zoom camera's model (FrameStyle.camera): the clamped magnifier every\n * existing document was cut with, or the stage camera a new take opens on.\n */\nexport type CameraModel = 'card' | 'stage'\n\n/** The frame's camera model; absent = the magnifier. */\nexport function cameraModel(\n frame: { camera?: string | null } | null | undefined,\n): CameraModel {\n return frame?.camera === 'stage' ? 'stage' : 'card'\n}\n\n/**\n * The stage camera's centring is a component of the zoom TRACK (0 at rest,\n * 1 at an apex, eased with the level), so the card's slide and its scale\n * are one motion. This band is the FALLBACK for a track lowered before the\n * component existed: centring as a smoothstep of the level over\n * 1 .. 1 + CAMERA_CENTRE_RAMP. Keyed to the level it front-loaded the\n * slide, since any ease crosses the band in its first frames (the twitch).\n */\nexport const CAMERA_CENTRE_RAMP = 0.3\n\n/**\n * The fraction of the frame (each axis, centred) the card must still cover\n * at a stage-camera apex. 0.8 leaves a strip of ground a tenth of the\n * frame wide beside a corner target, the composition a filmed card reads\n * as; 1 would be the magnifier's rule, 0 no clamp at all.\n */\nexport const CAMERA_COVER_MIN = 0.8\n\n/** 0..1: the stage camera's centring at `level` (MIRRORS ON_FRAME). */\nexport function cameraCentring(level: number): number {\n const u = clamp01((level - 1) / CAMERA_CENTRE_RAMP)\n return u * u * (3 - 2 * u)\n}\n\n/**\n * The zoom transform at one instant, in canvas px: content point p maps to\n * `(ox + (p.x − wcx)·level, oy + (p.y − wcy)·level)`. `fx/fy` is the focus\n * in canvas px; `wcx/wcy` is the content point that lands at the frame's\n * centre `ox/oy`. Under the magnifier `wc = f + (o − f) / L`, which is the\n * old \"scale about the focus\" transform written the long way (the focus\n * stays where it is on screen); under the stage camera that pull toward\n * the focus fades out with cameraCentring, so `wc → f` and the target sits\n * at the centre. ON_FRAME's zoom block mirrors this — change them together\n * (layout.test.ts pins the pair).\n */\nexport interface ZoomView {\n level: number\n fx: number\n fy: number\n wcx: number\n wcy: number\n ox: number\n oy: number\n}\n\nexport function zoomView(\n level: number,\n cx: number,\n cy: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n /**\n * The stage camera's centring at this instant, 0..1: the zoom track's\n * fourth component (0 at rest, 1 at an apex, eased with the level).\n * Absent = the apex (1) under the stage camera, which is what a span's\n * stored focus describes; always 0 under the magnifier.\n */\n centring?: number,\n): ZoomView {\n const L = Math.max(1, level)\n const fx = layout.dx + cx * layout.dw\n const fy = layout.dy + cy * layout.dh\n const ox = layout.W / 2\n const oy = layout.H / 2\n const t = camera === 'stage' ? clamp01(centring ?? 1) : 0\n const wcx = fx + ((ox - fx) / L) * (1 - t)\n const wcy = fy + ((oy - fy) / L) * (1 - t)\n return { level: L, fx, fy, wcx, wcy, ox, oy }\n}\n\n/**\n * The visible window as fractions of the canvas (x/y its top-left, w/h its\n * size, both 1/level): what the studio's aiming rect draws and what the\n * framing lint tests a click rect against. The window may leave the card\n * under the stage camera; it never does under the magnifier once the focus\n * is clamped.\n */\nexport function zoomViewport(\n level: number,\n cx: number,\n cy: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n centring?: number,\n): { x: number; y: number; w: number; h: number } {\n const v = zoomView(level, cx, cy, layout, camera, centring)\n const size = 1 / v.level\n return {\n x: (v.wcx - layout.W / (2 * v.level)) / layout.W,\n y: (v.wcy - layout.H / (2 * v.level)) / layout.H,\n w: size,\n h: size,\n }\n}\n\n/**\n * The inverse of zoomViewport's centre: the focus (normalized video coords)\n * that puts the visible window's centre at canvas fractions `cX/cY` at\n * `level`. The aiming rect's corner drag re-derives its focus through this\n * so the rect stays under the pointer in either camera model.\n */\nexport function focusForViewportCentre(\n cX: number,\n cY: number,\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n centring?: number,\n): { cx: number; cy: number } {\n const L = Math.max(1, level)\n const t = camera === 'stage' ? clamp01(centring ?? 1) : 0\n const a = 1 - (1 - t) / L\n if (a <= 1e-6) return { cx: 0.5, cy: 0.5 }\n const fx = (cX * layout.W - ((layout.W / 2) * (1 - t)) / L) / a\n const fy = (cY * layout.H - ((layout.H / 2) * (1 - t)) / L) / a\n return { cx: (fx - layout.dx) / layout.dw, cy: (fy - layout.dy) / layout.dh }\n}\n\n/**\n * The zoom level a focus rect of this canvas-fraction size\n * means — the aiming rect's size is purely 1/level, so a corner drag IS a\n * level drag, and this is the inverse. Floored a hair above the identity so\n * a drag can never reach level ≈ 1 and dismiss the aiming rect mid-gesture;\n * clamped and quantized like every stored level (clampZoomLevel).\n */\nexport function levelForFocusFraction(frac: number): number {\n if (!(frac > 0)) return clampZoomLevel(Number.POSITIVE_INFINITY)\n return clampZoomLevel(Math.max(1.1, 1 / frac))\n}\n","/**\n * Compositor v2 — the layer stage geometry.\n *\n * The stage turns the studio's single fullscreen-ortho quad into a three-layer mesh stack\n * under ONE perspective camera:\n *\n * overlay quad screen-space cam bubble + text/image/video overlays\n * card mesh world-space the existing 2D card painting, on a plane\n * that can TILT (doc.tilt spans)\n * background quad screen-space CSS fill + vos background loop\n *\n * Every layer is a plane placed perpendicular to the camera axis and centered\n * on it, sized to exactly fill the camera frustum at its depth. Perpendicular +\n * centered + frustum-filling ⇒ it projects to the full viewport regardless of\n * depth, so the background/overlay read as flat screen-space and the CARD, at\n * `tilt = 0`, projects PIXEL-IDENTICALLY to today's ortho fullscreen quad. Only\n * the card ever rotates; the perspective camera then gives it real\n * foreshortening (an ortho camera would only skew it).\n *\n * These are pure helpers (no THREE dependency) so the host can mirror the exact\n * projection the runtime draws with — the world-unit basis that\n * on-canvas picking builds on (host picks / instance renders). `stage.test.ts`\n * pins the math; the runtime (lowerToComposition CREATE_CONTENT/ON_FRAME) must\n * use these SAME constants — change them together.\n */\n\n/**\n * Camera field of view (degrees). Deliberately gentle (telephoto-ish product\n * shot) so a card tilt reads as a premium 3D lean, not a fisheye warp. Parity\n * at tilt = 0 is INDEPENDENT of this value (every layer is sized to fill the\n * frustum), so it is a pure aesthetic dial for how dramatic tilt looks.\n */\nexport const CARD_FOV = 30\n\n/**\n * Layer depths (world units in front of a camera at the origin looking down\n * −z). Absolute values are arbitrary — only the ORDER matters (painter's order\n * is set by renderOrder, not depth) and that near/far bracket them. The card\n * sits between the background (behind) and the overlay (in front).\n */\nexport const OVERLAY_Z = -2\nexport const CARD_Z = -4\nexport const BACKGROUND_Z = -6\n\n/**\n * The card layer's overscan is quantised UP to this step, so a pose that is\n * being scrubbed grows the canvas a few times, not once per tick (a resize\n * reallocates the texture and the plane).\n */\nexport const CARD_OVERSCAN_STEP = 0.05\n\n/**\n * The most the card layer will grow on an axis. The schema's 45° tilt\n * needs about 2.7 on a 16:9 frame and 3.8 on 21:9; past that the card's\n * receding edge nears the horizon and no finite texture covers it.\n */\nexport const CARD_OVERSCAN_MAX = 4\n\nexport const CAMERA_NEAR = 0.1\nexport const CAMERA_FAR = 100\n\n/**\n * The card mesh's pose, as ON_FRAME sets it: Euler XYZ rotation in DEGREES\n * (`rx` about the horizontal axis, `ry` about the vertical), a uniform\n * `scale` about the frame's centre and a rise `dy` as a fraction of the\n * frame plane's height (the card-pose track's [scale, dy] pair).\n */\nexport interface CardPose {\n rx: number\n ry: number\n scale?: number\n dy?: number\n}\n\n/**\n * How far past each frame edge the card plane is SEEN under a pose, as a\n * multiple of the frame plane's half-size on that axis (1 = the plane's own\n * edge at rest). Each frame corner's viewing ray is intersected with the\n * posed plane and read back in the plane's local units: a receding side\n * shows more plane than the frame is wide, an approaching side less, a\n * smaller card shows more all round. The visible region is the frame's\n * image under a homography, so its bounding box is its four corners'.\n * A corner whose ray no longer meets the plane in front of the camera\n * reads as `CARD_OVERSCAN_MAX`.\n *\n * This is the TypeScript twin of ON_FRAME's card-overscan math (the\n * `okx`/`oky` block in lowerToComposition) — `stage.test.ts` pins the two\n * to a three.js raycast; change them together.\n */\nexport function cardVisibleExtent(\n pose: CardPose,\n aspect: number,\n fovDeg = CARD_FOV,\n): { left: number; right: number; top: number; bottom: number } {\n const D = Math.abs(CARD_Z)\n const hh = D * Math.tan((fovDeg * Math.PI) / 180 / 2)\n const hw = hh * aspect\n const rx = (pose.rx * Math.PI) / 180\n const ry = (pose.ry * Math.PI) / 180\n const sc = Math.max(0.05, pose.scale ?? 1)\n const py = (pose.dy ?? 0) * 2 * hh\n const cx = Math.cos(rx)\n const sx = Math.sin(rx)\n const cy = Math.cos(ry)\n const sy = Math.sin(ry)\n // R = Rx(rx) · Ry(ry), three.js's Euler 'XYZ' with rz = 0, by column.\n const c0 = [cy, sx * sy, -cx * sy]\n const c1 = [0, cx, sx]\n const n = [sy, -sx * cy, cx * cy] // the plane's normal, R · ẑ\n const nDotPos = n[1] * py - n[2] * D\n let left = 0\n let right = 0\n let top = 0\n let bottom = 0\n for (const kx of [-1, 1]) {\n for (const ky of [-1, 1]) {\n const d = [kx * hw, ky * hh, -D]\n const nd = n[0] * d[0] + n[1] * d[1] + n[2] * d[2]\n const lam = nd === 0 ? -1 : nDotPos / nd\n if (!(lam > 0)) {\n left = right = top = bottom = CARD_OVERSCAN_MAX\n continue\n }\n const q = [lam * d[0], lam * d[1] - py, lam * d[2] + D]\n const lx = (c0[0] * q[0] + c0[1] * q[1] + c0[2] * q[2]) / sc / hw\n const ly = (c1[0] * q[0] + c1[1] * q[1] + c1[2] * q[2]) / sc / hh\n left = Math.max(left, -lx)\n right = Math.max(right, lx)\n top = Math.max(top, ly)\n bottom = Math.max(bottom, -ly)\n }\n }\n const cap = (v: number) => Math.min(CARD_OVERSCAN_MAX, v)\n return {\n left: cap(left),\n right: cap(right),\n top: cap(top),\n bottom: cap(bottom),\n }\n}\n\n/** Quantise an overscan factor UP to the step, never below 1. */\nexport function quantiseOverscan(k: number): number {\n if (!(k > 1)) return 1\n // In thousandths, so a value a hair under a step never rounds across it.\n const q =\n Math.ceil(\n Math.round((k + CARD_OVERSCAN_STEP / 10) * 1000) /\n (CARD_OVERSCAN_STEP * 1000),\n ) * CARD_OVERSCAN_STEP\n return Math.min(CARD_OVERSCAN_MAX, Math.round(q * 1000) / 1000)\n}\n\n/**\n * The card layer's overscan budget for a composition: the factor its canvas\n * and plane grow by on each axis, about the frame's centre, so that at\n * every pose the two tracks can reach the frame sees painted card and\n * never the texture's edge. The budget is the extent at the tracks'\n * EXTREMES (the largest lean on each axis, the smallest scale, the\n * largest rise, every sign), which bounds every interpolated frame because\n * the extent grows with each of them. `[1, 1]` for a card that never moves,\n * which is pixel-identical to a plane that fills the frame.\n *\n * Twin of the ON_FRAME block; see `cardVisibleExtent`.\n */\nexport function cardOverscanFor(\n tilt: { keyframes: { value: number[] }[] } | null | undefined,\n pose: { keyframes: { value: number[] }[] } | null | undefined,\n aspect: number,\n fovDeg = CARD_FOV,\n): [number, number] {\n let rxM = 0\n let ryM = 0\n for (const k of tilt?.keyframes ?? []) {\n rxM = Math.max(rxM, Math.abs(k.value[0] || 0))\n ryM = Math.max(ryM, Math.abs(k.value[1] || 0))\n }\n let scMin = 1\n let dyM = 0\n for (const k of pose?.keyframes ?? []) {\n scMin = Math.min(scMin, k.value[0] ?? 1)\n dyM = Math.max(dyM, Math.abs(k.value[1] || 0))\n }\n if (rxM === 0 && ryM === 0 && scMin >= 1 && dyM === 0) return [1, 1]\n let kx = 1\n let ky = 1\n for (const sr of [-1, 1]) {\n for (const sy of [-1, 1]) {\n for (const sd of [-1, 1]) {\n const e = cardVisibleExtent(\n { rx: sr * rxM, ry: sy * ryM, scale: scMin, dy: sd * dyM },\n aspect,\n fovDeg,\n )\n kx = Math.max(kx, e.left, e.right)\n ky = Math.max(ky, e.top, e.bottom)\n }\n }\n }\n return [quantiseOverscan(kx), quantiseOverscan(ky)]\n}\n\nexport interface PlaneSize {\n width: number\n height: number\n}\n\n/**\n * The world-space size of a plane that exactly fills a perspective camera's\n * frustum at `|distance|` in front of it. Height subtends the full vertical FOV;\n * width follows the viewport aspect. This is the one sizing primitive the whole\n * stack shares — every layer plane, and the host-side projection basis for\n * picking, derive from it.\n */\nexport function planeSizeAtDepth(\n distance: number,\n fovDeg: number,\n aspect: number,\n): PlaneSize {\n const height = 2 * Math.abs(distance) * Math.tan((fovDeg * Math.PI) / 180 / 2)\n return { width: height * aspect, height }\n}\n\n/**\n * Project a point on the (untilted) card plane, given in normalized card-canvas\n * coordinates (u, v ∈ [0,1], v measured from the TOP like a canvas), to\n * normalized screen coordinates (sx, sy ∈ [0,1], sy from the top). At tilt = 0\n * this is the identity — the card fills the viewport — so it is exact for the\n * common case and the basis the tilt/camera matrices extend for\n * on-canvas picking. Kept here so host and runtime never disagree on\n * where the card is.\n */\nexport function cardPointToScreen(\n u: number,\n v: number,\n): { sx: number; sy: number } {\n return { sx: u, sy: v }\n}\n","/**\n * Text-overlay presets + the host-side geometry mirror (compositor v2).\n *\n * Presets are the HOUSE text styles (Lexend for content, JetBrains Mono for\n * labels — the design-system families) and are RESOLVED AT LOWERING into plain\n * numbers/strings in ctx.data, so ON_FRAME reads no registry (the\n * MINIMAL_BAR_THEMES rule). ON_FRAME builds its canvas font string as\n * `weight + ' ' + size·scale·s + 'px ' + stack` — `overlayFontString` mirrors\n * that exactly and `overlayRect` mirrors the drawn bounding box, giving the\n * studio's on-canvas picking the same geometry the renderer paints\n * (host picks / instance renders). `overlayText.test.ts` pins the\n * mirrors to the generated code — change them together.\n *\n * Fonts load in SETUP from assets.vos.so (the self-hosted catalog — the\n * render fleet can only fetch that origin) via the FontFace API, ONLY when the doc has overlays, capped +\n * fail-open (a CDN failure degrades to the system stack, never a dead render).\n */\nimport { overlayFxSpec } from './anim'\nimport {\n findFontFamily,\n fontFaceUrl,\n fontStack,\n nearestFontWeight,\n} from '@vosjs/shared'\nimport { htmlLayerPictureBox, htmlLayerWidth } from './htmlLayer'\nimport {\n OVERLAY_LINE_HEIGHT,\n OVERLAY_MEDIA_DEFAULT_WIDTH,\n OVERLAY_TRANSITION_DUR,\n} from './types'\nimport type {\n OverlayClip,\n ProjectDoc,\n TextFxUnit,\n TextOverlayClip,\n TextOverlayPreset,\n TextOverlayStroke,\n} from './types'\n\n/** Text-box (background pill) defaults, EMs of the resolved font size. */\nexport const OVERLAY_BOX_PAD_X = 0.6\nexport const OVERLAY_BOX_PAD_Y = 0.35\nexport const OVERLAY_BOX_RADIUS = 0.25\n\n/** Baked pill geometry: design px at the clip's resolved font size. */\nexport interface ResolvedOverlayBox {\n color: string\n opacity: number\n /** Paddings/radius in design px (em multiples × resolved size). */\n padX: number\n padY: number\n radius: number\n}\n\n/**\n * Resolve a clip's background pill (null when absent). Mirrored by\n * `overlayRect`'s inflation and ON_FRAME's pill draw — change together.\n */\nexport function resolveOverlayBox(\n clip: TextOverlayClip,\n): ResolvedOverlayBox | null {\n if (!clip.box) return null\n const size = resolveOverlayStyle(clip).size\n return {\n color: clip.box.color,\n opacity: clip.box.opacity ?? 1,\n padX: (clip.box.paddingX ?? OVERLAY_BOX_PAD_X) * size,\n padY: (clip.box.paddingY ?? OVERLAY_BOX_PAD_Y) * size,\n radius: (clip.box.radius ?? OVERLAY_BOX_RADIUS) * size,\n }\n}\n\n/** A preset's base values (the 5-field house style). */\nexport interface OverlayPresetStyle {\n /** Full CSS font-family stack (primary + fallbacks). */\n stack: string\n weight: number\n /** Font size in design px (H = 1080 space), before transform.scale. */\n size: number\n color: string\n /** Legibility shadow strength 0..1 (0 = none). */\n shadow: number\n}\n\n/** Preset base + the full override surface, resolved to concrete values. */\nexport interface ResolvedOverlayStyle extends OverlayPresetStyle {\n fontStyle: 'normal' | 'italic'\n align: 'left' | 'center' | 'right'\n /** Design px at the resolved size. */\n letterSpacing: number\n /** Multiplier (default OVERLAY_LINE_HEIGHT). */\n lineHeight: number\n stroke: TextOverlayStroke | null\n}\n\n/** The catalog family each preset's stack leads with (for weight snapping). */\nconst PRESET_FAMILY: Record<TextOverlayPreset, string> = {\n title: 'Lexend',\n caption: 'Lexend',\n label: 'JetBrains Mono',\n}\n\n/** The house text styles. Sizes in design px; colors are ink-on-footage. */\nexport const TEXT_PRESETS: Record<TextOverlayPreset, OverlayPresetStyle> = {\n title: {\n stack: 'Lexend, -apple-system, system-ui, sans-serif',\n weight: 600,\n size: 64,\n color: '#fafafa',\n shadow: 0.45,\n },\n caption: {\n stack: 'Lexend, -apple-system, system-ui, sans-serif',\n weight: 400,\n size: 32,\n color: '#f4f4f5',\n shadow: 0.4,\n },\n label: {\n stack: \"'JetBrains Mono', ui-monospace, SFMono-Regular, monospace\",\n weight: 400,\n size: 22,\n color: '#e4e4e7',\n shadow: 0.35,\n },\n}\n\n/** Size override bounds (design px) — same range the inspector slider offers. */\nexport const OVERLAY_SIZE_MIN = 12\nexport const OVERLAY_SIZE_MAX = 200\n\n/**\n * woff2 faces SETUP preloads when the doc has overlays (latin subset only —\n * overlay text is product UI copy). The three base preset faces, resolved\n * through the hosted catalog so this module carries no URL of its own.\n */\nexport const OVERLAY_FONT_FACES: {\n family: string\n weight: number\n url: string\n}[] = [\n { family: 'Lexend', weight: 400, url: fontFaceUrl('lexend', 400) },\n { family: 'Lexend', weight: 600, url: fontFaceUrl('lexend', 600) },\n {\n family: 'JetBrains Mono',\n weight: 400,\n url: fontFaceUrl('jetbrains-mono', 400),\n },\n]\n\n/** Preset + per-clip overrides → the concrete style baked into ctx.data. */\nexport function resolveOverlayStyle(\n clip: TextOverlayClip,\n): ResolvedOverlayStyle {\n // Defensive lookup: agent-authored doc.json can carry an unknown preset name.\n const presetName = clip.preset in TEXT_PRESETS ? clip.preset : 'title'\n const base = TEXT_PRESETS[presetName]\n\n // Family/weight resolve against the hosted catalog. A catalog family swaps\n // the whole stack (category-true fallbacks); an unknown family fails open —\n // used verbatim ahead of the preset stack, so a locally-installed font\n // still previews while the fleet degrades to the preset. Weights snap to\n // hosted steps: canvas cannot synthesize weights.\n let stack = base.stack\n let weight = base.weight\n const familyEntry = findFontFamily(clip.family ?? PRESET_FAMILY[presetName])\n if (clip.family) {\n const quoted = clip.family.includes(' ') ? `'${clip.family}'` : clip.family\n stack = familyEntry ? fontStack(familyEntry) : `${quoted}, ${base.stack}`\n }\n if (clip.weight !== undefined || clip.family) {\n const wanted = clip.weight ?? base.weight\n weight = familyEntry ? nearestFontWeight(familyEntry, wanted) : wanted\n }\n\n return {\n ...base,\n stack,\n weight,\n ...(clip.size !== undefined\n ? {\n size: Math.min(\n OVERLAY_SIZE_MAX,\n Math.max(OVERLAY_SIZE_MIN, clip.size),\n ),\n }\n : {}),\n ...(clip.color ? { color: clip.color } : {}),\n ...(clip.shadow !== undefined\n ? { shadow: Math.min(1, Math.max(0, clip.shadow)) }\n : {}),\n fontStyle: clip.italic ? 'italic' : 'normal',\n align: clip.align ?? 'center',\n letterSpacing: clip.letterSpacing ?? 0,\n lineHeight: clip.lineHeight ?? OVERLAY_LINE_HEIGHT,\n stroke: clip.stroke ?? null,\n }\n}\n\nexport interface OverlayFontFace {\n family: string\n weight: number\n url: string\n}\n\n/**\n * The hosted face a clip's family/weight overrides resolve to, when it is\n * NOT one of the three base preset faces (null otherwise — parity: preset\n * clips carry nothing). Baked per-overlay so ON_FRAME can lazy-load it on a\n * live style edit (SET_DATA never re-runs SETUP); SETUP awaits the full list\n * from ctx.data on cold load, which is what export parity rides on.\n */\nexport function overlayFaceFor(clip: TextOverlayClip): OverlayFontFace | null {\n const entry = findFontFamily(\n clip.family ??\n PRESET_FAMILY[clip.preset in TEXT_PRESETS ? clip.preset : 'title'],\n )\n if (!entry) return null // unknown family: nothing hosted to load\n const weight = resolveOverlayStyle(clip).weight\n const inBase = OVERLAY_FONT_FACES.some(\n (f) => f.family === entry.family && f.weight === weight,\n )\n if (inBase) return null\n return {\n family: entry.family,\n weight,\n url: fontFaceUrl(entry.slug, weight),\n }\n}\n\n/**\n * Every woff2 face a doc's overlays need (SETUP await on cold load, and the\n * host document for measurement): the three base preset faces — ALWAYS, byte\n * parity for preset-only docs — plus one face per override.\n */\nexport function overlayFontFaces(\n doc: Pick<ProjectDoc, 'overlays'>,\n): OverlayFontFace[] {\n const faces = [...OVERLAY_FONT_FACES]\n const seen = new Set(faces.map((f) => `${f.family}|${f.weight}`))\n for (const o of doc.overlays ?? []) {\n if (o.kind !== 'text') continue\n const face = overlayFaceFor(o)\n if (!face) continue\n const key = `${face.family}|${face.weight}`\n if (seen.has(key)) continue\n seen.add(key)\n faces.push(face)\n }\n return faces\n}\n\nexport function overlayLines(text: string): string[] {\n const lines = text.split('\\n')\n return lines.length ? lines : ['']\n}\n\n/**\n * Word tokens with trailing whitespace preserved — the ONE tokenization\n * wrap and fx share (`overlaySegments`' word case): wrapped lines are\n * token concatenations, so char/word unit sequences are byte-identical\n * wrapped or not.\n */\nexport function overlayTokens(line: string): string[] {\n return line.match(/\\S+\\s*/g) ?? [line]\n}\n\n/**\n * Greedy token wrap at measured widths — the HOST mirror of ON_FRAME's\n * wrap (change together; overlayText.test.ts pins them). Explicit \\n lines\n * wrap independently; a token wider than the budget gets its own line.\n * Measures include each token's trailing space (the token IS the unit),\n * which over-counts the trailing gap at wrap points by design — identical\n * on both sides of the mirror, so geometry agrees.\n */\nexport function wrapOverlayLines(\n lines: string[],\n measure: (text: string) => number,\n maxPx: number,\n): string[] {\n if (!(maxPx > 0)) return lines\n const out: string[] = []\n for (const line of lines) {\n if (!line || measure(line) <= maxPx) {\n out.push(line)\n continue\n }\n let current = ''\n for (const token of overlayTokens(line)) {\n if (!current) {\n current = token\n continue\n }\n if (measure(current + token) <= maxPx) {\n current += token\n } else {\n out.push(current)\n current = token\n }\n }\n if (current) out.push(current)\n }\n return out.length ? out : ['']\n}\n\n// ---------------------------------------------------------------------------\n// Entrance animation. Segmentation happens HERE, at lowering, because\n// it is deterministic doc-derived data: ON_FRAME stays a pure interpreter\n// over baked units and seek stays f(t) (chunk cold-seeks agree by\n// construction). Units never cross line breaks.\n// ---------------------------------------------------------------------------\n\n/** Grapheme-safe char split; plain code-point split when Segmenter is absent. */\nfunction graphemesOf(line: string): string[] {\n if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {\n return [\n ...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(\n line,\n ),\n ].map((s) => s.segment)\n }\n return [...line]\n}\n\n/**\n * Per-line unit arrays for a fx spec. `word` units keep their trailing\n * whitespace (a typewriter reveals \"Hello \" then \"world\" with stable\n * geometry); `line` units are the lines themselves. `block` has NO per-unit\n * segmentation — ON_FRAME animates the whole clip through the normal\n * per-line draw (fillText cannot render '\\n'), which is exactly the legacy\n * enter behaviour generalized.\n */\nexport function overlaySegments(text: string, unit: TextFxUnit): string[][] {\n const lines = overlayLines(text)\n if (unit === 'block') return []\n if (unit === 'line') return lines.map((l) => [l])\n if (unit === 'word') return lines.map(overlayTokens)\n return lines.map(graphemesOf)\n}\n\n/** The baked fx payload ON_FRAME interprets (short keys — it rides ctx.data). */\nexport interface BakedOverlayFx {\n /** fx kind. */\n k: 'fade' | 'rise' | 'pop' | 'blur' | 'typewriter'\n /** unit granularity ('block' behaves exactly like the legacy enter). */\n u: TextFxUnit\n /** direction: 0 forward, 1 reverse, 2 center-out. */\n d: 0 | 1 | 2\n /** effective stagger seconds (clamped so the entrance fits the clip). */\n st: number\n /** per-unit duration seconds. */\n dur: number\n /** total entrance seconds — the redraw gate's animation window. */\n tt: number\n /** per-LINE unit arrays (empty for block — the whole clip animates). */\n units: string[][]\n /** total unit count across lines. */\n n: number\n}\n\nconst FX_DIR: Record<string, 0 | 1 | 2> = { forward: 0, reverse: 1, center: 2 }\n\n/**\n * Normalize a clip's fx spec into the baked payload. Pure function of the\n * doc (determinism is the contract): defaults resolve here, stagger clamps\n * so `stagger·(n−1) + duration` never exceeds ~90% of the clip.\n */\nexport function resolveOverlayFx(\n clip: TextOverlayClip,\n clipDuration: number,\n): BakedOverlayFx | null {\n // The step on `anim`, or the legacy `fx` field for a clip read unmigrated.\n const spec = overlayFxSpec(clip) ?? clip.fx ?? null\n if (!spec) return null\n const unit = spec.unit ?? 'block'\n const units = overlaySegments(clip.text, unit)\n const n =\n unit === 'block' ? 1 : units.reduce((sum, line) => sum + line.length, 0)\n // Wrapping happens at DRAW time (it needs measurement), so with maxWidth\n // active a 'line' unit means WRAPPED lines — lowering can't know how\n // many, but it CAN bound them: wrapped lines never exceed word tokens.\n // The bound drives the stagger clamp and the redraw-gate window (tt);\n // actual per-line delays regroup in ON_FRAME.\n const upperN =\n unit === 'line' && clip.maxWidth\n ? overlayLines(clip.text).reduce(\n (sum, line) => sum + overlayTokens(line).length,\n 0,\n )\n : n\n const typewriter = spec.fx === 'typewriter'\n // Typewriter is a step reveal: the per-unit duration is irrelevant, keep\n // it tiny so the last unit lands with the stagger, not 0.35s after it.\n const dur = typewriter\n ? 0.001\n : Math.min(2, Math.max(0.05, spec.duration ?? OVERLAY_TRANSITION_DUR))\n const defaultStagger = typewriter ? 0.05 : unit === 'block' ? 0 : 0.06\n let st = Math.min(2, Math.max(0, spec.stagger ?? defaultStagger))\n if (upperN > 1) {\n const maxTotal = Math.max(dur, clipDuration * 0.9)\n st = Math.min(st, Math.max(0, (maxTotal - dur) / (upperN - 1)))\n }\n const round = (v: number) => Math.round(v * 10000) / 10000\n return {\n k: spec.fx,\n u: unit,\n d: FX_DIR[spec.direction ?? 'forward'] ?? 0,\n st: round(st),\n dur: round(dur),\n tt: round(st * (upperN - 1) + dur),\n units,\n n,\n }\n}\n\n/**\n * The canvas font string at a given comp scale — MIRRORS ON_FRAME's\n * `olW + ' ' + olPx + 'px ' + olStack` (pinned by test).\n */\nexport function overlayFontString(\n style: ResolvedOverlayStyle,\n scale: number,\n s: number,\n): string {\n const italic = style.fontStyle === 'italic' ? 'italic ' : ''\n return `${italic}${style.weight} ${style.size * scale * s}px ${style.stack}`\n}\n\nexport interface OverlayRect {\n /** Center-anchored box in DESIGN px (pre-rotation). */\n cx: number\n cy: number\n w: number\n h: number\n /** Rotation in degrees (the caller rotates points into local space to hit-test). */\n rotation: number\n}\n\n/**\n * The drawn bounding box of a text overlay in DESIGN px — the picking\n * geometry. `measure(text, font)` returns the text width in px for a font\n * string (the host passes a scratch-canvas measureText; tests stub it).\n * `frameW`/`frameH` are the design frame size (docCardLayout's W/H) — the\n * clip's transform.x/y are FRACTIONS of the frame, so the anchor is\n * x·frameW / y·frameH. Measured at s = 1 (design space), so the result maps\n * to the canvas by ·s and to CSS by the player's display scale.\n */\nexport function overlayRect(\n clip: OverlayClip,\n measure: (text: string, font: string, letterSpacingPx?: number) => number,\n frameW: number,\n frameH = 1080,\n /** Media kinds: natural aspect (w/h) once known — null/absent = assume 16:9. */\n mediaAspect?: number | null,\n): OverlayRect {\n const scale = clip.transform.scale || 1\n const base = {\n cx: clip.transform.x * frameW,\n cy: clip.transform.y * frameH,\n rotation: clip.transform.rotation || 0,\n }\n if (clip.kind === 'html') {\n // An HTML layer's aspect is its own PICTURE box (the design box plus its\n // bleed), known without probing anything, and its width defaults to the\n // design size. MIRRORS the lowering's `htmlLayerPayload`.\n const pb = htmlLayerPictureBox(clip)\n const w = htmlLayerWidth(clip) * frameW * scale\n return { ...base, w, h: w * (pb.height / pb.width) }\n }\n if (clip.kind !== 'text') {\n // MIRRORS ON_FRAME's media sizing: width fraction of the FRAME × scale,\n // height from the media's natural aspect.\n const w = (clip.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH) * frameW * scale\n return { ...base, w, h: w / (mediaAspect || 16 / 9) }\n }\n const style = resolveOverlayStyle(clip)\n const font = overlayFontString(style, scale, 1)\n const ls = style.letterSpacing * scale\n // Wrap BEFORE measuring the block — mirrors ON_FRAME's wrap (maxWidth is\n // a frame-width fraction; this rect works in design px, so the budget is\n // maxWidth × frameW directly).\n const lines = clip.maxWidth\n ? wrapOverlayLines(\n overlayLines(clip.text),\n (t) => measure(t, font, ls),\n clip.maxWidth * frameW,\n )\n : overlayLines(clip.text)\n let w = 0\n for (const line of lines) w = Math.max(w, measure(line, font, ls))\n const lineH = style.size * scale * style.lineHeight\n // The background pill extends the drawn (and thus pickable) bounds —\n // mirrors ON_FRAME's pill geometry exactly.\n const box = resolveOverlayBox(clip)\n const padX = box ? box.padX * scale : 0\n const padY = box ? box.padY * scale : 0\n return {\n ...base,\n w: Math.max(w, style.size * scale * 0.6) + padX * 2, // empty text still selectable\n h: lines.length * lineH + padY * 2,\n }\n}\n\n/** Point-in-overlay test (design px), rotation-aware (point → local space). */\nexport function overlayHit(\n rect: OverlayRect,\n px: number,\n py: number,\n padPx = 8,\n): boolean {\n let dx = px - rect.cx\n let dy = py - rect.cy\n if (rect.rotation) {\n const a = (-rect.rotation * Math.PI) / 180\n const rx = dx * Math.cos(a) - dy * Math.sin(a)\n const ry = dx * Math.sin(a) + dy * Math.cos(a)\n dx = rx\n dy = ry\n }\n return (\n Math.abs(dx) <= rect.w / 2 + padPx && Math.abs(dy) <= rect.h / 2 + padPx\n )\n}\n","/**\n * An HTML layer: a UI component authored as markup and CSS, laid out and\n * painted by the browser itself, composited over the footage like a picture.\n *\n * WHY. A product video points at a piece of UI, and the camera is the only\n * pointing device a recording has: a zoom magnifies recorded pixels, so the\n * thing the video is about becomes the blurriest thing on screen. A layer\n * rasterized FROM SOURCE at the size each frame asks for has no such ceiling\n * (measured ~4x sharper than the same component magnified at 6x).\n *\n * HOW. An SVG carrying a `<foreignObject>` lays out real HTML with real CSS,\n * and an `<img>` pointed at that SVG rasterizes it. Two rules force the shape\n * everything here serves. A foreignObject SVG is origin-clean ONLY as a\n * `data:` URI (served from a URL it taints the canvas even same-origin, and a\n * tainted frame cannot be uploaded as a WebGL texture, so every layer vanishes\n * at once). And an SVG loaded through `<img>` may fetch NOTHING: a\n * `@font-face` naming a URL is silently ignored and the text falls back. So\n * the document keeps the SOURCE (a few hundred bytes), the page fetches the\n * faces by ordinary `fetch` and inlines them, and what the compositor draws\n * is an inline, vector, origin-clean picture.\n *\n * ONE COMPOSER. `htmlLayerSvg` here is the SVG document. The page builder in\n * `lower/studioEntry.ts` is a function STRING and cannot import it, so it\n * carries a mirror (`HTML_LAYER_SVG_CODE`), pinned byte-for-byte by\n * `htmlOverlay.test.ts`. Change them together.\n *\n * Pure: the studio, the CLI's lint and a test get the same answer for the\n * same source.\n */\nimport { findFontFamily, fontFaceUrl, nearestFontWeight } from '@vosjs/shared'\nimport type { HtmlOverlayClip, HtmlOverlayFont } from './types'\n\n/** The design frame an HTML layer's `box` is measured against (1080p). */\nexport const DESIGN_FRAME_WIDTH = 1920\n\n/** Past this many bytes of markup plus CSS the lint warns, and past the max it refuses. */\nexport const HTML_LAYER_WARN_BYTES = 32_000\nexport const HTML_LAYER_MAX_BYTES = 128_000\n\n/** A face the page inlines: a URL the builder fetches, or already bytes. */\nexport interface HtmlLayerFace {\n family: string\n url: string\n weight: number\n style: 'normal' | 'italic'\n}\n\n/** What the composer takes: the source and the faces AS BYTES. */\nexport interface HtmlLayerSource {\n html: string\n css?: string\n box: { width: number; height: number }\n bleed?: number\n /**\n * A LIVE layer (§3.13): the picture is a function of clip-local time.\n * `t` is the moment being drawn, `data` the clip's own values for the\n * `{{data.<name>}}` placeholders. Absent = a still.\n */\n live?: boolean\n t?: number\n data?: Record<string, string | number | boolean>\n}\nexport interface HtmlLayerInlineFace {\n family: string\n weight: number\n style: 'normal' | 'italic'\n dataUri: string\n}\n/** An image the source names by URL, fetched and inlined by the page. */\nexport interface HtmlLayerInlineAsset {\n url: string\n dataUri: string\n}\n\n/**\n * The rate a live layer is re-rasterized at: clip-local time is quantised\n * to this grid, so a frame asks for one picture and two frames at the same\n * grid step share it. 60 Hz covers every export rate the product offers.\n * MIRRORED in `HTML_LAYER_LIVE_HZ_CODE`'s user in `lower/studioEntry.ts`.\n */\nexport const HTML_LAYER_LIVE_HZ = 60\n\n/** The host an SVG image's page can fetch an asset from (CORS and the fleet). */\nexport const HTML_LAYER_ASSET_HOST = 'https://assets.vos.so/'\n\n// ---------------------------------------------------------------- faces --\n\n/**\n * Split a CSS value on the commas that separate its LIST ITEMS, not the\n * ones inside `rgba(...)` or `url(...)`.\n */\nfunction splitTopLevel(value: string): string[] {\n const out: string[] = []\n let depth = 0\n let cur = ''\n for (const ch of value) {\n if (ch === '(') depth++\n else if (ch === ')') depth--\n if (ch === ',' && depth === 0) {\n out.push(cur)\n cur = ''\n } else cur += ch\n }\n if (cur.trim()) out.push(cur)\n return out\n}\n\nconst unquote = (s: string): string =>\n s.trim().replace(/^['\"]/, '').replace(/['\"]$/, '').trim()\n\n/** The families the CSS names, in order, deduped, generic families dropped. */\nconst GENERIC_FAMILIES = new Set([\n 'serif',\n 'sans-serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'system-ui',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'emoji',\n 'math',\n 'fangsong',\n 'inherit',\n 'initial',\n 'unset',\n])\n\nexport function cssFamilies(css: string | undefined): string[] {\n if (!css) return []\n const out: string[] = []\n const seen = new Set<string>()\n for (const decl of css.match(/font-family\\s*:[^;}]+/gi) ?? []) {\n for (const item of splitTopLevel(decl.replace(/^[^:]*:/, ''))) {\n const name = unquote(item.replace(/!important/i, ''))\n if (!name) continue\n const lower = name.toLowerCase()\n if (GENERIC_FAMILIES.has(lower) || lower.startsWith('-apple-')) continue\n if (seen.has(lower)) continue\n seen.add(lower)\n out.push(name)\n }\n }\n return out\n}\n\n/**\n * The weights the CSS and the markup ask for: `font-weight` numbers and\n * keywords (`bold` is 700, `normal` 400), plus 700 when the markup carries a\n * `<b>`, `<strong>` or a heading, because the browser sets those bold. 400\n * is always in the list: it is what an unweighted run sets.\n */\nexport function cssWeights(css: string | undefined, html = ''): number[] {\n const out = new Set<number>([400])\n for (const decl of css?.match(/font-weight\\s*:[^;}]+/gi) ?? []) {\n const value = decl.replace(/^[^:]*:/, '').toLowerCase()\n for (const n of value.match(/\\b[1-9]00\\b/g) ?? []) out.add(Number(n))\n if (/\\bbold(er)?\\b/.test(value)) out.add(700)\n }\n if (/<(b|strong|h[1-6])[\\s>]/i.test(html)) out.add(700)\n return [...out].sort((a, b) => a - b)\n}\n\n/**\n * The faces the page inlines for a layer: every family the CSS names that\n * the CATALOG hosts, at every weight the source asks for (snapped to the\n * steps the catalog hosts, deduped), then the clip's explicit `fonts` for a\n * face outside the catalog. Nobody types a catalog URL: the CSS names the\n * family and this finds it, the same way a text clip's `family` resolves.\n * A family the catalog does not host resolves to nothing here and falls back\n * to the wrapper's system stack in the page, which is what `vos validate`\n * warns about.\n */\nexport function htmlLayerFaces(\n clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'fonts'>,\n): HtmlLayerFace[] {\n const faces: HtmlLayerFace[] = []\n const seen = new Set<string>()\n const add = (f: HtmlLayerFace) => {\n const key = `${f.family.toLowerCase()}|${f.weight}|${f.style}`\n if (seen.has(key)) return\n seen.add(key)\n faces.push(f)\n }\n const weights = cssWeights(clip.css, clip.html)\n for (const family of cssFamilies(clip.css)) {\n const entry = findFontFamily(family)\n if (!entry) continue\n for (const w of weights)\n add({\n family: entry.family,\n url: fontFaceUrl(entry.slug, nearestFontWeight(entry, w)),\n weight: nearestFontWeight(entry, w),\n style: 'normal',\n })\n }\n for (const f of clip.fonts ?? [])\n add({\n family: f.family,\n url: f.url,\n weight: f.weight ?? 400,\n style: f.style ?? 'normal',\n })\n return faces\n}\n\n/** The families the CSS names that the catalog does NOT host and no explicit font covers. */\nexport function htmlLayerUnhostedFamilies(\n clip: Pick<HtmlOverlayClip, 'css' | 'fonts'>,\n): string[] {\n const explicit = new Set(\n (clip.fonts ?? []).map((f) => f.family.toLowerCase()),\n )\n return cssFamilies(clip.css).filter(\n (f) => !findFontFamily(f) && !explicit.has(f.toLowerCase()),\n )\n}\n\n// --------------------------------------------------------------- assets --\n\n/**\n * The images the source names by URL: `src=\"https://…\"` in the markup and\n * `url(https://…)` in the CSS. An SVG loaded through <img> fetches nothing,\n * so the page fetches these the way it fetches faces and the composer\n * swaps each URL for its bytes. Deduped, in source order. Only a host the\n * page can fetch (CORS) and the fleet can reach counts as inlinable; the\n * lint says so about any other.\n */\nexport function htmlLayerAssetUrls(\n clip: Pick<HtmlOverlayClip, 'html' | 'css'>,\n): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n const add = (u: string) => {\n if (!seen.has(u)) {\n seen.add(u)\n out.push(u)\n }\n }\n // As written in the source (the composer swaps the literal), a\n // protocol-relative `//host/…` included: it resolves against the page.\n for (const m of clip.html.matchAll(\n /\\bsrc\\s*=\\s*[\"']((?:https?:)?\\/\\/[^\"']+)[\"']/gi,\n ))\n add(m[1])\n for (const text of [clip.html, clip.css ?? ''])\n for (const m of text.matchAll(\n /url\\(\\s*[\"']?((?:https?:)?\\/\\/[^\"')\\s]+)[\"']?\\s*\\)/gi,\n ))\n add(m[1])\n return out\n}\n\n/** The named assets the page can inline, and the ones it cannot. */\nexport function htmlLayerAssets(clip: Pick<HtmlOverlayClip, 'html' | 'css'>): {\n inlinable: string[]\n foreign: string[]\n} {\n const inlinable: string[] = []\n const foreign: string[] = []\n for (const u of htmlLayerAssetUrls(clip))\n (u.replace(/^\\/\\//, 'https://').startsWith(HTML_LAYER_ASSET_HOST)\n ? inlinable\n : foreign\n ).push(u)\n return { inlinable, foreign }\n}\n\n// ---------------------------------------------------------------- bleed --\n\n/**\n * How much room the CSS's shadows need outside the component's box, in\n * design px: the furthest any shadow reaches (offset plus blur plus spread),\n * rounded up with a margin.\n *\n * The SVG viewport clips hard. A card that fills its box exactly gets its\n * shadow sliced off at the edge, and because a shadow is usually offset\n * downward it pools in the lower corners, so the layer's rounded corners\n * come out as a grey square instead of transparency (measured: corner alpha\n * 6, 6 and 32 with no bleed, 0 everywhere with it). A rough upper bound read\n * off the declarations, not a CSS parser: over-reserving costs a few\n * transparent pixels, under-reserving is the clipped-corner bug.\n */\nexport function htmlLayerBleedFor(css: string | undefined): number {\n if (!css) return 0\n let worst = 0\n const consider = (layer: string) => {\n // An inset shadow paints inside the box and needs no room outside it.\n if (/\\binset\\b/i.test(layer)) return\n // Drop colours before reading lengths: a colour carries numbers of its\n // own, and a unitless 0 is a legal length, so they cannot be told apart\n // after.\n const lengths = layer\n .replace(/[a-z-]+\\([^)]*\\)/gi, ' ')\n .replace(/#[0-9a-f]{3,8}/gi, ' ')\n .match(/-?\\d*\\.?\\d+/g)\n if (!lengths) return\n const [x = 0, y = 0, blur = 0, spread = 0] = lengths.map(Number)\n worst = Math.max(\n worst,\n Math.abs(x) + blur + spread,\n Math.abs(y) + blur + spread,\n )\n }\n for (const decl of css.match(/box-shadow\\s*:[^;}]+/gi) ?? [])\n for (const layer of splitTopLevel(decl.replace(/^[^:]*:/, '')))\n consider(layer)\n // `filter` reaches outside the box only through drop-shadow(); a blur() or\n // a brightness() stays within it.\n for (const fn of css.match(/drop-shadow\\(([^)]*)\\)/gi) ?? [])\n consider(fn.replace(/^drop-shadow\\(/i, '').replace(/\\)$/, ''))\n return worst ? Math.ceil(worst * 1.15) : 0\n}\n\n/** The bleed a layer renders with: the stated one, else what its CSS needs. */\nexport function htmlLayerBleed(\n clip: Pick<HtmlOverlayClip, 'css' | 'bleed'>,\n): number {\n return Math.max(0, Math.round(clip.bleed ?? htmlLayerBleedFor(clip.css)))\n}\n\n// ------------------------------------------------------------- geometry --\n\n/**\n * The picture's box: the component plus its bleed on every side. This is\n * what `width` sizes and what the hit rect measures, because the bleed is\n * part of the rasterized picture (transparent, but there).\n */\nexport function htmlLayerPictureBox(\n clip: Pick<HtmlOverlayClip, 'box' | 'css' | 'bleed'>,\n): { width: number; height: number; bleed: number } {\n const bleed = htmlLayerBleed(clip)\n return {\n width: clip.box.width + bleed * 2,\n height: clip.box.height + bleed * 2,\n bleed,\n }\n}\n\n/**\n * The layer's width as a fraction of the FRAME width (the media overlay\n * convention; ON_FRAME paints `w · W`). Absent, the DESIGN SIZE: the picture\n * box over 1920, so a component designed at 452 px lands at 452 px in a\n * 1080p frame and scales with the export. The bleed is in the picture, so it\n * is in the default too, or a shadowed component would land narrower than\n * it was designed.\n */\nexport function htmlLayerWidth(\n clip: Pick<HtmlOverlayClip, 'width' | 'box' | 'css' | 'bleed'>,\n): number {\n if (clip.width != null) return clip.width\n return Math.min(1, htmlLayerPictureBox(clip).width / DESIGN_FRAME_WIDTH)\n}\n\n// ---------------------------------------------------------------- label --\n\nconst ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n}\n\n/** The words the markup carries, for a lane label: tags stripped, entities read. */\nexport function htmlLayerText(html: string): string {\n return html\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<[^>]*>/g, ' ')\n .replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);/gi, (m, ref: string) => {\n if (ref[0] === '#')\n return String.fromCodePoint(\n ref[1] === 'x' ? parseInt(ref.slice(2), 16) : parseInt(ref.slice(1)),\n )\n return ENTITIES[ref.toLowerCase()] ?? m\n })\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** The timeline item's label: the first words, else the kind. */\nexport function htmlLayerLabel(clip: Pick<HtmlOverlayClip, 'html'>): string {\n const words = htmlLayerText(clip.html)\n return words ? words.slice(0, 24) : 'HTML'\n}\n\n// -------------------------------------------------------------- problems --\n\nexport type HtmlLayerProblemCode =\n | 'empty'\n | 'unclosed-void'\n | 'bare-ampersand'\n | 'unquoted-attr'\n | 'too-large'\n | 'foreign-field'\n | 'shadow-clipped'\n | 'external-resource'\n | 'wall-clock'\n | 'live-delay'\n | 'placeholder-still'\n | 'unhosted-family'\n\n/** `{{t}}` or `{{data.<name>}}` anywhere in a text. */\nexport const HTML_LAYER_PLACEHOLDER = /\\{\\{\\s*(t|data\\.[A-Za-z0-9_.-]+)\\s*\\}\\}/\n\nexport interface HtmlLayerProblem {\n code: HtmlLayerProblemCode\n /** A problem stops the layer painting; a warning is a layer that paints wrong. */\n level: 'problem' | 'warning'\n message: string\n}\n\n/** XML rules HTML forgives and `foreignObject` does not. */\nconst VOID_TAGS = [\n 'br',\n 'img',\n 'hr',\n 'input',\n 'source',\n 'col',\n 'area',\n 'base',\n 'wbr',\n 'meta',\n 'link',\n]\n\n/** Media-clip fields an HTML layer refuses: its CSS is its chrome. */\nexport const HTML_LAYER_FOREIGN_FIELDS = [\n 'key',\n 'radius',\n 'shadow',\n 'border',\n 'loop',\n 'frame',\n 'fx',\n 'text',\n 'preset',\n] as const\n\n/**\n * Everything wrong with a layer that can be seen in its source, in words. A\n * malformed document does not warn in the page: the `<img>` fails to decode,\n * the layer never paints, and nothing anywhere says why. Checked here so an\n * author is told instead, the same answer in the studio, in `vos validate`\n * and in a test.\n */\nexport function htmlLayerProblems(\n clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'bleed' | 'fonts' | 'live'>,\n): HtmlLayerProblem[] {\n const html = clip.html ?? ''\n const css = clip.css\n const raw = clip as unknown as Record<string, unknown>\n const problems: HtmlLayerProblem[] = []\n const push = (\n code: HtmlLayerProblemCode,\n level: HtmlLayerProblem['level'],\n message: string,\n ) => problems.push({ code, level, message })\n\n if (!html.trim()) push('empty', 'problem', 'The layer has no markup.')\n\n for (const tag of VOID_TAGS) {\n const open = new RegExp(`<${tag}(\\\\s[^>]*?)?>`, 'gi')\n let m: RegExpExecArray | null\n let said = false\n while ((m = open.exec(html))) {\n if (!m[0].trimEnd().endsWith('/>') && !said) {\n said = true\n push(\n 'unclosed-void',\n 'problem',\n `<${tag}> must close itself as <${tag} /> inside a foreignObject.`,\n )\n }\n }\n }\n const bare = html.match(/&(?!(#\\d+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);)/g)\n if (bare)\n push(\n 'bare-ampersand',\n 'problem',\n `A bare \"&\" is a parse error; write &. Found ${bare.length}.`,\n )\n if (/<[a-zA-Z][^>]*?\\s[a-zA-Z-]+=(?![\"'])[^\\s>]+/.test(html))\n push(\n 'unquoted-attr',\n 'problem',\n 'Attribute values must be quoted inside a foreignObject.',\n )\n\n const bytes = html.length + (css?.length ?? 0)\n if (bytes > HTML_LAYER_MAX_BYTES)\n push(\n 'too-large',\n 'problem',\n `The layer's source is ${Math.round(bytes / 1000)} KB; the ceiling is ${HTML_LAYER_MAX_BYTES / 1000} KB. Inline less, or split the layer.`,\n )\n else if (bytes > HTML_LAYER_WARN_BYTES)\n push(\n 'too-large',\n 'warning',\n `The layer's source is ${Math.round(bytes / 1000)} KB. A document carries it twice (the doc and the composed config); keep a layer under ${HTML_LAYER_WARN_BYTES / 1000} KB.`,\n )\n\n for (const field of HTML_LAYER_FOREIGN_FIELDS)\n if (raw[field] !== undefined)\n push(\n 'foreign-field',\n 'problem',\n `An HTML layer has no \"${field}\": its CSS is its chrome (shadow, radius, border) and its markup its content.`,\n )\n\n // A shadow with no room to land is clipped square by the SVG viewport, so\n // the layer's rounded corners come out grey. Only a STATED bleed can be\n // too small: an absent one is derived from the CSS at lowering.\n const needed = htmlLayerBleedFor(css)\n if (clip.bleed != null && needed > clip.bleed)\n push(\n 'shadow-clipped',\n 'warning',\n `The CSS paints up to ${needed}px outside the component and bleed is ${clip.bleed}, so the shadow is clipped square at the layer's edge. Set bleed to ${needed}, or leave it out to derive it.`,\n )\n\n // An SVG loaded through <img> fetches nothing, so the page fetches what\n // the source names by URL and inlines it, the way it inlines faces. It\n // can do that only from the host the page may read (CORS) and the fleet\n // can reach; anything else paints as a blank.\n for (const u of htmlLayerAssets({ html, css }).foreign)\n push(\n 'external-resource',\n 'warning',\n `The source names ${u} by URL. The page inlines only ${HTML_LAYER_ASSET_HOST}… (the host it may fetch, and the one the fleet reaches), so this will paint blank. Upload it (vos asset upload) or inline it as a data: URI.`,\n )\n if (/\\bhref\\s*=\\s*[\"']\\s*(https?:)?\\/\\//i.test(html))\n push(\n 'external-resource',\n 'warning',\n 'The source names a stylesheet or a link by URL. An SVG image fetches no href; inline the styles in the CSS.',\n )\n\n // A CSS animation inside a STILL layer runs on the WALL CLOCK from decode\n // time, never on the video's t: an export that cold-seeks catches it at an\n // arbitrary phase and never matches the preview. A LIVE layer scrubs the\n // same keyframes to t (paused, delayed by -t), which is the honest form;\n // it overrides an authored delay to do so.\n const animated = /\\b(animation|transition)(-[a-z-]+)?\\s*:/i.test(css ?? '')\n if (animated && !clip.live)\n push(\n 'wall-clock',\n 'warning',\n \"CSS animations inside a layer run on the wall clock, not the timeline: the export will not match the preview. Set live: true to scrub them to the clip's time, or animate the layer with motion and anim.\",\n )\n if (clip.live && /\\banimation-delay\\s*:/i.test(css ?? ''))\n push(\n 'live-delay',\n 'warning',\n 'A live layer scrubs its keyframes by setting animation-delay to -t, so the authored animation-delay is overridden. Fold the delay into the keyframes instead.',\n )\n if (\n !clip.live &&\n (HTML_LAYER_PLACEHOLDER.test(html) ||\n HTML_LAYER_PLACEHOLDER.test(css ?? ''))\n )\n push(\n 'placeholder-still',\n 'warning',\n 'The source carries {{t}} or {{data.…}} placeholders, which fill only on a live layer (live: true); a still layer shows them as written.',\n )\n\n for (const family of htmlLayerUnhostedFamilies(clip))\n push(\n 'unhosted-family',\n 'warning',\n `The CSS names \"${family}\", which is not a hosted family (GET https://vos.so/api/fonts); the system stack stands in.`,\n )\n\n return problems\n}\n\n/**\n * The real XML question, asked of a real parser: the studio hands in\n * `DOMParser`, a test hands in a stub. The CLI does not call this (Node\n * ships no parser) and relies on the heuristics above plus the page's own\n * decode report.\n */\nexport function wellFormedXml(\n svg: string,\n parseXml: (text: string) => {\n getElementsByTagName: (name: string) => ArrayLike<{\n textContent: string | null\n }>\n },\n): { ok: true } | { ok: false; error: string } {\n const doc = parseXml(svg)\n const errors = doc.getElementsByTagName('parsererror')\n if (!errors.length) return { ok: true }\n const text = (errors[0].textContent ?? '').replace(/\\s+/g, ' ').trim()\n return { ok: false, error: text || 'The markup is not well-formed XML.' }\n}\n\n// -------------------------------------------------------------- composer --\n\n/** `@font-face` blocks for the inlined faces, or '' when there are none. */\nfunction fontFaceCss(faces: readonly HtmlLayerInlineFace[]): string {\n let out = ''\n for (const f of faces)\n out +=\n `@font-face{font-family:'${f.family}';` +\n `src:url(${f.dataUri}) format('woff2');` +\n `font-weight:${f.weight};` +\n `font-style:${f.style};` +\n `font-display:block}`\n return out\n}\n\n/** Clip-local time as it is written into the picture: up to 3 decimals, no trailing zeros. */\nconst fmtT = (t: number): string => String(Math.round(t * 1000) / 1000)\n\n/** A placeholder's value, escaped for XML (a value is data, never markup). */\nconst escapeXml = (v: string | number | boolean): string =>\n String(v)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n\n/**\n * The SVG document for a layer: self-contained, laid out in DESIGN units\n * (the viewBox), so the browser rasterizes it at whatever size a frame asks\n * for. The wrapper fixes the box, insets the component by its bleed and\n * resets the two properties an SVG document does not inherit sensibly;\n * everything else is the author's.\n *\n * Assets the source names by URL are swapped for their bytes. A LIVE layer\n * is composed for one moment: `{{t}}` and `{{data.<name>}}` fill, and a\n * scrub block after the author's CSS pauses every animation and delays it\n * by -t, so the browser lays the frame out at exactly that offset (the\n * standard way to scrub a CSS animation, here applied per picture).\n *\n * MIRRORED by `HTML_LAYER_SVG_CODE` in `lower/studioEntry.ts`. Change both.\n */\nexport function htmlLayerSvg(\n src: HtmlLayerSource,\n faces: readonly HtmlLayerInlineFace[] = [],\n assets: readonly HtmlLayerInlineAsset[] = [],\n): string {\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 const t = src.t ?? 0\n const fill = (text: string): string => {\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(\n /\\{\\{\\s*data\\.([A-Za-z0-9_.-]+)\\s*\\}\\}/g,\n (_m, name: string) => {\n const v = src.data ? src.data[name] : undefined\n return v == null ? '' : escapeXml(v)\n },\n )\n }\n return out\n }\n const scrub = src.live\n ? `.vos-html-layer{--vos-t:${fmtT(t)}}` +\n `.vos-html-layer,.vos-html-layer *{animation-play-state:paused!important;` +\n `animation-delay:calc(var(--vos-t) * -1s)!important}`\n : ''\n const wrapper =\n `.vos-html-layer{width:${boxW}px;height:${boxH}px;` +\n `box-sizing:border-box;margin:0;padding:${bleed}px;` +\n `font-family:-apple-system,system-ui,sans-serif;` +\n `-webkit-font-smoothing:antialiased;text-rendering:geometricPrecision}`\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" ` +\n `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>${fontFaceCss(faces)}${wrapper}${fill(src.css ?? '')}${scrub}</style>` +\n fill(src.html) +\n `</div></foreignObject></svg>`\n )\n}\n\n// ------------------------------------------------------------------- key --\n\n/** FNV-1a over a string: cheap, stable, and enough to tell two sources apart. */\nfunction fnv1a(s: string): string {\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193) >>> 0\n }\n return h.toString(36)\n}\n\n/**\n * The layer's CACHE KEY: content-addressed over everything the picture is\n * made of (markup, CSS, box, bleed, the resolved faces), so an edit to the\n * source lands on a new key and invalidates the picture cache, the build\n * guard and the overlay canvas's dirty signature at once. Moving, retiming,\n * scaling or animating the clip does not re-key it: none of those repaint.\n * The key is a handle, never a URL: nothing may fetch it.\n */\nexport function htmlLayerKey(\n clip: Pick<HtmlOverlayClip, 'id' | 'html' | 'css' | 'box' | 'live' | 'data'>,\n faces: readonly HtmlLayerFace[],\n bleed: number,\n assets: readonly string[] = [],\n): string {\n const src =\n clip.html +\n '\u0000' +\n (clip.css ?? '') +\n '\u0000' +\n clip.box.width +\n 'x' +\n clip.box.height +\n '\u0000' +\n bleed +\n '\u0000' +\n faces.map((f) => `${f.family}:${f.url}:${f.weight}:${f.style}`).join('|') +\n ' ' +\n assets.join('|') +\n // A live layer's picture is also a function of t: ON_FRAME appends the\n // moment to this key per frame. The data rides here, the moment there.\n (clip.live ? ' live ' + JSON.stringify(clip.data ?? null) : '')\n return `html:${clip.id}:${fnv1a(src)}`\n}\n\n/** The shape the page is handed for a layer: the source plus resolved faces. */\nexport interface HtmlLayerPayload {\n markup: string\n css: string\n box: { width: number; height: number }\n bleed: number\n faces: HtmlLayerFace[]\n /** Images the source names by URL, for the page to fetch and inline. */\n assets: string[]\n /** A live layer: the page composes the picture per frame at clip-local t. */\n live?: true\n data?: Record<string, string | number | boolean>\n}\n\n/** Everything the lowering emits for a layer, from the clip alone. */\nexport function htmlLayerPayload(clip: HtmlOverlayClip): {\n key: string\n width: number\n html: HtmlLayerPayload\n} {\n const faces = htmlLayerFaces(clip)\n const bleed = htmlLayerBleed(clip)\n const assets = htmlLayerAssets(clip).inlinable\n return {\n key: htmlLayerKey(clip, faces, bleed, assets),\n width: htmlLayerWidth(clip),\n html: {\n markup: clip.html,\n css: clip.css ?? '',\n box: { width: clip.box.width, height: clip.box.height },\n bleed,\n faces,\n assets,\n ...(clip.live\n ? { live: true as const, ...(clip.data ? { data: clip.data } : {}) }\n : {}),\n },\n }\n}\n\nexport type { HtmlOverlayFont }\n","/**\n * 3D text — lowering-side resolution, mirroring how text presets\n * resolve in overlayText.ts: the doc carries intent (typeface slug, material\n * preset name), the baked data carries plain values (URL + constructor\n * params), and ON_FRAME stays a generic interpreter with no registry.\n *\n * Material presets are FLEET-AUDITED: everything single-sided (THREE's\n * default FrontSide — DoubleSide on a transmission material hard-hangs\n * SwiftShader), no `dispersion` (blows preview-job deadlines), transmission\n * only single-sided. Keep new presets inside those constraints.\n */\nimport { DEFAULT_TYPEFACE_SLUG, findTypeface, typefaceUrl } from '@vosjs/shared'\nimport type { ObjectAsset, Text3dMaterial } from './types'\n\nexport const TEXT3D_DEPTH_DEFAULT = 0.25\nexport const TEXT3D_DEPTH_MIN = 0.02\nexport const TEXT3D_DEPTH_MAX = 1\n\nexport interface BakedText3dMaterial {\n /** THREE constructor family: MeshStandardMaterial | MeshPhysicalMaterial. */\n type: 'standard' | 'physical'\n params: Record<string, unknown>\n}\n\nexport interface BakedText3dAsset {\n kind: 'text3d'\n text: string\n /** Resolved typeface JSON URL (assets.vos.so — the fleet's one origin). */\n url: string\n /** Extrusion depth as a fraction of the glyph height. */\n depth: number\n bevel: boolean\n mat: BakedText3dMaterial\n}\n\nconst DEFAULT_INK = '#e4e4e7' // the primitive-prop default\n\nfunction materialFor(\n preset: Text3dMaterial,\n color: string,\n): BakedText3dMaterial {\n switch (preset) {\n case 'metal':\n return {\n type: 'standard',\n params: { color, metalness: 1, roughness: 0.22 },\n }\n case 'glass':\n // Deliberately NO transmission: its internal render pass composites\n // nothing in the layered compositor (measured black on SwiftShader —\n // the verify caught a fully invisible mesh), and the fleet has no env\n // map to refract anyway. Glass here is translucency + clearcoat\n // highlights; the span fade multiplies onto the base opacity.\n return {\n type: 'physical',\n params: {\n color,\n opacity: 0.55,\n metalness: 0,\n roughness: 0.06,\n clearcoat: 1,\n clearcoatRoughness: 0.15,\n },\n }\n case 'neon':\n // No bloom pass exists — the glow is emissive intensity, not post.\n return {\n type: 'standard',\n params: {\n color,\n emissive: color,\n emissiveIntensity: 1.6,\n metalness: 0,\n roughness: 0.4,\n },\n }\n default:\n return {\n type: 'standard',\n params: { color, metalness: 0.2, roughness: 0.45 },\n }\n }\n}\n\n/** Normalize a doc text3d asset into the baked payload (pure, deterministic). */\nexport function resolveText3dAsset(\n asset: Extract<ObjectAsset, { kind: 'text3d' }>,\n): BakedText3dAsset {\n const entry = asset.typeface ? findTypeface(asset.typeface) : null\n const slug = entry?.slug ?? DEFAULT_TYPEFACE_SLUG\n const depth = Math.min(\n TEXT3D_DEPTH_MAX,\n Math.max(TEXT3D_DEPTH_MIN, asset.depth ?? TEXT3D_DEPTH_DEFAULT),\n )\n return {\n kind: 'text3d',\n text: asset.text,\n url: typefaceUrl(slug),\n depth: Math.round(depth * 1000) / 1000,\n bevel: asset.bevel !== false,\n mat: materialFor(asset.material ?? 'standard', asset.color ?? DEFAULT_INK),\n }\n}\n","/**\n * Where each DOC segment lands on the output timeline: its rated pieces\n * (its own media's speed spans applied, the freezes on its footage placed)\n * and, from them, its output start and length. One home for the Video\n * row, the boundary transitions and the lint, so a clip's edges cannot be\n * three numbers. A freeze at a boundary two contiguous clips of one media\n * share belongs to the LATER clip (the frame is the same either way; the\n * band draws where the moment is), the rule `placeFreezes` keeps.\n */\nimport { splitBySpeed, totalDuration } from '@vosjs/timeline'\nimport { docFreezes, withFreezes } from './motion'\nimport { sameMedia } from '../media'\nimport { anchorSourceDuration, isRecordingDoc } from '../doc/studioDoc'\nimport type { Segment } from '@vosjs/timeline'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { FreezeSpan, SpeedSpan } from '../types'\n\n/** The doc's segments in canonical explicit form (empty = one full-source span). */\nexport function effectiveSegments(doc: StudioDoc): Segment[] {\n if (isRecordingDoc(doc) && doc.segments.length) return doc.segments\n return [{ in: 0, out: anchorSourceDuration(doc) }]\n}\n\n/** Does segment i start exactly where segment i-1 ends, on the same media (a split, not a cut)? */\nexport const contiguous = (\n segments: readonly (Segment & { media?: string })[],\n i: number,\n): boolean =>\n i > 0 &&\n sameMedia(segments[i - 1].media, segments[i].media) &&\n Math.abs(segments[i - 1].out - segments[i].in) < 1e-9\n\n/**\n * One DOC segment's rated pieces: its own media's speed spans applied and\n * the freezes on its footage placed (a freeze at its very end belongs to\n * it, one at its start to the segment before, when there is one).\n */\nexport const segmentPieces = (\n seg: Segment & { media?: string },\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[],\n leading: boolean,\n): Segment[] => {\n const own = freezes.filter(\n (f) =>\n sameMedia(f.media, seg.media) &&\n f.at <= seg.out + 1e-9 &&\n (leading ? f.at >= seg.in - 1e-9 : f.at > seg.in + 1e-9),\n )\n return withFreezes(\n splitBySpeed(\n [seg],\n speeds.filter((sp) => sameMedia(sp.media, seg.media)),\n ),\n own,\n )\n}\n\n/** Output-time length of one DOC segment (speed- and freeze-aware). */\nexport const outputLen = (\n seg: Segment & { media?: string },\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[] = [],\n leading = true,\n): number => totalDuration(segmentPieces(seg, speeds, freezes, leading))\n\n/** Output-time starts of the DOC segments (speed- and freeze-aware). */\nexport const segmentStarts = (\n segments: readonly (Segment & { media?: string })[],\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[] = [],\n): number[] => {\n const starts: number[] = []\n let acc = 0\n segments.forEach((s, i) => {\n starts.push(acc)\n acc += outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))\n })\n return starts\n}\n\n/** One DOC segment's place on the output timeline. */\nexport interface SegmentExtent {\n start: number\n end: number\n}\n\n/**\n * Every DOC segment's output extent, in order: where each clip starts and\n * ends on the timeline, freezes included.\n */\nexport function segmentOutputExtents(doc: StudioDoc): SegmentExtent[] {\n const segments = effectiveSegments(doc) as (Segment & { media?: string })[]\n const speeds = doc.speed ?? []\n const freezes = isRecordingDoc(doc) ? docFreezes(doc) : []\n const starts = segmentStarts(segments, speeds, freezes)\n return segments.map((s, i) => ({\n start: starts[i],\n end:\n starts[i] +\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n }))\n}\n","/**\n * Transitions at the boundaries between footage clips. A clip's `anim`\n * says how it meets the clip beside it: its `exit` at the boundary after\n * it, its `enter` at the boundary before it. The lowering turns every\n * boundary that names one into a RECORD in output seconds, which ON_FRAME\n * reads with no document in hand: during the window the INCOMING clip is\n * the live one (`mapTime` lands on it as it would without a transition, so\n * the zoom remap, the audio splice and every chunk inherit the boundary\n * unchanged) and the OUTGOING card is a snapshot of its last frame on a\n * second plane, moving away. A frozen outgoing is what the references do\n * (a cut at a moment of stillness) and what makes the primitive chunk-safe:\n * a cold seek inside the window decodes one extra frame, never a second\n * time mapping. The camera rests through the window (a span that starts\n * inside it loses its head), so the move is the only motion. The card's\n * own `slide` enter at the open is the same record with no outgoing.\n */\nimport { segmentOutputExtents } from './segmentStarts'\nimport {\n enterOf,\n exitOf,\n slideSide,\n transitionSeconds,\n TRANSITION_SECONDS_MIN,\n} from '../anim'\nimport { mediaAtOutput, sameMedia } from '../media'\nimport type { Segment } from '@vosjs/timeline'\nimport type { AnimSide, AnimStep, ProjectDoc } from '../types'\n\n/** How far a slide travels, in plane widths or heights: fully off, plus a hair. */\nexport const TRANSITION_TRAVEL = 1.04\n/** How much a `scale` transition shrinks the card at its far end. */\nexport const TRANSITION_SCALE_STEP = 0.08\n/** The ghost plane sits a hair in front of the card plane, above it in the painter's order and below the stack's card planes. */\nexport const GHOST_Z_LIFT = 0.004\nexport const GHOST_RENDER_ORDER = 1.1\n\n/** The kinds a boundary moves by. */\nexport type TransitionKind = 'slide' | 'fade' | 'scale'\n\n/** One side of a transition: how a card moves, for how long. */\nexport interface TransitionMove {\n kind: TransitionKind\n /** A slide's side (where an enter comes from, where an exit goes to). */\n side: AnimSide\n /** Seconds this side takes, from the boundary. */\n d: number\n}\n\n/** The outgoing card: which media's last frame it shows, and how it leaves. */\nexport interface TransitionOut extends TransitionMove {\n /** The media (`''` = the primary). */\n media: string\n /** SOURCE seconds of the frame it shows (the clip's last frame). */\n at: number\n}\n\n/** A boundary transition in OUTPUT seconds. */\nexport interface Transition {\n /** The boundary: where the incoming clip starts. */\n t: number\n /** The window: the longer of the two sides. */\n d: number\n /** How the incoming (live) card arrives; absent = it holds still. */\n in?: TransitionMove\n /** The outgoing snapshot and how it leaves; absent = it is simply gone. */\n out?: TransitionOut\n}\n\n/** The hair of source time before a clip's `out` that holds its last frame. */\nconst LAST_FRAME_BACK = 0.002\n\nconst round3 = (v: number): number => Math.round(v * 1000) / 1000\n\nfunction move(\n step: AnimStep | null,\n which: 'enter' | 'exit',\n cap: number,\n): TransitionMove | null {\n const secs = transitionSeconds(step)\n if (!secs || !step) return null\n const kind = step.kind\n if (kind !== 'slide' && kind !== 'fade' && kind !== 'scale') return null\n // Never longer than half the shorter clip: the clip must be seen still\n // before it moves (the lint says so in words; the lowering keeps it true).\n const d = Math.min(secs, cap)\n if (d < TRANSITION_SECONDS_MIN) return null\n return { kind, side: slideSide(step, which), d: round3(d) }\n}\n\n/**\n * The document's transitions: one record per boundary that names an exit\n * or an enter, plus the card's own `slide` enter at the open. Empty for a\n * document with none, so it lowers as before.\n */\nexport function docTransitions(doc: ProjectDoc): Transition[] {\n const out: Transition[] = []\n const segments = doc.segments\n const extents = segmentOutputExtents(doc)\n const open = enterOf(doc.frame.anim)\n if (open?.kind === 'slide' && extents.length) {\n const first = extents[0]\n const m = move(open, 'enter', Math.max(0, first.end - first.start) / 2)\n if (m) out.push({ t: 0, d: m.d, in: m })\n }\n for (let i = 0; i + 1 < segments.length; i++) {\n const a = segments[i]\n const b = segments[i + 1]\n const la = extents[i].end - extents[i].start\n const lb = extents[i + 1].end - extents[i + 1].start\n const cap = Math.min(la, lb) / 2\n const exit = move(exitOf(a.anim), 'exit', cap)\n const enter = move(enterOf(b.anim), 'enter', cap)\n if (!exit && !enter) continue\n const t = round3(extents[i + 1].start)\n const rec: Transition = {\n t,\n d: Math.max(exit?.d ?? 0, enter?.d ?? 0),\n }\n if (enter) rec.in = enter\n if (exit)\n rec.out = {\n ...exit,\n media: a.media ?? '',\n at: round3(Math.max(a.in, a.out - LAST_FRAME_BACK)),\n }\n out.push(rec)\n }\n return out\n}\n\n/** How close to a boundary a span may start before the window rests it. */\nconst REST_LEAD = 0.1\n\n/**\n * The camera rests through a transition: a source-anchored span (zoom,\n * tilt) whose output start falls inside a boundary's window loses its\n * head, starting where the window ends instead, and a span too short to\n * survive the cut is dropped. Spans that start before the window keep\n * their own end. The open's record (t = 0) is the entrance's business\n * (`prependEntrance`) and is skipped here.\n */\nexport function restSpansThroughTransitions<\n T extends { in: number; out: number; media?: string },\n>(\n spans: readonly T[],\n rated: readonly (Segment & { media?: string })[],\n transitions: readonly Transition[],\n minLen: number,\n): T[] {\n const windows = transitions.filter((tr) => tr.t > 0 && tr.d > 0)\n if (!windows.length) return [...spans]\n const out: T[] = []\n for (const span of spans) {\n const start = outputStartOf(rated, span.in, span.out, span.media)\n const win =\n start === null\n ? undefined\n : windows.find((tr) => start >= tr.t - REST_LEAD && start < tr.t + tr.d)\n if (start === null || !win) {\n out.push(span)\n continue\n }\n const after = win.t + win.d\n if (mediaAtOutput(rated, after) !== (span.media ?? '')) continue\n const head = sourceAt(rated, after)\n if (head === null || head > span.out - minLen) continue\n out.push({ ...span, in: round3(head) })\n }\n return out\n}\n\n/**\n * Where a source span's kept footage STARTS on the output timeline, through\n * its media's pieces (null when the cut dropped it all).\n */\nfunction outputStartOf(\n rated: readonly (Segment & { media?: string; rate?: number })[],\n sIn: number,\n sOut: number,\n media: string | undefined,\n): number | null {\n let acc = 0\n for (const p of rated) {\n const rate = p.rate ?? 1\n const len = Math.max(0, p.out - p.in) / rate\n if (sameMedia(p.media, media)) {\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) return acc + (ovIn - p.in) / rate\n }\n acc += len\n }\n return null\n}\n\n/** The source second of the piece under an output moment. */\nfunction sourceAt(\n rated: readonly (Segment & { media?: string; rate?: number })[],\n t: number,\n): number | null {\n let acc = 0\n for (const p of rated) {\n const rate = p.rate ?? 1\n const len = Math.max(0, p.out - p.in) / rate\n if (t < acc + len) return p.in + (t - acc) * rate\n acc += len\n }\n return null\n}\n","/**\n * Gain envelope for an audio clip, in OUTPUT-timeline seconds — the single\n * source of truth shared by preview and export: the lowering bakes these\n * points into `ctx.data.audio[i].env`, the program applies them with\n * setValueAtTime/linearRampToValueAtTime, and the export applies the SAME\n * points in its OfflineAudioContext mix. Fades that together exceed the clip\n * span are scaled down proportionally so they meet instead of crossing.\n */\nimport { clipLength } from '../types'\nimport type { AudioClip } from '../types'\n\nexport interface EnvelopePoint {\n /** output-timeline seconds. */\n t: number\n /** linear gain 0..1. */\n g: number\n}\n\nexport function clipEnvelope(\n clip: Pick<\n AudioClip,\n 'start' | 'in' | 'out' | 'gain' | 'fadeIn' | 'fadeOut' | 'loop' | 'loopLen'\n >,\n): EnvelopePoint[] {\n // Fades span the PLACED length (a looped clip fades over its full run).\n const span = clipLength(clip)\n const end = clip.start + span\n let fi = Math.max(0, clip.fadeIn)\n let fo = Math.max(0, clip.fadeOut)\n if (fi + fo > span && fi + fo > 0) {\n const scale = span / (fi + fo)\n fi *= scale\n fo *= scale\n }\n const g = Math.max(0, Math.min(1, clip.gain))\n const pts: EnvelopePoint[] = []\n pts.push({ t: clip.start, g: fi > 0 ? 0 : g })\n if (fi > 0) pts.push({ t: clip.start + fi, g })\n if (fo > 0 && end - fo > clip.start + fi) pts.push({ t: end - fo, g })\n pts.push({ t: end, g: fo > 0 ? 0 : g })\n // Dedupe collapsed points (zero-span or zero-fade edge cases).\n return pts.filter((p, i) => i === 0 || p.t > pts[i - 1].t + 1e-9)\n}\n\n/** Envelope value at output time `t` (linear interpolation; 0 outside the clip). */\nexport function envelopeValueAt(env: EnvelopePoint[], t: number): number {\n if (!env.length || t < env[0].t || t > env[env.length - 1].t) return 0\n for (let i = 1; i < env.length; i++) {\n if (t <= env[i].t) {\n const a = env[i - 1]\n const b = env[i]\n const f = b.t > a.t ? (t - a.t) / (b.t - a.t) : 1\n return a.g + (b.g - a.g) * f\n }\n }\n return env[env.length - 1].g\n}\n","/**\n * Cursor-follow focus —\n * the Recordly dead-zone model, baked DETERMINISTICALLY at lowering time,\n * tuned per camera style.\n *\n * OpenScreen chases the cursor with a stateful per-frame spring; Recordly only\n * recenters when the cursor nears the edge of the visible crop — calmer, and\n * it reduces to a handful of focus keyframes we can bake into the zoom track,\n * keeping seek a pure function of t (export, backward scrub, and every verify\n * script depend on that). Cursorful adds one more trick we adopt: a LOOK-AHEAD\n * — the recenter targets where the cursor is heading (sampled from the real\n * track slightly in the future), so the camera leads the pointer instead of\n * chasing a stale position. All three knobs (safe-zone ratio, recenter glide\n * duration, look-ahead) come from the doc's zoom style.\n *\n * Semantics per span with focusMode 'auto':\n * - entry focus = the cursor position at span.in (\"land where the cursor is\")\n * - while inside the span, a recenter event fires when the cursor exits the\n * central safeRatio of the visible crop; the camera glides to the (clamped,\n * look-ahead) cursor over `recenter` seconds, then waits for the next exit\n * - the focus FREEZES for the zoom-out (the caller keeps the last focus)\n *\n * One capture subtlety: the extension's cursor recorder is event-driven with a\n * distance gate — a parked cursor emits NO move samples, so stillness appears\n * as a time GAP between samples, not as repeated samples. All the math here\n * works on positions at their timestamps, so gaps behave correctly (no events\n * → no recenters), and the look-ahead interpolates between real samples.\n */\nimport { clampFocus } from '../layout'\nimport { dragsFromTrack } from '../planner/autoZoom'\nimport { clampZoomLevel } from '../types'\nimport { ZOOM_STYLES } from '../zoomStyle'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type { CursorTrack, ZoomSpan } from '../types'\n\n/** Legacy defaults (= the default style's values); prefer FollowOptions. */\nexport const FOLLOW_SAFE_RATIO = ZOOM_STYLES.glide.followSafeRatio\nexport const FOLLOW_RECENTER = ZOOM_STYLES.glide.followRecenter\n/** seconds between path samples through a drag (10 Hz reads as a pan). */\nexport const FOLLOW_PATH_STEP = 0.1\n/** the path's smoothing window, seconds each side of a sample. */\nexport const FOLLOW_PATH_SMOOTH = 0.08\n\nexport interface FollowOptions {\n /** recenter when the cursor exits this central fraction of the crop. */\n safeRatio?: number\n /** seconds the camera takes to glide to a recentered focus. */\n recenter?: number\n /** target the cursor this many seconds ahead of the exit moment. */\n lookahead?: number\n /** the frame's camera model: the stage camera never clamps a focus. */\n camera?: CameraModel\n}\n\nexport interface FollowEvent {\n /** SOURCE seconds — the moment the recenter starts. */\n t: number\n /**\n * A PATH sample: the camera is at this focus AT `t` (a linear segment\n * from the previous keyframe), not the start of a glide toward it. Baked\n * every FOLLOW_PATH_STEP through a drag, so the camera pans with the\n * pointer for the whole press.\n */\n path?: true\n cx: number\n cy: number\n}\n\ninterface Pt {\n t: number\n nx: number\n ny: number\n}\n\nexport function followFocusEvents(\n span: ZoomSpan,\n cursor: CursorTrack,\n space: { w: number; h: number },\n layout: CardLayout,\n options: FollowOptions = {},\n): { entry: { cx: number; cy: number } | null; events: FollowEvent[] } {\n const safeRatio = options.safeRatio ?? FOLLOW_SAFE_RATIO\n const recenter = options.recenter ?? FOLLOW_RECENTER\n const lookahead = options.lookahead ?? 0\n const camera = options.camera ?? 'card'\n const level = clampZoomLevel(span.level)\n if (!cursor.length || !space.w || !space.h || level <= 1.001) {\n return { entry: null, events: [] }\n }\n // Only real cursor positions steer the follow — scroll/focus/key events\n // carry stale or synthesized points (see cursorIdle.ts for the doctrine).\n const pts: Pt[] = cursor\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({\n t: e.t / 1000,\n nx: clamp01(e.x / space.w),\n ny: clamp01(e.y / space.h),\n }))\n if (!pts.length) return { entry: null, events: [] }\n\n // Entry: the span's OWN focus. The planner derived it from the clicked\n // element's rect (or a hand wrote it), and that is where the camera must\n // land; the follow then steers from there. Seeding from the cursor's\n // last sample before span.in landed the camera where the pointer was\n // `lead` seconds before it reached the target, and the first correction\n // could not fire for another recenter period, so a click zoom opened\n // beside its target every time.\n const entry = clampFocus(span.cx, span.cy, level, layout, camera)\n\n // Exit threshold in normalized VIDEO units: the visible crop spans W/level\n // canvas px → (W/level)/dw of the video's width; half of that is the\n // center-to-edge distance, and the safe zone keeps safeRatio of it.\n const thrX = (safeRatio * layout.W) / (2 * level * layout.dw)\n const thrY = (safeRatio * layout.H) / (2 * level * layout.dh)\n\n const events: FollowEvent[] = []\n let cx = entry.cx\n let cy = entry.cy\n // Give the zoom-in arrival room to land before the first recenter.\n let nextAllowed = span.in + recenter\n\n // Drags inside the span are PATH-followed: a sample every FOLLOW_PATH_STEP\n // at the pointer's smoothed position for the whole press, so the camera\n // pans with a slider thumb or a scrubber instead of waiting for it to\n // leave the dead zone. The dead-zone follow resumes from the release.\n const drags = dragsFromTrack(cursor, space.w, space.h).filter(\n (d) => d.t1 > span.in && d.t0 < span.out,\n )\n const pathEvents: FollowEvent[] = []\n for (const d of drags) {\n const from = Math.max(d.t0, span.in)\n const to = Math.min(d.t1, span.out)\n for (let t = from; ; t += FOLLOW_PATH_STEP) {\n const at = Math.min(t, to)\n const a = sampleAt(pts, at - FOLLOW_PATH_SMOOTH)\n const b = sampleAt(pts, at)\n const c = sampleAt(pts, at + FOLLOW_PATH_SMOOTH)\n const f = clampFocus(\n (a.nx + b.nx + c.nx) / 3,\n (a.ny + b.ny + c.ny) / 3,\n level,\n layout,\n camera,\n )\n pathEvents.push({\n t: round(at),\n path: true,\n cx: round(f.cx),\n cy: round(f.cy),\n })\n if (at >= to) break\n }\n }\n let dragIdx = 0\n for (const p of pts) {\n if (p.t < span.in) continue\n if (p.t > span.out) break\n // Inside a drag the path owns the camera; past its release the\n // dead-zone follow measures from where the path left it.\n while (dragIdx < drags.length && p.t > drags[dragIdx].t1) {\n const last = pathEvents.filter((e) => e.t <= drags[dragIdx].t1).at(-1)\n if (last) {\n cx = last.cx\n cy = last.cy\n }\n nextAllowed = drags[dragIdx].t1 + recenter\n dragIdx++\n }\n if (dragIdx < drags.length && p.t >= drags[dragIdx].t0) continue\n if (p.t < nextAllowed) continue\n if (Math.abs(p.nx - cx) > thrX || Math.abs(p.ny - cy) > thrY) {\n // Look-ahead: aim at where the cursor will be, not where it was.\n const target =\n lookahead > 0 ? sampleAt(pts, Math.min(p.t + lookahead, span.out)) : p\n const f = clampFocus(target.nx, target.ny, level, layout, camera)\n // The clamp can pin distinct cursor points to the same focus — skip no-ops.\n if (Math.abs(f.cx - cx) < 1e-3 && Math.abs(f.cy - cy) < 1e-3) continue\n events.push({ t: round(p.t), cx: round(f.cx), cy: round(f.cy) })\n cx = f.cx\n cy = f.cy\n nextAllowed = p.t + recenter\n }\n }\n const merged = [...events, ...pathEvents].sort((a, b) => a.t - b.t)\n return { entry, events: merged }\n}\n\n/** Interpolate the cursor position at time t (holds the ends; pts time-sorted). */\nfunction sampleAt(pts: Pt[], t: number): Pt {\n if (t <= pts[0].t) return pts[0]\n for (let i = 1; i < pts.length; i++) {\n if (pts[i].t >= t) {\n const a = pts[i - 1]\n const b = pts[i]\n const k = b.t > a.t ? (t - a.t) / (b.t - a.t) : 1\n return { t, nx: a.nx + (b.nx - a.nx) * k, ny: a.ny + (b.ny - a.ny) * k }\n }\n }\n return pts[pts.length - 1]\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Pinned layers — a layer placed beside its REFERENT and carried with it as\n * the camera moves it on screen.\n *\n * A callout is a statement bound to the thing it is about. The binding is\n * proximity (the layer sits a gap off one side of the referent), a connector\n * (an optional leader) and a mark on the subject (an optional ring or\n * underline). Nothing here paints: the placement is resolved at lowering\n * into the clip's ordinary motion track — the same clip-local\n * [x, y, scale, rotation, opacity] track a `motion` pose bakes to — so the\n * studio entry that paints layers stays camera-blind, picking in the\n * studio reads the same numbers, and every renderer (preview, export,\n * chunk, still) agrees by construction.\n *\n * The referent lives in the recording's cursor space (normalised video\n * fractions); the camera is the one function `layout.ts` pins to ON_FRAME\n * (`zoomView`), sampled from the OUTPUT-time zoom track at PIN_SAMPLE_HZ\n * through the layer's life and simplified to the samples a linear\n * interpolation cannot predict. The layer's SIZE stays screen-space: a\n * callout is the video's, not the app's, so it never shrinks with the app;\n * only its place follows.\n */\nimport { lerpArray, mapTime, sample } from '@vosjs/timeline'\nimport { htmlLayerPictureBox, htmlLayerWidth } from '../htmlLayer'\nimport { zoomView } from '../layout'\nimport { overlayRect } from '../overlayText'\nimport { dragsFromTrack } from '../planner/autoZoom'\nimport { OVERLAY_MEDIA_DEFAULT_WIDTH } from '../types'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type {\n CursorTrack,\n OverlayClip,\n OverlayPin,\n PinSide,\n ProjectDoc,\n Rect,\n StepSpan,\n} from '../types'\n\n/** Design px between the referent and the layer's visible box. */\nexport const PIN_GAP = 24\n/** Design px the layer keeps from the frame's edge. */\nexport const PIN_MARGIN = 24\n/** Placement samples per output second through the layer's life. */\nexport const PIN_SAMPLE_HZ = 30\n/** Design px a sample may deviate from its neighbours' line and be dropped. */\nexport const PIN_SIMPLIFY_PX = 0.5\n/** The layer's own entrance/exit length (mirrors the entry's olTD). */\nconst PIN_EDGE_S = 0.35\n/** A press counts as \"nearest\" within this many source seconds. */\nconst PIN_PRESS_WINDOW_S = 0.5\n/** Rough glyph advance for a text layer's box at lowering (no canvas). */\nconst PIN_TEXT_ADVANCE = 0.55\n\nexport type PinSideResolved = Exclude<PinSide, 'auto'>\nconst SIDE_ORDER: PinSideResolved[] = ['right', 'left', 'below', 'above']\n\n/**\n * A referent the POINTER carries: a slider thumb, a scrubber, a card being\n * dragged. Its rect was measured at the press; through the drag it sits\n * wherever the pointer has taken it, and it stays at the release. SOURCE\n * seconds; `x0`/`y0` the press in normalised video fractions.\n */\nexport interface PinCarry {\n t0: number\n t1: number\n x0: number\n y0: number\n}\n\n/** The referent's rect in normalised video fractions, and how it was found. */\nexport interface PinReferent {\n rect: Rect\n by: 'step' | 'press' | 'rect'\n /** The step that named it, when `by` is 'step'. */\n step?: StepSpan\n /** SOURCE second the referent was measured at (a step's start, the press). */\n at: number | null\n /** Set when the press began a drag: the referent moves with the pointer. */\n carry?: PinCarry\n}\n\n/**\n * Find a pin's referent in the recording. A `step` reads the step's own\n * rect (the recorder measures every selector step's box), else the union\n * of the presses inside the step's window (`down` and `key` events carry\n * the element's rect), so a take recorded before the recorder kept step\n * rects still resolves. A `press` is the nearest press to that source\n * second. A `rect` is taken as written. Null when nothing names a rect.\n */\nexport function pinReferent(\n doc: ProjectDoc,\n pin: OverlayPin,\n): PinReferent | null {\n const meta = doc.source.meta\n const w = meta.width || 0\n const h = meta.height || 0\n if (pin.rect && validRect(pin.rect)) {\n return { rect: { ...pin.rect }, by: 'rect', at: null }\n }\n if (pin.step !== undefined) {\n const step = findStep(meta.steps ?? [], pin.step)\n if (!step || step.skipped) return null\n const carry = dragCarry(doc, step.tStart - 0.05, step.tEnd + 0.05)\n if (step.rect && validRect(step.rect) && w > 0 && h > 0) {\n return {\n rect: normRect(step.rect, w, h),\n by: 'step',\n step,\n at: step.tStart,\n ...(carry ? { carry } : {}),\n }\n }\n const union = unionRects(\n pressRects(doc, step.tStart - 0.05, step.tEnd + 0.05),\n )\n if (!union || w <= 0 || h <= 0) return null\n return {\n rect: normRect(union, w, h),\n by: 'step',\n step,\n at: step.tStart,\n ...(carry ? { carry } : {}),\n }\n }\n if (pin.press !== undefined && Number.isFinite(pin.press)) {\n let best: { t: number; rect: Rect } | null = null\n for (const e of doc.source.cursor) {\n if (e.type !== 'down' || !e.rect || !validRect(e.rect)) continue\n const t = e.t / 1000\n const d = Math.abs(t - pin.press)\n if (d > PIN_PRESS_WINDOW_S) continue\n if (!best || d < Math.abs(best.t - pin.press)) best = { t, rect: e.rect }\n }\n if (!best || w <= 0 || h <= 0) return null\n const carry = dragCarry(doc, best.t - 0.01, best.t + 0.01)\n return {\n rect: normRect(best.rect, w, h),\n by: 'press',\n at: best.t,\n ...(carry ? { carry } : {}),\n }\n }\n return null\n}\n\n/**\n * The drag that begins inside [from, to], as a carry: the planner's own\n * drag test (a press that travelled before its release), so a press held\n * still stays a still referent.\n */\nfunction dragCarry(doc: ProjectDoc, from: number, to: number): PinCarry | null {\n const meta = doc.source.meta\n if (!(meta.width > 0) || !(meta.height > 0)) return null\n const d = dragsFromTrack(doc.source.cursor, meta.width, meta.height).find(\n (g) => g.t0 >= from && g.t0 <= to,\n )\n if (!d) return null\n return { t0: d.t0, t1: d.t1, x0: d.nx, y0: d.ny }\n}\n\n/** The raw pointer position at SOURCE second t, normalised: interpolated\n * between the samples around t, held at the ends. */\nexport function cursorAt(\n cursor: CursorTrack,\n t: number,\n space: { w: number; h: number },\n): { nx: number; ny: number } | null {\n let prev: { t: number; nx: number; ny: number } | null = null\n for (const e of cursor) {\n if (e.type !== 'move' && e.type !== 'down' && e.type !== 'up') continue\n const cur = { t: e.t / 1000, nx: e.x / space.w, ny: e.y / space.h }\n if (cur.t >= t) {\n if (!prev) return { nx: cur.nx, ny: cur.ny }\n const k = cur.t > prev.t ? (t - prev.t) / (cur.t - prev.t) : 1\n return {\n nx: prev.nx + (cur.nx - prev.nx) * k,\n ny: prev.ny + (cur.ny - prev.ny) * k,\n }\n }\n prev = cur\n }\n return prev ? { nx: prev.nx, ny: prev.ny } : null\n}\n\n/** A step a layer could be pinned to: it has an element, and its window overlaps the layer's. */\nexport interface PinCandidate {\n step: string | number\n do: string\n selector?: string\n /** The step's OUTPUT window. */\n output: { start: number; end: number }\n /** Seconds of the layer's window the step's covers. */\n overlap: number\n}\n\n/** The step a pin names: its id first, then its record-time index. */\nexport function findStep(\n steps: readonly StepSpan[],\n ref: string | number,\n): StepSpan | undefined {\n const byId = steps.find((s) => s.id !== undefined && s.id === ref)\n if (byId) return byId\n const idx = typeof ref === 'number' ? ref : Number(ref)\n if (!Number.isInteger(idx)) return undefined\n return steps.find((s) => s.step === idx)\n}\n\nfunction pressRects(doc: ProjectDoc, from: number, to: number): Rect[] {\n const out: Rect[] = []\n for (const e of doc.source.cursor) {\n if (e.type !== 'down' && e.type !== 'key') continue\n if (!e.rect || !validRect(e.rect)) continue\n const t = e.t / 1000\n if (t < from || t > to) continue\n out.push(e.rect)\n }\n return out\n}\n\nfunction unionRects(rects: readonly Rect[]): Rect | null {\n if (!rects.length) return null\n let x0 = Infinity\n let y0 = Infinity\n let x1 = -Infinity\n let y1 = -Infinity\n for (const r of rects) {\n x0 = Math.min(x0, r.x)\n y0 = Math.min(y0, r.y)\n x1 = Math.max(x1, r.x + r.w)\n y1 = Math.max(y1, r.y + r.h)\n }\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }\n}\n\nfunction validRect(r: Rect): boolean {\n return (\n Number.isFinite(r.x) &&\n Number.isFinite(r.y) &&\n Number.isFinite(r.w) &&\n Number.isFinite(r.h) &&\n r.w > 0 &&\n r.h > 0\n )\n}\n\nfunction normRect(r: Rect, w: number, h: number): Rect {\n return { x: r.x / w, y: r.y / h, w: r.w / w, h: r.h / h }\n}\n\n/**\n * The layer's VISIBLE box in design px at scale 1 — the part a viewer reads\n * as the card. An html layer's picture carries transparent bleed around its\n * design box (the room its shadow paints in); the gap is measured from the\n * box, never the bleed, or a shadowed card reads as standing off. A text\n * layer is measured with a rough advance (there is no canvas at lowering);\n * a media layer assumes 16:9 until its bytes are known.\n */\nexport function pinBox(\n clip: OverlayClip,\n layout: CardLayout,\n): { w: number; h: number } {\n const scale = clip.transform.scale || 1\n if (clip.kind === 'html') {\n const pb = htmlLayerPictureBox(clip)\n const wPic = htmlLayerWidth(clip) * layout.W * scale\n const unit = wPic / pb.width\n return { w: clip.box.width * unit, h: clip.box.height * unit }\n }\n if (clip.kind !== 'text') {\n const w = (clip.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH) * layout.W * scale\n return { w, h: w / (16 / 9) }\n }\n const r = overlayRect(clip, estimateMeasure, layout.W, layout.H)\n return { w: r.w, h: r.h }\n}\n\nfunction estimateMeasure(\n text: string,\n font: string,\n letterSpacingPx = 0,\n): number {\n const m = /(\\d+(?:\\.\\d+)?)px/.exec(font)\n const px = m ? Number(m[1]) : 16\n return text.length * (px * PIN_TEXT_ADVANCE + letterSpacingPx)\n}\n\n/**\n * A rect in normalised video fractions as the camera shows it at OUTPUT\n * time t, in design px: the zoom track sampled at t, mapped through\n * zoomView (the one camera function ON_FRAME mirrors).\n */\nexport function pinRectOnScreen(\n rect: Rect,\n t: number,\n layout: CardLayout,\n camera: CameraModel,\n zoomTrack?: KeyframeTrack<number[]> | null,\n): Rect {\n let level = 1\n let cx = 0.5\n let cy = 0.5\n let centring: number | undefined\n if (zoomTrack && zoomTrack.keyframes.length) {\n const z = sample(zoomTrack, t, lerpArray)\n level = z[0]\n cx = z[1]\n cy = z[2]\n if (z.length > 3) centring = z[3]\n }\n const v = zoomView(level, cx, cy, layout, camera, centring)\n const map = (nx: number, ny: number) => {\n const px = layout.dx + nx * layout.dw\n const py = layout.dy + ny * layout.dh\n return {\n x: v.ox + (px - v.wcx) * v.level,\n y: v.oy + (py - v.wcy) * v.level,\n }\n }\n const a = map(rect.x, rect.y)\n const b = map(rect.x + rect.w, rect.y + rect.h)\n return { x: a.x, y: a.y, w: b.x - a.x, h: b.y - a.y }\n}\n\nexport interface PinPlacementInput {\n clip: OverlayClip\n referent: Rect\n layout: CardLayout\n camera: CameraModel\n /** The OUTPUT-time zoom track ([level, cx, cy, centring?]); absent = no camera. */\n zoomTrack?: KeyframeTrack<number[]> | null\n /** The clip's base [x, y, scale, rotation, opacity]. */\n base: readonly number[]\n /** The clip's own motion track, for scale, rotation and opacity. */\n motion?: KeyframeTrack<number[]> | null\n /**\n * A carried referent (a drag): the rect is offset by the pointer's\n * displacement at each sample's SOURCE moment, read from `cursor` in\n * `space` through `rated` (output → source).\n */\n carry?: PinCarry | null\n cursor?: CursorTrack\n space?: { w: number; h: number }\n rated?: readonly Segment[]\n}\n\n/**\n * The referent's rect at OUTPUT time t: the measured rect, moved by where\n * the pointer has carried it when the press began a drag (held at the\n * release; nothing before the press).\n */\nexport function pinReferentAt(\n referent: Rect,\n t: number,\n input: Pick<PinPlacementInput, 'carry' | 'cursor' | 'space' | 'rated'>,\n): Rect {\n const { carry, cursor, space, rated } = input\n if (!carry || !cursor || !space || !rated) return referent\n const s = mapTime(rated as Segment[], t)\n const sc = Math.max(carry.t0, Math.min(carry.t1, s))\n const p = cursorAt(cursor, sc, space)\n if (!p) return referent\n return {\n x: referent.x + (p.nx - carry.x0),\n y: referent.y + (p.ny - carry.y0),\n w: referent.w,\n h: referent.h,\n }\n}\n\nexport interface PinPlacement {\n side: PinSideResolved\n /** Clip-local [x, y, scale, rotation, opacity] in frame fractions. */\n track: KeyframeTrack<number[]>\n /** Clip-local [x, y] of the leader's tip: the layer's near-edge midpoint. */\n tip: KeyframeTrack<number[]>\n /** The most the layer had to move, in design px, to stay inside the frame. */\n clamped: number\n /** The layer's visible box in design px. */\n box: { w: number; h: number }\n}\n\n/**\n * Place a pinned layer through its life. The referent is mapped through the\n * camera at each sample; the layer's centre sits a gap off the chosen side;\n * the centre is clamped inside the frame's margin; the samples are\n * simplified. `side: 'auto'` is decided ONCE (never per frame, so the layer\n * cannot flip sides mid-life): the first side in order whose box fits at\n * the layer's start, middle and end, else the side with the most room.\n */\nexport function pinPlacement(input: PinPlacementInput): PinPlacement {\n const { clip, referent, layout, camera, zoomTrack, base, motion } = input\n const dur = Math.max(0.001, clip.duration)\n const gap = clip.pin?.gap ?? PIN_GAP\n const box = pinBox(clip, layout)\n const W = layout.W\n const H = layout.H\n\n const refAt = (local: number): Rect =>\n pinRectOnScreen(\n pinReferentAt(referent, clip.start + local, input),\n clip.start + local,\n layout,\n camera,\n zoomTrack,\n )\n\n const centreFor = (r: Rect, side: PinSideResolved) => {\n switch (side) {\n case 'right':\n return { x: r.x + r.w + gap + box.w / 2, y: r.y + r.h / 2 }\n case 'left':\n return { x: r.x - gap - box.w / 2, y: r.y + r.h / 2 }\n case 'below':\n return { x: r.x + r.w / 2, y: r.y + r.h + gap + box.h / 2 }\n case 'above':\n return { x: r.x + r.w / 2, y: r.y - gap - box.h / 2 }\n }\n }\n const fits = (c: { x: number; y: number }) =>\n c.x - box.w / 2 >= PIN_MARGIN &&\n c.x + box.w / 2 <= W - PIN_MARGIN &&\n c.y - box.h / 2 >= PIN_MARGIN &&\n c.y + box.h / 2 <= H - PIN_MARGIN\n const room = (c: { x: number; y: number }) => {\n const x0 = Math.max(PIN_MARGIN, c.x - box.w / 2)\n const x1 = Math.min(W - PIN_MARGIN, c.x + box.w / 2)\n const y0 = Math.max(PIN_MARGIN, c.y - box.h / 2)\n const y1 = Math.min(H - PIN_MARGIN, c.y + box.h / 2)\n return Math.max(0, x1 - x0) * Math.max(0, y1 - y0)\n }\n\n let side: PinSideResolved\n const asked = clip.pin?.side ?? 'auto'\n if (asked !== 'auto') side = asked\n else {\n const edge = Math.min(PIN_EDGE_S, dur / 3)\n const probes = [edge, dur / 2, dur - edge].map(refAt)\n const fitting = SIDE_ORDER.find((s) =>\n probes.every((r) => fits(centreFor(r, s))),\n )\n if (fitting) side = fitting\n else {\n let best: PinSideResolved = SIDE_ORDER[0]\n let bestRoom = -1\n for (const s of SIDE_ORDER) {\n const total = probes.reduce((acc, r) => acc + room(centreFor(r, s)), 0)\n if (total > bestRoom) {\n bestRoom = total\n best = s\n }\n }\n side = best\n }\n }\n\n const clampC = (c: { x: number; y: number }) => {\n const minX = Math.min(W / 2, PIN_MARGIN + box.w / 2)\n const maxX = Math.max(W / 2, W - PIN_MARGIN - box.w / 2)\n const minY = Math.min(H / 2, PIN_MARGIN + box.h / 2)\n const maxY = Math.max(H / 2, H - PIN_MARGIN - box.h / 2)\n return {\n x: Math.max(minX, Math.min(maxX, c.x)),\n y: Math.max(minY, Math.min(maxY, c.y)),\n }\n }\n const tipFor = (c: { x: number; y: number }) => {\n switch (side) {\n case 'right':\n return { x: c.x - box.w / 2, y: c.y }\n case 'left':\n return { x: c.x + box.w / 2, y: c.y }\n case 'below':\n return { x: c.x, y: c.y - box.h / 2 }\n case 'above':\n return { x: c.x, y: c.y + box.h / 2 }\n }\n }\n\n const n = Math.max(1, Math.ceil(dur * PIN_SAMPLE_HZ))\n const poses: Keyframe<number[]>[] = []\n const tips: Keyframe<number[]>[] = []\n let clamped = 0\n for (let i = 0; i <= n; i++) {\n const local = i === n ? dur : i / PIN_SAMPLE_HZ\n const want = centreFor(refAt(local), side)\n const c = clampC(want)\n clamped = Math.max(clamped, Math.hypot(c.x - want.x, c.y - want.y))\n const pose = motion?.keyframes.length\n ? sample(motion, local, lerpArray)\n : base\n poses.push({\n t: local,\n value: [c.x / W, c.y / H, pose[2], pose[3], pose[4]],\n ease: 'linear',\n })\n const tp = tipFor(c)\n tips.push({ t: local, value: [tp.x / W, tp.y / H], ease: 'linear' })\n }\n return {\n side,\n track: { keyframes: simplify(poses, [W, H, 1, 1, 1]) },\n tip: { keyframes: simplify(tips, [W, H]) },\n clamped: round(clamped),\n box,\n }\n}\n\n/**\n * Drop every sample its neighbours' straight line predicts within\n * PIN_SIMPLIFY_PX (per component, scaled to design px by `scalePx`): a hold\n * collapses to its two ends, a smooth ease keeps the samples its curvature\n * needs. The first and the last sample always stay.\n */\nexport function simplify(\n keys: readonly Keyframe<number[]>[],\n scalePx: readonly number[],\n): Keyframe<number[]>[] {\n if (keys.length <= 2) return keys.map(roundKey)\n const out: Keyframe<number[]>[] = [roundKey(keys[0])]\n let a = 0\n for (let i = 2; i <= keys.length - 1; i++) {\n // Would the line a → i predict every sample strictly between them?\n const ka = keys[a]\n const ki = keys[i]\n let ok = true\n for (let j = a + 1; j < i && ok; j++) {\n const kj = keys[j]\n const u = (kj.t - ka.t) / (ki.t - ka.t || 1)\n for (let k = 0; k < kj.value.length; k++) {\n const pred = ka.value[k] + (ki.value[k] - ka.value[k]) * u\n if (\n Math.abs(pred - kj.value[k]) * (scalePx[k] ?? 1) >\n PIN_SIMPLIFY_PX\n ) {\n ok = false\n break\n }\n }\n }\n if (!ok) {\n a = i - 1\n out.push(roundKey(keys[a]))\n }\n }\n const last = keys[keys.length - 1]\n if (out[out.length - 1].t !== round(last.t)) out.push(roundKey(last))\n return out\n}\n\nfunction roundKey(k: Keyframe<number[]>): Keyframe<number[]> {\n return { t: round(k.t), value: k.value.map(round4), ease: k.ease }\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\nfunction round4(v: number): number {\n return Math.round(v * 100000) / 100000\n}\n","/**\n * Idle cursor fade — the dwell detector behind `CursorStyle.hideWhenIdle`.\n *\n * A parked cursor is the most common blemish in screen footage: the dot sits in\n * frame through every scroll, every typing passage, every pause, drawing the eye\n * to nothing. This bakes a sparse opacity curve so ON_FRAME can fade it out\n * during dwells and bring it back the moment the cursor moves again.\n *\n * Pure and deterministic — seek must stay a pure function of `t`, so there are\n * no springs and no state. Output is SOURCE-anchored (like the cursor samples\n * themselves), which is what makes trims, cuts and speed spans inherit the fade\n * from one seam.\n *\n * Detection runs on the RAW track, not the smoothed path. The smoothed path is\n * resampled at a fixed cadence and linearly interpolates across gaps, so during\n * a park it creeps toward wherever the cursor goes next — ground truth for \"the\n * user isn't moving\" is the absence of raw events. The smoothing settle after\n * the last real move is bounded and well under `CURSOR_IDLE_HOLD`, so the dot\n * is always at rest before the fade begins.\n *\n * Two things deliberately do NOT count as movement:\n *\n * - **Scrolling.** `scroll` events re-emit the last known position, so a reading\n * pause looks \"active\" if you detect idleness from sample gaps. They carry no\n * real cursor motion and are ignored here, so a long scroll correctly fades\n * the cursor away — it isn't doing anything.\n * - **Focus jumps and typing.** `focus`/`key` events synthesize a position at\n * the focused element's centre, which the cursor never visited — and during\n * typing the caret is the actor, so the parked dot SHOULD fade.\n *\n * Clicks DO break a dwell: a press is not idle. The window before a click ends\n * `CURSOR_IDLE_FADE_IN` early so the dot is back at full opacity when the ring\n * blooms under it, rather than ghosting in behind its own click effect.\n */\nimport type { CursorTrack } from '../types'\n\n/** Seconds of stillness before the cursor starts fading out. */\nexport const CURSOR_IDLE_HOLD = 1\n/** Fade-out ramp, seconds. */\nexport const CURSOR_IDLE_FADE_OUT = 0.35\n/** Fade-in ramp, seconds. Snappier than the way out — motion draws the eye. */\nexport const CURSOR_IDLE_FADE_IN = 0.18\n/**\n * Movement epsilon as a fraction of the capture's short edge, floored at 2px\n * (the recorder's own distance gate). Relative so a 4K take isn't held to a\n * 1080p take's pixel budget.\n */\nexport const CURSOR_IDLE_EPS_FRAC = 0.0025\n\nexport interface CursorIdleOptions {\n /** Capture space, for the movement epsilon. */\n space: { w: number; h: number }\n /** SOURCE seconds; the trailing dwell runs to here. */\n sourceDuration: number\n}\n\n/** One point on the baked opacity curve. SOURCE seconds → alpha 0..1. */\nexport interface CursorFadeKey {\n t: number\n a: number\n}\n\ninterface Point {\n t: number\n x: number\n y: number\n}\n\nconst round = (n: number): number => Math.round(n * 1000) / 1000\n\n/**\n * Bake the opacity curve for a cursor track. Returns an empty array when\n * nothing dwells long enough to be worth hiding — callers then emit no\n * `cursorFade` key at all, so a take with a busy cursor lowers byte-identically\n * to the pre-feature lowering.\n */\nexport function cursorIdleFade(\n track: CursorTrack,\n o: CursorIdleOptions,\n): CursorFadeKey[] {\n // Only these three carry a position the cursor actually occupied.\n const moves: Point[] = track\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n if (moves.length === 0) return []\n\n const eps = Math.max(\n 2,\n CURSOR_IDLE_EPS_FRAC * Math.min(o.space.w || 0, o.space.h || 0),\n )\n\n // Virtual edge points so the head and tail parks are detectable: the drawn\n // dot holds the first sample's position before it and the last one's after\n // it, so those stretches are dwells even though no event lands in them.\n const pts: Point[] = []\n const first = moves[0]\n const last = moves[moves.length - 1]\n if (first.t > 0) pts.push({ t: 0, x: first.x, y: first.y })\n pts.push(...moves)\n if (o.sourceDuration > last.t) {\n pts.push({ t: o.sourceDuration, x: last.x, y: last.y })\n }\n\n // Still-windows: a window runs until the cursor leaves its anchor by `eps`.\n // Anchoring on the window START (not the previous point) is what makes slow\n // drift accumulate into a break instead of creeping unnoticed.\n const windows: { s: number; e: number }[] = []\n let anchor = pts[0]\n for (let i = 1; i < pts.length; i++) {\n const dx = pts[i].x - anchor.x\n const dy = pts[i].y - anchor.y\n if (dx * dx + dy * dy > eps * eps) {\n windows.push({ s: anchor.t, e: pts[i].t })\n anchor = pts[i]\n }\n }\n windows.push({ s: anchor.t, e: pts[pts.length - 1].t })\n\n const clicks = track\n .filter((e) => e.type === 'down' || e.type === 'up')\n .map((e) => e.t / 1000)\n .sort((a, b) => a - b)\n\n const keys: CursorFadeKey[] = []\n for (const w of windows) {\n let s = w.s\n for (;;) {\n const c = clicks.find((t) => t > s && t <= w.e)\n if (c === undefined) {\n emit(keys, s, w.e)\n break\n }\n // End early enough to be back at full opacity on the press itself.\n emit(keys, s, c - CURSOR_IDLE_FADE_IN)\n s = c\n }\n }\n return keys\n}\n\n/**\n * Append the four keys describing one hidden stretch, skipping windows too\n * short to complete the fade — a partial fade that immediately reverses reads\n * as a flicker, which is worse than leaving the cursor up.\n */\nfunction emit(keys: CursorFadeKey[], s: number, e: number): void {\n const from = s + CURSOR_IDLE_HOLD\n if (e - from < CURSOR_IDLE_FADE_OUT) return\n push(keys, from, 1)\n push(keys, from + CURSOR_IDLE_FADE_OUT, 0)\n push(keys, e, 0)\n push(keys, e + CURSOR_IDLE_FADE_IN, 1)\n}\n\n/** Keys are strictly increasing in t; a coincident key would divide by zero. */\nfunction push(keys: CursorFadeKey[], t: number, a: number): void {\n const rt = round(t)\n if (keys.length > 0 && rt <= keys[keys.length - 1].t) {\n keys[keys.length - 1].a = a\n return\n }\n keys.push({ t: rt, a })\n}\n","import { timelineRuntimeCode } from '@vosjs/timeline/bundle'\nimport { HTML_LAYER_LIVE_HZ } from '../htmlLayer'\nimport { OVERLAY_FONT_FACES } from '../overlayText'\nimport { CARD_FOV, CARD_Z } from '../stage'\nimport {\n LAYER_CARD_SHADOW,\n OVERLAY_LINE_HEIGHT,\n OVERLAY_TRANSITION_DUR,\n} from '../types'\n\n/**\n * The studio's program: the SHARED layers (text/image/video overlay clips, the\n * 3D prop pool) as ONE engine stack entry (`config.stack`, @vosjs/core ≥0.21)\n * that runs after the anchor's program on the same ctx — same scene, camera,\n * overlayScene, renderer, master clock — with its OWN `ctx.data` and its own\n * error boundary. The same entry rides every anchor: a recording's card\n * program and a user's own config alike.\n *\n * Everything here is CONSTANT text: a layer edit is `SET_DATA { target }` on\n * this entry, never a program change (the liveEdit invariant). The paint code\n * is the take editor's compositor, moved out of its main program unchanged;\n * it reads only what an entry is given — the renderer size, the output\n * clock (`ctx.time` on an entry IS the output time), the shared\n * `window.__vos__` caches and `globalThis.__vosTimeline` — never the anchor's\n * card geometry.\n *\n * The overlay layer mounts in `ctx.overlayScene` (the engine's 2D group,\n * rendered after every 3D group under the ortho `overlayCamera`), sized to that\n * camera's bounds, so it fills the frame on any anchor whatever its camera.\n * Props mount in `ctx.scene` at renderOrder 1.5 (between a recording's card and\n * its cam bubble) on the ANCHOR's camera: a perspective camera anywhere, or an\n * orthographic one (a program's `fullscreen` preset), where the prop group\n * carries the camera pose and a pixel-aspect squash. Lights: the entry adds\n * its pair when its data says `lights` (the recording anchor), and lazily,\n * once, when a program's scene turns out to have none (a shader program).\n */\n\nexport const STUDIO_ENTRY_ID = 'vosso.studio'\n\nexport interface StudioEntry {\n id: string\n data: Record<string, unknown>\n setup: string\n createContent: string\n onFrame: string\n}\n\nexport function studioEntry(data: Record<string, unknown>): StudioEntry {\n return {\n id: STUDIO_ENTRY_ID,\n data,\n setup: STUDIO_SETUP,\n createContent: STUDIO_CONTENT,\n onFrame: STUDIO_FRAME,\n }\n}\n\n/**\n * The HTML layer's SVG document, as page JS. This is the MIRROR of\n * `htmlLayerSvg` in `../htmlLayer.ts`: SETUP is a function string and cannot\n * import, so the composer is carried twice and `htmlOverlay.test.ts` pins\n * the two byte-for-byte. Change them together.\n */\nexport 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\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}`\n\n// Fonts, overlay media and prop assets warm-load here so the first captured\n// frame is complete (preview/export parity). The timeline runtime is installed\n// when the anchor's program did not (a user's config has no reason to).\nexport const STUDIO_SETUP = `async (ctx) => {\n if (!globalThis.__vosTimeline) { ${timelineRuntimeCode} }\n const ns = (window.__vos__ = window.__vos__ || {})\n // The transport's pause state (the engine's video-renderer contract): the\n // bridge toggles it through setGlobalPaused ONLY when something installed\n // it. The card program does; a bare program has no element renderers and\n // installs nothing, so without this the audio scheduler below read\n // isPaused as undefined, never \"playing\", and a soundtrack on a program\n // was silent in the studio while the offline export mix carried it.\n if (ns.isPaused === undefined) ns.isPaused = true\n if (!ns.setGlobalPaused) ns.setGlobalPaused = (p) => { ns.isPaused = p }\n const cache = ns.videoCache || (ns.videoCache = new Map())\n const load = async (src, muted) => {\n let v = cache.get(src)\n if (v) return v\n v = document.createElement('video')\n v.src = src\n v.crossOrigin = 'anonymous'\n v.muted = muted\n v.playsInline = true\n v.preload = 'auto'\n await new Promise((res, rej) => {\n v.oncanplay = () => res()\n // The MediaError rides along: code 4 is an unreadable/unsupported source\n // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network\n // stall. A bare \"failed to load\" gave the fleet log nothing to act on.\n v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))\n v.load()\n })\n cache.set(src, v)\n return v\n }\n const loadImage = (src) => {\n const hit = cache.get(src)\n if (hit) return Promise.resolve(hit)\n return new Promise((res, rej) => {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.onload = () => { cache.set(src, img); res(img) }\n img.onerror = () => rej(new Error('[voila] image failed to load'))\n img.src = src\n })\n }\n // Text-overlay fonts (compositor v2): the house faces from the CDN, loaded\n // ONLY when the doc has overlays. Awaiting here means the first captured frame\n // already has the faces (preview/export parity). Capped + fail-open: a CDN\n // failure degrades to the stack's system fallbacks, never a dead LOAD.\n const olv = ctx.data.overlays\n if (olv && olv.length && typeof FontFace !== 'undefined') {\n try {\n const faces = ctx.data.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}\n // Mark what SETUP registers, so ON_FRAME's live path (a text overlay\n // arriving through SET_DATA) never adds the same face twice.\n const ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\n const loads = faces.map((f) => {\n ofSet[f.family + '|' + f.weight] = 1\n const ff = new FontFace(f.family, 'url(' + f.url + ')', { weight: String(f.weight) })\n document.fonts.add(ff)\n return ff.load()\n })\n await Promise.race([\n Promise.all(loads).catch(() => {}),\n new Promise((res) => setTimeout(res, 4000)),\n ])\n } catch (e) { console.warn('[voila] overlay fonts failed to load', e) }\n }\n // An HTML layer arrives as SOURCE, and the picture is built HERE.\n //\n // Two rules force this shape. An SVG carrying a <foreignObject> is\n // origin-clean ONLY as a data: URI (served from a URL it taints the canvas\n // even same-origin, and a tainted frame cannot be uploaded as a WebGL\n // texture, so every layer vanishes at once), and an SVG loaded through\n // <img> may fetch nothing of its own, so a @font-face naming a URL is\n // silently ignored. So the faces are fetched here by ordinary fetch,\n // inlined, and what lands in the cache is an inline, vector picture under\n // the clip's content-addressed key.\n //\n // Per-clip state on the namespace: htmlWant (the key a clip wants NOW, so a\n // build a keystroke made stale is dropped before it decodes), htmlLast (the\n // last picture built for a clip, drawn while the next rasterizes so an edit\n // never blinks the layer out), htmlKeys (the key in the cache for a clip,\n // evicted when a new one lands), htmlErrors (why a clip did not decode).\n // faceData caches a face's bytes by URL across every layer on the page.\n const htmlSvg = ${HTML_LAYER_SVG_CODE}\n ns.htmlWant = ns.htmlWant || {}\n ns.htmlLast = ns.htmlLast || {}\n ns.htmlKeys = ns.htmlKeys || {}\n ns.htmlErrors = ns.htmlErrors || {}\n // One byte cache for faces AND the images a layer names by URL, keyed by\n // URL: fetched once per page however many layers name it, inlined as a\n // data: URI (a face is always woff2; an image carries its own type).\n const faceData = ns.faceData || (ns.faceData = new Map())\n const bytesOf = (url, mime, what) => {\n let p = faceData.get(url)\n if (p) return p\n p = fetch(url).then(async (r) => {\n if (!r.ok) throw new Error('HTTP ' + r.status)\n const buf = new Uint8Array(await r.arrayBuffer())\n // Chunked: String.fromCharCode.apply throws on a large spread.\n let str = ''\n for (let i = 0; i < buf.length; i += 0x8000) str += String.fromCharCode.apply(null, buf.subarray(i, i + 0x8000))\n const type = mime || ((r.headers.get('content-type') || 'application/octet-stream').split(';')[0])\n return 'data:' + type + ';base64,' + btoa(str)\n }).catch((e) => {\n // Fail open per URL, and NOT sticky: the layer paints without it now\n // and the next build asks again.\n faceData.delete(url)\n console.warn('[voila] html layer ' + what + ' failed to load', url, e)\n return ''\n })\n faceData.set(url, p)\n return p\n }\n const faceOf = (f) => bytesOf(f.url, 'font/woff2', 'face')\n const assetOf = (url) => bytesOf(url, '', 'asset')\n // The second argument names the moment a LIVE layer is composed for: its\n // own key (the clip's key plus the moment) and t. A still builds under\n // the clip's key.\n const buildHtmlLayer = async (oc, live) => {\n const key = live ? live.key : oc.key\n const want = () => ns.htmlWant[oc.id] === key\n ns.htmlWant[oc.id] = key\n const h = oc.html\n const uris = await Promise.all((h.faces || []).map(faceOf))\n const assetUris = await Promise.all((h.assets || []).map(assetOf))\n if (!want()) return\n const faces = []\n for (let i = 0; i < uris.length; i++) {\n if (uris[i]) faces.push({ family: h.faces[i].family, weight: h.faces[i].weight, style: h.faces[i].style, dataUri: uris[i] })\n }\n const assets = []\n for (let i = 0; i < assetUris.length; i++) {\n if (assetUris[i]) assets.push({ url: h.assets[i], dataUri: assetUris[i] })\n }\n const svg = htmlSvg({ html: h.markup, css: h.css, box: h.box, bleed: h.bleed, live: !!live, t: live ? live.t : 0, data: h.data }, faces, assets)\n const uri = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))\n const img = await new Promise((res, rej) => {\n const i = new Image()\n i.onload = () => res(i)\n // foreignObject is XML: malformed markup fails to DECODE, silently, and\n // the layer would simply never appear. Say so instead.\n i.onerror = () => rej(new Error('did not decode: is the markup well-formed XML?'))\n i.src = uri\n })\n if (!want()) return\n // A browser that taints a canvas drawing a foreignObject SVG (WebKit's\n // history) would lose EVERY layer to one SecurityError at the texture\n // upload. Probe once on a scratch canvas and, failing, drop the HTML\n // layers and nothing else.\n if (ns.htmlSafe === undefined) {\n try {\n const pc = document.createElement('canvas')\n pc.width = pc.height = 1\n const px = pc.getContext('2d')\n px.drawImage(img, 0, 0, 1, 1)\n px.getImageData(0, 0, 1, 1)\n ns.htmlSafe = true\n } catch (e) {\n ns.htmlSafe = false\n console.error('[voila] html layers are off: this browser taints a canvas that draws a foreignObject SVG', e)\n }\n }\n // One picture per clip in the cache: the previous key goes when a new\n // one lands, which for a live layer is every frame.\n const prev = ns.htmlKeys[oc.id]\n if (prev && prev !== key) cache.delete(prev)\n cache.set(key, img)\n ns.htmlKeys[oc.id] = key\n ns.htmlLast[oc.id] = img\n delete ns.htmlErrors[oc.id]\n }\n // ON_FRAME is its own function string, so the builder is handed over for a\n // layer that arrives by live edit after setup has run. A failure is\n // recorded on the clip and said at the level a render page forwards.\n ns.buildHtmlLayer = (oc, live) => buildHtmlLayer(oc, live).catch((e) => {\n ns.htmlErrors[oc.id] = String((e && e.message) || e)\n console.error('[voila] html layer ' + oc.id + ' ' + ns.htmlErrors[oc.id])\n })\n // The capture contract for a LIVE layer, on ANY anchor. A recording's own\n // program installs the settle machinery (pendingDecodes, the wait a\n // capture awaits, a frame-prep hook that requests the frame's footage\n // before the paint); a program anchor installs none of it, and a live\n // layer on one would export a frame behind (the last landed picture),\n // since nothing awaited the build. So the entry installs what is absent\n // and registers its own hook: the moment every visible live layer needs\n // at t is asked for BEFORE the paint and put on pendingDecodes, so the\n // first paint draws the exact picture and a still at t is t. ON_FRAME\n // asks the same way (one source, inlined in both scopes) for the preview,\n // where the last landed picture stands in meanwhile.\n ns.pendingDecodes = ns.pendingDecodes || new Set()\n if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {\n if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])\n }\n ns.framePrep = ns.framePrep || new Map()\n ns.framePrep.set('vosso.studio', (t) => {\n if (ns.isPaused === false) return\n for (const ol of (ctx.data.overlays || [])) {\n if (!ol.html || !ol.html.live) continue\n const olT = t - ol.start\n if (olT < 0 || olT > ol.dur) continue\n const q = Math.round(olT * ${HTML_LAYER_LIVE_HZ})\n const key = ol.key + '@' + q\n if (cache.get(key) || ns.htmlWant[ol.id] === key) continue\n const p = ns.buildHtmlLayer(ol, { key, t: q / ${HTML_LAYER_LIVE_HZ} })\n if (p && p.finally) {\n ns.pendingDecodes.add(p)\n p.finally(() => ns.pendingDecodes.delete(p))\n }\n }\n })\n // Media overlays (V1b): warm-load through the shared cache so the first\n // frame draws complete. Fail-open per clip (a bad key just doesn't draw).\n // A live layer warms at its first moment; ON_FRAME re-composes it per frame.\n for (const oc of (ctx.data.overlays || [])) {\n if (oc.html) { await ns.buildHtmlLayer(oc, oc.html.live ? { key: oc.key + '@0', t: 0 } : undefined); continue }\n if (oc.kind !== 'image' && oc.kind !== 'video') continue\n try {\n if (oc.kind === 'image') await loadImage(oc.key)\n else await load(oc.key, true)\n } catch (e) { console.warn('[voila] overlay media failed to load', oc.key, e) }\n }\n // GLB object assets (V3b): load via the engine's GLTFLoader addon into a\n // shared cache, bbox-NORMALIZED (norm = 1/maxDim) so transform3d.scale means\n // the same thing for every model. Fail-open per key — a bad model just\n // doesn't render (the prop pool skips unloaded keys).\n const objs = ctx.data.objects || []\n const objCache = ns.objCache || (ns.objCache = new Map())\n for (const oc of objs) {\n if (!oc.asset || oc.asset.kind !== 'gltf' || !oc.asset.key || objCache.has(oc.asset.key)) continue\n try {\n const GL = ctx.loaders && ctx.loaders.GLTFLoader\n if (!GL) { console.warn('[voila] GLTFLoader unavailable'); continue }\n const gltf = await new Promise((res, rej) => new GL().load(oc.asset.key, res, undefined, rej))\n const box = new ctx.THREE.Box3().setFromObject(gltf.scene)\n const size = new ctx.THREE.Vector3()\n box.getSize(size)\n const center = new ctx.THREE.Vector3()\n box.getCenter(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n objCache.set(oc.asset.key, { scene: gltf.scene, norm: 1 / maxDim, center: center })\n } catch (e) { console.warn('[voila] glb failed to load', oc.asset.key, e) }\n }\n // 3D-text typefaces: FontLoader JSONs into a shared cache, keyed by\n // URL. Awaited here so frame 0 is complete on cold loads (export/chunk\n // parity); live SET_DATA additions lazy-load in ON_FRAME instead. Fail-open\n // per URL — the prop pool skips unloaded typefaces.\n const fontCache = ns.fontCache || (ns.fontCache = new Map())\n for (const oc of objs) {\n if (!oc.asset || oc.asset.kind !== 'text3d' || !oc.asset.url || fontCache.has(oc.asset.url)) continue\n try {\n const FL = ctx.loaders && ctx.loaders.FontLoader\n if (!FL) { console.warn('[voila] FontLoader unavailable'); continue }\n const font = await new Promise((res, rej) => new FL().load(oc.asset.url, res, undefined, rej))\n fontCache.set(oc.asset.url, font)\n } catch (e) { console.warn('[voila] typeface failed to load', oc.asset.url, e) }\n }\n // Audio clips (music/SFX): pre-decode into a shared cache so first play is\n // instant. The AudioContext starts suspended (autoplay policy) — onFrame\n // resumes it on the first play. Failures degrade to a silent clip.\n const clips = ctx.data.audio || []\n if (clips.length && window.AudioContext) {\n const actx = ns.audioCtx || (ns.audioCtx = new window.AudioContext())\n const bufs = ns.audioBuffers || (ns.audioBuffers = new Map())\n const pend = ns.audioPending || (ns.audioPending = new Set())\n await Promise.all(clips.map(async (c) => {\n if (bufs.has(c.key) || pend.has(c.key)) return\n pend.add(c.key)\n try {\n const res = await fetch(c.key)\n bufs.set(c.key, await actx.decodeAudioData(await res.arrayBuffer()))\n } catch (e) {\n console.warn('[voila] audio decode failed', c.key, e)\n } finally {\n pend.delete(c.key)\n }\n }))\n }\n}`\n\nexport const STUDIO_CONTENT = `(ctx) => {\n const THREE = ctx.THREE\n const gl = ctx.renderer && ctx.renderer.domElement\n const res = ctx.resolution\n const W0 = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || 1280))\n const H0 = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || 720))\n // The overlay layer: one canvas on a plane that fills the engine's 2D\n // overlay camera, above every element and every 3D group.\n const canvas = document.createElement('canvas')\n canvas.width = W0\n canvas.height = H0\n const c2d = canvas.getContext('2d')\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.generateMipmaps = false\n const ocam = ctx.overlayCamera\n const pw = ocam ? ocam.right - ocam.left : W0\n const ph = ocam ? ocam.top - ocam.bottom : H0\n const mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(pw, ph),\n new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })\n )\n mesh.frustumCulled = false\n mesh.renderOrder = 1e6\n ;(ctx.overlayScene || ctx.scene).add(mesh)\n // Object clips: a world-space group (per-mesh renderOrder 1.5; meshes\n // depth-test among THEMSELVES). Lights only where the data asks: the\n // recording anchor's card program has none of its own.\n const objGroup = new THREE.Group()\n ctx.scene.add(objGroup)\n const objects = [mesh, objGroup]\n if (ctx.data && ctx.data.lights) {\n const amb = new THREE.AmbientLight(0xffffff, 0.75)\n const dir = new THREE.DirectionalLight(0xffffff, 1.4)\n dir.position.set(2, 3, 4)\n ctx.scene.add(amb)\n ctx.scene.add(dir)\n objects.push(amb, dir)\n }\n return {\n objects: objects,\n refs: {\n ov: { canvas: canvas, c2d: c2d, texture: texture, mesh: mesh, pw: pw, ph: ph },\n objects: { group: objGroup, pool: new Map() },\n },\n }\n}`\n\n// The studio compositor. Deterministic: a pure function of ctx.time (the\n// OUTPUT clock) + this entry's ctx.data. One `var` scope, ol*/ob* prefixed.\nexport const STUDIO_FRAME = `(ctx, content, dt) => {\n var r = content.refs\n // Stub-context tests build only the flat { c2d, canvas, texture }: fall\n // back to it like the card program does, so they drive this entry too.\n var ov = r.ov || r\n if (!ov || !ov.c2d) return\n var ovC = ov.c2d\n var res = ctx.resolution\n var gl = ctx.renderer && ctx.renderer.domElement\n var W = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || ov.canvas.width))\n var H = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || ov.canvas.height))\n // Resize: the backing canvas follows the renderer (dispose the texture so\n // THREE reallocates at the new dims), the plane follows the overlay camera.\n if (ov.canvas.width !== W || ov.canvas.height !== H) {\n ov.canvas.width = W; ov.canvas.height = H\n if (ov.texture && ov.texture.dispose) ov.texture.dispose()\n ov.sig = null\n }\n var ocam = ctx.overlayCamera\n if (ocam && ov.mesh && ctx.THREE) {\n var opw = ocam.right - ocam.left, oph = ocam.top - ocam.bottom\n if (opw !== ov.pw || oph !== ov.ph) {\n if (ov.mesh.geometry && ov.mesh.geometry.dispose) ov.mesh.geometry.dispose()\n ov.mesh.geometry = new ctx.THREE.PlaneGeometry(opw, oph)\n ov.pw = opw; ov.ph = oph\n }\n }\n var d = ctx.data || {}\n var TL = globalThis.__vosTimeline\n var t = ctx.time || 0\n var s = H / 1080 // scale design-px controls to comp px\n var ns = window.__vos__ || {}\n var playing = ns.isPaused === false\n\n // --- audio clips (music/SFX): Web Audio scheduler against the transport ---\n // Buffer sources are one-shot: (re)schedule everything on play / clip-data change\n // (SET_DATA) / drift; kill everything on pause. Seeks arrive paused (bridge forces\n // it), so scrubbing is silent like the video seek path. Export forces isPaused=true,\n // so none of this runs there — the export mixes offline from the same env points.\n var clips = d.audio || []\n var actx = ns.audioCtx\n if (!actx && clips.length && window.AudioContext) actx = ns.audioCtx = new window.AudioContext()\n if (actx) {\n var AS = ns.audioSched || (ns.audioSched = { on: false, nodes: [], sig: '', t0: 0, at0: 0, last: 0 })\n var bufs = ns.audioBuffers || (ns.audioBuffers = new Map())\n var pend = ns.audioPending || (ns.audioPending = new Set())\n // Clips added after LOAD (SET_DATA) decode lazily; completion clears the\n // signature so the next playing frame reschedules with the new buffer.\n for (var di = 0; di < clips.length; di++) {\n ;(function (cc) {\n if (bufs.has(cc.key) || pend.has(cc.key)) return\n pend.add(cc.key)\n fetch(cc.key)\n .then(function (res) { return res.arrayBuffer() })\n .then(function (ab) { return actx.decodeAudioData(ab) })\n .then(function (b) { bufs.set(cc.key, b); AS.sig = '' })\n .catch(function (e) { console.warn('[voila] audio decode failed', cc.key, e) })\n .then(function () { pend.delete(cc.key) })\n })(clips[di])\n }\n // Duck multiplier curve (output-time points, merged in by useComposition).\n var dEnv = d.duckEnv || []\n // Cheap change signature for the duck curve (full stringify would run 60×/s\n // over hundreds of points) — length + endpoints tracks every real change.\n var dSig = dEnv.length ? dEnv.length + ':' + dEnv[0].g + ',' + dEnv[dEnv.length - 1].t + ',' + dEnv[dEnv.length - 1].g : '0'\n var sig = JSON.stringify(clips) + '#' + dSig\n // Envelope value at output time tt (linear interp; def outside/empty).\n var auEnvAt = function (env, tt, def) {\n if (!env.length) return def\n if (tt <= env[0].t) return env[0].g\n if (tt >= env[env.length - 1].t) return env[env.length - 1].g\n for (var ii = 1; ii < env.length; ii++) {\n if (tt <= env[ii].t) {\n var A = env[ii - 1], B = env[ii]\n return A.g + (B.g - A.g) * ((tt - A.t) / Math.max(1e-9, B.t - A.t))\n }\n }\n return def\n }\n var kill = function () {\n for (var ki = 0; ki < AS.nodes.length; ki++) {\n try { if (AS.nodes[ki].stop) AS.nodes[ki].stop() } catch (e) {}\n try { AS.nodes[ki].disconnect() } catch (e) {}\n }\n AS.nodes = []\n }\n var drift = AS.on ? Math.abs((actx.currentTime - AS.at0) - (t - AS.t0)) : 0\n if (!playing && AS.on) { kill(); AS.on = false }\n var needSched = playing && (!AS.on || sig !== AS.sig ||\n (drift > 0.08 && actx.currentTime - AS.last > 0.25))\n if (needSched) {\n kill()\n if (actx.state === 'suspended') { try { actx.resume() } catch (e) {} }\n var now = actx.currentTime\n AS.on = true; AS.sig = sig; AS.t0 = t; AS.at0 = now; AS.last = now\n // NOTE: ON_FRAME is one var scope — locals here are prefixed (au*) so they\n // can't shadow the compositor's c/cur/etc used later in the function.\n for (var ai = 0; ai < clips.length; ai++) {\n var au = clips[ai]\n var auSpan = au.out - au.in\n var auLen = au.len || auSpan\n var auEnd = au.start + auLen\n if (auSpan <= 0 || auEnd <= t + 0.001) continue\n var auBuf = bufs.get(au.key)\n if (!auBuf) continue\n var auSrc = actx.createBufferSource()\n auSrc.buffer = auBuf\n var auGain = actx.createGain()\n auSrc.connect(auGain)\n var auTail = auGain\n // Duck under speech: a second gain stage driven by the shared curve.\n if (au.duck && dEnv.length) {\n var auDuck = actx.createGain()\n auGain.connect(auDuck); auDuck.connect(actx.destination)\n auDuck.gain.setValueAtTime(auEnvAt(dEnv, t, 1), now)\n for (var qi = 0; qi < dEnv.length; qi++) {\n if (dEnv[qi].t > t && dEnv[qi].t <= auEnd) auDuck.gain.linearRampToValueAtTime(dEnv[qi].g, now + (dEnv[qi].t - t))\n }\n auTail = auDuck\n } else {\n auGain.connect(actx.destination)\n }\n // Envelope points (output-time, baked by the lowering) → audio-clock times.\n var env = au.env || []\n auGain.gain.setValueAtTime(auEnvAt(env, t, au.gain != null ? au.gain : 1), now)\n for (var ri = 0; ri < env.length; ri++) {\n if (env[ri].t > t) auGain.gain.linearRampToValueAtTime(env[ri].g, now + (env[ri].t - t))\n }\n // Looping: native Web Audio loop over the source span fills the placed length.\n var auOff = au.in + Math.max(0, t - au.start)\n if (au.loop) {\n auSrc.loop = true\n auSrc.loopStart = au.in\n auSrc.loopEnd = au.out\n auOff = au.in + (Math.max(0, t - au.start) % auSpan)\n }\n auSrc.start(now + Math.max(0, au.start - t), auOff, auEnd - Math.max(t, au.start))\n AS.nodes.push(auSrc); AS.nodes.push(auGain); if (auTail !== auGain) AS.nodes.push(auTail)\n }\n }\n }\n\n function rr(x, y, w, h, rd, cx) {\n var cc = cx || ovC\n if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }\n else { cc.beginPath(); cc.rect(x, y, w, h) }\n }\n\n // Text overlays: visibility signature + \"a transition is animating\".\n // During a hold the drawn pixels are static, so the signature alone drives\n // redraws (position/text/style edits change it — live SET_DATA); during\n // enter/exit windows alpha/offset change per-frame, so olAnim forces redraw.\n var ols = d.overlays || []\n var olTD = ${OVERLAY_TRANSITION_DUR}\n var olAnim = false, olVisSig = ''\n for (var oi = 0; oi < ols.length; oi++) {\n var ol0 = ols[oi]\n if (t < ol0.start || t > ol0.start + ol0.dur) continue\n // A page that taints on foreignObject draws no HTML layer (setup's probe).\n if (ol0.html && ns.htmlSafe === false) continue\n olVisSig += ol0.id + ':' + (ol0.text || ol0.key) + ':' + ol0.x + ',' + ol0.y + ',' + ol0.scale + ',' + ol0.rot + ',' + (ol0.fs || ol0.w) + ',' + (ol0.color || ol0.radius) + ',' + (ol0.opacity == null ? 1 : ol0.opacity) + (ol0.fx ? ',' + ol0.fx.k + ol0.fx.u + ol0.fx.d + ol0.fx.st : '') + (ol0.mw ? ',w' + ol0.mw : '') + ';'\n // fx widens the entrance window to the whole staggered span (tt);\n // without fx it is the legacy enter transition window.\n var olEW = ol0.fx ? ol0.fx.tt : olTD\n if (t < ol0.start + olEW || t > ol0.start + ol0.dur - olTD) olAnim = true\n // Pose keyframes: a clip with a motion track animates its transform\n // over its whole life, so it must repaint every visible frame — the\n // signature alone would freeze it between edits (the olVisSig trap).\n if (ol0.track) olAnim = true\n // A layer CARD is painted on its own plane every frame it is visible.\n if (ol0.card) olAnim = true\n // An HTML layer whose source just changed is rasterizing: keep repainting\n // until its picture lands, or the edit would not appear until something\n // else happened to dirty the canvas.\n if (ol0.html && !(ns.videoCache && ns.videoCache.get(ol0.key))) olAnim = true\n // A LIVE html layer is a new picture every frame it is on screen.\n if (ol0.html && ol0.html.live) olAnim = true\n // Video overlays advance every frame; images redraw until decoded.\n if (ol0.kind === 'video') olAnim = true\n else if (ol0.kind === 'image') {\n var olEl0 = ns.videoCache && ns.videoCache.get(ol0.key)\n if (!(olEl0 && olEl0.complete && olEl0.naturalWidth)) olAnim = true\n }\n }\n // A clip that left the document takes its picture with it: the cache\n // holds decoded images, and a session's edits would otherwise pile up.\n if (ns.htmlLast) {\n for (var hlK in ns.htmlLast) {\n var hlSeen = false\n for (var hlI = 0; hlI < ols.length; hlI++) if (ols[hlI].id === hlK) { hlSeen = true; break }\n if (hlSeen) continue\n if (ns.htmlKeys && ns.htmlKeys[hlK] && ns.videoCache) ns.videoCache.delete(ns.htmlKeys[hlK])\n delete ns.htmlLast[hlK]\n if (ns.htmlKeys) delete ns.htmlKeys[hlK]\n if (ns.htmlWant) delete ns.htmlWant[hlK]\n if (ns.htmlErrors) delete ns.htmlErrors[hlK]\n }\n }\n var ovSig = W + 'x' + H + '|' + olVisSig\n var ovDirty = ov.sig !== ovSig || ov.active || olAnim\n // The layer cards visible this frame, with their pose, for the card\n // planes below (the overlay loop collects them instead of painting).\n var lcList = []\n // Acquire a media overlay's element through the shared cache and sync it\n // to CLIP-LOCAL time (pure f(t); muted always — soundtracks belong to\n // doc.audio). Null until it has a frame to draw. One seam for the flat\n // picture and the layer card.\n function olAcquire(ol, olT) {\n var olEl = ns.videoCache ? ns.videoCache.get(ol.key) : null\n // An HTML layer's key is a CACHE HANDLE, not a URL: never hand it to an\n // <img>. A miss means setup has not built it (a live edit), so ask once\n // per key and let a later frame pick it up; a build that failed stays\n // asked, and the next source edit is a new key.\n if (ol.html) {\n if (ol.html.live) {\n // A live layer's picture is f(t): the key carries clip-local time on\n // the ${HTML_LAYER_LIVE_HZ} Hz grid, the builder keeps ONE picture per clip\n // (the previous moment's is evicted as the next lands) and the last\n // landed picture is drawn while the next composes, so the preview\n // never blinks. A build in flight for this very moment is not asked\n // again (htmlWant holds the moment). In capture the build rides\n // pendingDecodes, so the frame settles and paints again with the\n // exact picture (the harness's two-phase settle): the export is\n // deterministic where the preview is merely prompt.\n var hlQ = Math.round(olT * ${HTML_LAYER_LIVE_HZ})\n var hlKey = ol.key + '@' + hlQ\n var hlEl = ns.videoCache ? ns.videoCache.get(hlKey) : null\n if (!hlEl && ns.buildHtmlLayer && !(ns.htmlWant && ns.htmlWant[ol.id] === hlKey)) {\n var hlP = ns.buildHtmlLayer(ol, { key: hlKey, t: hlQ / ${HTML_LAYER_LIVE_HZ} })\n if (ns.pendingDecodes && hlP && hlP.finally) {\n ns.pendingDecodes.add(hlP)\n hlP.finally(function () { ns.pendingDecodes.delete(hlP) })\n }\n }\n return hlEl || (ns.htmlLast && ns.htmlLast[ol.id]) || null\n }\n if (!olEl && ns.buildHtmlLayer) {\n var hlB = ns.htmlBuilding || (ns.htmlBuilding = {})\n if (!hlB[ol.key]) { hlB[ol.key] = 1; ns.buildHtmlLayer(ol) }\n }\n // While the edited source rasterizes, keep the clip's previous picture.\n return olEl || (ns.htmlLast && ns.htmlLast[ol.id]) || null\n }\n if (!olEl && ns.videoCache) {\n if (ol.kind === 'image') {\n olEl = new Image()\n olEl.crossOrigin = 'anonymous'\n olEl.src = ol.key\n } else {\n olEl = document.createElement('video')\n olEl.crossOrigin = 'anonymous'\n olEl.muted = true\n olEl.playsInline = true\n olEl.preload = 'auto'\n olEl.src = ol.key\n olEl.load()\n }\n ns.videoCache.set(ol.key, olEl)\n }\n if (!olEl) return null\n var olIsImg = ol.kind === 'image'\n if (!olIsImg && olEl.play) {\n var olDur = olEl.duration || 0\n var olMT = olT\n if (ol.loop && olDur > 0) olMT = olT % olDur\n else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)\n try {\n if (playing) {\n if (olEl.playbackRate !== 1) olEl.playbackRate = 1\n var olDrift = Math.abs(olEl.currentTime - olMT)\n if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT\n if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }\n else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }\n } else {\n if (!olEl.paused) olEl.pause()\n var olTarget = Math.min(olMT, olEl.duration || olMT)\n // Coalesce scrub seeks (the backgroundMedia pattern): re-assigning\n // currentTime aborts the in-flight seek, so a per-frame scrub keeps\n // a remote source seeking forever. Defer until 'seeked' lands.\n if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {\n if (ns.pendingDecodes) {\n var olDp = new Promise(function (resolve) {\n var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }\n olEl.addEventListener('seeked', olDone)\n setTimeout(olDone, 250)\n })\n ns.pendingDecodes.add(olDp)\n olDp.finally(function () { ns.pendingDecodes.delete(olDp) })\n }\n olEl.currentTime = olTarget\n }\n }\n } catch (e) {}\n }\n // Video readiness is STICKY through seeks (the cam-bubble pattern):\n // readyState dips below HAVE_CURRENT_DATA while a scrub seek is in\n // flight, and this layer repaints every frame a video clip is visible —\n // gating each frame on it would blink the clip out for the whole drag.\n // After the first decoded frame, keep drawing: Chrome paints the\n // element's retained frame mid-seek.\n if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true\n var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)\n return olReady ? olEl : null\n }\n if (ovDirty) {\n ovC.clearRect(0, 0, W, H)\n // --- text overlays (compositor v2): OUTPUT-anchored clips, styles resolved\n // at lowering (fs/weight/stack/color/shadow are plain values — no registry),\n // drawn ABOVE the cam bubble in screen space. Enter/exit are pure f(t): fade\n // and rise over the transition window; center-anchored multi-line text with a\n // legibility shadow. ol.x/ol.y are FRACTIONS of the frame [0..1] (the zoom\n // cx/cy convention) so positions survive aspect-ratio changes; font size is\n // design px × s (H-relative — stable across aspects). Geometry MIRRORS\n // overlayText.ts overlayRect/overlayFontString (change together — the\n // on-canvas picking depends on it). Locals ol-prefixed (one var scope).\n for (var oj = 0; oj < ols.length; oj++) {\n var ol = ols[oj]\n var olT = t - ol.start\n if (olT < 0 || olT > ol.dur) continue\n if (ol.html && ns.htmlSafe === false) continue\n var olA = 1, olYof = 0, olScl = 1, olBlur = 0\n if (ol.fx && ol.fx.u === 'block') {\n // fx owns the entrance; block unit = the legacy presets generalized\n // (fade/rise identical math, plus pop/blur/typewriter at clip level).\n if (olT < ol.fx.dur && ol.fx.k !== 'typewriter') {\n var olU2 = Math.max(0, olT / ol.fx.dur)\n var olE2 = 1 - Math.pow(1 - olU2, 3)\n if (ol.fx.k === 'pop') {\n olA = Math.min(1, olU2 * 2)\n // easeOutBack on the RAW progress (olE2 is already eased).\n olScl = 1 + 2.70158 * Math.pow(olU2 - 1, 3) + 1.70158 * Math.pow(olU2 - 1, 2)\n } else {\n olA = olE2\n if (ol.fx.k === 'rise') olYof = (1 - olE2) * 24 * s\n if (ol.fx.k === 'blur') olBlur = (1 - olE2) * ol.fs * ol.scale * s * 0.12\n }\n }\n if (ol.fx.k === 'typewriter' && olT < ol.fx.dur) olA = 0\n } else if (!ol.fx && ol.enter !== 'none' && olT < olTD) {\n var olU = olT / olTD\n olU = 1 - Math.pow(1 - olU, 3)\n olA = olU\n if (ol.enter === 'rise') olYof = (1 - olU) * 24 * s\n }\n if (ol.exit !== 'none' && ol.dur - olT < olTD) {\n var olV = (ol.dur - olT) / olTD\n olV = 1 - Math.pow(1 - olV, 3)\n olA = Math.min(olA, olV)\n if (ol.exit === 'rise') olYof = -(1 - olV) * 24 * s\n }\n // Pose keyframes: sample the clip-local [x, y, scale, rot, opacity]\n // track at olT; absent = the static transform. Pose opacity is a\n // MULTIPLIER on the entrance/exit alpha.\n var olPose = null\n if (ol.track && ol.track.keyframes && ol.track.keyframes.length) olPose = TL.sample(ol.track, olT, TL.lerpArray)\n var olMX = olPose ? olPose[0] : ol.x\n var olMY = olPose ? olPose[1] : ol.y\n var olMS = olPose ? olPose[2] : ol.scale\n var olMR = olPose ? olPose[3] : ol.rot\n if (olPose) olA *= Math.max(0, Math.min(1, olPose[4]))\n if (olA <= 0.004) continue\n if (ol.kind === 'image' || ol.kind === 'video') {\n // A layer CARD is painted by the card painter on its own plane, below:\n // collect it with its pose and skip the flat picture.\n if (ol.card) {\n lcList.push({ ol: ol, t: olT, a: olA * (ol.opacity == null ? 1 : ol.opacity), x: olMX, y: olMY, s: olMS, r: olMR, yof: olYof })\n continue\n }\n // Media overlay: lazy-acquire through the shared cache (SET_DATA-added\n // clips load without a LOAD — the backgroundMedia pattern), sync video\n // to CLIP-LOCAL time (pure f(t)), draw a rounded media card centered on\n // the fraction anchor. Muted always — soundtracks belong to doc.audio.\n var olEl = olAcquire(ol, olT)\n if (!olEl) continue\n var olIsImg = ol.kind === 'image'\n var olNW = (olIsImg ? olEl.naturalWidth : olEl.videoWidth) || 16\n var olNH = (olIsImg ? olEl.naturalHeight : olEl.videoHeight) || 9\n var olDW = ol.w * W * olMS\n var olDH = olDW * (olNH / olNW)\n var olRad = Math.min((ol.radius || 0) * s, olDH / 2)\n ovC.save()\n ovC.globalAlpha = olA * (ol.opacity == null ? 1 : ol.opacity)\n ovC.translate(olMX * W, olMY * H + olYof)\n if (olMR) ovC.rotate(olMR * Math.PI / 180)\n // Card shadow (absent = 'soft', the baked look docs predating the field render;\n // 'strong' floats harder; 'none' is the flat cutout), then the media\n // clipped to rounded corners, then an optional border stroke drawn\n // OVER the edge — outside the clip, or half the stroke vanishes.\n var olShadow = ol.shadow || 'soft'\n if (olShadow !== 'none') {\n ovC.save()\n ovC.shadowColor = olShadow === 'strong' ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.35)'\n ovC.shadowBlur = (olShadow === 'strong' ? 48 : 24) * s\n ovC.shadowOffsetY = (olShadow === 'strong' ? 16 : 8) * s\n ovC.fillStyle = '#000'\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.fill()\n ovC.restore()\n }\n ovC.save()\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.clip()\n try { ovC.drawImage(olEl, -olDW / 2, -olDH / 2, olDW, olDH) } catch (e) {}\n ovC.restore()\n if (ol.border && ol.border.width > 0) {\n ovC.strokeStyle = ol.border.color || '#ffffff'\n ovC.lineWidth = ol.border.width * s\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.stroke()\n }\n ovC.restore()\n continue\n }\n var olPx = ol.fs * olMS * s\n // Faces arriving LIVE: SET_DATA never re-runs SETUP, so the first text\n // overlay a session gains registers the house faces here (a title added\n // to a fresh document used to paint in the stack's system fallback until\n // the next cold load), and an override face lazy-loads the same way.\n // Keyed per window, so SETUP's cold-load faces and repeat frames never\n // re-register; fail-open, frames repaint as each face lands.\n if (typeof FontFace !== 'undefined') {\n var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\n var ofBase = d.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}\n for (var ofi = 0; ofi < ofBase.length; ofi++) {\n var ofB = ofBase[ofi]\n var ofBK = ofB.family + '|' + ofB.weight\n if (ofSet[ofBK]) continue\n ofSet[ofBK] = 1\n try {\n var ofBF = new FontFace(ofB.family, 'url(' + ofB.url + ')', { weight: String(ofB.weight) })\n document.fonts.add(ofBF)\n ofBF.load().catch(function () {})\n } catch (e) {}\n }\n if (ol.face) {\n var ofKey = ol.face.f + '|' + ol.face.w\n if (!ofSet[ofKey]) {\n ofSet[ofKey] = 1\n try {\n var ofFace = new FontFace(ol.face.f, 'url(' + ol.face.u + ')', { weight: String(ol.face.w) })\n document.fonts.add(ofFace)\n ofFace.load().catch(function () {})\n } catch (e) {}\n }\n }\n }\n ovC.save()\n ovC.globalAlpha = olA\n ovC.translate(olMX * W, olMY * H + olYof)\n if (olMR) ovC.rotate(olMR * Math.PI / 180)\n // Block-unit fx entrance (pop scale / blur) — clip-level, about the anchor.\n if (olScl !== 1) ovC.scale(olScl, olScl)\n if (olBlur > 0.05) ovC.filter = 'blur(' + olBlur.toFixed(2) + 'px)'\n // Style overrides ride ctx.data (sty/ls/lh/align/stroke baked only when\n // non-default — parity — but READ unconditionally: every knob is a live\n // SET_DATA by construction).\n ovC.font = (ol.sty ? ol.sty + ' ' : '') + ol.weight + ' ' + olPx + 'px ' + ol.stack\n ovC.textAlign = 'center'\n ovC.textBaseline = 'middle'\n ovC.letterSpacing = ((ol.ls || 0) * olMS * s) + 'px'\n var olLines = ol.lines || ['']\n // maxWidth wrap (ol.mw = frame-width fraction): greedy over word tokens\n // (/\\\\S+\\\\s*/ — the SAME tokenization fx uses, trailing spaces kept, so\n // unit sequences stay byte-identical) at measured widths. MIRRORS\n // wrapOverlayLines in overlayText.ts — change together. A token wider\n // than the budget gets its own line; explicit \\\\n lines wrap independently.\n if (ol.mw) {\n var olWMax = ol.mw * W\n var olWrapped = []\n for (var olwl = 0; olwl < olLines.length; olwl++) {\n var olWLine = olLines[olwl]\n if (!olWLine || ovC.measureText(olWLine).width <= olWMax) {\n olWrapped.push(olWLine)\n continue\n }\n var olToks = olWLine.match(/\\\\S+\\\\s*/g) || [olWLine]\n var olCur = ''\n for (var olti = 0; olti < olToks.length; olti++) {\n if (!olCur) { olCur = olToks[olti]; continue }\n if (ovC.measureText(olCur + olToks[olti]).width <= olWMax) {\n olCur += olToks[olti]\n } else {\n olWrapped.push(olCur)\n olCur = olToks[olti]\n }\n }\n if (olCur) olWrapped.push(olCur)\n }\n olLines = olWrapped.length ? olWrapped : ['']\n }\n var olLH = olPx * (ol.lh || ${OVERLAY_LINE_HEIGHT})\n var olY0 = -((olLines.length - 1) * olLH) / 2\n // Per-line widths: needed by the pill, by left/right alignment (lines\n // draw centered; alignment is an x offset against the widest line), and\n // by per-unit fx (units place by prefix advance from the line's left edge).\n var olFx = ol.fx && ol.fx.units.length ? ol.fx : null\n // With wrap active, baked per-line unit arrays regroup onto the WRAPPED\n // lines. Wrapping never reorders: word/char units consume in flat order\n // by string length (wrapped lines are token concatenations); 'line'\n // units become one per wrapped line. Flat delay order is unchanged.\n if (olFx && ol.mw) {\n var olFlat = []\n for (var olfi = 0; olfi < olFx.units.length; olfi++) {\n for (var olfj = 0; olfj < olFx.units[olfi].length; olfj++) {\n olFlat.push(olFx.units[olfi][olfj])\n }\n }\n var olRe = []\n if (olFx.u === 'line') {\n for (var olri = 0; olri < olLines.length; olri++) olRe.push([olLines[olri]])\n } else {\n var olFk = 0\n for (var olri2 = 0; olri2 < olLines.length; olri2++) {\n var olNeed = olLines[olri2].length\n var olArr = []\n var olGot = 0\n while (olFk < olFlat.length && olGot < olNeed) {\n olArr.push(olFlat[olFk])\n olGot += olFlat[olFk].length\n olFk++\n }\n olRe.push(olArr)\n }\n }\n var olReN = 0\n for (var olrn = 0; olrn < olRe.length; olrn++) olReN += olRe[olrn].length\n olFx = { k: olFx.k, u: olFx.u, d: olFx.d, st: olFx.st, dur: olFx.dur, tt: olFx.tt, units: olRe, n: olReN }\n }\n var olLWs = null, olMaxW = 0\n if (ol.box || ol.align || olFx) {\n olLWs = []\n for (var olwi = 0; olwi < olLines.length; olwi++) {\n var olw = ovC.measureText(olLines[olwi]).width\n olLWs.push(olw)\n if (olw > olMaxW) olMaxW = olw\n }\n }\n // Background pill (ol.box, baked design px at fs): drawn BEFORE the text\n // and before the legibility shadow config, so the pill never inherits the\n // text shadow. Geometry mirrors overlayRect's inflation — change together.\n if (ol.box) {\n var obPX = ol.box.px * olMS * s\n var obPY = ol.box.py * olMS * s\n var obFullW = olMaxW + obPX * 2\n var obFullH = olLines.length * olLH + obPY * 2\n var obR = Math.min(ol.box.r * olMS * s, obFullH / 2)\n ovC.save()\n ovC.globalAlpha = olA * ol.box.o\n ovC.fillStyle = ol.box.c\n rr(-obFullW / 2, -obFullH / 2, obFullW, obFullH, obR, ovC)\n ovC.fill()\n ovC.restore()\n }\n if (ol.shadow > 0) {\n ovC.shadowColor = 'rgba(0,0,0,' + ol.shadow + ')'\n ovC.shadowBlur = olPx * 0.25\n ovC.shadowOffsetY = olPx * 0.04\n }\n ovC.fillStyle = ol.color\n if (!olFx) {\n for (var ok = 0; ok < olLines.length; ok++) {\n var olXof = 0\n if (ol.align && olLWs) {\n olXof = ol.align === 'left'\n ? (olLWs[ok] - olMaxW) / 2\n : (olMaxW - olLWs[ok]) / 2\n }\n var olLY = olY0 + ok * olLH\n if (ol.stroke) {\n ovC.strokeStyle = ol.stroke.c\n ovC.lineWidth = ol.stroke.w * olMS * s\n ovC.lineJoin = 'round'\n ovC.strokeText(olLines[ok], olXof, olLY)\n }\n ovC.fillText(olLines[ok], olXof, olLY)\n }\n } else {\n // Per-unit entrance: units draw LEFT-aligned at prefix advances\n // measured from the full line (exact bar cross-unit kerning), so the\n // settled frame matches the non-fx layout. Per-unit progress is pure\n // f(t): delay = order(index)·st, eased over dur; typewriter is a step\n // reveal. Stroke-under-fill per unit; pill/shadow config above apply.\n ovC.textAlign = 'left'\n var olIdx = 0\n for (var ok2 = 0; ok2 < olFx.units.length; ok2++) {\n var olUs = olFx.units[ok2]\n var olLW2 = olLWs ? olLWs[ok2] : 0\n var olLY2 = olY0 + ok2 * olLH\n var olXof2 = 0\n if (ol.align) {\n olXof2 = ol.align === 'left'\n ? (olLW2 - olMaxW) / 2\n : (olMaxW - olLW2) / 2\n }\n var olXb = olXof2 - olLW2 / 2\n var olPref = '', olPW = 0\n for (var ou = 0; ou < olUs.length; ou++, olIdx++) {\n var olOrd = olFx.d === 1\n ? (olFx.n - 1 - olIdx)\n : olFx.d === 2\n ? Math.abs(olIdx - (olFx.n - 1) / 2)\n : olIdx\n var olT2 = olT - olOrd * olFx.st\n var olNext = olPref + olUs[ou]\n var olNW = ovC.measureText(olNext).width\n var olUW = olNW - olPW\n var olUX = olXb + olPW\n var olUA = 1, olUu = 1\n if (olFx.k === 'typewriter') {\n olUA = olT2 >= 0 ? 1 : 0\n } else {\n olUu = Math.max(0, Math.min(1, olT2 / olFx.dur))\n var olUE = 1 - Math.pow(1 - olUu, 3)\n olUA = olFx.k === 'pop' ? Math.min(1, olUu * 2) : olUE\n }\n var olUnit = olUs[ou]\n olPref = olNext\n olPW = olNW\n if (olUA <= 0.004) continue\n ovC.save()\n ovC.globalAlpha = olA * olUA\n if (olUu < 1) {\n if (olFx.k === 'rise') {\n ovC.translate(0, (1 - (1 - Math.pow(1 - olUu, 3))) * 24 * s)\n } else if (olFx.k === 'pop') {\n var olPS = 1 + 2.70158 * Math.pow(olUu - 1, 3) + 1.70158 * Math.pow(olUu - 1, 2)\n ovC.translate(olUX + olUW / 2, olLY2)\n ovC.scale(olPS, olPS)\n ovC.translate(-(olUX + olUW / 2), -olLY2)\n } else if (olFx.k === 'blur') {\n ovC.filter = 'blur(' + ((1 - olUu) * olPx * 0.12).toFixed(2) + 'px)'\n }\n }\n if (ol.stroke) {\n ovC.strokeStyle = ol.stroke.c\n ovC.lineWidth = ol.stroke.w * olMS * s\n ovC.lineJoin = 'round'\n ovC.strokeText(olUnit, olUX, olLY2)\n }\n ovC.fillText(olUnit, olUX, olLY2)\n ovC.restore()\n }\n }\n }\n ovC.restore()\n }\n ov.sig = ovSig\n ov.active = olVisSig !== ''\n if (ov.texture) ov.texture.needsUpdate = true\n }\n\n // --- object clips: reconcile a mesh pool against d.objects — the\n // interpreter pattern in 3D. Add/remove/asset-change are live SET_DATA\n // (create/dispose here); transforms + span fades + animation are pure f(t).\n // Frame-fraction position maps onto the frustum plane at the object's depth\n // (stage.ts math); scale is a fraction of the frame height at the CARD depth\n // (closer objects render bigger — the perspective cue). Locals ob*.\n var obC = r.objects\n var THREE3 = ctx.THREE\n if (obC && obC.group && obC.pool && THREE3) {\n var obs = d.objects || []\n var obSeen = {}\n var obTan = Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var obRefH = 2 * Math.abs(${CARD_Z}) * obTan // frame height at the reference depth\n var obAspect = W / H\n // The anchor's camera: a prop sits on THAT camera's frustum plane\n // at its depth, at the frame fraction it was placed at, so the host's\n // picking rect (the same fraction) holds on any anchor. The recording's\n // camera (the origin, looking down -z, CARD_FOV) reduces this to the\n // constants its card program shares; a user program's camera can be\n // anywhere, and its lights light the props. Scale stays \"a fraction of\n // the frame height at the reference depth\" because obRefH follows the\n // camera's fov.\n var obCam = ctx.camera && ctx.camera.isPerspectiveCamera && ctx.camera.quaternion ? ctx.camera : null\n // An ORTHOGRAPHIC anchor camera (a program's \\`fullscreen\\` preset is\n // OrthographicCamera(-1, 1, 1, -1, 0, 1); the generic ortho preset spans\n // width/zoom): the prop sits on the camera's own box at mid-depth, at the\n // frame fraction, scaled against the box's height. The perspective\n // constants put it metres behind a far plane of 1, which is how a prop on\n // a shader program drew its picking box and nothing else.\n var obOrtho = !obCam && ctx.camera && ctx.camera.isOrthographicCamera && ctx.camera.quaternion ? ctx.camera : null\n var obB = null\n if (obCam || obOrtho) {\n var obBCam = obCam || obOrtho\n obB = obC.basis || (obC.basis = { f: new THREE3.Vector3(), r: new THREE3.Vector3(), u: new THREE3.Vector3(), q: new THREE3.Quaternion(), e: new THREE3.Euler() })\n obB.f.set(0, 0, -1).applyQuaternion(obBCam.quaternion)\n obB.r.set(1, 0, 0).applyQuaternion(obBCam.quaternion)\n obB.u.set(0, 1, 0).applyQuaternion(obBCam.quaternion)\n }\n var obOW = 0, obOH = 0, obOCX = 0, obOCY = 0, obOD = 0\n if (obCam) {\n obTan = Math.tan(obCam.fov * Math.PI / 180 / 2)\n obRefH = 2 * Math.abs(${CARD_Z}) * obTan\n } else if (obOrtho) {\n var obOZ = obOrtho.zoom || 1\n obOW = (obOrtho.right - obOrtho.left) / obOZ\n obOH = (obOrtho.top - obOrtho.bottom) / obOZ\n obOCX = (obOrtho.right + obOrtho.left) / 2 / obOZ\n obOCY = (obOrtho.top + obOrtho.bottom) / 2 / obOZ\n obOD = obOrtho.near + (obOrtho.far - obOrtho.near) * 0.5\n obRefH = obOH\n // The box's units are not square on the canvas (the fullscreen preset\n // is -1..1 both ways over 16:9), so a sphere would draw as an ellipse.\n // The correction is a camera-space squash AFTER the prop's own\n // rotation: the group is aligned with the camera and scaled on its x\n // by the pixel aspect, and ortho props are placed in the group's\n // local space.\n var obAX = (obOW * H) / (obOH * W)\n obC.group.position.copy(obOrtho.position)\n obC.group.quaternion.copy(obOrtho.quaternion)\n obC.group.scale.set(obAX, 1, 1)\n }\n // A program's scene lights its own props, when it has lights at all: a\n // shader program has none, and an unlit MeshStandardMaterial is black.\n // Once, when the first prop appears: add the entry's pair only if no light\n // is in the scene (the recording's card program carries its own).\n if (obs.length && !obC.lit && ctx.scene && ctx.scene.traverse) {\n obC.lit = true\n var obHasLight = false\n ctx.scene.traverse(function (obN0) { if (obN0.isLight) obHasLight = true })\n if (!obHasLight) {\n var obAmb = new THREE3.AmbientLight(0xffffff, 0.75)\n var obDir = new THREE3.DirectionalLight(0xffffff, 1.4)\n obDir.position.set(2, 3, 4)\n obC.group.add(obAmb)\n obC.group.add(obDir)\n }\n }\n for (var bi = 0; bi < obs.length; bi++) {\n var ob = obs[bi]\n var obIsGltf = ob.asset.kind === 'gltf'\n var obIsT3 = ob.asset.kind === 'text3d'\n if (obIsGltf && !(ns.objCache && ns.objCache.get(ob.asset.key))) continue // not loaded (yet)\n if (obIsT3 && !(ns.fontCache && ns.fontCache.get(ob.asset.url))) {\n // Live SET_DATA additions never re-run SETUP — lazy-load the\n // typeface once (fail-open) and skip the clip until it lands.\n var obT3P = ns.fontPending || (ns.fontPending = {})\n if (!obT3P[ob.asset.url] && ctx.loaders && ctx.loaders.FontLoader) {\n obT3P[ob.asset.url] = 1\n try {\n new ctx.loaders.FontLoader().load(ob.asset.url, (function (obT3U) {\n return function (obT3F) {\n var obT3C = ns.fontCache || (ns.fontCache = new Map())\n obT3C.set(obT3U, obT3F)\n }\n })(ob.asset.url), undefined, function () {})\n } catch (e) {}\n }\n continue\n }\n obSeen[ob.id] = true\n var obSig = obIsGltf ? 'gltf|' + ob.asset.key\n : obIsT3 ? 'text3d|' + JSON.stringify(ob.asset)\n : ob.asset.shape + '|' + ob.asset.color\n var obE = obC.pool.get(ob.id)\n if (obE && obE.sig !== obSig) {\n obC.group.remove(obE.mesh)\n if (obE.mesh.traverse) obE.mesh.traverse(function (obN4) {\n if (obN4.geometry && obN4.geometry.dispose) obN4.geometry.dispose()\n if (obN4.material && obN4.material.dispose) obN4.material.dispose()\n })\n if (obE.mesh.geometry) obE.mesh.geometry.dispose()\n if (obE.mesh.material) obE.mesh.material.dispose()\n obE = null\n }\n if (!obE && obIsGltf) {\n // Clone the cached scene with CLONED materials (fade opacity must not\n // leak across instances); normalize scale via the cached bbox factor.\n var obSrc = ns.objCache.get(ob.asset.key)\n var obRoot = obSrc.scene.clone(true)\n obRoot.traverse(function (obN) {\n if (obN.isMesh) {\n obN.material = obN.material.clone()\n obN.material.transparent = true\n obN.renderOrder = 1.5\n }\n })\n obC.group.add(obRoot)\n obE = { mesh: obRoot, sig: obSig, norm: obSrc.norm, gltf: true }\n obC.pool.set(ob.id, obE)\n }\n if (!obE && obIsT3 && ctx.utils && ctx.utils.TextGeometry) {\n // Extruded text from the cached typeface. Geometry is centered and\n // bbox-normalized (norm = 1/maxDim, the GLB convention) so\n // transform3d.scale means the same thing for every asset kind.\n // Materials come pre-resolved from lowering (plain params, single-\n // sided by default — the SwiftShader-safe shape).\n var obT3Font = ns.fontCache.get(ob.asset.url)\n var obT3Geo = new ctx.utils.TextGeometry(ob.asset.text, {\n font: obT3Font,\n size: 1,\n depth: ob.asset.depth,\n curveSegments: 8,\n bevelEnabled: !!ob.asset.bevel,\n bevelThickness: 0.02,\n bevelSize: 0.015,\n bevelSegments: 2,\n })\n obT3Geo.computeBoundingBox()\n obT3Geo.center()\n var obT3Box = obT3Geo.boundingBox\n var obT3Max = Math.max(\n obT3Box.max.x - obT3Box.min.x,\n obT3Box.max.y - obT3Box.min.y,\n obT3Box.max.z - obT3Box.min.z,\n ) || 1\n var obT3Mat = ob.asset.mat.type === 'physical'\n ? new THREE3.MeshPhysicalMaterial(ob.asset.mat.params)\n : new THREE3.MeshStandardMaterial(ob.asset.mat.params)\n obT3Mat.transparent = true\n var obT3Mesh = new THREE3.Mesh(obT3Geo, obT3Mat)\n obT3Mesh.renderOrder = 1.5\n obC.group.add(obT3Mesh)\n // baseA: presets may be translucent (glass) — the span fade\n // multiplies onto it instead of stomping it to 1 during holds.\n obE = {\n mesh: obT3Mesh,\n sig: obSig,\n norm: 1 / obT3Max,\n baseA: ob.asset.mat.params.opacity == null ? 1 : ob.asset.mat.params.opacity,\n }\n obC.pool.set(ob.id, obE)\n }\n if (!obE && !obIsT3) {\n var obGeo = ob.asset.shape === 'sphere' ? new THREE3.SphereGeometry(0.55, 32, 20)\n : ob.asset.shape === 'torus' ? new THREE3.TorusGeometry(0.45, 0.18, 20, 40)\n : ob.asset.shape === 'knot' ? new THREE3.TorusKnotGeometry(0.4, 0.13, 80, 14)\n : new THREE3.BoxGeometry(0.9, 0.9, 0.9)\n var obMat = new THREE3.MeshStandardMaterial({\n color: ob.asset.color, metalness: 0.55, roughness: 0.35, transparent: true,\n })\n var obMesh = new THREE3.Mesh(obGeo, obMat)\n obMesh.renderOrder = 1.5\n obC.group.add(obMesh)\n obE = { mesh: obMesh, sig: obSig }\n obC.pool.set(ob.id, obE)\n }\n if (!obE) continue // text3d without the TextGeometry util — skip\n var obM = obE.mesh\n // Span gate with soft edge fades (OUTPUT-anchored, like overlays).\n var obA = 1\n if (ob.span) {\n var obT = t - ob.span.start\n if (obT < 0 || obT > ob.span.duration) { obM.visible = false; continue }\n var obTD = ${OVERLAY_TRANSITION_DUR}\n if (obT < obTD) obA = obT / obTD\n if (ob.span.duration - obT < obTD) obA = Math.min(obA, (ob.span.duration - obT) / obTD)\n }\n obM.visible = true\n if (obE.gltf) {\n var obA2 = obA\n obM.traverse(function (obN2) { if (obN2.isMesh) obN2.material.opacity = obA2 })\n } else {\n obM.material.opacity = obA * (obE.baseA == null ? 1 : obE.baseA)\n }\n // Pose keyframes: a clip-local [x,y,z,rx,ry,rz,scale] track over\n // the full 3D transform; spin/float presets compose ADDITIVELY on top\n // of the sampled pose (they are offsets, poses are the base).\n var obPose = null\n if (ob.track && ob.track.keyframes && ob.track.keyframes.length) obPose = TL.sample(ob.track, t - (ob.span ? ob.span.start : 0), TL.lerpArray)\n var obPX = obPose ? obPose[0] : ob.x\n var obPY = obPose ? obPose[1] : ob.y\n var obPZ = obPose ? obPose[2] : ob.z\n var obPRX = obPose ? obPose[3] : ob.rx\n var obPRY = obPose ? obPose[4] : ob.ry\n var obPRZ = obPose ? obPose[5] : ob.rz\n var obPS = obPose ? obPose[6] : ob.scale\n var obDist = Math.abs(${CARD_Z}) - obPZ // z is toward the camera\n var obPlaneH = 2 * obDist * obTan\n var obY = -(obPY - 0.5) * obPlaneH\n if (ob.anim === 'float') obY += Math.sin(t * (Math.PI * 2 / 5)) * obRefH * 0.012\n var obX = (obPX - 0.5) * obPlaneH * obAspect\n var obRy = obPRY * Math.PI / 180\n if (ob.anim === 'spin') obRy += t * 0.9\n if (obOrtho) {\n // Group-local (the group carries the camera pose and the aspect squash).\n obM.position.set((obOCX + (obPX - 0.5) * obOW) / obAX, obOCY - (obPY - 0.5) * obOH, -obOD)\n obB.e.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n obM.quaternion.setFromEuler(obB.e)\n } else if (obB) {\n obM.position.copy(obCam.position).addScaledVector(obB.f, obDist).addScaledVector(obB.r, obX).addScaledVector(obB.u, obY)\n obB.e.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n obM.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))\n } else {\n obM.position.set(obX, obY, ${CARD_Z} + obPZ)\n obM.rotation.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n }\n obM.scale.setScalar(obPS * obRefH * (obE.norm || 1))\n }\n // --- layer cards (many cards): a media overlay that names a frame is\n // drawn by the card painter on its OWN plane in the prop group\n // (renderOrder 1.25: above the primary card, below the props): the\n // layered shadow (the primary's table and strengths), the media clipped\n // to its corners, the browser bar after it, the border, and for a\n // document media its click rings and cursor dot inside the card, at\n // clip-local time. Chrome scales WITH the card (a smaller window has\n // smaller chrome), the way the primary's scales with cf. The plane is\n // sized to the canvas at the card depth, placed at the clip's anchor,\n // leaned by the card's own rx/ry (the tilt convention) and turned by the\n // clip's rotation. Locals lc*.\n var lcC = obC.cards || (obC.cards = new Map())\n var lcSeen = {}\n var lcK = obRefH / H\n for (var lci = 0; lci < lcList.length; lci++) {\n var lcI = lcList[lci], lcO = lcI.ol, lcCard = lcO.card\n var lcEl = olAcquire(lcO, lcI.t)\n if (!lcEl) continue\n lcSeen[lcO.id] = true\n var lcE = lcC.get(lcO.id)\n if (!lcE) {\n var lcCv = document.createElement('canvas')\n var lcTx = new THREE3.CanvasTexture(lcCv)\n if (THREE3.SRGBColorSpace) lcTx.colorSpace = THREE3.SRGBColorSpace\n if (THREE3.LinearFilter) { lcTx.minFilter = THREE3.LinearFilter; lcTx.magFilter = THREE3.LinearFilter }\n lcTx.generateMipmaps = false\n var lcM = new THREE3.Mesh(new THREE3.PlaneGeometry(1, 1), new THREE3.MeshBasicMaterial({ map: lcTx, transparent: true, depthTest: false, depthWrite: false }))\n lcM.renderOrder = 1.25\n lcM.frustumCulled = false\n obC.group.add(lcM)\n lcE = { mesh: lcM, canvas: lcCv, c2d: lcCv.getContext('2d'), texture: lcTx }\n lcC.set(lcO.id, lcE)\n }\n var lcIsImg = lcO.kind === 'image'\n var lcCrop = lcO.crop || null\n var lcNW = lcCrop ? lcCrop.w : ((lcIsImg ? lcEl.naturalWidth : lcEl.videoWidth) || 16)\n var lcNH = lcCrop ? lcCrop.h : ((lcIsImg ? lcEl.naturalHeight : lcEl.videoHeight) || 9)\n var lcW = Math.max(2, lcO.w * W * lcI.s)\n var lcS = s * (lcW / W)\n var lcBar = lcCard.bar || {}\n var lcBarH = lcBar.kind && lcBar.kind !== 'none' ? (lcBar.height || 44) * lcS : 0\n var lcMH = lcW * (lcNH / lcNW)\n var lcH = lcMH + lcBarH\n var lcRad = Math.min((lcO.radius || 0) * lcS, lcH / 2)\n var lcMg = Math.ceil(170 * lcS)\n var lcCW = Math.ceil(lcW + 2 * lcMg), lcCH = Math.ceil(lcH + 2 * lcMg)\n var lcCv2 = lcE.canvas, lcX = lcE.c2d\n if (lcCv2.width !== lcCW || lcCv2.height !== lcCH) { lcCv2.width = lcCW; lcCv2.height = lcCH }\n lcX.clearRect(0, 0, lcCW, lcCH)\n var lcX0 = lcMg, lcY0 = lcMg\n var lcSh = lcCard.shadow == null ? ${LAYER_CARD_SHADOW} : lcCard.shadow\n var lcRgb = '0,0,0'\n var lcHex = lcCard.shadowColor\n if (typeof lcHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(lcHex)) lcRgb = parseInt(lcHex.slice(1, 3), 16) + ',' + parseInt(lcHex.slice(3, 5), 16) + ',' + parseInt(lcHex.slice(5, 7), 16)\n // the shadows are cast by a body drawn off the canvas and brought back\n // by shadowOffsetX (the primary's seam rule)\n var lcD = lcCW * 2 + lcCH * 2\n if (lcSh > 0) {\n var lcL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]\n for (var lcj = 0; lcj < lcL.length; lcj++) {\n lcX.save()\n lcX.shadowColor = 'rgba(' + lcRgb + ',' + +(lcSh * lcL[lcj][2]).toFixed(3) + ')'\n lcX.shadowBlur = lcL[lcj][0] * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = lcL[lcj][1] * lcS\n lcX.fillStyle = '#000'\n rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()\n lcX.restore()\n }\n }\n if (lcCard.shadowContact > 0) {\n lcX.save()\n lcX.shadowColor = 'rgba(' + lcRgb + ',' + lcCard.shadowContact + ')'\n lcX.shadowBlur = 10 * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = 3 * lcS\n lcX.fillStyle = '#000'\n rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()\n lcX.restore()\n }\n lcX.save()\n rr(lcX0, lcY0, lcW, lcH, lcRad, lcX); lcX.clip()\n var lcMY = lcY0 + lcBarH\n try {\n if (lcCrop) lcX.drawImage(lcEl, lcCrop.x, lcCrop.y, lcCrop.w, lcCrop.h, lcX0, lcMY, lcW, lcMH)\n else lcX.drawImage(lcEl, lcX0, lcMY, lcW, lcMH)\n } catch (e) {}\n if (lcBarH > 0) {\n var lcDark = lcBar.kind.indexOf('dark') >= 0, lcMin = lcBar.kind === 'minimal'\n var lcThm = (lcMin && lcBar.theme) || null\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.bar : '#141417') : lcDark ? '#2a2a2e' : '#e9e9eb'\n lcX.fillRect(lcX0, lcY0, lcW, lcBarH)\n lcX.fillStyle = lcDark || (lcMin && !(lcThm && lcThm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'\n lcX.fillRect(lcX0, lcY0 + lcBarH - lcS, lcW, lcS)\n var lcMid = lcY0 + lcBarH / 2\n if (lcBar.showControls !== false && !lcMin) {\n if (lcBar.kind.indexOf('mac') === 0) {\n var lcLights = ['#ff5f57', '#febc2e', '#28c840']\n for (var lcl = 0; lcl < 3; lcl++) { lcX.fillStyle = lcLights[lcl]; lcX.beginPath(); lcX.arc(lcX0 + (20 + lcl * 20) * lcS, lcMid, 6 * lcS, 0, Math.PI * 2); lcX.fill() }\n } else {\n lcX.strokeStyle = lcDark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'\n lcX.lineWidth = 1.5 * lcS\n var lcG = 4.5 * lcS, lcGx = lcX0 + lcW - 22 * lcS\n lcX.beginPath(); lcX.moveTo(lcGx - lcG, lcMid - lcG); lcX.lineTo(lcGx + lcG, lcMid + lcG); lcX.moveTo(lcGx + lcG, lcMid - lcG); lcX.lineTo(lcGx - lcG, lcMid + lcG); lcX.stroke()\n lcX.strokeRect(lcGx - 28 * lcS - lcG, lcMid - lcG, lcG * 2, lcG * 2)\n lcX.beginPath(); lcX.moveTo(lcGx - 56 * lcS - lcG, lcMid); lcX.lineTo(lcGx - 56 * lcS + lcG, lcMid); lcX.stroke()\n }\n }\n if (lcBar.showUrl !== false && lcBar.url) {\n var lcPW = Math.min(lcW * 0.5, Math.max(200 * lcS, lcW * 0.34)), lcPH = lcBarH - 16 * lcS\n var lcPx = lcX0 + (lcW - lcPW) / 2, lcPy = lcY0 + 8 * lcS\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.pill : '#26262b') : lcDark ? '#1d1d20' : '#ffffff'\n rr(lcPx, lcPy, lcPW, lcPH, lcPH / 2, lcX); lcX.fill()\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.text : '#9a9aa1') : lcDark ? '#a1a1a6' : '#5f5f64'\n lcX.font = 13 * lcS + 'px -apple-system, system-ui, sans-serif'\n lcX.textAlign = 'center'; lcX.textBaseline = 'middle'\n var lcLabel = String(lcBar.url), lcMaxW = lcPW - 28 * lcS\n if (lcX.measureText(lcLabel).width > lcMaxW) {\n while (lcLabel.length > 1 && lcX.measureText(lcLabel + '\\u2026').width > lcMaxW) lcLabel = lcLabel.slice(0, -1)\n lcLabel += '\\u2026'\n }\n lcX.fillText(lcLabel, lcPx + lcPW / 2, lcPy + lcPH / 2)\n lcX.textAlign = 'start'; lcX.textBaseline = 'alphabetic'\n }\n }\n // the media's facts: click rings and the cursor dot, video-anchored,\n // at clip-local time\n var lcCu = lcO.cur\n if (lcCu && lcCu.pts && lcCu.pts.length) {\n var lcSp = lcCu.space || { w: lcNW, h: lcNH }\n var lcSize = (lcCu.size || 24) * lcS * 0.5\n var lcT = lcI.t\n var lcCks = lcCu.clicks || []\n for (var lcc = 0; lcc < lcCks.length; lcc++) {\n var lcCk = lcCks[lcc]\n var lcAge = lcT - lcCk.t\n if (lcAge < -0.08 || lcAge > 0.6) continue\n var lcU = Math.max(0, Math.min(1, (lcAge + 0.08) / 0.68))\n var lcFade = 1 - Math.pow(1 - lcU, 3)\n lcX.save()\n lcX.strokeStyle = 'rgba(255,255,255,' + (0.9 * (1 - lcU)) + ')'\n lcX.lineWidth = Math.max(1, 3 * lcS * (1 - lcU * 0.6))\n lcX.beginPath(); lcX.arc(lcX0 + (lcCk.x / (lcSp.w || lcNW)) * lcW, lcMY + (lcCk.y / (lcSp.h || lcNH)) * lcMH, lcSize * 0.8 + lcFade * 44 * lcS, 0, Math.PI * 2); lcX.stroke()\n lcX.restore()\n }\n var lcPts = lcCu.pts, lcPX = lcPts[0].x, lcPY = lcPts[0].y\n for (var lcp = 0; lcp < lcPts.length; lcp++) { if (lcPts[lcp].t <= lcT) { lcPX = lcPts[lcp].x; lcPY = lcPts[lcp].y } }\n lcX.save()\n lcX.fillStyle = 'rgba(255,255,255,0.95)'; lcX.strokeStyle = 'rgba(0,0,0,0.4)'; lcX.lineWidth = 2 * lcS\n lcX.beginPath(); lcX.arc(lcX0 + (lcPX / (lcSp.w || lcNW)) * lcW, lcMY + (lcPY / (lcSp.h || lcNH)) * lcMH, lcSize, 0, Math.PI * 2); lcX.fill(); lcX.stroke()\n lcX.restore()\n }\n lcX.restore()\n if (lcO.border && lcO.border.width > 0) {\n var lcBW = lcO.border.width * lcS\n lcX.save(); lcX.strokeStyle = lcO.border.color || '#fff'; lcX.lineWidth = lcBW\n rr(lcX0 - lcBW / 2, lcY0 - lcBW / 2, lcW + lcBW, lcH + lcBW, lcRad > 0 ? lcRad + lcBW / 2 : 0, lcX); lcX.stroke()\n lcX.restore()\n }\n lcE.texture.needsUpdate = true\n var lcM2 = lcE.mesh\n lcM2.visible = true\n if (lcM2.material) lcM2.material.opacity = lcI.a\n var lcLean = lcCard.lean || [0, 0]\n var lcRx = (lcLean[0] || 0) * Math.PI / 180, lcRy = (lcLean[1] || 0) * Math.PI / 180, lcRz = -(lcI.r || 0) * Math.PI / 180\n var lcPlW = lcCW * lcK, lcPlH = lcCH * lcK\n var lcWX = (lcI.x - 0.5) * obRefH * obAspect\n var lcWY = -(lcI.y - 0.5) * obRefH - lcI.yof * lcK\n if (obOrtho) {\n lcM2.position.set((obOCX + (lcI.x - 0.5) * obOW) / obAX, obOCY - (lcI.y - 0.5) * obOH, -obOD)\n obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.setFromEuler(obB.e)\n } else if (obB) {\n lcM2.position.copy(obCam.position).addScaledVector(obB.f, Math.abs(${CARD_Z}) - 0.01).addScaledVector(obB.r, lcWX).addScaledVector(obB.u, lcWY)\n obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))\n } else {\n lcM2.position.set(lcWX, lcWY, ${CARD_Z} + 0.01)\n lcM2.rotation.set(lcRx, lcRy, lcRz)\n }\n lcM2.scale.set(lcPlW, lcPlH, 1)\n }\n // A card off screen rests; one gone from the data is disposed.\n lcC.forEach(function (lcE2, lcId) {\n if (lcSeen[lcId]) return\n var lcStill = false\n for (var lcq = 0; lcq < ols.length; lcq++) if (ols[lcq].id === lcId && ols[lcq].card) lcStill = true\n if (lcStill) { lcE2.mesh.visible = false; return }\n obC.group.remove(lcE2.mesh)\n if (lcE2.mesh.geometry && lcE2.mesh.geometry.dispose) lcE2.mesh.geometry.dispose()\n if (lcE2.mesh.material && lcE2.mesh.material.dispose) lcE2.mesh.material.dispose()\n if (lcE2.texture && lcE2.texture.dispose) lcE2.texture.dispose()\n lcC.delete(lcId)\n })\n // Dispose props no longer in the data (live removal).\n obC.pool.forEach(function (obE2, obId) {\n if (!obSeen[obId]) {\n obC.group.remove(obE2.mesh)\n obE2.mesh.traverse\n ? obE2.mesh.traverse(function (obN3) {\n if (obN3.geometry && obN3.geometry.dispose) obN3.geometry.dispose()\n if (obN3.material && obN3.material.dispose) obN3.material.dispose()\n })\n : null\n if (obE2.mesh.geometry) obE2.mesh.geometry.dispose()\n if (obE2.mesh.material) obE2.mesh.material.dispose()\n obC.pool.delete(obId)\n }\n })\n }\n\n}`\n","/**\n * Click extraction — the lowering step that\n * turns the raw cursor track's down/up events into the compact, OUTPUT-anchored\n * records ON_FRAME draws click effects from.\n *\n * OUTPUT-anchored so effects read at constant *viewer* speed: evaluated in\n * source time an 8× speed span would compress a 450 ms ripple to ~56 ms.\n * Baking the output instants here (instead of mapping per frame) is safe\n * because every segment/speed edit re-runs the lowering and ships fresh data —\n * the baked times can never go stale. Pure & deterministic.\n */\nimport { segmentRate } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\nimport type { Segment } from '@vosjs/timeline'\nimport type { CursorTrack, Rect } from '../types'\n\n/** Anticipation lead — effects start this many output seconds before the click. */\nexport const CLICK_FX_PRE = 0.06\n/** Base effect durations in output seconds (× the intensity's `dur`). */\nexport const CLICK_RIPPLE_DUR = 0.45\nexport const CLICK_PULSE_DUR = 0.35\nexport const CLICK_HIGHLIGHT_FADE = 0.35\n/** Synthetic press length when the matching `up` is missing (nav killed it). */\nexport const CLICK_SYNTH_RELEASE = 0.12\n/** A down→up pair longer than this is treated as unmatched (lost `up`). */\nexport const CLICK_PAIR_MAX = 10\n/** Highlight uses the element rect only when it covers ≤ this viewport fraction. */\nexport const CLICK_RECT_MAX_FRAC = 0.35\n/** …and only when the click point sits inside the rect (grown by this margin). */\nconst RECT_CONTAIN_MARGIN = 8\n\nexport interface LoweredClick {\n /** OUTPUT seconds of mousedown. */\n ot: number\n /** OUTPUT seconds of release (real up, clamped into kept footage). */\n up: number\n /** SOURCE seconds of mousedown — ON_FRAME's cross-cut proximity guard. */\n st: number\n /** click point in cursorSpace px. */\n x: number\n y: number\n /** pointer button (0=left). */\n b: number\n /** element rect [x,y,w,h] in cursorSpace px — present only when the\n * highlight style wants it AND it passed the size/containment gates\n * (ON_FRAME stays branch-light: r present = draw highlight). */\n r?: [number, number, number, number]\n}\n\nexport interface ExtractClickOptions {\n /** attach gated element rects (highlight style). */\n rects?: boolean\n /** cursorSpace dims — the rect-size gate's denominator. */\n space: { w: number; h: number }\n /** The media the track belongs to; absent = the primary. Its clicks map through that media's pieces only. */\n media?: string\n}\n\n/**\n * OUTPUT time of the last kept source moment in [sIn, sOut] — the clamped\n * release for presses whose `up` fell in trimmed footage. Same accumulation\n * as lowerToComposition's spanOutputExtent (not imported: that would cycle).\n */\ntype MediaPiece = Segment & { media?: string }\n\n/** A source moment's OUTPUT time through the pieces of its media (null when cut away). */\nfunction outputAt(\n segments: readonly MediaPiece[],\n sourceT: number,\n media?: string,\n): number | null {\n if (!segments.length) return sourceT\n let acc = 0\n for (const s of segments) {\n const rate = segmentRate(s)\n if (sameMedia(s.media, media) && sourceT >= s.in && sourceT <= s.out)\n return acc + (sourceT - s.in) / rate\n acc += Math.max(0, s.out - s.in) / rate\n }\n return null\n}\n\nfunction keptOutputEnd(\n segments: readonly MediaPiece[],\n sIn: number,\n sOut: number,\n media?: string,\n): number | null {\n let acc = 0\n let end: number | null = null\n for (const p of segments) {\n const rate = segmentRate(p)\n if (sameMedia(p.media, media)) {\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) end = acc + (ovOut - p.in) / rate\n }\n acc += Math.max(0, p.out - p.in) / rate\n }\n return end\n}\n\nfunction gatedRect(\n rect: Rect,\n x: number,\n y: number,\n space: { w: number; h: number },\n): [number, number, number, number] | null {\n const area = rect.w * rect.h\n const frame = space.w * space.h\n if (!(area > 0) || !(frame > 0) || area / frame > CLICK_RECT_MAX_FRAC)\n return null\n const m = RECT_CONTAIN_MARGIN\n const inside =\n x >= rect.x - m &&\n x <= rect.x + rect.w + m &&\n y >= rect.y - m &&\n y <= rect.y + rect.h + m\n return inside\n ? [round(rect.x), round(rect.y), round(rect.w), round(rect.h)]\n : null\n}\n\n/**\n * Extract OUTPUT-anchored clicks from a raw cursor track. Downs in trimmed-away\n * footage are dropped (they follow their footage, like every source-anchored\n * feature); a press pairs with the next `up` of the same button unless another\n * `down` of that button intervenes (a lost `up` must not chain two presses).\n */\nexport function extractClicks(\n track: CursorTrack,\n segments: readonly MediaPiece[],\n opts: ExtractClickOptions,\n): LoweredClick[] {\n const out: LoweredClick[] = []\n for (let i = 0; i < track.length; i++) {\n const e = track[i]\n if (e.type !== 'down') continue\n const button = e.button ?? 0\n const st = e.t / 1000\n\n // real release: next same-button `up`, unless a same-button `down` intervenes\n let pressLen = CLICK_SYNTH_RELEASE\n for (let j = i + 1; j < track.length; j++) {\n const n = track[j]\n if ((n.button ?? 0) !== button) continue\n if (n.type === 'down') break\n if (n.type === 'up') {\n const len = (n.t - e.t) / 1000\n if (len > 0 && len <= CLICK_PAIR_MAX) pressLen = len\n break\n }\n }\n\n const ot = outputAt(segments, st, opts.media)\n if (ot === null) continue\n const upSrc = st + Math.max(pressLen, 0.02)\n const up =\n keptOutputEnd(segments, st, upSrc, opts.media) ?? ot + CLICK_SYNTH_RELEASE\n\n const click: LoweredClick = {\n ot: round(ot),\n up: round(Math.max(up, ot + 0.02)),\n st: round(st),\n x: round(e.x),\n y: round(e.y),\n b: button,\n }\n if (opts.rects && e.rect) {\n const r = gatedRect(e.rect, e.x, e.y, opts.space)\n if (r) click.r = r\n }\n out.push(click)\n }\n return out.sort((a, b) => a.ot - b.ot)\n}\n\n/** '#rgb'/'#rrggbb' → [r,g,b] for ctx.data (ON_FRAME composes rgba() per frame). */\nexport function hexToRgbTriplet(hex: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim())\n if (!m) return null\n let h = m[1]\n if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]\n const n = parseInt(h, 16)\n return [n >> 16, (n >> 8) & 255, n & 255]\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Dynamic-tilt planner:\n * derive tilt spans FROM the zoom spans — the camera leans toward each zoom's\n * focus, so emphasis reads in depth as well as in scale. Zoom spans are the\n * studio's condensed \"what matters\" signal (planner clicks + dwells + human\n * curation, focus points attached), which is why this planner consumes them\n * rather than re-reading the click track: extents align by construction (the\n * two tracks ramp and chain together) and no second planner competes with\n * auto-zoom for the same clicks.\n *\n * Pure and deterministic: same spans + same intensity → same suggestions,\n * ids keyed by the source zoom span (`t-<zoomId>`). Every emitted span is\n * `source: 'auto'` — the regenerate replaces them freely and NEVER touches\n * 'manual' spans (the auto-zoom wand contract; merge lives in the store).\n *\n * Direction (verified against the real renderer — the tilt-direction\n * render scenario pins it): with the camera at the origin looking\n * down −z and the card at CARD_Z,\n * +rx swings the card's TOP edge toward the camera,\n * +ry swings the LEFT edge toward the camera (the +x edge moves away).\n * \"Lean toward the focus\" therefore means rx ∝ −(cy − 0.5), ry ∝ −(cx − 0.5).\n */\nimport { spanOutputExtent } from '../lower/lowerToComposition'\nimport { TILT_INTENSITY_MAX, clampTiltDeg } from '../types'\nimport type { Segment } from '@vosjs/timeline'\nimport type { TiltSpan, TiltStyleName, ZoomSpan } from '../types'\n\n/** Zoom spans shorter than this (OUTPUT seconds) get no tilt — a pose that\n * can't settle before the zoom leaves reads as wobble, not emphasis. */\nexport const TILT_AUTO_MIN = 1.2\n\n/** Focus offsets under this (|cx−0.5| fraction) don't tilt that axis — a\n * centered zoom keeps the pure push-in feel; tilt is for off-center focus. */\nexport const TILT_AUTO_DEAD_ZONE = 0.12\n\nexport interface PlanTiltOptions {\n /** Intensity ladder — max degrees per axis (TILT_INTENSITY_MAX). */\n intensity: Exclude<TiltStyleName, 'off'>\n}\n\n/**\n * One tilt span per qualifying zoom span, SAME source extents (the tracks\n * ramp/chain together), pose aimed at the zoom's focus. Spans whose footage\n * is cut away, whose output run is too short, or whose focus is centered\n * (both axes inside the dead zone) emit nothing.\n */\nexport function planAutoTilt(\n zoom: readonly ZoomSpan[],\n segments: Segment[],\n options: PlanTiltOptions,\n): TiltSpan[] {\n const max = TILT_INTENSITY_MAX[options.intensity]\n const spans: TiltSpan[] = []\n for (const z of [...zoom].sort((a, b) => a.in - b.in)) {\n const ext = spanOutputExtent(segments, z.in, z.out)\n if (!ext || ext.end - ext.start < TILT_AUTO_MIN) continue\n // Focus offset from center; auto-focus (cursor-follow) spans use their\n // stored entry focus — the tilt pose HOLDS while the zoom's internal\n // focus glides (one move per beat, never a re-aiming wobble).\n const rx = axisLean(-(z.cy - 0.5), max)\n const ry = axisLean(-(z.cx - 0.5), max)\n if (rx === 0 && ry === 0) continue\n spans.push({\n id: `t-${z.id}`,\n in: z.in,\n out: z.out,\n rx,\n ry,\n source: 'auto',\n })\n }\n return spans\n}\n\n/** Linear lean over the dead zone: offset ±0.5 → ±max degrees, quantized. */\nfunction axisLean(offset: number, max: number): number {\n if (Math.abs(offset) < TILT_AUTO_DEAD_ZONE) return 0\n return clampTiltDeg(Math.max(-1, Math.min(1, offset / 0.5)) * max)\n}\n","/**\n * Auto-speed planner: propose speed-up spans for the three\n * stretches everyone compresses in a screen recording — typing passages,\n * long scrolls, and idle gaps — read deterministically from the cursor track.\n * The wand contract is auto-zoom's: spans arrive `source:'auto'`, any gesture\n * promotes to 'manual', and a re-plan replaces only the auto ones.\n *\n * Signals, in priority order (higher wins an overlap):\n * 1. TYPING — `key` activity pings grouped into sessions (the typing-zoom\n * grouping rule: a ping joins while the silence stays small). The caret is the\n * actor and nothing else moves, so 3× still reads.\n * 2. SCROLL — runs of `scroll` events with small gaps: skimming.\n * 3. IDLE — a gap between ANY two consecutive events (plus the head before\n * the first and the tail after the last): nothing happened at all. Padded\n * so the moment of stopping and resuming plays at 1×.\n *\n * Deterministic, pure, and empty-track-safe (a browser-recorder take with no\n * cursor track plans nothing).\n */\nimport { clampSpeedRate } from '../types'\nimport type { CursorEvent, SpeedSpan } from '../types'\n\nexport interface SpeedParams {\n /** Seconds of no input at all before a stretch counts as idle. */\n idleMin: number\n /** Rate applied to idle stretches. */\n idleRate: number\n /** Seconds a typing session must last to earn a span. */\n typingMin: number\n /** Rate applied to typing passages. */\n typingRate: number\n /** Seconds a scroll run must last to earn a span. */\n scrollMin: number\n /** Rate applied to scroll runs. */\n scrollRate: number\n}\n\n/** Conservative defaults: only stretches nobody wants to watch in real time. */\nexport const DEFAULT_SPEED_PARAMS: SpeedParams = {\n idleMin: 5,\n idleRate: 4,\n typingMin: 3,\n typingRate: 3,\n scrollMin: 2.5,\n scrollRate: 2,\n}\n\n/** Max silence inside a typing session (mirrors the typing-zoom grouping scale). */\nconst TYPING_GAP = 1.5\n/** Max gap inside a scroll run. */\nconst SCROLL_GAP = 0.8\n/** Idle spans start/end this far inside the gap so stop/resume play at 1×. */\nconst IDLE_PAD = 0.6\n/** Shortest span worth proposing (source seconds). */\nconst MIN_SPAN = 1\n/**\n * An idle gap whose measured frame activity (the digest's per-second\n * changed-pixel fraction) averages above this is the video PLAYING — the\n * recording's own playback, a render in progress — not idle. Speeding it up\n * compresses the payoff. Five real takes (2026-08-25) each had one; the\n * cursor track alone cannot tell, so this needs the activity witness, and\n * without one (the studio's ingest) the gap still plans as idle.\n */\nexport const PLAYBACK_ACTIVITY = 0.1\n\ninterface Candidate {\n in: number\n out: number\n rate: number\n}\n\nexport function planAutoSpeed(\n track: readonly CursorEvent[],\n opts: {\n durationMs: number\n params?: Partial<SpeedParams>\n /** Per-SOURCE-second motion bins (0..1) when a digest measured them. */\n activity?: readonly number[] | null\n },\n): SpeedSpan[] {\n const p = { ...DEFAULT_SPEED_PARAMS, ...opts.params }\n const durS = opts.durationMs / 1000\n if (!track.length || !(durS > 0)) return []\n const evs = [...track].sort((a, b) => a.t - b.t)\n\n const cands: Candidate[] = []\n\n // 1. typing sessions\n collectRuns(\n evs.filter((e) => e.type === 'key'),\n TYPING_GAP,\n p.typingMin,\n (start, last) => cands.push({ in: start, out: last, rate: p.typingRate }),\n )\n\n // 2. scroll runs\n collectRuns(\n evs.filter((e) => e.type === 'scroll'),\n SCROLL_GAP,\n p.scrollMin,\n (start, last) => cands.push({ in: start, out: last, rate: p.scrollRate }),\n )\n\n // 3. idle gaps — between ANY events, plus the head and the tail\n for (const [a, b] of idleGaps(evs, durS, p.idleMin)) {\n if (isPlayback(opts.activity, a, b)) continue\n const start = a + IDLE_PAD\n const end = b - IDLE_PAD\n if (end - start >= MIN_SPAN)\n cands.push({ in: start, out: end, rate: p.idleRate })\n }\n\n // Resolve overlaps by priority (candidates arrive typing → scroll → idle):\n // a later candidate is clipped to the space the accepted ones left, and a\n // clipped crumb below MIN_SPAN is dropped.\n const accepted: Candidate[] = []\n for (const c of cands) {\n let pieces: Candidate[] = [\n { ...c, in: Math.max(0, c.in), out: Math.min(durS, c.out) },\n ]\n for (const a of accepted) {\n pieces = pieces.flatMap((pc) => {\n if (pc.out <= a.in || pc.in >= a.out) return [pc]\n const kept: Candidate[] = []\n if (a.in - pc.in >= MIN_SPAN) kept.push({ ...pc, out: a.in })\n if (pc.out - a.out >= MIN_SPAN) kept.push({ ...pc, in: a.out })\n return kept\n })\n }\n accepted.push(...pieces.filter((pc) => pc.out - pc.in >= MIN_SPAN))\n }\n\n accepted.sort((a, b) => a.in - b.in)\n return accepted.map((c, i) => ({\n id: `s${i}`,\n in: round(c.in),\n out: round(c.out),\n rate: clampSpeedRate(c.rate),\n source: 'auto' as const,\n }))\n}\n\n/**\n * Scroll runs as [start, last] seconds — the same grouping the speed planner\n * proposes 2× over, exported for the take digest.\n */\nexport function scrollRuns(\n track: readonly CursorEvent[],\n minLen = 1,\n): [number, number][] {\n const out: [number, number][] = []\n collectRuns(\n [...track].filter((e) => e.type === 'scroll').sort((a, b) => a.t - b.t),\n SCROLL_GAP,\n minLen,\n (a, b) => out.push([a, b]),\n )\n return out\n}\n\n/**\n * Idle gaps as [start, end] seconds: no event of ANY kind for ≥ idleMin,\n * head and tail included — the digest's `idle` moments and the speed\n * planner's 4× candidates come from this one derivation.\n */\nexport function idleGaps(\n track: readonly CursorEvent[],\n durationS: number,\n idleMin = DEFAULT_SPEED_PARAMS.idleMin,\n): [number, number][] {\n const gaps: [number, number][] = []\n let prev = 0\n for (const e of [...track].sort((a, b) => a.t - b.t)) {\n const t = e.t / 1000\n if (t - prev >= idleMin) gaps.push([prev, t])\n if (t > prev) prev = t\n }\n if (durationS - prev >= idleMin) gaps.push([prev, durationS])\n return gaps\n}\n\n/** Mean activity over [a, b) source seconds exceeds PLAYBACK_ACTIVITY. */\nexport function isPlayback(\n activity: readonly number[] | null | undefined,\n a: number,\n b: number,\n): boolean {\n if (!activity?.length) return false\n const lo = Math.max(0, Math.floor(a))\n const hi = Math.min(activity.length, Math.ceil(b))\n if (hi <= lo) return false\n let sum = 0\n for (let i = lo; i < hi; i++) sum += activity[i]\n return sum / (hi - lo) > PLAYBACK_ACTIVITY\n}\n\n/** Group events into runs: one joins while the silence stays ≤ gap. */\nfunction collectRuns(\n evs: readonly CursorEvent[],\n gap: number,\n minLen: number,\n emit: (startS: number, lastS: number) => void,\n) {\n let start = -1\n let last = -1\n const flush = () => {\n if (start >= 0 && last - start >= minLen) emit(start, last)\n }\n for (const e of evs) {\n const t = e.t / 1000\n if (start >= 0 && t - last <= gap) {\n last = t\n } else {\n flush()\n start = t\n last = t\n }\n }\n flush()\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * The take digest's MOMENTS: the instants the cursor telemetry\n * says matter, each in the doc's own units so an agent's decision is a copy,\n * never a conversion. One grouping, shared with the planners (groupTrack,\n * dwellSpans, scrollRuns, idleGaps) — the digest lists exactly what the\n * planners zoom, speed and tilt over, plus the head, the tail, and the visual\n * scene changes a frame-diff pass finds. Pure and deterministic: same doc →\n * same moments, same ids.\n *\n * Time: `source` extents are footage seconds (SOURCE-anchored like the spans\n * they sit on); `output` is the same window mapped through the rate map, or\n * null when a trim cut it away. `at` is the source instant the digest frames.\n */\nimport {\n clusterFocus,\n dwellSpans,\n groupTrack,\n planAutoZoom,\n} from '../planner/autoZoom'\nimport {\n DEFAULT_SPEED_PARAMS,\n idleGaps,\n planAutoSpeed,\n scrollRuns,\n} from '../planner/autoSpeed'\nimport { planAutoTilt } from '../planner/autoTilt'\nimport { resolveZoomStyle } from '../zoomStyle'\nimport { ratedSegments, spanOutputExtent } from '../lower/lowerToComposition'\nimport type { Click } from '../planner/autoZoom'\nimport type { ProjectDoc, SpeedSpan, TiltSpan, ZoomSpan } from '../types'\n\nexport type MomentKind =\n | 'head'\n | 'tail'\n | 'click'\n | 'typing'\n | 'scroll'\n | 'dwell'\n | 'idle'\n | 'scene'\n\n/** A rect in normalized [0..1] frame fractions (the zoom cx/cy convention). */\nexport interface NormRect {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport interface Moment {\n id: string\n kind: MomentKind\n /** Footage seconds. Instants (scene/head/tail) have in === out. */\n source: { in: number; out: number }\n /** The same window in OUTPUT seconds, null when trimmed away. */\n output: { in: number; out: number } | null\n /** The source instant the digest frames (null = no frame, e.g. idle). */\n at: number | null\n /** `at` mapped to output time (null when cut or frameless). */\n outputAt: number | null\n /** Normalized focus — copy into a ZoomSpan's cx/cy. */\n focus: { cx: number; cy: number } | null\n /** Normalized target bounds (element rect union), when the events had one. */\n rect: NormRect | null\n clicks?: number\n /**\n * A click cluster on a frame-sized element (a canvas, a panel, a drag):\n * the planner frames nothing on it (DRAG_FIT_LEVEL), so a zoom here is\n * the agent's call, never a proposal.\n */\n surface?: boolean\n pings?: number\n /** Motion in the window, 0..1 (fraction of changed pixels), null without frames. */\n activity: number | null\n /** Planner spans (from `plan`) that cover this moment, by id. */\n proposed: { zoom?: string; speed?: string; tilt?: string }\n /** Transcript text over the window, when a transcript was merged. */\n said: string | null\n}\n\nexport interface DigestPlan {\n zoom: ZoomSpan[]\n speed: SpeedSpan[]\n tilt: TiltSpan[]\n}\n\nexport interface TranscriptSegment {\n /** SOURCE seconds (the recording's own clock). */\n start: number\n end: number\n text: string\n}\n\nexport interface MomentsOptions {\n /** Per-SOURCE-second motion bins (0..1) from the frame-diff pass. */\n bins?: readonly number[] | null\n /** Source seconds of visual scene changes (see scenes.ts). */\n scenes?: readonly number[]\n transcript?: readonly TranscriptSegment[] | null\n /** Idle threshold (seconds); defaults to the speed planner's. */\n idleMin?: number\n}\n\n/**\n * The three planners, run fresh under the doc's own style — the proposals.\n * With activity bins (a digest's decode pass), the speed planner can tell\n * playback from idle.\n */\nexport function planForDigest(\n doc: ProjectDoc,\n activity?: readonly number[] | null,\n): DigestPlan {\n const { cursor, meta } = doc.source\n const zoom = planAutoZoom(cursor, {\n width: meta.width,\n height: meta.height,\n style: doc.zoomStyle,\n params: doc.zoomParams,\n })\n const speed = planAutoSpeed(cursor, {\n durationMs: meta.durationMs,\n params: doc.speedParams,\n activity,\n })\n const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const intensity = doc.tiltStyle ?? style.tilt.intensity\n const tilt =\n intensity === 'off'\n ? []\n : planAutoTilt(zoom, ratedSegments(doc), { intensity })\n return { zoom, speed, tilt }\n}\n\ninterface Draft {\n kind: MomentKind\n in: number\n out: number\n at: number | null\n focus: { cx: number; cy: number } | null\n rect: NormRect | null\n clicks?: number\n surface?: boolean\n pings?: number\n}\n\nexport function momentsFromDoc(\n doc: ProjectDoc,\n plan: DigestPlan,\n opts: MomentsOptions = {},\n): Moment[] {\n const { cursor: track, meta } = doc.source\n const width = meta.width\n const height = meta.height\n const dur = meta.durationMs / 1000\n if (!(dur > 0)) return []\n const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const drafts: Draft[] = []\n\n const edge = Math.min(0.1, dur / 4)\n drafts.push({\n kind: 'head',\n in: 0,\n out: 0,\n at: edge,\n focus: null,\n rect: null,\n })\n drafts.push({\n kind: 'tail',\n in: dur,\n out: dur,\n at: Math.max(0, dur - edge),\n focus: null,\n rect: null,\n })\n\n if (track.length) {\n const { sessions, clusters, surfaces } = groupTrack(track, {\n width,\n height,\n clusterGap: style.clusterGap,\n typingGap: style.typingGap,\n typingZoom: style.typingZoom,\n targetFill: style.targetFill,\n })\n const pressed = (c: Click[], surface: boolean) => {\n const f = clusterFocus(c, width, height)\n drafts.push({\n kind: 'click',\n in: c[0].t,\n out: c[c.length - 1].t,\n // The frame AT the press shows what was clicked; the consequence is\n // the next moment's (or a scene) frame.\n at: c[0].t,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n clicks: c.length,\n ...(surface ? { surface: true } : {}),\n })\n }\n for (const c of clusters) pressed(c, false)\n for (const c of surfaces) pressed(c, true)\n for (const s of sessions) {\n const f = clusterFocus(s.events, width, height)\n drafts.push({\n kind: 'typing',\n in: s.start,\n out: s.last,\n // The filled field: the last ping, after the caret stopped.\n at: s.last,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n pings: s.events.filter((e) => e.t >= s.first).length,\n })\n }\n const scrollEvents: Click[] = track\n .filter((e) => e.type === 'scroll')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y, rect: e.rect }))\n for (const [a, b] of scrollRuns(track, 1)) {\n const evs = scrollEvents.filter((e) => e.t >= a && e.t <= b)\n const f = clusterFocus(evs, width, height)\n drafts.push({\n kind: 'scroll',\n in: a,\n out: b,\n at: (a + b) / 2,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n })\n }\n // Dwells only where no click/typing moment already is (the planner's rule).\n const reserved: ZoomSpan[] = drafts\n .filter((d) => d.kind === 'click' || d.kind === 'typing')\n .map((d, i) => ({\n id: `r${i}`,\n in: d.in,\n out: d.out,\n level: 1,\n cx: 0.5,\n cy: 0.5,\n }))\n for (const d of dwellSpans(\n track,\n width,\n height,\n style.maxLevel,\n reserved,\n )) {\n drafts.push({\n kind: 'dwell',\n in: d.in,\n out: d.out,\n at: (d.in + d.out) / 2,\n focus: { cx: d.cx, cy: d.cy },\n rect: null,\n })\n }\n for (const [a, b] of idleGaps(\n track,\n dur,\n opts.idleMin ?? DEFAULT_SPEED_PARAMS.idleMin,\n )) {\n drafts.push({\n kind: 'idle',\n in: a,\n out: b,\n at: null,\n focus: null,\n rect: null,\n })\n }\n }\n\n for (const t of opts.scenes ?? []) {\n if (t <= edge || t >= dur - edge) continue\n drafts.push({\n kind: 'scene',\n in: t,\n out: t,\n // A hair past the change so a cold seek lands on the new frame.\n at: Math.min(dur, t + 0.04),\n focus: null,\n rect: null,\n })\n }\n\n const order: Record<MomentKind, number> = {\n head: 0,\n click: 1,\n typing: 2,\n scroll: 3,\n dwell: 4,\n scene: 5,\n idle: 6,\n tail: 7,\n }\n drafts.sort((a, b) => a.in - b.in || order[a.kind] - order[b.kind])\n\n const rated = ratedSegments(doc)\n // An instant is a hair-wide window; at the very end it leans back inside.\n const outputOf = (a: number, b: number) => {\n const lo = a >= dur ? Math.max(0, dur - 0.001) : a\n return spanOutputExtent(rated, lo, Math.max(b, lo + 0.001))\n }\n const covers = (s: { in: number; out: number }, d: Draft) =>\n d.in === d.out ? s.in <= d.in && d.in < s.out : s.in < d.out && s.out > d.in\n\n return drafts.map((d, i) => {\n const output = outputOf(d.in, d.out)\n const outAt = d.at === null ? null : outputOf(d.at, d.at)\n const proposed: Moment['proposed'] = {}\n const z = plan.zoom.find((s) => covers(s, d))\n if (z) proposed.zoom = z.id\n const sp = plan.speed.find((s) => covers(s, d))\n if (sp) proposed.speed = sp.id\n const tl = plan.tilt.find((s) => covers(s, d))\n if (tl) proposed.tilt = tl.id\n return {\n id: `m${String(i + 1).padStart(2, '0')}`,\n kind: d.kind,\n source: { in: round(d.in), out: round(d.out) },\n output: output\n ? { in: round(output.start), out: round(output.end) }\n : null,\n at: d.at === null ? null : round(d.at),\n outputAt: outAt ? round(outAt.start) : null,\n focus: d.focus ? { cx: round(d.focus.cx), cy: round(d.focus.cy) } : null,\n rect: d.rect\n ? {\n x: round(d.rect.x),\n y: round(d.rect.y),\n w: round(d.rect.w),\n h: round(d.rect.h),\n }\n : null,\n ...(d.clicks !== undefined ? { clicks: d.clicks } : {}),\n ...(d.surface ? { surface: true } : {}),\n ...(d.pings !== undefined ? { pings: d.pings } : {}),\n activity: activityOf(opts.bins, d),\n proposed,\n said: saidOver(opts.transcript, d),\n }\n })\n}\n\n/** Mean motion of the bins a window touches (or the bin at an instant). */\nfunction activityOf(\n bins: readonly number[] | null | undefined,\n d: Draft,\n): number | null {\n if (!bins || !bins.length) return null\n const a = Math.max(0, Math.floor(d.in))\n const b = Math.min(bins.length - 1, Math.max(a, Math.ceil(d.out) - 1))\n let sum = 0\n let n = 0\n for (let i = a; i <= b; i++) {\n sum += bins[i]\n n++\n }\n return n ? round(sum / n) : null\n}\n\nfunction saidOver(\n transcript: readonly TranscriptSegment[] | null | undefined,\n d: Draft,\n): string | null {\n if (!transcript?.length) return null\n const lo = d.in\n const hi = d.in === d.out ? d.in + 0.5 : d.out\n const text = transcript\n .filter((s) => s.start < hi && s.end > lo)\n .map((s) => s.text.trim())\n .filter(Boolean)\n .join(' ')\n return text || null\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Scene changes from the digest's motion bins: a SOURCE second whose\n * changed-pixel fraction jumps past `motion` after a second at or below\n * `quiet` is a scene — a navigation, a dialog, a page swap. A luma diff, not\n * a scene detector: a video playing inside the page is a permanent change,\n * and the moment kind says `scene`, never \"navigation\" — the agent looks at\n * the frame to say what it was.\n */\nexport interface SceneOptions {\n /** Changed-pixel fraction that reads as a change. */\n motion?: number\n /** The bin before must be at or below this. */\n quiet?: number\n}\n\n/**\n * Calibrated on a dark-theme CLI take (launch-d2, 2026-08-25): a page swap\n * moved 0.33–0.36 of a 64×36 luma thumb, a click's own ripple ≤0.03 — so a\n * quarter of the pixels is a scene and a tenth is still quiet.\n */\nexport const SCENE_MOTION = 0.25\nexport const SCENE_QUIET = 0.1\n\nexport function sceneChanges(\n bins: readonly number[],\n opts: SceneOptions = {},\n): number[] {\n const motion = opts.motion ?? SCENE_MOTION\n const quiet = opts.quiet ?? SCENE_QUIET\n const out: number[] = []\n for (let i = 1; i < bins.length; i++) {\n if (bins[i] >= motion && bins[i - 1] <= quiet) out.push(i)\n }\n return out\n}\n","/**\n * The framing check: does a zoom span's visible window contain the\n * thing it points at? A host-side mirror of ON_FRAME's transform, the same\n * derivation focusBounds uses (layout.ts) — content point p maps to\n * f + (p − f)·L around the anchor f = dx + cx·dw, so the canvas [0, W] shows\n * content [f − f/L, f + (W − f)/L]. `layout.test.ts`'s rule binds: change the\n * two together. Lowering clamps the focus first (clampFocus), so the window\n * is computed from the clamped focus, exactly what renders.\n */\nimport { clampFocus, zoomViewport } from '../layout'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type { NormRect } from './moments'\n\nexport interface ZoomWindow {\n x0: number\n x1: number\n y0: number\n y1: number\n}\n\n/**\n * The visible window in normalized video coords at `level` around the\n * focus, under the frame's camera model (absent = the magnifier, whose\n * focus is clamped first exactly as the lowering clamps it).\n */\nexport function zoomWindow(\n span: { level: number; cx: number; cy: number },\n layout: CardLayout,\n camera: CameraModel = 'card',\n): ZoomWindow {\n const L = Math.max(1, span.level)\n const { cx, cy } = clampFocus(span.cx, span.cy, L, layout, camera)\n const v = zoomViewport(L, cx, cy, layout, camera)\n const px0 = v.x * layout.W\n const px1 = (v.x + v.w) * layout.W\n const py0 = v.y * layout.H\n const py1 = (v.y + v.h) * layout.H\n return {\n x0: (px0 - layout.dx) / layout.dw,\n x1: (px1 - layout.dx) / layout.dw,\n y0: (py0 - layout.dy) / layout.dh,\n y1: (py1 - layout.dy) / layout.dh,\n }\n}\n\n/**\n * True when the rect (normalized) sits inside the zoom's visible window, with\n * `tol` of slack per edge (frame fractions). A level ≤ 1 zoom shows the whole\n * frame and covers everything.\n */\nexport function zoomCoversRect(\n span: { level: number; cx: number; cy: number },\n rect: NormRect,\n layout: CardLayout,\n tol = 0.02,\n camera: CameraModel = 'card',\n): boolean {\n if (span.level <= 1.001) return true\n const w = zoomWindow(span, layout, camera)\n return (\n rect.x >= w.x0 - tol &&\n rect.x + rect.w <= w.x1 + tol &&\n rect.y >= w.y0 - tol &&\n rect.y + rect.h <= w.y1 + tol\n )\n}\n","/**\n * Style as DATA: the fields of a signed-off take's doc that ARE its\n * style — camera, speed, tilt personality, frame, cursor, cam bubble, export.\n * `copyStyle` carries them onto another take deterministically, so a series\n * shares them by construction and the recipe (CUT.md) only has to say what a\n * number cannot. Never the spans, overlays or audio: those are the cut.\n *\n * A LAYOUT is the same idea for a poster: an exemplar document holds the\n * card's placement and presentation, the words' clips, the rest lean and\n * the trailing hold, and `copyLayout` carries exactly those onto another\n * take. A layout is never a name a document learns; it is a document on a\n * shelf, applied by copy.\n */\nimport { totalDuration } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\nimport { anchorSourceDuration } from '../doc/studioDoc'\nimport { docOutputDuration } from '../audioBeds'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport { docFreezes } from '../lower/motion'\nimport type {\n AudioClip,\n FrameStyle,\n FreezeSpan,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n} from '../types'\n\nexport const STYLE_FIELDS = [\n 'zoomStyle',\n 'zoomParams',\n 'speedParams',\n 'tiltStyle',\n 'frame',\n 'cursor',\n 'cam',\n 'export',\n] as const satisfies readonly (keyof ProjectDoc)[]\n\nexport type StyleField = (typeof STYLE_FIELDS)[number]\n\n/**\n * The `export` keys a style CARRIES. `resolution` is deliberately absent:\n * it names how big the output is, which is a fact of the FOOTAGE, not a\n * taste the seed gets to hold. Copying it whole meant a 1280x720 seed\n * stamped 720p onto a 2560x1440 take, so a series looked consistent by\n * throwing away half of every later take's pixels.\n */\nexport const STYLE_EXPORT_KEYS = [\n 'fps',\n 'format',\n] as const satisfies readonly (keyof ProjectDoc['export'])[]\n\n/** The style fields present on a doc, deep-cloned. */\nexport function pickStyle(\n doc: ProjectDoc,\n): Partial<Pick<ProjectDoc, StyleField>> {\n const out: Record<string, unknown> = {}\n for (const k of STYLE_FIELDS) {\n const v: unknown = doc[k]\n if (v !== undefined) out[k] = structuredClone(v)\n }\n return out as Partial<Pick<ProjectDoc, StyleField>>\n}\n\nexport interface CopyStyleOptions {\n /**\n * Carry the seed's LAYOUT too (`copyLayout`): its stage clips by id, its\n * rest lean and its trailing hold. Off by default, so a series cut keeps\n * copying style alone.\n */\n layout?: boolean\n /** Passed through to `copyLayout`. */\n keys?: Record<string, string>\n}\n\n/**\n * A new doc: `to` with `from`'s style fields. A field absent on `from` is\n * removed from the result (the seed's absence is a choice — the default).\n */\nexport function copyStyle(\n from: ProjectDoc,\n to: ProjectDoc,\n opts: CopyStyleOptions = {},\n): ProjectDoc {\n const next = structuredClone(to) as unknown as Record<string, unknown>\n const style = pickStyle(from) as Record<string, unknown>\n for (const k of STYLE_FIELDS) {\n if (k === 'export') continue // merged per key below\n if (k in style) next[k] = style[k]\n else delete next[k]\n }\n // `export` is the one style field that is PART taste and PART fact, so it\n // merges per key instead of replacing: the seed's frame rate and format\n // land, the target keeps the resolution its own footage earned. A seed\n // without an export block leaves the target's alone — the field is\n // required, and dropping it would hand back a doc that cannot render.\n const fromExport = style.export as ProjectDoc['export'] | undefined\n if (fromExport) {\n const merged = { ...((next.export ?? {}) as ProjectDoc['export']) }\n for (const k of STYLE_EXPORT_KEYS) {\n if (fromExport[k] !== undefined) merged[k] = fromExport[k] as never\n }\n next.export = merged\n }\n const styled = next as unknown as ProjectDoc\n return opts.layout\n ? copyLayout(from, styled, { keys: opts.keys }).doc\n : styled\n}\n\n// ── layout ──────────────────────────────────────────────────────────────\n\n/** The frame fields a layout owns. Never `aspectRatio` or `browserBar`. */\nexport const LAYOUT_FRAME_FIELDS = [\n 'background',\n 'padding',\n 'radius',\n 'shadow',\n 'shadowContact',\n 'shadowColor',\n 'inset',\n 'border',\n 'borderWidth',\n 'borderColor',\n 'fit',\n 'focus',\n 'focusFollow',\n 'parallax',\n 'anim',\n] as const satisfies readonly (keyof FrameStyle)[]\n\n/**\n * The id prefix an exemplar reserves for the clips that ARE its layout:\n * the words and the mark a poster places (`stage-title`, `stage-kicker`,\n * `stage-brand`, `stage-mark`). Every other clip is the take's own.\n */\nexport const STAGE_CLIP_PREFIX = 'stage-'\n\n/** The id of the whole-take lean a layout or a canvas gesture writes. */\nexport const REST_TILT_ID = 'rest'\n\nexport const isStageClip = (clip: Pick<OverlayClip, 'id'>): boolean =>\n clip.id.startsWith(STAGE_CLIP_PREFIX)\n\n/** The exemplar's rest lean: its `rest`/`stage` span, or a span covering (nearly) the whole source. */\nexport function restLeanOf(doc: ProjectDoc): { rx: number; ry: number } | null {\n const spans = doc.tilt ?? []\n const named = spans.find((z) => z.id === REST_TILT_ID || z.id === 'stage')\n if (named) return { rx: named.rx, ry: named.ry }\n const total = anchorSourceDuration(doc)\n const whole = spans.find((z) => z.out - z.in >= total * 0.9)\n return whole ? { rx: whole.rx, ry: whole.ry } : null\n}\n\n/** What a document carries of a layout, for a report. */\nexport interface LayoutParts {\n /** The layout-owned frame fields present on the document. */\n frame: string[]\n /** The stage clips' ids. */\n clips: string[]\n lean: boolean\n /** A trailing freeze (the poster's rest). */\n freeze: boolean\n}\n\nexport function layoutOf(doc: ProjectDoc): LayoutParts {\n const frame = doc.frame as unknown as Record<string, unknown>\n return {\n frame: LAYOUT_FRAME_FIELDS.filter((k) => frame[k] !== undefined),\n clips: (doc.overlays ?? []).filter(isStageClip).map((o) => o.id),\n lean: restLeanOf(doc) !== null,\n freeze: trailingFreeze(doc) !== null,\n }\n}\n\nexport interface CopyLayoutOptions {\n /**\n * Media keys this document should show in the exemplar's image clips,\n * by clip id (`{ 'stage-mark': 'brand/mark.svg' }`): an exemplar's mark\n * is its brand's, so without a key of its own (here, or already on the\n * document) an image stage clip is left out and said in `notes`.\n */\n keys?: Record<string, string>\n}\n\nexport interface CopiedLayout {\n doc: ProjectDoc\n /** What the copy left alone, in plain words (empty when nothing was). */\n notes: string[]\n}\n\n/**\n * `to` with `from`'s layout. Pure: returns a new document; never mutates\n * either input.\n *\n * Copied: the layout-owned frame fields (present on the exemplar ⇒ copied,\n * absent ⇒ removed; the animated ground rides along only when the exemplar\n * has one), the stage clips by id (a same-id text clip keeps the words this\n * document already has; an image clip keeps this document's key, or takes\n * one from `keys`), the rest lean as one whole-take span where this\n * document has no tilt spans of its own, and the trailing hold. Kept: the\n * aspect, the browser chrome, every clip that is not a stage clip, the cut.\n */\nexport function copyLayout(\n from: ProjectDoc,\n to: ProjectDoc,\n opts: CopyLayoutOptions = {},\n): CopiedLayout {\n const doc = structuredClone(to)\n const notes: string[] = []\n\n const frame = doc.frame as unknown as Record<string, unknown>\n const src = from.frame as unknown as Record<string, unknown>\n for (const k of LAYOUT_FRAME_FIELDS) {\n if (src[k] !== undefined) frame[k] = structuredClone(src[k])\n else delete frame[k]\n }\n if (from.frame.backgroundMedia)\n doc.frame.backgroundMedia = structuredClone(from.frame.backgroundMedia)\n\n const outDur = docOutputDuration(doc)\n const own = new Map((doc.overlays ?? []).map((o) => [o.id, o]))\n const merged: OverlayClip[] = (doc.overlays ?? []).filter(\n (o) => !isStageClip(o),\n )\n const skippedMarks: string[] = []\n for (const clip of (from.overlays ?? []).filter(isStageClip)) {\n const mine = own.get(clip.id)\n const next = structuredClone(clip)\n next.start = 0\n next.duration = Math.max(0.3, Math.round(outDur * 1000) / 1000)\n if (next.kind === 'text') {\n if (mine?.kind === 'text') next.text = mine.text\n } else if (next.kind === 'html') {\n // An HTML layer has no key: its content IS its source. There is nothing\n // to swap in, and nothing missing to skip the clip for.\n } else if (opts.keys?.[clip.id]) {\n next.key = opts.keys[clip.id]\n } else if (mine && mine.kind !== 'text' && mine.kind !== 'html') {\n next.key = mine.key\n } else {\n skippedMarks.push(clip.id)\n continue\n }\n merged.push(next)\n }\n for (const o of doc.overlays ?? []) {\n if (isStageClip(o) && !merged.some((m) => m.id === o.id)) merged.push(o)\n }\n doc.overlays = merged.length ? merged : undefined\n if (skippedMarks.length)\n notes.push(\n `${skippedMarks.join(', ')} left out: the layout's image is its own brand's; add yours`,\n )\n\n const lean = restLeanOf(from)\n if (lean) {\n if (!doc.tilt?.length) {\n doc.tilt = [\n {\n id: REST_TILT_ID,\n in: 0,\n out: Math.round(anchorSourceDuration(doc) * 1000) / 1000,\n rx: lean.rx,\n ry: lean.ry,\n source: 'manual',\n },\n ]\n } else {\n notes.push('lean kept: this take has tilt spans of its own')\n }\n }\n\n // The trailing freeze: the exemplar's rest becomes this take's, at the\n // end of ITS last segment, with the same seconds.\n const freeze = trailingFreeze(from)\n const last = doc.segments.at(-1)\n if (freeze && last) {\n // The last clip may play another media (concat): the freeze names\n // it, or its seconds would land on the primary's footage instead.\n const atEnd = (f: FreezeSpan) =>\n Math.abs(f.at - last.out) <= 1e-9 && sameMedia(f.media, last.media)\n const own = (doc.freeze ?? []).filter((f) => !atEnd(f))\n // A freeze the take OWNS at its end survives (it is the take's rest,\n // the frame that stands for it); it grows to the layout's seconds.\n const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from)\n const taken = new Set(own.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n doc.freeze = [\n ...own,\n kept\n ? { ...kept, seconds: Math.max(kept.seconds, freeze.seconds) }\n : {\n id: `f${n}`,\n at: last.out,\n seconds: freeze.seconds,\n ...(last.media ? { media: last.media } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n doc.segments = doc.segments.map((s) => {\n if (s.hold === undefined) return s\n const next = { ...s }\n delete next.hold\n return next\n })\n }\n\n return { doc, notes }\n}\n\n/** The freeze at the end of the document's last segment, if any. */\nfunction trailingFreeze(doc: ProjectDoc): FreezeSpan | null {\n // A template may be a PROGRAM document (one-layer members, a component on\n // the shelf): no footage, no segments, no freeze to carry.\n const last = doc.segments?.at(-1)\n if (!last) return null\n return docFreezes(doc).find((f) => Math.abs(f.at - last.out) < 1e-9) ?? null\n}\n\n/** Where a template's clips land on the take. */\nexport type TemplateAnchor = 'start' | 'end' | number\n\nexport interface ApplyTemplateOptions {\n /**\n * The anchor: `start` keeps the template's times; `end` lays them\n * relative to the take's footage end as they sat relative to the\n * template's own (an end card); a number is an output second the\n * template's clock starts at (a caption at a step's settle).\n */\n at: TemplateAnchor\n /** Words for the template's text clips, by clip id. */\n words?: Record<string, string>\n /** Media keys for the template's image and video clips, by clip id. */\n keys?: Record<string, string>\n /**\n * What the clips say they came from (the template's vos id). Absent =\n * `'template'`. A re-apply with the same `from` replaces its earlier\n * clips and leaves everything else.\n */\n from?: string\n /**\n * Carry the template's look too (the layout-owned frame fields, its\n * backdrop, the rest lean, the trailing hold): a poster. Absent = the\n * clips and the card's animation only.\n */\n look?: boolean\n}\n\nexport interface AppliedTemplate {\n doc: ProjectDoc\n notes: string[]\n}\n\nconst shiftBy = (at: TemplateAnchor, templateEnd: number, takeEnd: number) =>\n at === 'start' ? 0 : at === 'end' ? takeEnd - templateEnd : at\n\n/**\n * The clips a template placed on a document (by `from`), in document order.\n */\nexport function clipsFrom(\n doc: Pick<ProjectDoc, 'overlays' | 'objects' | 'audio'>,\n from: string,\n): { overlays: OverlayClip[]; objects: ObjectClip[]; audio: AudioClip[] } {\n return {\n overlays: (doc.overlays ?? []).filter((o) => o.from === from),\n objects: (doc.objects ?? []).filter((o) => o.from === from),\n audio: (doc.audio ?? []).filter((a) => a.from === from),\n }\n}\n\n/**\n * The document without the clips a template placed. The card's `anim` is\n * the card's own once set, so it stays; a caller that wants the card still\n * clears it. Returns the same object when nothing came from `from`.\n */\nexport function dropTemplate(doc: ProjectDoc, from: string): ProjectDoc {\n const has = clipsFrom(doc, from)\n const freezes = (doc.freeze ?? []).some((f) => f.from === from)\n if (\n !has.overlays.length &&\n !has.objects.length &&\n !has.audio.length &&\n !freezes\n )\n return doc\n const out: ProjectDoc = { ...doc }\n if (doc.overlays) out.overlays = doc.overlays.filter((o) => o.from !== from)\n if (doc.objects) out.objects = doc.objects.filter((o) => o.from !== from)\n out.audio = doc.audio.filter((a) => a.from !== from)\n if (doc.freeze) out.freeze = doc.freeze.filter((f) => f.from !== from)\n return out\n}\n\n/**\n * A TEMPLATE is a vos: a plain take document whose clips carry stable ids.\n * Applying it lays those clips onto another take at an ANCHOR, stamped\n * `from`, with the release's words and keys patched in by id; the take's\n * own same-id clip keeps its words unless `words` names them (the layout\n * rule); the template's card `anim` merges over the take's (an end card\n * brings the exit, a poster the entrance); with `look`, the layout-owned\n * frame fields, the backdrop, the rest lean and the hold come too, which\n * is `copyLayout`. The one assembly verb; a recipe says which template and\n * where, never the document.\n */\nexport function applyTemplate(\n template: ProjectDoc,\n take: ProjectDoc,\n opts: ApplyTemplateOptions,\n): AppliedTemplate {\n const from = opts.from ?? 'template'\n const notes: string[] = []\n const base = dropTemplate(take, from)\n const doc: ProjectDoc = structuredClone(base)\n // A template that ends on a freeze keeps its card under its clips; laid\n // at the end of a take, the same freeze goes on the take's last frame\n // (stamped `from`), and the clips place against the end that includes it.\n const tail = trailingFreeze(template)\n const lastSeg = doc.segments.at(-1)\n if (tail && opts.at === 'end' && lastSeg) {\n // The last clip may play another media (concat): the freeze names\n // it, or its seconds would land on the primary's footage instead.\n const atEnd = (f: FreezeSpan) =>\n Math.abs(f.at - lastSeg.out) <= 1e-9 && sameMedia(f.media, lastSeg.media)\n // A freeze the take OWNS at its end survives the template (it is the\n // take's rest, the frame that stands for it) and grows to the\n // template's seconds; only then is the template's own freeze laid,\n // stamped `from`, so a still never reads it as the rest.\n const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from)\n const own = (doc.freeze ?? []).filter((f) => !atEnd(f))\n const taken = new Set(own.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n doc.freeze = [\n ...own,\n kept\n ? { ...kept, seconds: Math.max(kept.seconds, tail.seconds) }\n : {\n id: `f${n}`,\n at: lastSeg.out,\n seconds: tail.seconds,\n from,\n ...(lastSeg.media ? { media: lastSeg.media } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n }\n const templateEnd = totalDuration(ratedSegments(template))\n const takeEnd = totalDuration(ratedSegments(doc))\n const shift = shiftBy(opts.at, templateEnd, takeEnd)\n const own = new Map((take.overlays ?? []).map((o) => [o.id, o]))\n\n const overlays: OverlayClip[] = (doc.overlays ?? []).filter(\n (o) => !(template.overlays ?? []).some((t) => t.id === o.id),\n )\n const skipped: string[] = []\n for (const clip of template.overlays ?? []) {\n const next = structuredClone(clip) as OverlayClip\n next.start = Math.max(0, round3(clip.start + shift))\n next.from = from\n const mine = own.get(clip.id)\n if (next.kind === 'text') {\n const word = opts.words?.[clip.id]\n if (word !== undefined) next.text = word\n else if (mine?.kind === 'text') next.text = mine.text\n } else if (next.kind === 'html') {\n // No key to swap: an HTML layer carries its own source.\n } else if (opts.keys?.[clip.id]) {\n next.key = opts.keys[clip.id]\n } else if (mine && mine.kind !== 'text' && mine.kind !== 'html') {\n next.key = mine.key\n } else if (!/^(https?:|\\/\\/)/.test(next.key)) {\n skipped.push(clip.id)\n continue\n }\n overlays.push(next)\n }\n if (skipped.length)\n notes.push(\n `${skipped.join(', ')}: the template's media key is its own; pass keys[id] or the take's clip`,\n )\n if (overlays.length || doc.overlays) doc.overlays = overlays\n\n const objects: ObjectClip[] = (doc.objects ?? []).filter(\n (o) => !(template.objects ?? []).some((t) => t.id === o.id),\n )\n for (const clip of template.objects ?? []) {\n const next = structuredClone(clip) as ObjectClip\n if (next.span)\n next.span.start = Math.max(0, round3(next.span.start + shift))\n next.from = from\n objects.push(next)\n }\n if (objects.length || doc.objects) doc.objects = objects\n\n const audio: AudioClip[] = doc.audio.filter(\n (a) => !(template.audio ?? []).some((t) => t.id === a.id),\n )\n for (const clip of template.audio ?? []) {\n const next = structuredClone(clip) as AudioClip\n next.start = Math.max(0, round3(clip.start + shift))\n next.from = from\n audio.push(next)\n }\n doc.audio = audio\n\n if (template.frame?.anim) {\n doc.frame = {\n ...doc.frame,\n anim: { ...(doc.frame.anim ?? {}), ...template.frame.anim },\n }\n }\n\n if (opts.look) {\n const laid = copyLayout(template, doc, { keys: opts.keys })\n // copyLayout owns the stage clips; everything above stays as placed.\n laid.doc.overlays = (laid.doc.overlays ?? []).map((o) =>\n isStageClip(o) ? { ...o, from } : o,\n )\n notes.push(...laid.notes)\n return { doc: laid.doc, notes }\n }\n return { doc, notes }\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n","/**\n * Music beds — the doc semantics of \"background music under the cut\".\n *\n * A bed is an ordinary AudioClip with music-true defaults: it starts at 0,\n * covers the whole output (looping to fill when the track is shorter than\n * the cut, trimming its tail when it is longer), ducks under the mic when\n * there is a voice to duck under, and fades out instead of stopping dead.\n * Every default is a plain clip field the user can change afterwards —\n * defaults, not policy.\n *\n * `refillAudioBeds` is the trim-follow rule: when an edit changes the output\n * duration, any clip whose END tracked the previous output end is re-fit to\n * the new one, inside the SAME edit (one undo step, never a stored rule in\n * the doc — lowering stays pure).\n */\nimport { totalDuration } from '@vosjs/timeline'\nimport { ratedSegments } from './lower/lowerToComposition'\nimport { migrateMotion, outputEnd } from './lower/motion'\nimport { isRecordingDoc } from './doc/studioDoc'\nimport type { StudioDoc } from './doc/studioDoc'\nimport type { AudioClip, ProjectDoc } from './types'\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n\n/** Rate-aware OUTPUT duration of a doc (what the viewer experiences). */\n/**\n * The OUTPUT length of either document: a recording's kept footage\n * through its speed spans, a program's own length (its `program.duration`,\n * else the config's).\n */\nexport function docOutputDuration(doc: StudioDoc): number {\n const footage = totalDuration(ratedSegments(doc))\n // A recording's output lasts until its last visual clip ends (a legacy\n // end card's clips are placed after the footage on read); a program's\n // length is its own.\n return isRecordingDoc(doc) ? outputEnd(migrateMotion(doc), footage) : footage\n}\n\nexport interface MusicBedInput {\n id: string\n /** Durable URL of the track (assets.vos.so catalog or an owned asset). */\n key: string\n name: string\n /** Full source-file length, seconds. */\n trackDuration: number\n /** Current output duration of the doc, seconds. */\n outputDuration: number\n /** Whether the recording carries a mic track (ducking default). */\n hasMic: boolean\n}\n\n/** A catalog track placed as the doc's background music bed. */\nexport function musicBedClip(input: MusicBedInput): AudioClip {\n const track = round3(input.trackDuration)\n const output = round3(input.outputDuration)\n const clip: AudioClip = {\n id: input.id,\n key: input.key,\n name: input.name,\n start: 0,\n in: 0,\n out: track,\n duration: track,\n // Under speech, not over it; catalog tracks are loudness-normalized so\n // one default means the same thing across the library.\n gain: 0.5,\n fadeIn: 0,\n // A bed that just stops reads as a glitch; clamp so a very short cut\n // still spends most of its time at full level.\n fadeOut: output > 0 ? Math.min(1.5, round3(output / 4)) : 1.5,\n duck: input.hasMic || undefined,\n }\n if (output <= 0) return clip\n if (track >= output) {\n // Track outruns the cut: trim the tail to the output end.\n clip.out = output\n } else {\n // Cut outruns the track: loop the whole track to fill it.\n clip.loop = true\n clip.loopLen = output\n }\n return clip\n}\n\n/**\n * Is this clip a music BED — background music covering the cut? Beds are\n * what \"add a track\" replaces (one bed at a time; trying another vibe must\n * not stack). A clip the user moved off 0 or shortened mid-cut stopped\n * being a bed on purpose, so it is theirs to manage and never auto-replaced.\n */\nexport function isMusicBed(clip: AudioClip, outputDuration: number): boolean {\n if (clip.start > 0.05) return false\n if (clip.loop) return true\n const placed = clip.out - clip.in\n return Math.abs(placed - outputDuration) <= 1\n}\n\n/**\n * Re-fit clips that tracked the output end after the duration changed from\n * `prevDuration` to `nextDuration` (both OUTPUT seconds). Mutates `doc`\n * (an immer draft in practice); returns whether anything changed.\n */\nexport function refillAudioBeds(\n doc: ProjectDoc,\n prevDuration: number,\n nextDuration: number,\n): boolean {\n const EPS = 0.05\n if (Math.abs(nextDuration - prevDuration) <= EPS || nextDuration <= 0) {\n return false\n }\n let changed = false\n for (const clip of doc.audio) {\n if (clip.start >= nextDuration) continue\n const placed = clip.loop\n ? Math.max(clip.out - clip.in, clip.loopLen ?? clip.out - clip.in)\n : clip.out - clip.in\n // Only clips whose end sat AT the previous output end follow it — a clip\n // deliberately placed mid-timeline is the user's to manage.\n if (Math.abs(clip.start + placed - prevDuration) > EPS) continue\n const span = clip.out - clip.in\n const nextLen = round3(nextDuration - clip.start)\n if (clip.loop) {\n if (nextLen >= span) {\n clip.loopLen = nextLen\n } else {\n // Shorter than one pass: a loop cannot shrink below its span\n // (clipLength floors at the span), so it becomes a plain trim.\n clip.loop = undefined\n clip.loopLen = undefined\n clip.out = round3(clip.in + nextLen)\n }\n } else {\n if (clip.in + nextLen <= clip.duration) {\n clip.out = round3(clip.in + nextLen)\n } else {\n // The cut outgrew the source file: loop the full remainder to fill.\n clip.out = clip.duration\n clip.loop = true\n clip.loopLen = nextLen\n }\n }\n changed = true\n }\n return changed\n}\n\n/**\n * The take's VOICE source key, or null when it has none: the mic sidecar when\n * the take was recorded split (AT), else the legacy mixed track (pre-split\n * takes carried mic+system in the recording's own file). Ducking, the duck-RMS\n * decode and every \"has a voice?\" UI gate share this one derivation.\n */\nexport function voiceKey(\n doc: Pick<ProjectDoc, 'source'> | StudioDoc,\n): string | null {\n // A program has no voice: the duck controls simply do not show.\n if (!('source' in doc)) return null\n const src = doc.source\n if (src.micKey) return src.micKey\n if (src.meta.hasAudio && !src.meta.hasMic) return src.videoKey\n return null\n}\n","/**\n * The digest's crop geometry, pure and shared by the CLI's page and\n * the fleet's page: cursor/meta coords are CSS px of the viewport (or the\n * crop space when `source.crop` is set); the frame is capture px. A window\n * take's crop applies to the FRAME, never to the already-cropped cursor\n * coords. Both hosts compute boxes here so the two never drift by a dpr.\n */\nimport type { ProjectDoc } from '../types'\nimport type { Moment } from './moments'\n\nexport interface PxRect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** A crop box is at least this fraction of the frame width (floor 320px). */\nexport const CROP_MIN_FRAC = 0.25\nexport const CROP_MIN_PX = 320\nexport const CROP_MAX_PX = 1024\nexport const CROP_PAD = 0.25\n/** Default long edges (px) of the emitted images — the agent's token budget. */\nexport const DIGEST_FULL_MAX = 960\nexport const DIGEST_CROP_MAX = 640\n/** Changed-pixel threshold (luma, 0..255) for the motion bins. */\nexport const MOTION_DELTA = 24\n\nexport interface FrameGeometry {\n /** The region of the frame the doc renders (the viewport crop, or all). */\n region: PxRect\n /** Cursor px → frame px. */\n scale: number\n}\n\n/** The frame's pixel size when no decode has told us: capture px, else CSS×dpr. */\nexport function expectedFrameSize(doc: ProjectDoc): {\n width: number\n height: number\n} {\n const meta = doc.source.meta\n const dpr = meta.dpr > 0 ? meta.dpr : 1\n return {\n width: meta.captureWidth ?? Math.round(meta.width * dpr),\n height: meta.captureHeight ?? Math.round(meta.height * dpr),\n }\n}\n\nexport function frameGeometry(\n doc: ProjectDoc,\n frameW: number,\n frameH: number,\n): FrameGeometry {\n const meta = doc.source.meta\n const crop = doc.source.crop\n const region: PxRect = crop\n ? {\n x: Math.max(0, Math.round(crop.x)),\n y: Math.max(0, Math.round(crop.y)),\n w: Math.min(frameW, Math.round(crop.w)),\n h: Math.min(frameH, Math.round(crop.h)),\n }\n : { x: 0, y: 0, w: frameW, h: frameH }\n return { region, scale: region.w / Math.max(1, meta.width) }\n}\n\n/** The crop box (frame px) around a moment's rect or focus point, or null. */\nexport function cropBox(\n m: Pick<Moment, 'rect' | 'focus'>,\n geo: FrameGeometry,\n meta: { width: number; height: number },\n): PxRect | null {\n if (!m.rect && !m.focus) return null\n const { region, scale } = geo\n const toPx = (nx: number, ny: number) => ({\n x: region.x + nx * meta.width * scale,\n y: region.y + ny * meta.height * scale,\n })\n let x0: number\n let y0: number\n let x1: number\n let y1: number\n if (m.rect) {\n const a = toPx(m.rect.x, m.rect.y)\n const b = toPx(m.rect.x + m.rect.w, m.rect.y + m.rect.h)\n const pad = CROP_PAD * Math.max(b.x - a.x, b.y - a.y)\n x0 = a.x - pad\n y0 = a.y - pad\n x1 = b.x + pad\n y1 = b.y + pad\n } else {\n const p = toPx(m.focus!.cx, m.focus!.cy)\n x0 = x1 = p.x\n y0 = y1 = p.y\n }\n const min = Math.max(CROP_MIN_PX, CROP_MIN_FRAC * region.w)\n const cx = (x0 + x1) / 2\n const cy = (y0 + y1) / 2\n let w = Math.max(min, x1 - x0)\n let h = Math.max(min, y1 - y0)\n const cap = Math.min(CROP_MAX_PX, region.w, region.h)\n if (Math.max(w, h) > cap) {\n const s = cap / Math.max(w, h)\n w *= s\n h *= s\n }\n let x = cx - w / 2\n let y = cy - h / 2\n x = Math.max(region.x, Math.min(x, region.x + region.w - w))\n y = Math.max(region.y, Math.min(y, region.y + region.h - h))\n return {\n x: Math.round(x),\n y: Math.round(y),\n w: Math.round(Math.min(w, region.w)),\n h: Math.round(Math.min(h, region.h)),\n }\n}\n","/**\n * The digest document (digest.json), assembled from a take's doc, the\n * planners' proposals, the moments, and whatever a decode pass measured —\n * one pure builder for the CLI and the fleet, so a hosted digest\n * is byte-comparable with a local one. `images` maps moment ids to the\n * files a page wrote and their sizes; a builder with none (no frames) emits\n * a frameless digest.\n */\nimport { docOutputDuration } from '../audioBeds'\nimport { pickStyle } from './style'\nimport type { ProjectDoc, RecordingMeta } from '../types'\nimport type { DigestPlan, Moment, TranscriptSegment } from './moments'\nimport type { PxRect } from './geometry'\n\nexport const DIGEST_VERSION = 1\n\nexport interface DigestImageRef {\n full: string | null\n crop: string | null\n /** The crop's source box in FRAME px (what `crop` shows). */\n box: PxRect | null\n fullSize?: { width: number; height: number } | null\n cropSize?: { width: number; height: number } | null\n}\n\nexport interface DigestTakeFacts {\n sourceDuration: number\n outputDuration: number\n width: number\n height: number\n captureWidth: number | null\n captureHeight: number | null\n frameWidth: number | null\n frameHeight: number | null\n surface: string\n producer: string\n pageUrl: string | null\n pageTitle: string | null\n hasMic: boolean\n hasSystemAudio: boolean\n hasCursor: boolean\n windowFocusedFrac: number | null\n /**\n * The recorder did not land where it was asked (a sign-in, a redirect) and\n * the take was recorded anyway: the footage may be the wrong page. null\n * when it landed where it was asked, or the recorder does not check.\n */\n wall: RecordingMeta['wall'] | null\n /** Sensitive-looking text the recorder saw in the frame (kind + place, never the string). */\n exposures: NonNullable<RecordingMeta['exposures']>\n /** Masks the script asked for, with how many elements each reached. */\n masks: NonNullable<RecordingMeta['masks']>\n}\n\nexport interface Digest {\n digestVersion: number\n take: DigestTakeFacts\n units: {\n source: 'seconds of footage'\n output: 'seconds of the rendered video (trims and speed applied)'\n focus: 'fractions of the video frame [0..1], the zoom cx/cy convention'\n activity: 'fraction of pixels that changed, per SOURCE second'\n }\n moments: (Moment & {\n full: string | null\n crop: string | null\n box: PxRect | null\n })[]\n activity: number[] | null\n plan: DigestPlan\n doc: {\n manual: { zoom: number; speed: number; tilt: number; overlays: number }\n zoomStyle: string | null\n tiltStyle: string | null\n }\n style: { from: string; fields: Record<string, unknown> } | null\n transcript: TranscriptSegment[] | null\n images: {\n full: number\n crop: number\n sheet: string | null\n /** Sum of (w×h)/750 over the full + crop images: a reading-budget hint, not a bill. */\n tokensEstimate: number\n /** @deprecated The same number under its old name; gone next minor. */\n tokensEstimateClaude: number\n }\n}\n\nexport interface BuildDigestInput {\n doc: ProjectDoc\n plan: DigestPlan\n moments: Moment[]\n outputDuration: number\n bins: number[] | null\n frame: { width: number; height: number } | null\n images: Map<string, DigestImageRef>\n sheet: string | null\n style?: { from: string; doc: ProjectDoc } | null\n transcript?: readonly TranscriptSegment[] | null\n}\n\nexport function buildDigest(input: BuildDigestInput): Digest {\n const { doc, meta } = { doc: input.doc, meta: input.doc.source.meta }\n let tokens = 0\n const withFiles = input.moments.map((m) => {\n const ref = input.images.get(m.id)\n for (const s of [ref?.fullSize, ref?.cropSize]) {\n if (s) tokens += (s.width * s.height) / 750\n }\n return {\n ...m,\n full: ref?.full ?? null,\n crop: ref?.crop ?? null,\n box: ref?.crop ? (ref.box ?? null) : null,\n }\n })\n return {\n digestVersion: DIGEST_VERSION,\n take: {\n sourceDuration: round(meta.durationMs / 1000),\n outputDuration: round(input.outputDuration),\n width: meta.width,\n height: meta.height,\n captureWidth: meta.captureWidth ?? null,\n captureHeight: meta.captureHeight ?? null,\n frameWidth: input.frame?.width ?? null,\n frameHeight: input.frame?.height ?? null,\n surface: meta.captureSurface ?? 'tab',\n producer: meta.producer ?? 'extension',\n pageUrl: meta.pageUrl ?? null,\n pageTitle: meta.pageTitle ?? null,\n hasMic: Boolean(doc.source.micKey) || meta.hasMic === true,\n hasSystemAudio: meta.hasAudio === true,\n hasCursor: doc.source.cursor.length > 0,\n windowFocusedFrac: meta.windowFocusedFrac ?? null,\n wall: meta.wall ?? null,\n exposures: meta.exposures ?? [],\n masks: meta.masks ?? [],\n },\n units: {\n source: 'seconds of footage',\n output: 'seconds of the rendered video (trims and speed applied)',\n focus: 'fractions of the video frame [0..1], the zoom cx/cy convention',\n activity: 'fraction of pixels that changed, per SOURCE second',\n },\n moments: withFiles,\n activity: input.bins,\n plan: input.plan,\n doc: {\n manual: {\n zoom: doc.zoom.filter((z) => z.source === 'manual').length,\n speed: (doc.speed ?? []).filter((s) => s.source !== 'auto').length,\n tilt: (doc.tilt ?? []).filter((t) => t.source === 'manual').length,\n overlays: doc.overlays?.length ?? 0,\n },\n zoomStyle: doc.zoomStyle ?? null,\n tiltStyle: doc.tiltStyle ?? null,\n },\n style: input.style\n ? { from: input.style.from, fields: pickStyle(input.style.doc) }\n : null,\n transcript: input.transcript ? [...input.transcript] : null,\n images: {\n full: withFiles.filter((m) => m.full).length,\n crop: withFiles.filter((m) => m.crop).length,\n sheet: input.sheet,\n tokensEstimate: Math.round(tokens),\n tokensEstimateClaude: Math.round(tokens),\n },\n }\n}\n\n/** OUTPUT seconds of a doc: its kept footage through the rate map. */\nexport function outputDurationOf(doc: ProjectDoc): number {\n return docOutputDuration(doc)\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Range-first chip actions: pure recipes over the doc's span\n * arrays for a SOURCE-time range the user selected on the Video row. The\n * chips are the primary create path — creation and placement collapse into\n * one act, so the trim handles become a correction tool, not the way things\n * are made.\n */\nimport { mapTime } from '@vosjs/timeline'\nimport { DEFAULT_ZOOM_LEVEL, ZOOM_SPAN_MIN, clampSpeedRate } from '../types'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport type { Segment } from '@vosjs/timeline'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { SpeedSpan, ZoomSpan } from '../types'\n\n/** Map an OUTPUT-time range to SOURCE seconds through the doc's rate map. */\nexport function outputRangeToSource(\n doc: StudioDoc,\n t0: number,\n t1: number,\n): { srcIn: number; srcOut: number } {\n const rated = ratedSegments(doc)\n return {\n srcIn: mapTime(rated, Math.max(0, t0)),\n srcOut: mapTime(rated, Math.max(0, t1)),\n }\n}\n\nconst EPS = 1e-6\n/** A trimmed remainder below this (source seconds) is a crumb — drop it. */\nconst MIN_REMAINDER = 0.25\n/** Kept-segment floor after a cut (matches the video lane's split guard). */\nconst MIN_SEGMENT = 0.05\n\n/**\n * Re-rate a SOURCE range: spans overlapping it are trimmed (a span split in\n * two mints a fresh id for the second half), then a manual span at `rate`\n * covers the range. `rate: null` just clears — the 1× chip.\n */\nexport function setSpeedInRange(\n spans: readonly SpeedSpan[],\n srcIn: number,\n srcOut: number,\n rate: number | null,\n): SpeedSpan[] {\n if (srcOut - srcIn < EPS) return [...spans]\n const kept: SpeedSpan[] = []\n const used = new Set(spans.map((s) => s.id))\n for (const s of spans) {\n if (s.out <= srcIn + EPS || s.in >= srcOut - EPS) {\n kept.push(s)\n continue\n }\n if (srcIn - s.in >= MIN_REMAINDER)\n kept.push({ ...s, out: round(srcIn), source: 'manual' })\n if (s.out - srcOut >= MIN_REMAINDER)\n kept.push({\n ...s,\n id: mintId(used),\n in: round(srcOut),\n source: 'manual',\n })\n }\n if (rate != null)\n kept.push({\n id: mintId(used),\n in: round(srcIn),\n out: round(srcOut),\n rate: clampSpeedRate(rate),\n source: 'manual',\n })\n return kept.sort((a, b) => a.in - b.in)\n}\n\n/** The Remove chip: drop every span the range touches, whole. */\nexport function removeSpeedInRange(\n spans: readonly SpeedSpan[],\n srcIn: number,\n srcOut: number,\n): SpeedSpan[] {\n return spans.filter((s) => s.out <= srcIn + EPS || s.in >= srcOut - EPS)\n}\n\n/**\n * The Cut chip: subtract a SOURCE range from the kept segments. Segment\n * order (and any reorder) is preserved; a remainder below MIN_SEGMENT is\n * dropped with its parent. Never empties the take: cutting everything\n * returns the original list unchanged.\n */\nexport function removeSourceRange(\n segments: readonly Segment[],\n srcIn: number,\n srcOut: number,\n): Segment[] {\n const next: Segment[] = []\n for (const seg of segments) {\n if (seg.out <= srcIn + EPS || seg.in >= srcOut - EPS) {\n next.push(seg)\n continue\n }\n if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round(srcIn) })\n if (seg.out - srcOut >= MIN_SEGMENT)\n next.push({ ...seg, in: round(srcOut) })\n }\n return next.length ? next : [...segments]\n}\n\n/**\n * The Zoom chip: a manual zoom span covering as much of the range as the\n * lane's non-overlap rule allows — clipped against existing spans, starting\n * at the first free moment inside the range. Null when the free room is\n * below the zoom floor (the chip greys out).\n */\nexport function zoomSpanForRange(\n zoom: readonly ZoomSpan[],\n srcIn: number,\n srcOut: number,\n): ZoomSpan | null {\n let start = srcIn\n const covering = zoom.find((z) => z.in <= start + EPS && z.out > start + EPS)\n if (covering) start = covering.out\n let end = srcOut\n for (const z of zoom) {\n if (z.in >= start - EPS && z.in < end) end = z.in\n }\n if (end - start < ZOOM_SPAN_MIN) return null\n const used = new Set(zoom.map((z) => z.id))\n let n = 0\n while (used.has(`u${n}`)) n++\n return {\n id: `u${n}`,\n in: round(start),\n out: round(end),\n level: DEFAULT_ZOOM_LEVEL,\n cx: 0.5,\n cy: 0.5,\n source: 'manual',\n }\n}\n\n/** Smallest unused `sp{n}` id, reserving it in `used` for the next mint. */\nfunction mintId(used: Set<string>): string {\n let n = 0\n while (used.has(`sp${n}`)) n++\n const id = `sp${n}`\n used.add(id)\n return id\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","import { applyTimelineEdits } from '@vosjs/shared/timelineEdits'\nimport { totalDuration } from '@vosjs/timeline'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport {\n lowerToComposition,\n ratedSegments,\n studioLayerData,\n} from './lowerToComposition'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\nimport type { ProgramAnchorDoc, StudioDoc } from '../doc/studioDoc'\nimport type { LoweredComposition } from './lowerToComposition'\n\n/**\n * ONE lowering for the document family: the anchor's program\n * plus the studio stack entry, on every anchor.\n *\n * - A recording lowers to its card program (`lowerToComposition`), which\n * already carries the entry.\n * - A program lowers to the user's config, COMPLETE and untouched (the\n * execution IR, D1) with its tween-timing overlay baked into\n * `createTimeline`, plus the same entry carrying the shared layers. Params\n * and Looks ride the config as authored. `retime` arrives with speed spans\n * absent, the engine's identity is the identity.\n *\n * The composed config is what the platform stores for a layered program and\n * what the fleet compiles; the player runs it MINUS every data object (the\n * structural hash), with `data` and `stack` delivered live.\n */\nexport function lowerStudioDoc(\n doc: StudioDoc,\n opts: LowerProgramOptions = {},\n): LoweredComposition {\n return isRecordingDoc(doc)\n ? lowerToComposition(doc)\n : lowerProgramDoc(doc, opts)\n}\n\nexport interface LowerProgramOptions {\n /**\n * Bake the tween overlay into `createTimeline` (the STORED composed\n * config: the fleet, the watch page and `vos render` have no bridge to\n * hand an overlay to). Off by default: the player runs the user's\n * timeline and retimes it live, so the program string is constant\n * across every timing edit.\n */\n bake?: boolean\n}\n\nexport { programDuration } from '../doc/studioDoc'\n\n/**\n * `config.retime` for a program with speed spans: output time →\n * program time through the RATED segments on `data.retime` (the same map\n * `mapTime` performs; inlined so the config stays self-contained, tested\n * against it). Reads data live, so a rate edit is SET_DATA, never a LOAD.\n */\nexport const PROGRAM_RETIME = `(t, data) => {\n var s = data && data.retime\n if (!s || !s.length) return t\n var acc = 0\n for (var i = 0; i < s.length; i++) {\n var r = s[i].rate && s[i].rate > 0 ? s[i].rate : 1\n var d = (s[i].out - s[i].in) / r\n if (t < acc + d) return s[i].in + (t - acc) * r\n acc += d\n }\n return s[s.length - 1].out\n}`\n\n/**\n * With speed spans the OUTPUT length is not the program's own, but the\n * engine hands ONE \\`duration\\` to both the clock and \\`createTimeline\\`. The\n * composed config's \\`duration\\` is the output length (the clock, the fleet's\n * render length); this wrapper hands the user's function the program's own\n * length from \\`data.programDuration\\` — data, so a rate edit stays live.\n */\nexport function wrapProgramLength(source: string): string {\n return `(ctx, content, duration) => {\n const __base = (${source});\n const __own = ctx && ctx.data && typeof ctx.data.programDuration === 'number' ? ctx.data.programDuration : duration;\n return __base(ctx, content, __own);\n}`\n}\n\nexport function lowerProgramDoc(\n doc: ProgramAnchorDoc,\n opts: LowerProgramOptions = {},\n): LoweredComposition {\n const edits = Object.values(doc.program.tweenEdits ?? {})\n const anchor = (\n opts.bake\n ? applyTimelineEdits(\n doc.program.config as { createTimeline?: unknown },\n edits,\n )\n : doc.program.config\n ) as Record<string, unknown>\n // Speed spans retime the program on the engine. The program's own\n // length is the source clock; the rated segments give the output length.\n const own = programDuration(doc)\n const rated = ratedSegments(doc)\n const duration = own > 0 ? totalDuration(rated) : 0\n const entryData: Record<string, unknown> = studioLayerData(doc, duration)\n const baseData =\n anchor.data && typeof anchor.data === 'object'\n ? (anchor.data as Record<string, unknown>)\n : {}\n const retimed = !!doc.speed?.length && own > 0\n const data: Record<string, unknown> = retimed\n ? { ...baseData, retime: rated, programDuration: own }\n : baseData\n const config: Record<string, unknown> = {\n ...anchor,\n ...(retimed\n ? {\n duration,\n data,\n retime: PROGRAM_RETIME,\n createTimeline: wrapProgramLength(\n String(anchor.createTimeline ?? ''),\n ),\n }\n : {}),\n stack: [studioEntry(entryData)],\n }\n return {\n config,\n data,\n stack: { [STUDIO_ENTRY_ID]: entryData },\n ...(opts.bake ? {} : { tweenEdits: edits }),\n duration,\n }\n}\n","// GENERATED by packages/studio-core/scripts/build-destinations.mjs — do not edit.\n// Source: packages/cli/schema/channel-specs.json (verified 2026-08-04).\n// Re-run the script and commit whenever the specs change; destinations.test.ts\n// gates staleness on CHANNEL_SPECS_HASH.\n\n/**\n * A destination is where a release's media goes — the output twin of the\n * doors registry's \"a door is what you bring\". One row per channel asset,\n * derived from the verified channel specs; `vos deliver` loops these and\n * the kit manifest records them.\n */\nexport interface Destination {\n /** `${channel}-${asset}` — the id `vos deliver --to` and kit.json use. */\n id: string\n channel: string\n asset: string\n label: string\n kind: 'video' | 'still' | 'still-set'\n /** Reduced aspect ratio, the exportSizeFor convention. */\n ratio: string\n px: { w: number; h: number }\n /** still-set only: how many the channel takes. */\n count?: { min: number; max: number }\n /**\n * Image genre: 'screenshot' = real UI from the take (store policy demands\n * real UX); 'card' = a COMPOSED cover — rendered from the maker's poster\n * program when `vos deliver --poster` has one.\n */\n genre?: 'screenshot' | 'card'\n minSeconds?: number\n maxSeconds?: number\n maxBytes?: number\n /** The format the kit renders. */\n format: 'mp4' | 'png'\n /** What the channel accepts, in the spec's own words. */\n accepts: string\n /** How footage meets an off-ratio frame (a still fills, a video letterboxes). */\n fit: 'contain' | 'cover'\n /**\n * The destination's word policy: 'none' = the picture carries it alone (a\n * tile, a store screenshot), 'allowed', 'expected' = a card that should\n * say something. The picture checks read it.\n */\n text: 'none' | 'allowed' | 'expected'\n /**\n * The rect, as fractions of the frame, every text box must sit inside:\n * the platform's own chrome or crop covers the rest.\n */\n safe: { x: number; y: number; w: number; h: number }\n /**\n * The poster template a card destination renders from by default (the\n * family that ships with the CLI); absent on screenshots and videos.\n */\n template?: string\n notes: string\n}\n\nexport const CHANNEL_SPECS_VERIFIED = '2026-08-04'\n\nexport const CHANNEL_SPECS_HASH =\n 'e48926c22737b5dfaabf5cb33916a50a5eccd4b93498c2059a1468bd963a0a86'\n\nexport const DESTINATIONS: Destination[] = [\n {\n id: 'youtube-main-demo',\n channel: 'youtube',\n asset: 'main-demo',\n label: 'YouTube demo',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 60,\n maxSeconds: 120,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC)',\n fit: 'contain',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n 'Captions burned in. The same public upload serves the Chrome Web Store promo video and Product Hunt video (both take YouTube URLs only).',\n },\n {\n id: 'x-feed-cut',\n channel: 'x',\n asset: 'feed-cut',\n label: 'X feed cut',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 30,\n maxSeconds: 140,\n maxBytes: 536870912,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC ONLY — HEVC/VP9/AV1 rejected)',\n fit: 'contain',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n '30–60s plays best; 140s/512MB is the free-tier ceiling. 1:1 also allowed. Native upload, never a link post.',\n },\n {\n id: 'shorts-linkedin-vertical-cut',\n channel: 'shorts-linkedin',\n asset: 'vertical-cut',\n label: 'Shorts / LinkedIn vertical cut',\n kind: 'video',\n ratio: '9:16',\n px: {\n w: 1080,\n h: 1920,\n },\n minSeconds: 30,\n maxSeconds: 90,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC)',\n fit: 'contain',\n text: 'expected',\n safe: {\n x: 0.0833,\n y: 0.1979,\n w: 0.8333,\n h: 0.6042,\n },\n notes:\n 'Critical text inside a centered ~900×1160 safe zone — platform chrome covers the rest. Serves YouTube Shorts and LinkedIn native vertical.',\n },\n {\n id: 'github-readme-loop',\n channel: 'github',\n asset: 'readme-loop',\n label: 'GitHub README loop',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 10,\n maxSeconds: 20,\n maxBytes: 10485760,\n format: 'mp4',\n accepts: 'mp4 (H.264)',\n fit: 'contain',\n text: 'none',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n '≤10MB is the free-plan attachment ceiling; two-pass target bitrate from duration.',\n },\n {\n id: 'youtube-thumbnail',\n channel: 'youtube',\n asset: 'thumbnail',\n label: 'YouTube thumbnail',\n kind: 'still',\n ratio: '16:9',\n px: {\n w: 1280,\n h: 720,\n },\n genre: 'card',\n maxBytes: 2097152,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '',\n },\n {\n id: 'cws-screenshot',\n channel: 'cws',\n asset: 'screenshot',\n label: 'Chrome Web Store screenshot',\n kind: 'still-set',\n ratio: '8:5',\n px: {\n w: 1280,\n h: 800,\n },\n count: {\n min: 1,\n max: 5,\n },\n genre: 'screenshot',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes:\n 'Real UX only — misleading listing images are a removal-grade violation. Full bleed, square corners.',\n },\n {\n id: 'cws-small-promo-tile',\n channel: 'cws',\n asset: 'small-promo-tile',\n label: 'Chrome Web Store small promo tile',\n kind: 'still',\n ratio: '11:7',\n px: {\n w: 440,\n h: 280,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n template: 'card-on-gradient',\n notes:\n 'Listings without one rank lower. No text; fill the region; subject centered.',\n },\n {\n id: 'cws-marquee',\n channel: 'cws',\n asset: 'marquee',\n label: 'Chrome Web Store marquee',\n kind: 'still',\n ratio: '5:2',\n px: {\n w: 1400,\n h: 560,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: 'Carousel eligibility.',\n },\n {\n id: 'cws-icon',\n channel: 'cws',\n asset: 'icon',\n label: 'Chrome Web Store icon',\n kind: 'still',\n ratio: '1:1',\n px: {\n w: 128,\n h: 128,\n },\n format: 'png',\n accepts: 'png',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes: '96×96 art + 16px transparent padding.',\n },\n {\n id: 'producthunt-thumbnail',\n channel: 'producthunt',\n asset: 'thumbnail',\n label: 'Product Hunt thumbnail',\n kind: 'still',\n ratio: '1:1',\n px: {\n w: 240,\n h: 240,\n },\n genre: 'card',\n maxBytes: 3145728,\n format: 'png',\n accepts: 'gif|png',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0.06,\n y: 0.06,\n w: 0.88,\n h: 0.88,\n },\n template: 'card-on-gradient',\n notes: 'GIF animates on hover only — the first frame must stand alone.',\n },\n {\n id: 'producthunt-gallery',\n channel: 'producthunt',\n asset: 'gallery',\n label: 'Product Hunt gallery',\n kind: 'still-set',\n ratio: '127:76',\n px: {\n w: 1270,\n h: 760,\n },\n count: {\n min: 4,\n max: 8,\n },\n genre: 'screenshot',\n format: 'png',\n accepts: 'png|jpg|gif',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes: 'First image is the hero.',\n },\n {\n id: 'x-feed-image',\n channel: 'x',\n asset: 'feed-image',\n label: 'X feed image',\n kind: 'still',\n ratio: '16:9',\n px: {\n w: 1200,\n h: 675,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '',\n },\n {\n id: 'linkedin-feed-image',\n channel: 'linkedin',\n asset: 'feed-image',\n label: 'LinkedIn feed image',\n kind: 'still',\n ratio: '400:209',\n px: {\n w: 1200,\n h: 627,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '1080×1350 vertical also performs in the mobile feed.',\n },\n {\n id: 'og-card',\n channel: 'og',\n asset: 'card',\n label: 'OG card',\n kind: 'still',\n ratio: '40:21',\n px: {\n w: 1200,\n h: 630,\n },\n genre: 'card',\n maxBytes: 1048576,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.0238,\n w: 0.9,\n h: 0.9524,\n },\n template: 'split-cover',\n notes:\n 'Text in the center ~1080×600. Set twitter:card=summary_large_image explicitly — og:image alone gets the small card.',\n },\n {\n id: 'github-social-preview',\n channel: 'github',\n asset: 'social-preview',\n label: 'GitHub social preview',\n kind: 'still',\n ratio: '2:1',\n px: {\n w: 1280,\n h: 640,\n },\n genre: 'card',\n maxBytes: 1048576,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.0391,\n y: 0.0781,\n w: 0.9219,\n h: 0.8438,\n },\n template: 'card-on-gradient',\n notes: 'Key text ≥50px from every edge.',\n },\n]\n\nexport function destinationById(id: string): Destination | undefined {\n return DESTINATIONS.find((d) => d.id === id)\n}\n\nexport function destinationsForChannel(channel: string): Destination[] {\n return DESTINATIONS.filter((d) => d.channel === channel)\n}\n","/**\n * The house starters for an HTML layer: what one press of the studio's HTML\n * verb puts on the canvas, and what its panel's Starter select swaps in.\n * Product defaults the way the three text presets are (code, not shelf\n * data), five and not fifty: a LIBRARY of components is an official project\n * on the shelf that an agent applies as a template.\n *\n * Each is real, readable markup and CSS a person can retune, in the house\n * look (a dark card, Inter for words, JetBrains Mono for code, both hosted so\n * the faces resolve from the CSS alone), with a `box-shadow` so the derived\n * bleed is exercised. `htmlStarters.test.ts` proves every one passes the\n * gate with no problem and no warning.\n */\nimport { HOUSE_REGISTER, calloutClip } from './callout'\nimport type { HtmlOverlayClip } from './types'\n\nexport interface HtmlStarter {\n id: 'note' | 'tag' | 'code' | 'terminal' | 'keys' | 'badge'\n name: string\n html: string\n css: string\n box: { width: number; height: number }\n}\n\n/** The grammar's shape as a starter: its source in the house register. */\nfunction grammarStarter(\n id: 'note' | 'tag' | 'code',\n name: string,\n words: Parameters<typeof calloutClip>[2],\n): HtmlStarter {\n const clip = calloutClip(id, HOUSE_REGISTER, words, {\n id,\n start: 0,\n duration: 1,\n scale: 1.4,\n })\n return { id, name, html: clip.html, css: clip.css ?? '', box: clip.box }\n}\n\nconst WORDS =\n \"font-family:'Inter',-apple-system,system-ui,sans-serif;color:#fafafa;-webkit-font-smoothing:antialiased;\"\nconst MONO = \"font-family:'JetBrains Mono',ui-monospace,monospace;\"\nconst CARD =\n 'background:#0b0b0d;border:1px solid rgba(255,255,255,.08);border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45);'\n\nexport const HTML_STARTERS: readonly HtmlStarter[] = [\n // The callout grammar (three shapes a viewer learns once), in the house\n // register: a dark card in the house hue over a light app.\n grammarStarter('note', 'Note', {\n kicker: 'Presets',\n title: 'One of 43, picked here.',\n body: 'One line that says what this is about.',\n }),\n grammarStarter('tag', 'Tag', { kicker: 'New in 2.0' }),\n grammarStarter('code', 'Code', {\n kicker: 'index.css',\n code: ':root {\\n --primary: oklch(0.63 0.06 4.6);\\n --radius: 2.25rem;\\n}',\n }),\n {\n id: 'terminal',\n name: 'Terminal',\n box: { width: 460, height: 152 },\n html:\n '<div class=\"term\"><span class=\"prompt\">$</span> <span class=\"cmd\">npx vos record https://your.app</span>\\n' +\n '<span class=\"mut\">recording 1920x1080 at 30 fps</span>\\n' +\n '<span class=\"mut\">saved take/</span></div>',\n css:\n `.term{${MONO}${CARD}background:#08080a;color:#e8e8ec;box-sizing:border-box;width:460px;height:152px;padding:22px 24px;font-size:14px;line-height:1.8;white-space:pre}` +\n '.prompt{color:#4ade80}.mut{color:#5b5b66}',\n },\n {\n id: 'keys',\n name: 'Keys',\n box: { width: 220, height: 64 },\n html: '<div class=\"keys\"><kbd>⌘</kbd><span class=\"plus\">+</span><kbd>K</kbd></div>',\n css:\n `.keys{${WORDS}display:flex;align-items:center;justify-content:center;gap:10px;width:220px;height:64px}` +\n 'kbd{display:inline-flex;align-items:center;justify-content:center;min-width:48px;height:48px;padding:0 14px;border-radius:10px;background:#1a1a1f;border:1px solid rgba(255,255,255,.14);border-bottom-width:3px;box-shadow:0 10px 24px rgba(0,0,0,.4);font-size:22px;font-weight:600}' +\n '.plus{color:#8b8b94;font-size:18px}',\n },\n {\n id: 'badge',\n name: 'Badge',\n box: { width: 200, height: 52 },\n html: '<div class=\"badge\"><span class=\"dot\"></span>New in 2.0</div>',\n css:\n `.badge{${WORDS}display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 20px;border-radius:99px;background:#1a1a1f;border:1px solid rgba(255,255,255,.12);box-shadow:0 12px 30px rgba(0,0,0,.4);font-size:16px;font-weight:600;letter-spacing:-0.01em}` +\n '.dot{width:8px;height:8px;border-radius:99px;background:#3b82f6;box-shadow:0 0 10px rgba(59,130,246,.9)}',\n },\n]\n\n/**\n * A starter as a clip at a moment: the studio's verb and the panel's Starter\n * select both call this, so what a press creates and what a swap replaces\n * are one shape. `bleed` is left absent (derived), `width` absent (the\n * design size), the transform centred.\n */\nexport function htmlStarterClip(\n starter: HtmlStarter,\n id: string,\n start: number,\n duration: number,\n): HtmlOverlayClip {\n return {\n id,\n kind: 'html',\n html: starter.html,\n css: starter.css,\n box: { ...starter.box },\n start,\n duration,\n transform: { x: 0.5, y: 0.5, scale: 1, rotation: 0 },\n }\n}\n","import type { EnvelopePoint } from './audioEnvelope'\n\n/**\n * The studio's audio clips as an ENGINE audio plan:\n * the shape `@vosjs/core/audio`'s `mixAudio` renders. One builder for every\n * export path (the device exporter, the fleet's audio page) from the same\n * lowered data the preview scheduler plays, so what you hear is what exports.\n *\n * A clip is OUTPUT-anchored: it plays from `start` for `len` seconds, reading\n * the source from `in` (looping over `[in, out]` when `loop`), at its gain\n * envelope (`env`, absolute output seconds, fades included) times the duck\n * curve when it ducks. The plan samples that at `step` (240/s, the engine's\n * default); the mixer interpolates between points and treats the loop's\n * wrap as a seek.\n *\n * Structurally typed: the lowered clip, not the doc — this runs on the fleet\n * from stored config data as well as in the studio.\n */\nexport interface LoweredAudioClip {\n key: string\n start: number\n in: number\n out: number\n gain: number\n loop: boolean\n len: number\n duck: boolean\n env: EnvelopePoint[]\n}\n\nexport interface AudioPlanPoint {\n t: number\n on: boolean\n pos: number\n gain: number\n}\n\nexport interface AudioPlanTrack {\n id: string\n src: string\n loop: boolean\n points: AudioPlanPoint[]\n}\n\nexport interface StudioAudioPlan {\n duration: number\n step: number\n tracks: AudioPlanTrack[]\n}\n\nexport const AUDIO_PLAN_STEP = 1 / 240\n\n/** Linear interpolation over absolute-time envelope points; `def` outside an empty one. */\nexport function envelopeAt(\n env: readonly EnvelopePoint[],\n t: number,\n def: number,\n): number {\n const n = env.length\n if (!n) return def\n if (t <= env[0].t) return env[0].g\n if (t >= env[n - 1].t) return env[n - 1].g\n let lo = 0\n let hi = n - 1\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1\n if (env[mid].t <= t) lo = mid\n else hi = mid\n }\n const a = env[lo]\n const b = env[hi]\n if (b.t <= a.t) return b.g\n return a.g + ((b.g - a.g) * (t - a.t)) / (b.t - a.t)\n}\n\nexport function studioAudioPlan(\n clips: readonly LoweredAudioClip[],\n duckEnv: readonly EnvelopePoint[],\n duration: number,\n step = AUDIO_PLAN_STEP,\n): StudioAudioPlan {\n const count = Math.max(0, Math.ceil(duration / step)) + 1\n const tracks: AudioPlanTrack[] = []\n clips.forEach((clip, i) => {\n const span = clip.out - clip.in\n const len = clip.len > 0 ? clip.len : span\n if (!(span > 0) || !(len > 0) || clip.start >= duration) return\n const end = clip.start + len\n const points: AudioPlanPoint[] = new Array(count)\n for (let k = 0; k < count; k++) {\n const t = k * step\n const on = t >= clip.start && t < end\n const local = Math.max(0, t - clip.start)\n const pos = clip.loop\n ? clip.in + (local % span)\n : clip.in + Math.min(local, span)\n const gain = on\n ? Math.max(\n 0,\n envelopeAt(clip.env, t, clip.gain) *\n (clip.duck ? envelopeAt(duckEnv, t, 1) : 1),\n )\n : 0\n points[k] = { t, on, pos, gain }\n }\n // The mixer loops over the WHOLE source; a clip loops over `[in, out]`,\n // which the positions above express themselves (the wrap is a seek).\n tracks.push({ id: `clip${i}`, src: clip.key, loop: false, points })\n })\n return { duration, step, tracks }\n}\n","/**\n * A LOOK is how the card is presented: the ground it sits on, how much of\n * the frame it takes, the shadow that makes it an object, the stroke that\n * separates a light card from a light ground. Three house looks cover the\n * feature-clip and poster grammar the premium tools default to (a cream\n * plate with a soft wide shadow; a warm gradient; a near-black plate with a\n * light streak and a glow), and a maker's brand kit resolves to one of them\n * with its own colours. Pure: a look resolves to `FrameStyle` fields, so\n * every renderer and every still reads the same placement.\n */\nimport type { FrameStyle } from './types'\n\nexport type LookKind = 'plate' | 'gradient' | 'dark'\n\n/**\n * Where the card goes. `hero` is the feature-clip grammar: the card at\n * ~84% of the width with headroom above and its bottom edge running off\n * the frame (\"this continues\"). `card` is the poster grammar: the card\n * centred on the ground with even room around it; on a frame too wide to\n * hold the whole card it falls back to `hero`.\n */\nexport type LookPlacement = 'hero' | 'card'\n\nexport interface Look {\n kind: LookKind\n /** CSS ground painted under the card (a colour or a gradient). */\n ground: string\n /** The card's width as a fraction of the frame. */\n cardWidth: number\n /** Room above the card in the hero placement, a fraction of the height. */\n headroom: number\n /** Corner radius in design px. */\n radius: number\n /** Ambient shadow 0..1. */\n shadow: number\n /** Contact shadow 0..1. */\n shadowContact: number\n /** `#rrggbb`; absent = black. */\n shadowColor?: string\n /** Hairline stroke alpha 0..1 (0 = none). */\n border: number\n borderColor?: string\n}\n\n/** The frontmatter roles a brand kit carries that a look reads. */\nexport interface LookBrand {\n look?: string | null\n ground?: string | null\n bgA?: string | null\n bgB?: string | null\n bgC?: string | null\n accent?: string | null\n ink?: string | null\n}\n\n/** The house coral, the ground every take opened on before looks existed. */\nexport const HOUSE_GRADIENT = 'linear-gradient(135deg, #ff5148, #ffb03a)'\n\n/** The Chrome feature-clip plate, measured on three clips. */\nexport const PLATE_GROUND = '#f0f2f4'\n\n/** Near-black with a warm light streak from the top right. */\nexport const DARK_GROUND =\n 'radial-gradient(ellipse at 82% 0%, #2a1d16, #07080b)'\n\nconst LOOKS: Record<LookKind, Look> = {\n plate: {\n kind: 'plate',\n ground: PLATE_GROUND,\n cardWidth: 0.84,\n headroom: 0.16,\n radius: 12,\n shadow: 0.42,\n shadowContact: 0,\n border: 0.08,\n borderColor: '#000000',\n },\n gradient: {\n kind: 'gradient',\n ground: HOUSE_GRADIENT,\n cardWidth: 0.84,\n headroom: 0.16,\n radius: 12,\n shadow: 0.5,\n shadowContact: 0,\n border: 0,\n },\n dark: {\n kind: 'dark',\n ground: DARK_GROUND,\n cardWidth: 0.86,\n headroom: 0.14,\n radius: 12,\n shadow: 0.7,\n shadowContact: 0,\n shadowColor: '#000000',\n border: 0.12,\n borderColor: '#ffffff',\n },\n}\n\nexport const LOOK_KINDS: LookKind[] = ['plate', 'gradient', 'dark']\n\nexport function isLookKind(v: unknown): v is LookKind {\n return typeof v === 'string' && (LOOK_KINDS as string[]).includes(v)\n}\n\n/** One of the three house looks, copied. */\nexport function houseLook(kind: LookKind): Look {\n return { ...LOOKS[kind] }\n}\n\nconst HEX = /^#([0-9a-f]{6})$/i\n\nfunction luma(hex: string): number | null {\n const m = HEX.exec(hex.trim())\n if (!m) return null\n const n = parseInt(m[1], 16)\n const r = (n >> 16) & 255\n const g = (n >> 8) & 255\n const b = n & 255\n return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255\n}\n\n/**\n * Which house look a site's own ground asks for: a paper site is a plate, a\n * dark site is dark, anything else takes the gradient. What `vos brand`\n * writes as the `look` role, so an outside maker never gets the house coral\n * by accident.\n */\nexport function lookKindForGround(bgA: string | null | undefined): LookKind {\n const l = bgA ? luma(bgA) : null\n if (l === null) return 'gradient'\n if (l >= 0.85) return 'plate'\n if (l <= 0.2) return 'dark'\n return 'gradient'\n}\n\n/**\n * Resolve a brand kit to a look: the `look` role picks the kind (else the\n * site's ground decides), `ground` overrides the ground outright, and the\n * kit's colours colour the house look (plate = bgB, gradient = accent →\n * bgC, dark = bgA with a lifted shadow). A kit with none of them is the\n * house look unchanged.\n */\nexport function lookFromBrand(brand: LookBrand | null | undefined): Look {\n const b = brand ?? {}\n const kind = isLookKind(b.look) ? b.look : lookKindForGround(b.bgA)\n const look = houseLook(kind)\n if (kind === 'plate' && b.bgB && HEX.test(b.bgB)) look.ground = b.bgB\n if (kind === 'gradient' && b.accent && b.bgC && HEX.test(b.accent)) {\n look.ground = `linear-gradient(135deg, ${b.accent}, ${b.bgC})`\n }\n if (kind === 'dark' && b.bgA && HEX.test(b.bgA)) {\n look.ground = `radial-gradient(ellipse at 82% 0%, ${mix(b.bgA, b.accent ?? '#ffffff', 0.18)}, ${b.bgA})`\n }\n if (b.ground && b.ground.trim()) look.ground = b.ground.trim()\n return look\n}\n\nfunction mix(a: string, b: string, t: number): string {\n const pa = HEX.exec(a)\n const pb = HEX.exec(b)\n if (!pa || !pb) return a\n const na = parseInt(pa[1], 16)\n const nb = parseInt(pb[1], 16)\n const ch = (sh: number) => {\n const x = (na >> sh) & 255\n const y = (nb >> sh) & 255\n return Math.round(x + (y - x) * t)\n }\n return `#${((ch(16) << 16) | (ch(8) << 8) | ch(0)).toString(16).padStart(6, '0')}`\n}\n\n/**\n * The card's per-side inset for a frame of `frameW:frameH` holding footage\n * of `videoW:videoH` under contain fit, as fractions of the frame. Card\n * height follows from the width (contain keeps the footage aspect): a card\n * that fits is centred (`card`) or given headroom (`hero`); one taller than\n * the frame bleeds off the bottom by at least 2%, the feature-clip cue.\n */\nexport function cardInset(\n look: Pick<Look, 'cardWidth' | 'headroom'>,\n frame: { w: number; h: number },\n video: { w: number; h: number },\n placement: LookPlacement,\n): NonNullable<FrameStyle['inset']> {\n const R = frame.w / Math.max(1, frame.h)\n const V = video.w / Math.max(1, video.h)\n const cw = look.cardWidth\n const ch = (cw * R) / V\n const side = (1 - cw) / 2\n if (placement === 'card' && ch <= 1 - look.headroom) {\n const v = (1 - ch) / 2\n return { left: side, right: side, top: v, bottom: v }\n }\n const top = look.headroom\n const bottom = Math.min(-0.02, 1 - top - ch)\n return { left: side, right: side, top, bottom }\n}\n\n/**\n * Apply a look to a frame for one output size and placement: the ground,\n * the inset, the radius, both shadow layers and the hairline. The bar and\n * the media layer are the document's own (a look presents the card, it\n * does not decide what chrome the card wears). `fit` is forced to contain,\n * because the card IS the footage's aspect under a look.\n */\nexport function applyLook(\n frame: FrameStyle,\n look: Look,\n size: { w: number; h: number },\n video: { w: number; h: number },\n placement: LookPlacement,\n): FrameStyle {\n const out: FrameStyle = {\n ...frame,\n background: look.ground,\n fit: 'contain',\n inset: cardInset(look, size, video, placement),\n radius: look.radius,\n shadow: look.shadow,\n shadowContact: look.shadowContact,\n border: look.border,\n }\n delete out.focus\n if (look.shadowColor) out.shadowColor = look.shadowColor\n else delete out.shadowColor\n if (look.border > 0) {\n out.borderWidth = 1\n out.borderColor = look.borderColor ?? '#000000'\n }\n return out\n}\n","/**\n * The studio's lane adapters — the app's opinion of its timeline: a video lane\n * (segments as clips; trim/split/remove), a speed lane (rate spans; retime/\n * re-rate/remove), and a zoom lane (zoom regions as clips; move/resize/add/\n * remove). Zoom spans and speed spans are SOURCE-anchored in the doc, so lanes\n * map them through the RATED segment list both ways (display: sourceToTimeline/\n * spanOutputExtent; gestures: mapTime) — output positions contract/stretch\n * with speed changes.\n */\nimport {\n mapTime,\n removeSegment,\n segmentRate,\n sourceToTimeline,\n splitBySpeed,\n totalDuration,\n trimSegment,\n} from '@vosjs/timeline'\nimport { ratedSegments, spanOutputExtent } from '../lower/lowerToComposition'\nimport { docFreezes, freezeOutputExtents, withFreezes } from '../lower/motion'\nimport {\n contiguous,\n effectiveSegments,\n outputLen,\n segmentPieces,\n segmentStarts,\n} from '../lower/segmentStarts'\nimport { mediaAtOutput, mediaDuration, mediaSource } from '../media'\nimport { docOutputDuration, voiceKey } from '../audioBeds'\nimport { htmlLayerLabel } from '../htmlLayer'\nimport { anchorSourceDuration } from '../doc/studioDoc'\nimport {\n CAM_SPAN_MIN,\n DEFAULT_CAM_POSE,\n FREEZE_DEFAULT_SECONDS,\n FREEZE_SECONDS_MIN,\n clampFreezeSeconds,\n DEFAULT_TILT_POSE,\n DEFAULT_ZOOM_LEVEL,\n OVERLAY_MIN_DURATION,\n SPEED_SPAN_MIN,\n TILT_SPAN_MIN,\n ZOOM_SPAN_MIN,\n clipLength,\n} from '../types'\nimport type { Segment } from '@vosjs/timeline'\nimport type { FreezeSpan, ProjectDoc } from '../types'\nimport type { LaneAdapter, LaneItem } from '@vosjs/editor'\n\nexport { effectiveSegments }\n\nexport const videoLane: LaneAdapter<ProjectDoc> = {\n id: 'video',\n label: 'Video',\n\n items(doc): LaneItem[] {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n // A clip is its footage plus the freezes on it (a freeze is output\n // time, so it lengthens the clip and pushes what follows).\n return segments.map((s, i) => ({\n id: `seg-${i}`,\n kind: 'clip',\n t: round(starts[i]),\n duration: round(\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n ),\n }))\n },\n\n gesture(doc, g) {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n switch (g.type) {\n case 'move': {\n // Dragging a segment REORDERS the cut sequence: pull it out, then\n // insert where the dragged start lands among the remaining segments\n // (midpoint rule). Single-segment docs have nothing to reorder.\n // NOTE ids are index-based (`seg-N`) — mid-drag the live doc reorders\n // under a frozen id, which is fine for the math (the anchoring\n // contract evaluates against the pointer-down doc) but means the drag\n // highlight can momentarily sit on a neighbor. Cosmetic only.\n if (segments.length < 2) return null\n const index = segIndex(g.id)\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n const others = segments.filter((_, i) => i !== index)\n let acc = 0\n let insert = others.length\n for (let i = 0; i < others.length; i++) {\n const dur = outputLen(others[i], speeds, freezes)\n if (g.t < acc + dur / 2) {\n insert = i\n break\n }\n acc += dur\n }\n if (insert === index) return null\n const next = [...others]\n next.splice(insert, 0, seg)\n return (d) => {\n d.segments = next\n }\n }\n case 'resize': {\n const index = segIndex(g.id)\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n // Translate the dragged output delta into a SOURCE edge position by\n // walking the full-source rate map (speed spans apply everywhere, so\n // an edge dragged across a 2× span consumes source 2× as fast — and\n // trimmed footage can still be dragged back out past the segment).\n const starts = segmentStarts(segments, speeds, freezes)\n const fullMap = splitBySpeed([{ in: 0, out: sourceDuration }], speeds)\n const edgeSrc = g.edge === 'start' ? seg.in : seg.out\n // The edge's output position as drawn, freezes included: the delta\n // to the pointer is then pure footage (speed-mapped), and a freeze\n // at the dragged end rides along with its frame.\n const edgeOutNow =\n g.edge === 'start'\n ? starts[index]\n : starts[index] +\n outputLen(\n seg,\n speeds,\n freezes.filter((f) => f.at < seg.out - 1e-9),\n index === 0 || !contiguous(segments, index),\n )\n const anchorOut =\n sourceToTimeline(fullMap, Math.min(edgeSrc, sourceDuration)) ??\n edgeSrc\n const sourceT = mapTime(fullMap, anchorOut + (g.t - edgeOutNow))\n const next = trimSegment(\n segments,\n index,\n g.edge === 'start' ? 'in' : 'out',\n sourceT,\n sourceDuration,\n )\n return (d) => {\n d.segments = next\n }\n }\n case 'create': {\n // Split under the playhead: locate the DOC segment whose output span\n // contains g.t (speed-aware starts), map the local output offset to a\n // source moment through that segment's own rated pieces, split there.\n // Doc segments never carry rates — those stay in doc.speed. No-op at\n // boundaries (either half would be degenerate), like splitSegments.\n const starts = segmentStarts(segments, speeds, freezes)\n const index = segments.findIndex(\n (s, i) =>\n g.t >= starts[i] &&\n g.t <\n starts[i] +\n outputLen(\n s,\n speeds,\n freezes,\n i === 0 || !contiguous(segments, i),\n ),\n )\n if (index < 0) return null\n const s = segments[index]\n const leading = index === 0 || !contiguous(segments, index)\n // Inside a freeze there is no footage to split: the press does nothing.\n const local = g.t - starts[index]\n for (const m of freezeOutputExtents(\n splitBySpeed([s], speeds),\n freezes.filter(\n (f) =>\n f.at <= s.out + 1e-9 &&\n (leading ? f.at >= s.in - 1e-9 : f.at > s.in + 1e-9),\n ),\n ).values()) {\n if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)\n return null\n }\n const sourceT = round(\n mapTime(segmentPieces(s, speeds, freezes, leading), local),\n )\n if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null\n const next = [\n ...segments.slice(0, index),\n { ...s, out: sourceT },\n { ...s, in: sourceT },\n ...segments.slice(index + 1),\n ]\n return (d) => {\n d.segments = next\n }\n }\n case 'remove': {\n const next = removeSegment(segments, segIndex(g.id))\n if (next.length === segments.length) return null\n return (d) => {\n d.segments = next\n }\n }\n default:\n return null\n }\n },\n\n magnets(doc): number[] {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n const n = segments.length\n return [\n ...starts,\n ...(n\n ? [\n starts[n - 1] +\n outputLen(\n segments[n - 1],\n speeds,\n freezes,\n n === 1 || !contiguous(segments, n - 1),\n ),\n ]\n : []),\n ].map(round)\n },\n}\n\n/** The next free freeze id (`f{n}`). */\nfunction nextFreezeId(doc: ProjectDoc): string {\n const taken = new Set((doc.freeze ?? []).map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n return `f${n}`\n}\n\n/** Two freezes may not sit closer than this in source seconds. */\nconst FREEZE_GAP = 0.05\n\n/**\n * Freeze lane — freezes as hatched bands ON the footage clip, beside the\n * speed bands (the retime lane). A freeze is a SOURCE moment with output\n * seconds; the lane draws it where the rated pieces put it (a freeze\n * whose frame is cut away draws nothing) and the band's width is its\n * seconds. Create at the playhead (a press inside a freeze or off kept\n * footage no-ops); move re-anchors the moment pointer-true through the\n * rate map without this freeze; resize on the END edge sets the seconds,\n * on the START edge re-anchors the moment and keeps the end where it is.\n */\nexport const freezeLane: LaneAdapter<ProjectDoc> = {\n id: 'freeze',\n label: 'Freeze',\n\n items(doc): LaneItem[] {\n const freezes = docFreezes(doc)\n if (!freezes.length) return []\n const segs = effectiveSegments(doc)\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n freezes,\n )\n return freezes.flatMap((f) => {\n const m = marks.get(f.id)\n return m\n ? [\n {\n id: f.id,\n kind: 'clip' as const,\n t: round(m.t),\n duration: round(m.duration),\n label: `${trimZeros(f.seconds)}s`,\n },\n ]\n : []\n })\n },\n\n gesture(doc, g) {\n const freezes = docFreezes(doc)\n const segs = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const sourceDuration = anchorSourceDuration(doc)\n const kept = (t: number): boolean =>\n segs.some((s) => t >= s.in - 1e-9 && t <= s.out + 1e-9)\n const clear = (t: number, except?: string): boolean =>\n !freezes.some((f) => f.id !== except && Math.abs(f.at - t) < FREEZE_GAP)\n\n if (g.type === 'create') {\n const rated = ratedSegments(doc)\n // A press inside a freeze band is on that freeze, not on footage.\n for (const m of freezeOutputExtents(\n splitBySpeed(segs, speeds),\n freezes,\n ).values()) {\n if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null\n }\n // Past the footage's end there is no frame to freeze.\n if (g.t > totalDuration(rated) + 1e-9) return null\n const srcT = round(mapTime(rated, Math.max(0, g.t)))\n if (!kept(srcT) || !clear(srcT)) return null\n const id = nextFreezeId(doc)\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n return (d) => {\n d.freeze = [\n ...(d.freeze ?? []),\n {\n id,\n at: srcT,\n seconds: FREEZE_DEFAULT_SECONDS,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n }\n }\n\n const f = freezes.find((x) => x.id === g.id)\n if (!f) return null\n // A legacy segment hold is read as a freeze but lives on the segment:\n // spell it as one before editing it, so the edit has a home.\n const own = (d: ProjectDoc): FreezeSpan | undefined => {\n const list = d.freeze ?? []\n let mine = list.find((x) => x.id === f.id)\n if (!mine) {\n const id = nextFreezeId(d)\n mine = { id, at: f.at, seconds: f.seconds }\n d.freeze = [...list, mine]\n d.segments = d.segments.map((sg) => {\n if (sg.hold === undefined || Math.abs(sg.out - f.at) > 1e-9) return sg\n const next = { ...sg }\n delete next.hold\n return next\n })\n }\n return mine\n }\n // POINTER-TRUE: map through the rate map WITHOUT this freeze.\n const base = withFreezes(\n splitBySpeed(segs, speeds),\n freezes.filter((x) => x.id !== f.id),\n )\n\n if (g.type === 'move' || (g.type === 'resize' && g.edge === 'start')) {\n const marks = freezeOutputExtents(splitBySpeed(segs, speeds), freezes)\n const mine = marks.get(f.id)\n const endOut = mine ? mine.t + mine.duration : null\n const srcT = round(\n Math.min(sourceDuration, mapTime(base, Math.max(0, g.t))),\n )\n if (!kept(srcT) || !clear(srcT, f.id)) return null\n return (d) => {\n const x = own(d)\n if (!x) return\n x.at = srcT\n if (g.type === 'resize' && endOut !== null) {\n // The end stays put: the seconds absorb the moved start.\n const after = freezeOutputExtents(\n splitBySpeed(effectiveSegments(d), d.speed ?? []),\n docFreezes({\n ...d,\n freeze: (d.freeze ?? []).map((y) =>\n y.id === x.id ? { ...y, seconds: FREEZE_SECONDS_MIN } : y,\n ),\n }),\n ).get(x.id)\n if (after) x.seconds = clampFreezeSeconds(endOut - after.t)\n }\n d.freeze!.sort((a, b) => a.at - b.at)\n }\n }\n\n if (g.type === 'resize') {\n const marks = freezeOutputExtents(splitBySpeed(segs, speeds), freezes)\n const mine = marks.get(f.id)\n if (!mine) return null\n const seconds = clampFreezeSeconds(g.t - mine.t)\n if (Math.abs(seconds - f.seconds) < 1e-6) return null\n return (d) => {\n const x = own(d)\n if (x) x.seconds = seconds\n }\n }\n\n if (g.type === 'remove') {\n return (d) => {\n const x = own(d)\n d.freeze = (d.freeze ?? []).filter((y) => y !== x)\n }\n }\n return null\n },\n\n magnets(doc): number[] {\n return freezeLane\n .items(doc)\n .flatMap((it) => [it.t, it.t + (it.duration ?? 0)])\n },\n}\n\n/** `2` for 2, `2.5` for 2.5: a band label without trailing zeros. */\nfunction trimZeros(n: number): string {\n return String(Math.round(n * 100) / 100)\n}\n\n/**\n * Zoom lane — zoom regions as clips (\"1.80×\"). Spans are SOURCE-anchored\n * (footage-anchored like speed spans and the cam window); the lane displays\n * the output extent of each span's KEPT footage (spanOutputExtent — partial\n * cuts snap the clip's edges, full cuts hide it until the trim is undone).\n * Move/resize are pointer-true through the FULL rated map — zoom never alters\n * rates, so no exclusion trick is needed (unlike speedLane). Spans never\n * overlap: create no-ops inside an existing span, move pushes out of\n * collisions (or no-ops), resize clamps against neighbors. Level/focus are\n * edited in the toolbar/inspector, not by gesture. Any gesture promotes the\n * span to source:'manual' — it survives an auto-zoom regenerate.\n */\nexport const zoomLane: LaneAdapter<ProjectDoc> = {\n id: 'zoom',\n label: 'Zoom',\n\n items(doc): LaneItem[] {\n const segments = ratedSegments(doc)\n return doc.zoom.flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: `${z.level.toFixed(2)}×`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.zoom\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n // The media under the playhead: the span is its, clears its spans\n // only, and runs to its own end.\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n const here = spans.filter((z) => (z.media ?? '') === mediaHere)\n const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source)\n if (here.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = here\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(mediaLen, next ? next.in : mediaLen)\n const len = Math.min(spanDefaultLen(mediaLen), limit - srcT)\n if (len < ZOOM_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextZoomId(doc)\n // Seed the focus from the cursor at the playhead (element-aware capture\n // means the cursor usually sits on the thing worth framing); center for\n // uploads with no track.\n const { cx, cy } = cursorFocusAt(doc, srcT)\n return (d) => {\n d.zoom = [\n ...d.zoom,\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n level: DEFAULT_ZOOM_LEVEL,\n cx,\n cy,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = d.zoom.find((x) => x.id === g.id)\n if (!z) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.zoom.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n // The floor is OUTPUT seconds: convert through the rate in force so a\n // span under a 5× speed-up cannot shrink to a sliver of screen time.\n // Never larger than the span's CURRENT length — a floor that exceeded\n // the room to a neighbour would shove the edge PAST the neighbour, and\n // a span already below floor must stay resizable, not grow by force.\n const minSrc = Math.min(\n ZOOM_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = d.zoom.find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.zoom = d.zoom.filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return zoomLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Focus seed for a new zoom: the cursor position nearest the playhead's source\n * moment, normalized to the cursor coordinate space (meta.width/height —\n * CursorEvent.x/y after normalizeCaptureSpace). Center when there's no track.\n */\nfunction cursorFocusAt(\n doc: ProjectDoc,\n srcT: number,\n): { cx: number; cy: number } {\n const track = doc.source.cursor\n const { width, height } = doc.source.meta\n if (!track.length || !width || !height) return { cx: 0.5, cy: 0.5 }\n const ms = srcT * 1000\n let best = track[0]\n for (const e of track)\n if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e\n const clamp01 = (v: number) => Math.max(0, Math.min(1, v))\n return {\n cx: round(clamp01(best.x / width)),\n cy: round(clamp01(best.y / height)),\n }\n}\n\n/**\n * Tilt lane — card-pose regions as clips (label = \"rx°/ry°\"). SOURCE-anchored\n * like zoom spans (footage-anchored through trims and speed changes; the full\n * rated map applies — tilt doesn't alter rates); non-overlapping. The pose\n * itself is edited in the span editor (like zoom level), not by gesture. Any\n * gesture promotes the span to source:'manual' — it survives a Dynamic-tilt\n * regenerate (the auto-zoom wand contract).\n */\nexport const tiltLane: LaneAdapter<ProjectDoc> = {\n id: 'tilt',\n label: 'Tilt',\n\n items(doc): LaneItem[] {\n const segments = ratedSegments(doc)\n return (doc.tilt ?? []).flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: `${formatDeg(z.rx)}°/${formatDeg(z.ry)}°`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.tilt ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n const here = spans.filter((z) => (z.media ?? '') === mediaHere)\n const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source)\n if (here.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = here\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(mediaLen, next ? next.in : mediaLen)\n const len = Math.min(spanDefaultLen(mediaLen), limit - srcT)\n if (len < TILT_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextTiltId(doc)\n return (d) => {\n d.tilt = [\n ...(d.tilt ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n rx: DEFAULT_TILT_POSE.rx,\n ry: DEFAULT_TILT_POSE.ry,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = (d.tilt ?? []).find((x) => x.id === g.id)\n if (!z || !d.tilt) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.tilt.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n const minSrc = Math.min(\n TILT_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = (d.tilt ?? []).find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.tilt = (d.tilt ?? []).filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return tiltLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/** Degrees for clip labels: whole numbers stay whole (\"6\", not \"6.0\"). */\nfunction formatDeg(v: number): string {\n return Number.isInteger(v) ? String(v) : v.toFixed(1)\n}\n\n/**\n * Cam-move lane — animated cam layout regions as clips (label = the\n * pose size as a percent when set). SOURCE-anchored like tilt spans (the\n * full rated map applies); non-overlapping. The pose itself is edited on the\n * canvas or in the span editor, never by lane gesture. Structurally the tilt\n * lane with a different payload; kept separate so neither lane's clamps can\n * drift the other's.\n */\nexport const camMoveLane: LaneAdapter<ProjectDoc> = {\n id: 'camMove',\n label: 'Cam move',\n\n items(doc): LaneItem[] {\n if (!doc.source.camKey) return []\n const segments = ratedSegments(doc)\n return (doc.camMotion ?? []).flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: z.size != null ? `${Math.round(z.size * 100)}%` : 'Move',\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.camMotion ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n if (!doc.source.camKey) return null\n const srcT = mapTime(rated, Math.max(0, g.t))\n if (spans.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = spans\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(sourceDuration, next ? next.in : sourceDuration)\n const len = Math.min(spanDefaultLen(sourceDuration), limit - srcT)\n if (len < CAM_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextCamMoveId(doc)\n return (d) => {\n d.camMotion = [\n ...(d.camMotion ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n ...DEFAULT_CAM_POSE,\n source: 'manual' as const,\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = (d.camMotion ?? []).find((x) => x.id === g.id)\n if (!z || !d.camMotion) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.camMotion.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n const minSrc = Math.min(\n CAM_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = (d.camMotion ?? []).find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.camMotion = (d.camMotion ?? []).filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return camMoveLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Webcam lane — the Cam member row of the take group. Its clips are the\n * visibility window INTERSECTED with each kept segment, at the video lane's\n * exact output positions, so a split on the Video row visibly splits this row\n * too. Gestures still edit only the WINDOW (`cam.window`, SOURCE time,\n * footage-anchored): move slides it (dragging any of its clips moves the one\n * window), resize lives on the window's REAL edges — the first clip's start\n * and the last clip's end; the cut boundaries between them belong to the\n * Video row. There is no remove — hide the bubble via its panel.\n */\nexport const camLane: LaneAdapter<ProjectDoc> = {\n id: 'cam',\n label: 'Cam',\n\n items(doc): LaneItem[] {\n if (!doc.source.camKey || !doc.cam.visible) return []\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n const first = segments[0]\n const last = segments[segments.length - 1]\n const win = doc.cam.window ?? { in: first.in, out: last.out }\n const items: LaneItem[] = []\n segments.forEach((s, i) => {\n const a = Math.max(s.in, win.in)\n const b = Math.min(s.out, win.out)\n if (b - a <= 1e-6) return\n const leading = i === 0 || !contiguous(segments, i)\n items.push({\n id: `cam-${i}`,\n kind: 'clip',\n t: round(\n starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading),\n ),\n duration: round(\n outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading),\n ),\n })\n })\n return items\n },\n\n gesture(doc, g) {\n if (g.type !== 'move' && g.type !== 'resize') return null\n if (!g.id.startsWith('cam-')) return null\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n const current = doc.cam.window ?? { in: 0, out: sourceDuration }\n const eff = effectiveSegments(doc)\n const first = eff[0]\n const last = eff[eff.length - 1]\n if (g.type === 'move') {\n // Slide the whole window by the dragged clip's SOURCE delta — all the\n // row's clips are ONE window, so dragging any of them moves it.\n const index = Number(g.id.slice(4))\n const seg = eff.at(index)\n if (!seg) return null\n const clipSrcStart = Math.max(seg.in, Math.max(current.in, first.in))\n const span = Math.max(0.05, current.out - current.in)\n const delta = mapTime(rated, Math.max(0, g.t)) - clipSrcStart\n const base = Math.max(first.in, current.in)\n const newIn = Math.min(\n Math.max(first.in, base + delta),\n Math.max(first.in, last.out - span),\n )\n return (d) => {\n d.cam.window = { in: round(newIn), out: round(newIn + span) }\n }\n }\n // Only the window's REAL edges resize; interior edges are cut boundaries.\n // Derived from the window itself (not items(), which gates on camKey):\n // the first/last kept segment the window overlaps carry its edges.\n const overlapping = eff\n .map((s, i) => ({\n i,\n len: Math.min(s.out, current.out) - Math.max(s.in, current.in),\n }))\n .filter((x) => x.len > 1e-6)\n const edgeIdx =\n g.edge === 'start' ? overlapping.at(0)?.i : overlapping.at(-1)?.i\n if (edgeIdx === undefined || g.id !== `cam-${edgeIdx}`) return null\n const sourceT = Math.min(Math.max(mapTime(rated, g.t), 0), sourceDuration)\n const next =\n g.edge === 'start'\n ? { in: Math.min(sourceT, current.out - 0.05), out: current.out }\n : { in: current.in, out: Math.max(sourceT, current.in + 0.05) }\n return (d) => {\n d.cam.window = {\n in: round(Math.max(0, next.in)),\n out: round(Math.min(sourceDuration, next.out)),\n }\n }\n },\n\n magnets(doc): number[] {\n return camLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Mic sub-row of the take group: mirrors the video lane's cut boundaries\n * EXACTLY — one clip per kept segment at the same output positions — so the\n * voice visibly cuts and splits with the footage. VIEW-ONLY by design: cutting\n * happens on the Video row, because the take has ONE shared `segments` list\n * (that is what makes sub-track desync structurally impossible; per-row\n * segments would only buy bugs). Selecting a clip opens the Voice panel\n * (level/mute); the waveform is the row's content, so items carry no label.\n */\nexport const micLane: LaneAdapter<ProjectDoc> = {\n id: 'mic',\n label: 'Mic',\n\n items(doc): LaneItem[] {\n if (!voiceKey(doc)) return []\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n return segments.map((s, i) => ({\n id: `mic-${i}`,\n kind: 'clip',\n t: round(starts[i]),\n duration: round(\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n ),\n }))\n },\n\n gesture() {\n return null\n },\n\n magnets() {\n return []\n },\n}\n\n/** Default new-span length in SOURCE seconds (openscreen-style: ≥1s, ~5% of the take). */\nconst spanDefaultLen = (sourceDuration: number): number =>\n Math.max(1, sourceDuration * 0.05)\n\n/** Rate in force at a SOURCE moment (1 outside every rated piece). */\nfunction rateAt(rated: Segment[], srcT: number): number {\n for (const p of rated)\n if (srcT >= p.in - 1e-9 && srcT < p.out + 1e-9) return segmentRate(p)\n return 1\n}\n\n/**\n * Keep a moved span's START on KEPT footage (as corrected\n * 2026-08-24). The only thing this guards is the span vanishing: a\n * start inside removed footage renders nothing and looks deleted. It is NOT\n * a wall at the cut — a span is source-anchored and legitimately straddles a\n * cut, so a drag pushes it ACROSS the line continuously (the first cut of\n * this clamp held a span flush behind the cut until its whole length had\n * passed, which read as \"nothing can be dragged into the next clip\"). The\n * tail may extend into cut footage; the lane draws the kept part.\n */\nfunction clampToKept(kept: Segment[], newIn: number, len: number): number {\n void len\n let home: Segment | null = null\n let bestD = Infinity\n for (const s of kept) {\n const d = newIn < s.in ? s.in - newIn : newIn >= s.out ? newIn - s.out : 0\n if (d < bestD) {\n bestD = d\n home = s\n }\n }\n if (!home) return newIn\n const hi = Math.max(home.in, home.out - MIN_VISIBLE)\n return Math.min(Math.max(home.in, newIn), hi)\n}\n\n/** A span start must keep at least this much kept footage under it (s). */\nconst MIN_VISIBLE = 0.05\n\n/** New spans speed UP by default — the archetypal screen-recording edit. */\nconst DEFAULT_SPEED_RATE = 2\n\n/**\n * Speed lane — rate spans as clips (\"2×\"). Spans are SOURCE-anchored (footage\n * follows them through trims); the lane displays them at the output positions\n * of the rated pieces they produce, so a span visually contracts as its rate\n * grows. Spans never overlap: create no-ops inside an existing span, move\n * pushes out of collisions (or no-ops), resize clamps against neighbors.\n * The rate itself is edited in the toolbar (like zoom level), not by gesture.\n * Move/resize are POINTER-TRUE: the dragged edge's resulting output position\n * is exactly the pointer's (mapped through the rate map without this span),\n * so edges never lag the pointer at 1/rate speed.\n */\nexport const speedLane: LaneAdapter<ProjectDoc> = {\n id: 'speed',\n label: 'Speed',\n\n items(doc): LaneItem[] {\n const rated = ratedSegments(doc)\n return (doc.speed ?? []).flatMap((sp) => {\n // Output extent: accumulate the rated pieces this span produced\n // (containment match — spans don't overlap and pieces never cross span\n // boundaries). A span whose footage is fully cut away has no pieces and\n // renders nothing — it follows its footage, like zoom keyframes.\n let acc = 0\n let start: number | null = null\n let end = 0\n for (const p of rated) {\n const len = Math.max(0, p.out - p.in) / segmentRate(p)\n if (\n p.rate === sp.rate &&\n p.in >= sp.in - 1e-9 &&\n p.out <= sp.out + 1e-9\n ) {\n if (start === null) start = acc\n end = acc + len\n }\n acc += len\n }\n return start === null\n ? []\n : [\n {\n id: sp.id,\n kind: 'clip' as const,\n t: round(start),\n duration: round(end - start),\n label: `${sp.rate}×`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.speed ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n if (spans.some((s) => srcT >= s.in && srcT < s.out)) return null\n const next = spans\n .filter((s) => s.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(sourceDuration, next ? next.in : sourceDuration)\n const len = Math.min(spanDefaultLen(sourceDuration), limit - srcT)\n if (len < SPEED_SPAN_MIN * DEFAULT_SPEED_RATE) return null\n const id = nextSpeedId(doc)\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n return (d) => {\n d.speed = [\n ...(d.speed ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n rate: DEFAULT_SPEED_RATE,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((s) => s.id === g.id)\n if (!sp) return null\n\n // POINTER-TRUE mapping: move/resize evaluate output positions through the\n // rate map WITHOUT the edited span. Mapping through the full map would\n // re-rate the footage being dragged across mid-gesture, making the edge\n // chase the pointer at 1/rate speed (and the clip land short of the drop).\n // With the span excluded, the dragged edge's resulting output position is\n // exactly g.t — the edge stays under the pointer.\n const others = spans.filter((o) => o.id !== g.id)\n const base = splitBySpeed(effectiveSegments(doc), others)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(base, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const s = d.speed?.find((x) => x.id === g.id)\n if (!s) return\n s.in = round(newIn)\n s.out = round(newIn + len)\n s.source = 'manual'\n d.speed!.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const prev = others.filter((o) => o.out <= sp.in).map((o) => o.out)\n const nextIn = others.filter((o) => o.in >= sp.out).map((o) => o.in)\n const lo = Math.max(0, ...prev)\n const hi = Math.min(sourceDuration, ...nextIn)\n // The floor is OUTPUT seconds through the span's OWN rate: a 2× span\n // may not shrink below 0.5s of source (= 0.25s of screen). The old bare\n // `0.1` was a SOURCE floor — at 5× that was 20ms of screen, the sliver.\n const minSrc = Math.min(SPEED_SPAN_MIN * sp.rate, sp.out - sp.in)\n let next: { in: number; out: number }\n if (g.edge === 'start') {\n // Footage before the new in-point is unaffected by this span, so its\n // output position IS mapTime(base, g.t) — pointer-true directly.\n const sourceT = mapTime(base, Math.max(0, g.t))\n next = {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n } else {\n // Place the new out-point so the span's output END lands at g.t:\n // the span occupies (out - in) / rate output seconds after its start.\n const startOut = sourceToTimeline(base, sp.in) ?? sp.in\n const sourceT = sp.in + sp.rate * Math.max(0, g.t - startOut)\n next = {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n }\n return (d) => {\n const s = d.speed?.find((x) => x.id === g.id)\n if (!s) return\n s.in = round(next.in)\n s.out = round(next.out)\n s.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.speed = (d.speed ?? []).filter((s) => s.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return speedLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Music/SFX lane — clips are OUTPUT-anchored (`clip.start` is final-cut\n * seconds; they do NOT follow footage through trims — see AudioClip). Move\n * retimes `start`; resizing trims into the source file: the start edge shifts\n * `in` and `start` together (content stays put under the untouched edge), the\n * end edge adjusts `out`. Clips are created from the audio inspector, not by\n * double-click (there is no meaningful \"blank\" audio clip).\n */\nexport const audioLane: LaneAdapter<ProjectDoc> = {\n id: 'audio',\n label: 'Audio',\n\n items(doc): LaneItem[] {\n return doc.audio.map((c) => ({\n id: c.id,\n kind: 'clip',\n t: round(c.start),\n duration: round(clipLength(c)),\n label: c.name,\n }))\n },\n\n gesture(doc, g) {\n if (g.type === 'move') {\n const clip = doc.audio.find((c) => c.id === g.id)\n if (!clip) return null\n const start = Math.max(0, g.t)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.start = round(start)\n }\n }\n if (g.type === 'resize') {\n const clip = doc.audio.find((c) => c.id === g.id)\n if (!clip) return null\n if (g.edge === 'start') {\n if (clip.loop) {\n // Looping head-trim: keep the END fixed, shrink/grow the placed length\n // (the loop phase, not the source in-point, is what the edge drags).\n const end = clip.start + clipLength(clip)\n const newStart = Math.min(Math.max(0, g.t), end - 0.1)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (!c) return\n c.start = round(newStart)\n c.loopLen = round(end - newStart)\n }\n }\n // Trim the head: consume/restore source material while the tail stays put.\n const delta = g.t - clip.start\n const newIn = Math.min(Math.max(0, clip.in + delta), clip.out - 0.05)\n const newStart = Math.max(0, clip.start + (newIn - clip.in))\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (!c) return\n c.in = round(newIn)\n c.start = round(newStart)\n }\n }\n if (clip.loop) {\n // Looping end-trim: the placed length is unbounded — the span repeats.\n const newLen = Math.max(0.1, g.t - clip.start)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.loopLen = round(newLen)\n }\n }\n const span = Math.max(0.05, g.t - clip.start)\n const newOut = Math.min(clip.in + span, clip.duration)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.out = round(Math.max(c.in + 0.05, newOut))\n }\n }\n if (g.type === 'remove') {\n if (!doc.audio.some((c) => c.id === g.id)) return null\n return (d) => {\n d.audio = d.audio.filter((c) => c.id !== g.id)\n }\n }\n return null\n },\n\n magnets(doc): number[] {\n return doc.audio.flatMap((c) => [\n round(c.start),\n round(c.start + clipLength(c)),\n ])\n },\n}\n\n/** Smallest unused overlay id. */\nfunction nextOverlayId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.overlays ?? []).some((o) => o.id === `t${n}`)) n++\n return `t${n}`\n}\n\n/**\n * Pose-diamond item ids: `{clipId}::k{index}` — a keyframe LaneItem\n * riding its clip's lane. Index-addressed into the clip's UNSORTED `motion`\n * array (writes never reorder it; the lowering sorts), so the id stays stable\n * through a drag that crosses a sibling pose.\n */\nconst POSE_ID = /^(.+)::k(\\d+)$/\nexport function parsePoseId(\n id: string,\n): { clipId: string; index: number } | null {\n const m = POSE_ID.exec(id)\n return m ? { clipId: m[1], index: Number(m[2]) } : null\n}\n\n/** Diamond items for a clip's poses (clip-local `at` → absolute lane time). */\nfunction poseItems(\n clipId: string,\n start: number,\n duration: number,\n motion: readonly { at: number }[] | undefined,\n): LaneItem[] {\n return (motion ?? []).map((p, i) => ({\n id: `${clipId}::k${i}`,\n kind: 'keyframe' as const,\n t: round(start + Math.min(Math.max(0, p.at), duration)),\n }))\n}\n\n/**\n * Text-overlay lane (compositor v2) — clips are OUTPUT-anchored like audio\n * (`start` is final-cut seconds; a title never retimes with trims/speed).\n * Overlaps are allowed (two titles can coexist — z-order is array order).\n * Create adds a house 'title' clip at the playhead, centered, lower-third.\n */\nexport const overlaysLane: LaneAdapter<ProjectDoc> = {\n id: 'overlays',\n label: 'Text',\n\n items(doc): LaneItem[] {\n return (doc.overlays ?? []).flatMap((o) => [\n {\n id: o.id,\n kind: 'clip' as const,\n t: round(o.start),\n duration: round(o.duration),\n label:\n o.kind === 'text'\n ? o.text.split('\\n')[0].slice(0, 24) || 'Text'\n : o.kind === 'html'\n ? htmlLayerLabel(o)\n : o.kind === 'image'\n ? 'Image'\n : 'Video',\n },\n // Pose diamonds render after the clips, so they sit on top.\n ...poseItems(o.id, o.start, o.duration, o.motion),\n ])\n },\n\n gesture(doc, g) {\n const overlays = doc.overlays ?? []\n\n // Pose diamonds: retime within the clip, or remove. The diamond's\n // absolute lane time maps back to clip-local `at`.\n if (g.type !== 'create') {\n const kf = parsePoseId(g.id)\n if (kf) {\n const clip = overlays.find((o) => o.id === kf.clipId)\n const pose = clip?.motion?.[kf.index]\n if (!clip || !pose) return null\n if (g.type === 'move') {\n const at = Math.min(Math.max(0, g.t - clip.start), clip.duration)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === kf.clipId)\n const p = o?.motion?.[kf.index]\n if (p) p.at = round(at)\n }\n }\n if (g.type === 'remove') {\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === kf.clipId)\n if (!o?.motion) return\n const next = o.motion.filter((_, i) => i !== kf.index)\n if (next.length) o.motion = next\n else delete o.motion\n }\n }\n return null\n }\n }\n\n if (g.type === 'create') {\n // Either document's output length: overlays ride the program\n // anchor too, whose length is not a footage sum.\n const outDur = docOutputDuration(doc)\n const start = Math.min(\n Math.max(0, g.t),\n Math.max(0, outDur - OVERLAY_MIN_DURATION),\n )\n const len = Math.max(OVERLAY_MIN_DURATION, Math.min(3, outDur - start))\n const id = nextOverlayId(doc)\n return (d) => {\n d.overlays = [\n ...(d.overlays ?? []),\n {\n id,\n kind: 'text' as const,\n start: round(start),\n duration: round(len),\n text: 'Title',\n preset: 'title' as const,\n // Frame fractions: centered, lower-third — at any aspect ratio.\n transform: { x: 0.5, y: 0.82, scale: 1, rotation: 0 },\n },\n ]\n }\n }\n\n const clip = overlays.find((o) => o.id === g.id)\n if (!clip) return null\n\n if (g.type === 'move') {\n const start = Math.max(0, g.t)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (o) o.start = round(start)\n }\n }\n if (g.type === 'resize') {\n if (g.edge === 'start') {\n // Keep the END fixed; the head drags start + duration together.\n const end = clip.start + clip.duration\n const newStart = Math.min(Math.max(0, g.t), end - OVERLAY_MIN_DURATION)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (!o) return\n o.start = round(newStart)\n o.duration = round(end - newStart)\n }\n }\n const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.start)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (o) o.duration = round(newLen)\n }\n }\n // Only 'remove' remains (the gesture union is exhausted above).\n return (d) => {\n d.overlays = (d.overlays ?? []).filter((o) => o.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return (doc.overlays ?? []).flatMap((o) => [\n round(o.start),\n round(o.start + o.duration),\n ])\n },\n}\n\n/**\n * Object lane — world-space props. Clips show the span (objects with no\n * span render as a full-length block and don't move — the span IS the lane's\n * noun). Created from the toolbar; asset/transform edited in the inspector.\n */\nexport const objectsLane: LaneAdapter<ProjectDoc> = {\n id: 'objects',\n label: '3D',\n\n items(doc): LaneItem[] {\n const outDur = docOutputDuration(doc)\n return (doc.objects ?? []).flatMap((o) => [\n {\n id: o.id,\n kind: 'clip' as const,\n t: round(o.span?.start ?? 0),\n duration: round(o.span?.duration ?? outDur),\n label:\n o.asset.kind === 'primitive'\n ? o.asset.shape\n : o.asset.kind === 'text3d'\n ? o.asset.text\n : 'model',\n },\n // Pose diamonds: clip-local `at` from the span start (0 span-less).\n ...poseItems(\n o.id,\n o.span?.start ?? 0,\n o.span?.duration ?? outDur,\n o.motion,\n ),\n ])\n },\n\n gesture(doc, g) {\n const objects = doc.objects ?? []\n if (g.type === 'create') return null // toolbar-created (needs asset choice)\n\n // Pose diamonds: retime within the clip, or remove.\n const kf = parsePoseId(g.id)\n if (kf) {\n const outDur = docOutputDuration(doc)\n const clip = objects.find((o) => o.id === kf.clipId)\n const pose = clip?.motion?.[kf.index]\n if (!clip || !pose) return null\n const start = clip.span?.start ?? 0\n const dur = clip.span?.duration ?? outDur\n if (g.type === 'move') {\n const at = Math.min(Math.max(0, g.t - start), dur)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === kf.clipId)\n const p = o?.motion?.[kf.index]\n if (p) p.at = round(at)\n }\n }\n if (g.type === 'remove') {\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === kf.clipId)\n if (!o?.motion) return\n const next = o.motion.filter((_, i) => i !== kf.index)\n if (next.length) o.motion = next\n else delete o.motion\n }\n }\n return null\n }\n\n const clip = objects.find((o) => o.id === g.id)\n if (!clip) return null\n if (g.type === 'move') {\n if (!clip.span) return null\n const start = Math.max(0, g.t)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (o?.span) o.span.start = round(start)\n }\n }\n if (g.type === 'resize') {\n if (!clip.span) return null\n if (g.edge === 'start') {\n const end = clip.span.start + clip.span.duration\n const newStart = Math.min(Math.max(0, g.t), end - OVERLAY_MIN_DURATION)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (!o?.span) return\n o.span.start = round(newStart)\n o.span.duration = round(end - newStart)\n }\n }\n const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.span.start)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (o?.span) o.span.duration = round(newLen)\n }\n }\n // remove\n return (d) => {\n d.objects = (d.objects ?? []).filter((o) => o.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return (doc.objects ?? []).flatMap((o) =>\n o.span\n ? [round(o.span.start), round(o.span.start + o.span.duration)]\n : [],\n )\n },\n}\n\n/** Smallest unused user-span id (planner spans are `z{n}`). */\nfunction nextZoomId(doc: ProjectDoc): string {\n let n = 0\n while (doc.zoom.some((z) => z.id === `u${n}`)) n++\n return `u${n}`\n}\n\n/** Smallest unused user tilt-span id (Dynamic-tilt wand spans are `t{n}`). */\nfunction nextTiltId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.tilt ?? []).some((z) => z.id === `u${n}`)) n++\n return `u${n}`\n}\n\n/** Smallest unused cam-move span id (`m{n}` — reserved `auto` never collides). */\nfunction nextCamMoveId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.camMotion ?? []).some((z) => z.id === `m${n}`)) n++\n return `m${n}`\n}\n\nfunction nextSpeedId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.speed ?? []).some((s) => s.id === `sp${n}`)) n++\n return `sp${n}`\n}\n\nfunction segIndex(id: string): number {\n return Number(id.replace('seg-', ''))\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Waveform peaks — pure downsampling for timeline clip rendering. The host\n * decodes the file (Web Audio) and hands channel data here; the result is one\n * max-|sample| value per bucket in [0..1], drawn as symmetric bars.\n */\nexport function computePeaks(\n channels: Float32Array[],\n buckets: number,\n): Float32Array {\n const peaks = new Float32Array(Math.max(1, buckets))\n if (!channels.length || !channels[0].length) return peaks\n const length = channels[0].length\n const perBucket = length / peaks.length\n for (let b = 0; b < peaks.length; b++) {\n const from = Math.floor(b * perBucket)\n const to = Math.min(\n length,\n Math.max(from + 1, Math.floor((b + 1) * perBucket)),\n )\n let peak = 0\n for (const ch of channels) {\n for (let i = from; i < to; i++) {\n const v = Math.abs(ch[i])\n if (v > peak) peak = v\n }\n }\n peaks[b] = Math.min(1, peak)\n }\n return peaks\n}\n","/**\n * Auto-ducking — lower music under speech. Pure math over a precomputed mic\n * loudness envelope, so both consumers apply identical values:\n *\n * host (async): decode mic → `micRms` (SOURCE-time loudness grid, cached)\n * host (sync): `duckCurve(rms, segments, duration)` → OUTPUT-time gain\n * points, merged into ctx.data as `duckEnv`\n * preview: per ducked clip, a second GainNode applies the points\n * export: same points in the OfflineAudioContext mix\n */\nimport { mapTime } from '@vosjs/timeline'\nimport type { Segment } from '@vosjs/timeline'\nimport type { EnvelopePoint } from './audioEnvelope'\n\n/** SOURCE-time loudness grid (RMS per window). */\nexport interface MicRms {\n /** RMS value per window, linear 0..1. */\n values: Float32Array\n /** windows per second. */\n rate: number\n}\n\nexport interface DuckOptions {\n /** RMS above this counts as speech. */\n threshold: number\n /** gain while ducked (≈ -12 dB). */\n duckTo: number\n /** seconds to reach the ducked level once speech starts. */\n attack: number\n /** seconds to recover after speech stops. */\n release: number\n /** output grid resolution, points per second. */\n gridHz: number\n}\n\nexport const DEFAULT_DUCK: DuckOptions = {\n threshold: 0.02,\n duckTo: 0.25,\n attack: 0.2,\n release: 0.5,\n gridHz: 20,\n}\n\n/** RMS windows from raw PCM — the host runs this once per recording (cached). */\nexport function computeMicRms(\n channels: Float32Array[],\n sampleRate: number,\n windowSec = 0.05,\n): MicRms {\n const rate = 1 / windowSec\n if (!channels.length || !channels[0].length)\n return { values: new Float32Array(0), rate }\n const length = channels[0].length\n const perWindow = Math.max(1, Math.round(sampleRate * windowSec))\n const windows = Math.ceil(length / perWindow)\n const values = new Float32Array(windows)\n for (let w = 0; w < windows; w++) {\n const from = w * perWindow\n const to = Math.min(length, from + perWindow)\n let sum = 0\n for (const ch of channels) {\n for (let i = from; i < to; i++) sum += ch[i] * ch[i]\n }\n values[w] = Math.sqrt(sum / Math.max(1, (to - from) * channels.length))\n }\n return { values, rate }\n}\n\n/**\n * The OUTPUT-time duck multiplier curve: walk an output grid, look up the mic\n * loudness at the mapped SOURCE moment, and smooth engage/recover with\n * attack/release one-poles. Points are thinned (emitted on ≥1% change).\n */\nexport function duckCurve(\n rms: MicRms,\n segments: Segment[],\n durationSec: number,\n opts: DuckOptions = DEFAULT_DUCK,\n): EnvelopePoint[] {\n if (!rms.values.length || durationSec <= 0) return []\n const dt = 1 / opts.gridHz\n const points: EnvelopePoint[] = []\n let g = 1\n let lastEmitted = Number.NaN\n const steps = Math.ceil(durationSec * opts.gridHz)\n for (let i = 0; i <= steps; i++) {\n const t = Math.min(durationSec, i * dt)\n const srcT = mapTime(segments, t)\n const w = Math.min(\n rms.values.length - 1,\n Math.max(0, Math.floor(srcT * rms.rate)),\n )\n const speech = rms.values[w] > opts.threshold\n const target = speech ? opts.duckTo : 1\n const tau = target < g ? opts.attack : opts.release\n g += (target - g) * Math.min(1, dt / Math.max(1e-3, tau))\n if (\n Number.isNaN(lastEmitted) ||\n Math.abs(g - lastEmitted) >= 0.01 ||\n i === steps\n ) {\n points.push({\n t: Math.round(t * 1000) / 1000,\n g: Math.round(g * 1000) / 1000,\n })\n lastEmitted = g\n }\n }\n return points\n}\n","/**\n * A backdrop is the loop (or still) a frame opens on, together with its\n * GROUND: the loop's average colour, written into `frame.background` so\n * the frame before the first decoded frame, the reduced-motion still and\n * the offline fail-open all land on the loop's own colour instead of\n * whatever the frame carried before.\n *\n * WHICH backdrop a new take opens on is the host's decision, never this\n * package's: the studio hands its pick to `projectFromArtifact` as `frame`,\n * the CLI reads the set the platform publishes. The document model carries\n * the mechanism only, so the default here is a frame with no backdrop.\n * A doc that already carries a frame is never touched either way.\n */\nimport type { BackgroundMedia, FrameStyle } from './types'\n\nexport interface Backdrop {\n /** The loop's URL, or a take-dir path (`/bg.webm`). */\n key: string\n /** `video` unless said otherwise. */\n kind?: BackgroundMedia['kind']\n /** The loop's period in seconds, a fact of the asset. Video only. */\n duration?: number\n /** A still to show before the first decoded frame and under reduced motion. */\n poster?: string\n /**\n * The loop's average colour. Written as `frame.background` when given; a\n * backdrop without one keeps the frame's own fill under it.\n */\n ground?: string\n}\n\n/** A backdrop as a doc's `frame.backgroundMedia`. */\nexport function backdropMedia(backdrop: Backdrop): BackgroundMedia {\n return {\n kind: backdrop.kind ?? 'video',\n key: backdrop.key,\n ...(backdrop.duration !== undefined ? { duration: backdrop.duration } : {}),\n ...(backdrop.poster ? { poster: backdrop.poster } : {}),\n dim: 0,\n }\n}\n\n/**\n * A frame opening on a backdrop: the media, and its ground when it has one,\n * in one write. Everything else on the frame is kept.\n */\nexport function withBackdrop(\n frame: FrameStyle,\n backdrop: Backdrop,\n): FrameStyle {\n return {\n ...frame,\n ...(backdrop.ground ? { background: backdrop.ground } : {}),\n backgroundMedia: backdropMedia(backdrop),\n }\n}\n","/**\n * Rejected proposals: the document's way to say \"not this one\".\n *\n * A planner proposal (an `auto` zoom, tilt or speed span) that the human or\n * the agent deleted is not data by itself: the doc records what was written,\n * not what was removed, so the next re-plan (a style pick, `vos plan`, a\n * re-record carried by `plan --reuse`) proposed it again and the deletion\n * had to be repeated by hand. `doc.rejected` keeps the deletion as a span\n * of its own — the lane and the source extent, plus the step anchor when\n * the span had one — and every auto-merge drops a fresh proposal that lands\n * on it. Manual spans never need this: a re-plan keeps them by contract.\n *\n * Matching is by extent, never by id: planner ids are positional and a\n * re-record moves every beat by tens of milliseconds, so a proposal is\n * rejected when it overlaps a rejected extent on the same lane by at least\n * REJECT_OVERLAP of the shorter of the two.\n */\nimport type { RejectedLane, RejectedSpan, StepAnchor } from './types'\n\n/** Fraction of the shorter extent two spans must share to be \"the same beat\". */\nexport const REJECT_OVERLAP = 0.5\n\ninterface Extent {\n in: number\n out: number\n}\n\n/** Shared length over the shorter length; 0 when apart or degenerate. */\nexport function overlapFraction(a: Extent, b: Extent): number {\n const shared = Math.min(a.out, b.out) - Math.max(a.in, b.in)\n if (shared <= 0) return 0\n const shorter = Math.min(a.out - a.in, b.out - b.in)\n return shorter > 0 ? shared / shorter : 0\n}\n\n/** Whether a proposal on this lane lands on a rejected extent. */\nexport function isRejected(\n lane: RejectedLane,\n span: Extent,\n rejected: RejectedSpan[] | undefined,\n): boolean {\n if (!rejected?.length) return false\n return rejected.some(\n (r) => r.lane === lane && overlapFraction(span, r) >= REJECT_OVERLAP,\n )\n}\n\n/** The proposals that survive the rejected list, in their given order. */\nexport function withoutRejected<T extends Extent>(\n lane: RejectedLane,\n spans: T[],\n rejected: RejectedSpan[] | undefined,\n): T[] {\n if (!rejected?.length) return spans\n return spans.filter((s) => !isRejected(lane, s, rejected))\n}\n\n/**\n * The entry a deleted auto span leaves behind. Ids are `r{n}` over the\n * existing list so a differ and a history can name the rejection; the\n * anchor rides along when the span had one, so a re-record re-times the\n * rejection exactly the way it re-times the span it replaced.\n */\nexport function rejectSpan(\n rejected: RejectedSpan[] | undefined,\n lane: RejectedLane,\n span: Extent & { anchor?: StepAnchor },\n note?: string,\n): RejectedSpan {\n const taken = new Set((rejected ?? []).map((r) => r.id))\n let n = 0\n while (taken.has(`r${n}`)) n++\n const entry: RejectedSpan = {\n id: `r${n}`,\n lane,\n in: +span.in.toFixed(3),\n out: +span.out.toFixed(3),\n }\n if (span.anchor) entry.anchor = { ...span.anchor }\n if (note) entry.note = note\n return entry\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAqHJ,kBAAkB;AAAA;AAAA;AAAA,sDAGC,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4EjE,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoDvB,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAyJb,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBA+DpB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAQE,kBAAkB;AAAA;AAAA;AAAA;AAAA,mEAIY,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCA+L9C,KAAK,UAAU,kBAAkB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAoEvC,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BA4K1B,QAAQ;AAAA,gCACH,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BA6BR,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAwJf,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAuBb,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAiBC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2CAuDA,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6EAsHiB,MAAM;AAAA;AAAA;AAAA,wCAG3C,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC97C9C,SAAS,mBAAmB;AAMrB,IAAM,eAAe;AAErB,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,uBAAuB;AAE7B,IAAM,sBAAsB;AAE5B,IAAM,iBAAiB;AAEvB,IAAM,sBAAsB;AAEnC,IAAM,sBAAsB;AAqC5B,SAAS,SACP,UACA,SACA,OACe;AACf,MAAI,CAAC,SAAS,OAAQ,QAAO;AAC7B,MAAI,MAAM;AACV,aAAW,KAAK,UAAU;AACxB,UAAM,OAAO,YAAY,CAAC;AAC1B,QAAI,UAAU,EAAE,OAAO,KAAK,KAAK,WAAW,EAAE,MAAM,WAAW,EAAE;AAC/D,aAAO,OAAO,UAAU,EAAE,MAAM;AAClC,WAAO,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI;AAAA,EACrC;AACA,SAAO;AACT;AAEA,SAAS,cACP,UACA,KACA,MACA,OACe;AACf,MAAI,MAAM;AACV,MAAI,MAAqB;AACzB,aAAW,KAAK,UAAU;AACxB,UAAM,OAAO,YAAY,CAAC;AAC1B,QAAI,UAAU,EAAE,OAAO,KAAK,GAAG;AAC7B,YAAM,OAAO,KAAK,IAAI,KAAK,EAAE,EAAE;AAC/B,YAAM,QAAQ,KAAK,IAAI,MAAM,EAAE,GAAG;AAClC,UAAI,QAAQ,KAAM,OAAM,OAAO,QAAQ,EAAE,MAAM;AAAA,IACjD;AACA,WAAO,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI;AAAA,EACrC;AACA,SAAO;AACT;AAEA,SAAS,UACP,MACA,GACA,GACA,OACyC;AACzC,QAAM,OAAO,KAAK,IAAI,KAAK;AAC3B,QAAM,QAAQ,MAAM,IAAI,MAAM;AAC9B,MAAI,EAAE,OAAO,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ;AAChD,WAAO;AACT,QAAM,IAAI;AACV,QAAM,SACJ,KAAK,KAAK,IAAI,KACd,KAAK,KAAK,IAAI,KAAK,IAAI,KACvB,KAAK,KAAK,IAAI,KACd,KAAK,KAAK,IAAI,KAAK,IAAI;AACzB,SAAO,SACH,CAACC,OAAM,KAAK,CAAC,GAAGA,OAAM,KAAK,CAAC,GAAGA,OAAM,KAAK,CAAC,GAAGA,OAAM,KAAK,CAAC,CAAC,IAC3D;AACN;AAQO,SAAS,cACd,OACA,UACA,MACgB;AAChB,QAAM,MAAsB,CAAC;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,MAAM,CAAC;AACjB,QAAI,EAAE,SAAS,OAAQ;AACvB,UAAM,SAAS,EAAE,UAAU;AAC3B,UAAM,KAAK,EAAE,IAAI;AAGjB,QAAI,WAAW;AACf,aAAS,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACzC,YAAM,IAAI,MAAM,CAAC;AACjB,WAAK,EAAE,UAAU,OAAO,OAAQ;AAChC,UAAI,EAAE,SAAS,OAAQ;AACvB,UAAI,EAAE,SAAS,MAAM;AACnB,cAAM,OAAO,EAAE,IAAI,EAAE,KAAK;AAC1B,YAAI,MAAM,KAAK,OAAO,eAAgB,YAAW;AACjD;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,SAAS,UAAU,IAAI,KAAK,KAAK;AAC5C,QAAI,OAAO,KAAM;AACjB,UAAM,QAAQ,KAAK,KAAK,IAAI,UAAU,IAAI;AAC1C,UAAM,KACJ,cAAc,UAAU,IAAI,OAAO,KAAK,KAAK,KAAK,KAAK;AAEzD,UAAM,QAAsB;AAAA,MAC1B,IAAIA,OAAM,EAAE;AAAA,MACZ,IAAIA,OAAM,KAAK,IAAI,IAAI,KAAK,IAAI,CAAC;AAAA,MACjC,IAAIA,OAAM,EAAE;AAAA,MACZ,GAAGA,OAAM,EAAE,CAAC;AAAA,MACZ,GAAGA,OAAM,EAAE,CAAC;AAAA,MACZ,GAAG;AAAA,IACL;AACA,QAAI,KAAK,SAAS,EAAE,MAAM;AACxB,YAAM,IAAI,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,KAAK;AAChD,UAAI,EAAG,OAAM,IAAI;AAAA,IACnB;AACA,QAAI,KAAK,KAAK;AAAA,EAChB;AACA,SAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;AACvC;AAGO,SAAS,gBAAgB,KAA8C;AAC5E,QAAM,IAAI,iCAAiC,KAAK,IAAI,KAAK,CAAC;AAC1D,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,IAAI,EAAE,CAAC;AACX,MAAI,EAAE,WAAW,EAAG,KAAI,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;AAC9D,QAAM,IAAI,SAAS,GAAG,EAAE;AACxB,SAAO,CAAC,KAAK,IAAK,KAAK,IAAK,KAAK,IAAI,GAAG;AAC1C;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAI,IAAI;AAChC;;;AbPO,IAAM,eAAe,YAAY,kBAAkB,EAAE;AACrD,IAAM,uBACX,YAAY,kBAAkB,EAAE;AAC3B,IAAM,gBAAgB,YAAY,kBAAkB,EAAE;AAEtD,IAAM,iBAAiB,YAAY,kBAAkB,EAAE;AAEvD,IAAM,WAAW,YAAY,kBAAkB,EAAE;AACjD,IAAM,YAAY,YAAY,kBAAkB,EAAE;AAClD,IAAM,gBAAgB,YAAY,kBAAkB,EAAE;AAYtD,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAEtB,IAAM,iBAAiB;AAEvB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,gBAAgB;AAWtB,IAAM,cAAc;AACpB,IAAM,eAAe;AAErB,IAAM,gBAAgB;AAEtB,IAAM,UAAU;AAChB,IAAM,WAAW;AACjB,IAAM,eAAe;AAM5B,IAAM,mBAAmB;AAQlB,SAAS,cAAc,KAA2B;AAGvD,QAAM,OAAO,eAAe,GAAG,IAC3B,IAAI,SAAS,SACX,IAAI,WACJ,CAAC,EAAE,IAAI,GAAG,KAAK,IAAI,OAAO,KAAK,aAAa,IAAK,CAAC,IACpD,CAAC,EAAE,IAAI,GAAG,KAAK,gBAAgB,GAAG,EAAE,CAAC;AAKzC,QAAM,UAAU,eAAe,GAAG,IAAI,WAAW,GAAG,IAAI,CAAC;AACzD,QAAM,SAAS,IAAI,SAAS,CAAC;AAC7B,QAAM,SAAS,KAAK;AAAA,IAAQ,CAAC,QAC3BC;AAAA,MACE,CAAC,GAAG;AAAA,MACJ,OAAO;AAAA,QAAO,CAAC,OACb,UAAU,GAAG,OAAQ,IAA2B,KAAK;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACA,SAAO,YAAY,QAAQ,OAAO;AACpC;AAEA,SAAS,YAAY,KAAiB,OAA0B;AAC9D,QAAM,UAAUC,eAAc,KAAK;AACnC,SAAO,UAAU,IAAI,UAAU,IAAI,OAAO,KAAK,aAAa;AAC9D;AASO,SAAS,iBACd,UACA,KACA,MACA,OACuC;AACvC,MAAI,MAAM;AACV,MAAI,QAAuB;AAC3B,MAAI,MAAqB;AACzB,aAAW,KAAK,UAAU;AACxB,UAAM,OAAOC,aAAY,CAAC;AAC1B,UAAM,MAAM,KAAK,IAAI,GAAG,EAAE,MAAM,EAAE,EAAE,IAAI;AAGxC,QAAI,CAAC,UAAU,EAAE,OAAO,KAAK,GAAG;AAC9B,aAAO;AACP;AAAA,IACF;AACA,UAAM,OAAO,KAAK,IAAI,KAAK,EAAE,EAAE;AAC/B,UAAM,QAAQ,KAAK,IAAI,MAAM,EAAE,GAAG;AAClC,QAAI,QAAQ,MAAM;AAChB,UAAI,UAAU,KAAM,SAAQ,OAAO,OAAO,EAAE,MAAM;AAClD,YAAM,OAAO,QAAQ,EAAE,MAAM;AAAA,IAC/B;AACA,WAAO;AAAA,EACT;AACA,SAAO,UAAU,QAAQ,QAAQ,QAAQ,MAAM,QAAQ,EAAE,OAAO,IAAI,IAAI;AAC1E;AAGA,SAAS,SACP,MACA,UAC+B;AAC/B,SAAQ,QAAQ,QAAQ,UAAU,OAAO;AAG3C;AAUO,IAAM,aAAa;AAc1B,SAAS,YACP,OACA,aACA,UACA,SACQ;AACR,MAAI,KAAK,IAAI,QAAQ,WAAW,IAAI,KAAM,QAAO,QAAQ;AACzD,QAAM,aAAa,cAAc;AACjC,QAAM,WAAW,KAAK,IAAI,QAAQ,UAAU,OAAO;AACnD,SAAO,KAAK,IAAI,QAAQ,YAAY,YAAY,QAAQ;AAC1D;AASA,SAAS,eAOP;AACA,QAAM,YAAkC,CAAC;AACzC,QAAMC,QAAO,CACX,GACA,OACA,SACW;AACX,UAAM,OAAO,UAAU,GAAG,EAAE;AAC5B,QAAI,KAAK,KAAK,IAAI,GAAG,CAAC;AACtB,QAAI,MAAM;AACR,UAAI,MAAM,KAAK,IAAI,QAAQ,QAAQ,KAAK,OAAO,KAAK,EAAG,QAAO,KAAK;AACnE,UAAI,MAAM,KAAK,IAAI,KAAM,MAAK,KAAK,IAAI;AAAA,IACzC;AACA,cAAU,KAAK,EAAE,GAAGC,OAAM,EAAE,GAAG,OAAO,MAAM,IAAIA,MAAK,GAAG,KAAK,CAAC;AAC9D,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,MAAAD,MAAK;AAC3B;AAoBO,SAAS,iBACd,MACA,UACA,QAAyB,YAAY,kBAAkB,GAC9B;AACzB,QAAM,UAAU,MAAM;AACtB,QAAM,SAAS,CAAC,GAAG,IAAI,EACpB,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,EAC1B,QAAQ,CAAC,MAAM;AACd,UAAM,MAAM,iBAAiB,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;AAC3D,WAAO,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;AAAA,EACzD,CAAC;AAGH,QAAM,EAAE,WAAW,MAAAA,MAAK,IAAI,aAAa;AAEzC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,EAAE,GAAG,KAAK,KAAK,IAAI,OAAO,CAAC;AAQjC,UAAM,QAAQ,CAAC,eAAe,EAAE,KAAK,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AAErD,QAAI,MAAM;AAGV,UAAM,IAAI,eAAe,EAAE,UAAU;AAErC,QAAI,CAAC,SAAS;AAGZ,YAAM,cAAc,OAAO,MAAM,SAAS,MAAM,iBAAiB;AACjE,YAAM,QAAQA,MAAK,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM;AAC1D,MAAAA;AAAA,QACE,YAAY,OAAO,aAAa,MAAM,SAAS,GAAG,IAAI;AAAA,QACtD;AAAA,QACA,SAAS,EAAE,MAAM,MAAM,IAAI;AAAA,MAC7B;AAAA,IACF;AAKA,QAAI,SAAS;AACb,eAAW,KAAK,EAAE,gBAAgB,CAAC,GAAG;AACpC,YAAM,OAAO,iBAAiB,UAAU,EAAE,CAAC;AAC3C,UAAI,SAAS,QAAQ,QAAQ,OAAO,QAAQ,KAAM;AAIlD,UAAI,OAAO,MAAM,cAAc,OAAO,OAAO,WAAY;AACzD,YAAME,QAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AACnC,UAAI,EAAE,MAAM;AAKV,YAAI,CAAC,QAAQ;AACX,UAAAF,MAAK,KAAK,IAAI,KAAK,OAAO,MAAM,cAAc,GAAG,KAAK,MAAM;AAC5D,UAAAA,MAAK,MAAME,OAAM,OAAO;AACxB,mBAAS;AAAA,QACX,OAAO;AACL,UAAAF,MAAK,MAAME,OAAM,QAAQ;AAAA,QAC3B;AAAA,MACF,OAAO;AACL,iBAAS;AACT,QAAAF,MAAK,MAAM,KAAK,MAAM;AACtB,QAAAA,MAAK,KAAK,IAAI,OAAO,MAAM,gBAAgB,IAAI,GAAGE,OAAM,OAAO;AAAA,MACjE;AACA,YAAMA;AAAA,IACR;AAGA,IAAAF,MAAK,MAAM,KAAK,MAAM;AAEtB,UAAM,OAAO,OAAO,GAAG,IAAI,CAAC;AAC5B,QAAI,QAAQ,KAAK,MAAM,QAAQ,MAAM,UAAU;AAI7C,YAAM,QAAQ,eAAe,KAAK,EAAE,UAAU;AAC9C,YAAM,YAAY,CAAC,eAAe,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,IAAI,KAAK,EAAE,IAAI,CAAC;AACxE,MAAAA;AAAA,QACE,KAAK;AAAA,UACH,OAAO;AAAA,UACP,KAAK;AAAA,YACH,OAAO,MAAM,MAAM;AAAA,YACnB,KAAK,MAAM,MAAM,gBAAgB;AAAA,UACnC;AAAA,QACF;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,OAAO;AAGL,MAAAA;AAAA,QACE,OAAO,MAAM,UAAU;AAAA,QACvB,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;AAAA,QACrB,SAAS,EAAE,MAAM,MAAM,IAAI;AAAA,MAC7B;AACA,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO,EAAE,WAAWG,eAAc,SAAS,EAAE;AAC/C;AAEA,SAAS,QAAQ,GAAa,GAAsB;AAClD,SAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI;AAC7E;AAmBO,SAAS,iBACd,MACA,UAIA,SAKI,CAAC,GACoB;AACzB,QAAM,YAAY,OAAO,UAAU;AACnC,QAAM,aAAa,OAAO,WAAW;AACrC,QAAM,WAAW,OAAO,YAAY;AACpC,QAAM,SAAS,OAAO,OAAO;AAC7B,QAAM,UAAU;AAChB,QAAM,OAAO,CAAC,GAAG,CAAC;AAClB,QAAM,SAAS,CAAC,GAAG,IAAI,EACpB,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,EAC1B,QAAQ,CAAC,MAAM;AACd,UAAM,MAAM,iBAAiB,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;AAC3D,WAAO,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;AAAA,EACzD,CAAC;AAEH,QAAM,EAAE,WAAW,MAAAH,MAAK,IAAI,aAAa;AAEzC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,EAAE,GAAG,KAAK,KAAK,IAAI,OAAO,CAAC;AACjC,UAAM,OAAO,CAAC,aAAa,EAAE,EAAE,GAAG,aAAa,EAAE,EAAE,CAAC;AAEpD,UAAM,IAAI,eAAe,EAAE,UAAU;AAErC,QAAI,CAAC,SAAS;AAEZ,YAAM,cAAc,MAAM,YAAY;AACtC,YAAM,QAAQA,MAAK,aAAa,MAAM,MAAM;AAC5C,MAAAA;AAAA,QACE,YAAY,OAAO,aAAa,YAAY,GAAG,IAAI;AAAA,QACnD;AAAA,QACA,SAAS,EAAE,MAAM,SAAS;AAAA,MAC5B;AAAA,IACF;AAGA,IAAAA,MAAK,MAAM,MAAM,MAAM;AAEvB,UAAM,OAAO,OAAO,GAAG,IAAI,CAAC;AAC5B,QAAI,QAAQ,KAAK,MAAM,QAAQ,UAAU;AAGvC,YAAM,WAAW,CAAC,aAAa,KAAK,EAAE,EAAE,GAAG,aAAa,KAAK,EAAE,EAAE,CAAC;AAClE,MAAAA;AAAA,QACE,KAAK;AAAA,UACH,OAAO;AAAA,UACP,KAAK,IAAI,OAAO,SAAS,eAAe,KAAK,EAAE,UAAU,GAAG,KAAK,GAAG;AAAA,QACtE;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,OAAO;AACL,MAAAA,MAAK,OAAO,aAAa,GAAG,MAAM,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7D,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO,EAAE,WAAWG,eAAc,SAAS,EAAE;AAC/C;AASO,SAAS,YAAY,KAAe,GAAW,IAAI,MAAgB;AACxE,QAAM,IAAI,cAAc,KAAK,GAAG,CAAC;AACjC,SAAO,EAAE,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,IAAI,EAAE,OAAO,KAAK,GAAG,EAAE,OAAO,CAAC;AACpE;AAgBO,SAAS,gBACd,KACA,OACA,UACA,GACA,IAAI,MACqB;AACzB,QAAM,OAAO,YAAY,KAAK,GAAG,CAAC;AAClC,QAAM,SAAS,CAAC,MAA6B;AAAA,IAC3C,EAAE,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC;AAAA,IACpD,EAAE,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC;AAAA,IACpD,EAAE,QAAQ,OAAO,aAAa,EAAE,IAAI,IAAI,KAAK,CAAC;AAAA,EAChD;AACA,QAAM,UAAU;AAChB,QAAM,SAAS,CAAC,GAAG,KAAK,EACrB,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,EAC1B,QAAQ,CAAC,MAAM;AACd,UAAM,MAAM,iBAAiB,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK;AAC3D,WAAO,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,OAAO,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC;AAAA,EACzD,CAAC;AAEH,QAAM,EAAE,WAAW,MAAAH,MAAK,IAAI,aAAa;AAEzC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,EAAE,GAAG,KAAK,KAAK,IAAI,OAAO,CAAC;AACjC,UAAM,OAAO,OAAO,CAAC;AAErB,UAAM,IAAI,eAAe,EAAE,UAAU;AAErC,QAAI,CAAC,SAAS;AAEZ,YAAM,QAAQA,MAAK,MAAM,cAAc,GAAG,MAAM,MAAM;AACtD,MAAAA,MAAK,QAAQ,cAAc,GAAG,MAAM,SAAS,EAAE,MAAM,QAAQ,CAAC;AAAA,IAChE;AAGA,IAAAA,MAAK,MAAM,MAAM,MAAM;AAEvB,UAAM,OAAO,OAAO,GAAG,IAAI,CAAC;AAC5B,QAAI,QAAQ,KAAK,MAAM,QAAQ,eAAe;AAG5C,MAAAA;AAAA,QACE,KAAK,IAAI,OAAO,UAAU,eAAe,KAAK,EAAE,UAAU,GAAG,KAAK,GAAG;AAAA,QACrE,OAAO,KAAK,CAAC;AAAA,QACb;AAAA,MACF;AACA,gBAAU;AAAA,IACZ,OAAO;AACL,MAAAA,MAAK,OAAO,eAAe,GAAG,MAAM,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC9D,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO,EAAE,WAAWG,eAAc,SAAS,EAAE;AAC/C;AAMO,SAAS,gBACd,MACA,KACA,GACA,IAAI,MACW;AACf,QAAM,IAAI,IAAI;AACd,QAAM,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;AACrC,SAAO;AAAA,IACL,GAAG,KAAK,CAAC,IAAI,IAAI,OAAO;AAAA,IACxB,GAAG,KAAK,CAAC,IAAI,IAAI,OAAO;AAAA,IACxB;AAAA,IACA,QAAQ,IAAI,UAAU,aAAa,IAAI,UAAU,MAAM,IAAI,OAAO;AAAA,EACpE;AACF;AASO,SAAS,gBAAgB,KAAiB,GAA0B;AACzE,QAAM,EAAE,GAAG,EAAE,IAAI,cAAc,GAAG;AAClC,MAAI,CAAC,IAAI,aAAa,CAAC,IAAI,UAAU,QAAQ;AAC3C,WAAO,cAAc,IAAI,KAAK,GAAG,CAAC;AAAA,EACpC;AACA,QAAM,QAAQ;AAAA,IACZ,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,cAAc,GAAG;AAAA,IACjB;AAAA,IACA;AAAA,EACF;AACA,MAAI,CAAC,MAAM,UAAU,OAAQ,QAAO,cAAc,IAAI,KAAK,GAAG,CAAC;AAC/D,SAAO,gBAAgBC,QAAO,OAAO,GAAGC,UAAS,GAAG,IAAI,KAAK,GAAG,CAAC;AACnE;AAgBO,SAAS,YACd,MACA,MACA,KACyB;AACzB,QAAM,SAAS,KACZ,OAAO,CAAC,MAAM,OAAO,SAAS,EAAE,EAAE,CAAC,EACnC,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;AAC7B,QAAM,QAAQ,OAAO,GAAG,CAAC;AACzB,MAAI,CAAC,MAAO,QAAO,EAAE,WAAW,CAAC,EAAE;AACnC,QAAM,EAAE,WAAW,MAAAL,MAAK,IAAI,aAAa;AACzC,MAAI,MAAM,KAAK,KAAO,CAAAA,MAAK,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM;AAC/C,aAAW,KAAK,QAAQ;AACtB,IAAAA;AAAA,MACE,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE,EAAE,GAAG,GAAG;AAAA,MAC/B,EAAE;AAAA,MACF,SAAS,EAAE,MAAM,WAAW;AAAA,IAC9B;AAAA,EACF;AACA,SAAO,EAAE,WAAWG,eAAc,SAAS,EAAE;AAC/C;AAGO,SAAS,kBAAkB,GAA0B;AAC1D,SAAO;AAAA,IACL,EAAE,UAAU;AAAA,IACZ,EAAE,UAAU;AAAA,IACZ,EAAE,UAAU,SAAS;AAAA,IACrB,EAAE,UAAU,YAAY;AAAA,IACxB;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,GAAgB,MAAyB;AAClE,UAAQ,EAAE,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,IAClC,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,OAAO;AAAA,MACL,EAAE,KAAK,KAAK,CAAC;AAAA,MACb,EAAE,KAAK,KAAK,CAAC;AAAA,MACb,EAAE,SAAS,KAAK,CAAC;AAAA,MACjB,EAAE,YAAY,KAAK,CAAC;AAAA,MACpB,EAAE,WAAW,KAAK,CAAC;AAAA,IACrB;AAAA,EACF,EAAE;AACJ;AAQO,SAAS,oBACd,GACA,GACiB;AACjB,MAAI,CAAC,EAAE,UAAU,CAAC,EAAE,OAAO,OAAQ,QAAO;AAC1C,QAAM,OAAO,kBAAkB,CAAC;AAChC,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,kBAAkB,GAAG,IAAI;AAAA,IACzB,KAAK,IAAI,sBAAsB,EAAE,QAAQ;AAAA,EAC3C;AACA,MAAI,CAAC,MAAM,UAAU,OAAQ,QAAO;AACpC,SAAO,CAAC,GAAGC,QAAO,OAAO,GAAGC,UAAS,CAAC;AACxC;AAGO,SAAS,iBAAiB,GAAyB;AACxD,QAAM,IAAI,EAAE;AACZ,SAAO,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,oBAAoB;AAC1E;AAEA,SAAS,iBAAiB,GAAe,MAAyB;AAChE,UAAQ,EAAE,UAAU,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,IAClC,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,OAAO;AAAA,MACL,EAAE,KAAK,KAAK,CAAC;AAAA,MACb,EAAE,KAAK,KAAK,CAAC;AAAA,MACb,EAAE,KAAK,KAAK,CAAC;AAAA,MACb,EAAE,MAAM,KAAK,CAAC;AAAA,MACd,EAAE,MAAM,KAAK,CAAC;AAAA,MACd,EAAE,MAAM,KAAK,CAAC;AAAA,MACd,EAAE,SAAS,KAAK,CAAC;AAAA,IACnB;AAAA,EACF,EAAE;AACJ;AAOO,SAAS,mBACd,GACA,GACA,SACiB;AACjB,MAAI,CAAC,EAAE,UAAU,CAAC,EAAE,OAAO,OAAQ,QAAO;AAC1C,QAAM,OAAO,iBAAiB,CAAC;AAC/B,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,iBAAiB,GAAG,IAAI;AAAA,IACxB,EAAE,MAAM,YAAY;AAAA,EACtB;AACA,MAAI,CAAC,MAAM,UAAU,OAAQ,QAAO;AACpC,SAAO,CAAC,GAAGD,QAAO,OAAO,GAAGC,UAAS,CAAC;AACxC;AAcO,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwG9B,IAAM,QAAQ;AAAA,KACTC,oBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWpB,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiSlB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wDAsVN,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAsD3D,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yDA+EwB,0BAA0B;AAAA;AAAA;AAAA;AAAA,0CAIzC,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAoDlD,YAAY;AAAA,iBACb,gBAAgB;AAAA,iBAChB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yDAUyB,oBAAoB,6BAA6B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCA0FvF,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBA+SvC,KAAK,IAAI,MAAM,CAAC,eAAe,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBA8CrC,KAAK,IAAI,MAAM,CAAC,eAAe,QAAQ;AAAA;AAA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","names":["kicker","push","outputEnd","clamp","clamp01","clamp","lerpArray","sample","segmentRate","sortKeyframes","splitBySpeed","totalDuration","timelineRuntimeCode","sc","dw","dh","clamp01","dx","dy","x","y","findFontFamily","fontFaceUrl","nearestFontWeight","push","fontFaceUrl","findFontFamily","nearestFontWeight","round","w","splitBySpeed","totalDuration","splitBySpeed","totalDuration","round3","clamp01","sampleAt","round","side","round","round4","round","round","splitBySpeed","totalDuration","segmentRate","push","round","next","sortKeyframes","sample","lerpArray","timelineRuntimeCode","round4","round","emit","round","totalDuration","totalDuration","round3","totalDuration","EPS","own","totalDuration","round3","round","mapTime","mapTime","round","totalDuration","totalDuration","mapTime","segmentRate","sourceToTimeline","splitBySpeed","totalDuration","round","splitBySpeed","sourceToTimeline","mapTime","s","f","totalDuration","clamp01","segmentRate","newStart","clip","mapTime","durationSec"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/callout.ts","../src/capture.ts","../src/ingest.ts","../src/lower/motion.ts","../src/anim.ts","../src/media.ts","../src/zoomStyle.ts","../src/docVersion.ts","../src/doc/studioDoc.ts","../src/planner/smoothing.ts","../src/planner/autoZoom.ts","../src/lower/lowerToComposition.ts","../src/layout.ts","../src/stage.ts","../src/overlayText.ts","../src/htmlLayer.ts","../src/text3d.ts","../src/lower/segmentStarts.ts","../src/lower/transitions.ts","../src/lower/audioEnvelope.ts","../src/lower/cursorFollow.ts","../src/lower/pin.ts","../src/lower/cursorIdle.ts","../src/lower/studioEntry.ts","../src/lower/extractClicks.ts","../src/planner/autoTilt.ts","../src/planner/autoSpeed.ts","../src/digest/moments.ts","../src/digest/scenes.ts","../src/digest/framing.ts","../src/digest/style.ts","../src/audioBeds.ts","../src/digest/geometry.ts","../src/digest/build.ts","../src/timeline/rangeActions.ts","../src/lower/lowerStudioDoc.ts","../src/destinations.ts","../src/htmlStarters.ts","../src/lower/audioPlan.ts","../src/look.ts","../src/timeline/lanes.ts","../src/waveform.ts","../src/lower/duckCurve.ts","../src/backdrop.ts","../src/rejected.ts"],"sourcesContent":["/**\n * The studio — shared contracts.\n *\n * These are the seams between the capture extension, the editor, the planner,\n * and the lowering. Branding lives at the app layer;\n * these types are intentionally generic so the core stays extraction-ready.\n */\nimport type { Segment } from '@vosjs/timeline'\nimport type { ZoomStyleName, ZoomStyleParams } from './zoomStyle'\nimport type { SpeedParams } from './planner/autoSpeed'\n\nexport type { Segment }\n\n/**\n * A kept span. `hold` is the LEGACY spelling of a trailing freeze (a\n * freeze on the segment's last frame for these output seconds): it is\n * read on every path and migrated into `doc.freeze` on read\n * (`migrateMotion`), so nothing new writes it.\n */\nexport type DocSegment = Segment & {\n /** The media this clip plays (`doc.media[].id`); absent = the primary, `doc.source`. */\n media?: string\n /**\n * How this clip meets the clips beside it: `enter` is its arrival at the\n * boundary with the clip BEFORE it, `exit` its leaving at the boundary\n * with the clip AFTER it (`slide` | `fade` | `scale` | `none`; a slide\n * names its `side`, a step its `seconds`). When an exit and the next\n * clip's enter meet at one boundary they run together, the outgoing\n * frozen on its last frame sliding away while the incoming plays: a\n * push. The first clip's enter and the last clip's exit are the CARD's\n * own (`frame.anim`) and are ignored here. Absent = a hard cut.\n */\n anim?: Anim\n /** @deprecated migrated on read into a `FreezeSpan` at this segment's `out`. */\n hold?: number\n}\n\n/** A single input event captured in the page, relative to the recording's t0. */\nexport interface CursorEvent {\n /** ms since t0 (recording start). */\n t: number\n /** viewport CSS px. */\n x: number\n y: number\n /**\n * Screen-coordinate CSS px (MouseEvent.screenX/Y) — the mapping anchor for\n * window/monitor captures, where the viewport is only part of the frame.\n * Carrying both spaces per event also gives an exact per-event viewport→screen\n * offset (sx−x, sy−y) for transforming element rects. Absent on old tracks.\n */\n sx?: number\n sy?: number\n /**\n * `key` is a typing-ACTIVITY ping (throttled): when and where typing is\n * happening, never what is typed — the payload must not carry key identity\n * or input contents at any layer. Position/rect follow the `focus`\n * convention: the focused editable element's center + bounds.\n */\n type: 'move' | 'down' | 'up' | 'scroll' | 'focus' | 'key'\n /** pointer button for down/up (0=left). */\n button?: 0 | 1 | 2\n /** target element bounds at event time — enables element-aware auto-zoom. */\n rect?: Rect\n}\n\nexport type CursorTrack = CursorEvent[]\n\nexport interface Rect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** Recording metadata needed to map captured pixels ↔ cursor coords ↔ time. */\nexport interface RecordingMeta {\n /** device pixel ratio at capture. */\n dpr: number\n /** page/browser zoom at capture. */\n zoom: number\n /** wall-clock origin (Date.now at first frame). */\n t0: number\n durationMs: number\n /** captured pixel dimensions. */\n width: number\n height: number\n fps: number\n /**\n * The recording's OWN file carries an audio track — unmute preview, mux into\n * export. After the AT split that track is SYSTEM/tab audio only (it rides\n * the same stream as the video, sample-aligned by construction); on takes\n * recorded before the split it is the legacy record-time mic+system mix.\n */\n hasAudio?: boolean\n /**\n * A separately-recorded microphone sidecar exists. The mic never\n * enters the video file — it records through its own audio-only\n * MediaRecorder so the studio can gain/mute/duck it independently.\n */\n hasMic?: boolean\n /**\n * Recorder start skew: wall-clock ms between the main recorder's start and\n * the mic/cam sidecar recorders' starts (positive = sidecar started later).\n * Lets the consume path trim/pad a sidecar head instead of assuming t0\n * equality. Absent on takes without the matching sidecar.\n */\n micT0DeltaMs?: number\n camT0DeltaMs?: number\n /**\n * Encoded frame dimensions in device px, from the capture track's settings.\n * `width`/`height` are the CSS-px viewport (the CursorEvent coordinate space);\n * these are the actual video pixels — same aspect when capture is constrained\n * to the tab size, but keep both spaces so mapping never assumes it.\n */\n captureWidth?: number\n captureHeight?: number\n /**\n * The tab viewport changed size mid-take. Capture resolution is fixed for the\n * whole take, so Chrome letterboxes the resized content — surface a notice.\n */\n resizedDuringTake?: boolean\n /**\n * Page URL/title at record start (seeds the browser-bar mock's address pill).\n * Query/hash are stripped at capture for privacy.\n */\n pageUrl?: string\n pageTitle?: string\n /**\n * What the frame contains. Absent = 'tab' (back-compat). Non-tab surfaces use\n * CursorEvent.sx/sy + the geometry rects below to map cursor → capture px\n * (see normalizeCaptureSpace); tab-only studio features (browser-bar mock,\n * letterbox notice) are gated off for them.\n */\n captureSurface?: 'tab' | 'window' | 'monitor'\n /**\n * Target-tab browser-window bounds at record start, screen-coord CSS px\n * (window.screenX/Y + outerWidth/Height). Anchor for 'window' captures.\n */\n windowRect?: Rect\n /**\n * FULL bounds of the display hosting the target window at record start,\n * screen-coord CSS px (chrome.system.display bounds — the true origin,\n * including the macOS menu bar; page availLeft/Top only as a fallback).\n * Anchor for 'monitor' captures; wrong-display shares surface as low\n * coverage and fall back to no auto-zoom.\n */\n screenRect?: Rect\n /**\n * The target window moved or resized during a 'window' take — the single\n * windowRect anchor can't map the whole track, so the studio drops the\n * cursor rather than rendering it at stale positions.\n */\n windowMovedDuringTake?: boolean\n /**\n * Viewport CSS-px size (innerWidth/Height) of the recorded tab at record\n * start. On 'window' takes this + windowRect + the cursor events' screen\n * coords derive the viewport crop that removes the real browser chrome from\n * the footage (deriveViewportCrop) so the synthetic browser bar applies.\n */\n viewport?: { w: number; h: number }\n /**\n * The tab viewport changed size mid-take on a display take (resize, devtools\n * dock, zoom) — the static viewport crop can't map the whole take, so crop\n * derivation fails closed.\n */\n viewportChangedDuringTake?: boolean\n /**\n * Fraction of a 'window' take during which the target tab's browser window\n * was the FOCUSED window (chrome.windows.onFocusChanged, pause-gated).\n * The wrong-window tell that geometry can't provide: cursor events come from\n * the recorded tab and its window geometry is self-consistent, so sharing a\n * DIFFERENT window (Finder, another app — even one with identical bounds)\n * still maps events \"in frame\". But driving that other window means focusing\n * it — a low fraction ⇒ the footage isn't the browser window, so cursor\n * effects and the viewport crop must fail closed (WINDOW_FOCUS_MIN).\n */\n windowFocusedFrac?: number\n /** Recorder OS (chrome.runtime.getPlatformInfo) — seeds the browser-bar style. */\n platform?: 'mac' | 'windows' | 'linux'\n /**\n * Which recorder produced the artifact. CLI takes synthesize the cursor\n * track from automation (exact coords, fresh rects, coverage 1 by\n * construction) and encode WebM; absent means the extension.\n */\n producer?: 'extension' | 'cli'\n /**\n * The step timeline: when each actions.json step ran, in SOURCE\n * seconds. This is what makes a cut re-anchorable across re-records — a\n * span anchored to a step re-times to wherever that step landed in the\n * new recording (`vos plan --reuse`). CLI takes only; a human recording\n * has no script and carries none.\n */\n steps?: StepSpan[]\n /**\n * The recorder did not land where it was asked: a sign-in, an identity\n * provider, a 401/403, or a redirect to somewhere else. Present only when\n * the take was recorded ANYWAY, so whoever reads it later (a digest, an\n * agent cutting the footage) knows the picture may be the wrong page.\n * `asked`/`landed` are origin + path, never a query or hash.\n */\n wall?: {\n kind: 'status' | 'idp' | 'signin' | 'redirect'\n asked: string\n landed: string\n }\n /**\n * Sensitive-looking text the recorder saw IN the frame: an email address,\n * something shaped like a key, a card number. The KIND and the place, never\n * the string. Each is listed once, at the first step it was visible\n * (`step` -1 = as the page opened); `seen` counts the scans that saw it.\n * `rect` is viewport CSS px. Absent on a recorder that does not look.\n */\n exposures?: {\n step: number\n kind: 'email' | 'key' | 'card' | 'card-tail'\n selector: string\n rect: { x: number; y: number; w: number; h: number }\n seen: number\n }[]\n /**\n * What the script asked the recorder to hide before the camera rolled, and\n * how many elements each selector reached. `hits: 0` is a mask that hid\n * nothing, so whatever it was for may be showing.\n */\n masks?: { selector: string; as: 'blur' | 'text'; hits: number }[]\n}\n\n/**\n * A span's tie to an actions.json step: metadata for `vos plan\n * --reuse`, which re-times the span onto a NEW recording of the same script\n * by resolving the step in the new `meta.steps`. NEVER read by lowering —\n * seconds stay the wire truth (`in`/`out` are always authoritative), so a\n * human recording with no steps renders identically with or without one.\n */\nexport interface StepAnchor {\n /** The step: its `id` from actions.json when it has one, else its index. */\n step: string | number\n /** Which edge of the step the span's `in` is measured from. Default 'start'. */\n at?: 'start' | 'end'\n /** Seconds from that edge to the span's `in` (negative = before it). */\n offset?: number\n}\n\n/** The lanes whose planners propose spans, and whose proposals can be rejected. */\nexport type RejectedLane = 'zoom' | 'tilt' | 'speed'\n\n/**\n * A planner proposal the human or the agent deleted, kept so no re-plan\n * proposes it again: the lane and the SOURCE extent of the deleted `auto`\n * span (plus its step anchor when it had one). Every auto-merge drops a\n * fresh proposal that lands on a rejected extent of the same lane;\n * `plan --reuse` re-times these the way it re-times a manual span.\n */\nexport interface RejectedSpan {\n /** `r{n}`, stable for the differ and the history. */\n id: string\n lane: RejectedLane\n /** The media the deleted span sat on; absent = the primary. */\n media?: string\n /** SOURCE seconds, the deleted span's extent. */\n in: number\n out: number\n anchor?: StepAnchor\n /** Why, in the deleter's words. Optional. */\n note?: string\n}\n\n/** One executed actions.json step's extent in the recording. */\nexport interface StepSpan {\n /** index into actions.steps at record time. */\n step: number\n /** the step's own id from actions.json, when it names one — an id lets a\n * step move or be reordered without breaking anchors (absent = the index\n * is the identity). */\n id?: string\n do: string\n selector?: string\n /** SOURCE seconds the gesture occupied, [tStart, tEnd]. */\n tStart: number\n tEnd: number\n /** the selector never became visible — the gesture did not run. */\n skipped?: boolean\n /**\n * The page's URL changed while the step ran: a click that navigated, or\n * the `wait` that let a navigation land. The planner proposes a\n * transition at the page change.\n */\n navigated?: boolean\n /**\n * The selector's element bounds at the gesture, in the cursor track's\n * space (the recorded viewport's CSS px, `meta.width` × `meta.height`) —\n * what a pinned layer names (`overlays[].pin.step`). Absent on a step\n * without a selector, and on takes recorded before the recorder kept it\n * (a pin then reads the presses inside the step's window).\n */\n rect?: Rect\n}\n\n/** Everything the capture extension hands off to the studio. */\nexport interface RecordingArtifact {\n /** OPFS key / object URL for the recorded video. */\n videoKey: string\n cursor: CursorTrack\n /** object URL for the separately-recorded mic sidecar (the mic/system split). */\n audioKey?: string\n /** object URL for a separately-recorded webcam track (drawn as an editable bubble). */\n camKey?: string\n meta: RecordingMeta\n}\n\n/**\n * Per-span transition speed — how fast the camera/bubble/card moves\n * into and out of a span's state, as NAMED steps (the category convention:\n * Screen Studio's speed words, Descript's one knob — never a curve editor).\n * Multipliers on the lane's own ramp constants, so 'smooth' (absent) is\n * byte-identical to the pre-feature motion and each lane keeps its feel.\n * 'instant' is a hard cut: the ramp collapses to the track emitter's 1ms\n * collision nudge.\n */\nexport type TransitionSpeed = 'instant' | 'fast' | 'smooth' | 'slow'\n\nexport const TRANSITION_SPEED_MULT: Record<TransitionSpeed, number> = {\n instant: 0,\n fast: 0.5,\n smooth: 1,\n slow: 1.6,\n}\n\n/** A span's ramp multiplier (absent = 'smooth' = 1, the exact legacy motion). */\nexport function transitionMult(t: TransitionSpeed | undefined): number {\n return t !== undefined && t in TRANSITION_SPEED_MULT\n ? TRANSITION_SPEED_MULT[t]\n : 1\n}\n\n/**\n * A speed-change region over a SOURCE-time span (seconds). Footage-anchored\n * like zoom keyframes — it follows its content through trims/splits, and a\n * span whose footage is fully cut away simply has no effect (and comes back\n * if the trim is undone). Non-overlapping (the lane clamps). The lowering\n * intersects spans with `segments` via @vosjs/timeline `splitBySpeed` into\n * rated segments; playback, export, and lane display all evaluate those.\n */\nexport interface SpeedSpan {\n /** Stable identity for selection/editing in the timeline UI. */\n id: string\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /** Playback rate (> 0): 2 = twice as fast, 0.5 = half speed. */\n rate: number\n /**\n * The auto-zoom wand contract: 'auto' = planner suggestion\n * (planAutoSpeed — typing/scroll/idle), replaced by a re-plan; 'manual' =\n * user/agent work, always preserved. Absent = manual (spans predating the contract).\n */\n source?: 'auto' | 'manual'\n}\n\n/**\n * A FREEZE: the footage frozen on one frame for `seconds` of output. A\n * SOURCE moment (`at`), footage-anchored like a speed span so it follows\n * its frame through trims and splits (a freeze whose frame is cut away has\n * no effect, and comes back if the trim is undone), plus output seconds.\n * The retime primitive beside `SpeedSpan`, on the same lane: a speed span\n * is a source RANGE with a rate, a freeze a source POINT with a length,\n * and a rate of zero is deliberately not how it is spelled (the pointer-\n * true gesture maps divide by the rate). Lowered as a rated piece whose\n * hair of source time plays for the seconds, so every consumer of the\n * rated list (mapTime, the zoom remap, the audio splice, the duration)\n * inherits it from one seam. The still every still-taking surface takes\n * is where the LAST freeze begins (`docRestTime`).\n */\nexport interface FreezeSpan {\n /** Stable identity for selection/editing (`f{n}`). */\n id: string\n /** The media the frozen frame is on; absent = the primary. */\n media?: string\n /** SOURCE seconds: the frame that freezes is the one just before `at`. */\n at: number\n /** OUTPUT seconds the frame holds. */\n seconds: number\n /** Re-record tie to an actions.json step; `at` stays the truth. */\n anchor?: StepAnchor\n /** The template that placed it (the clips' `from`), so a re-apply replaces it and a loop drops it. */\n from?: string\n}\n\n/** Freeze length bounds, output seconds. */\nexport const FREEZE_SECONDS_MIN = 0.25\nexport const FREEZE_SECONDS_MAX = 30\n/** A new freeze's length, output seconds. */\nexport const FREEZE_DEFAULT_SECONDS = 2\n\n/** Clamp + quantize a freeze's seconds for storage (3 decimals). */\nexport function clampFreezeSeconds(seconds: number): number {\n const s = Math.min(FREEZE_SECONDS_MAX, Math.max(FREEZE_SECONDS_MIN, seconds))\n return Math.round(s * 1000) / 1000\n}\n\n/**\n * Speed-rate bounds. 16 is also Chromium's HTMLMediaElement.playbackRate\n * ceiling, so preview (native playback) and export (offline resample) can\n * honor the same range.\n */\nexport const SPEED_RATE_MIN = 0.1\nexport const SPEED_RATE_MAX = 16\n\n/**\n * Minimum speed-span length in OUTPUT seconds (the lane converts through the\n * span's own rate: a 2× span may not shrink below 0.5s of source). A source\n * floor shrank with the rate — 0.1s of source at 5× was 20ms of screen, a\n * sliver nobody could grab again.\n */\nexport const SPEED_SPAN_MIN = 0.25\n\n/** Clamp + quantize a speed rate for storage (2 decimals, like \"1.75×\"). */\nexport function clampSpeedRate(rate: number): number {\n const r = Math.min(SPEED_RATE_MAX, Math.max(SPEED_RATE_MIN, rate))\n return Math.round(r * 100) / 100\n}\n\n/**\n * A zoom region over a SOURCE-time span (seconds) — one adjustable clip on the\n * zoom lane. Footage-anchored like SpeedSpan/CamStyle.window: it follows its\n * content through trims/splits (a span whose footage is fully cut away renders\n * nothing, and comes back if the trim is undone; a partially-cut span keeps its\n * kept extent). Non-overlapping (the lane clamps). The camera ramps in around\n * `in`, holds `[level, cx, cy]` until `out`, then ramps back to 1× — or pans\n * straight to the next span when the gap is short (see `zoomTrackFromDoc`).\n */\nexport interface ZoomSpan {\n /** Stable identity for selection/editing (`z{n}` planner, `u{n}` user). */\n id: string\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /** zoom level (1 = no zoom), ZOOM_LEVEL_MIN..ZOOM_LEVEL_MAX, 2 decimals. */\n level: number\n /** focus point in normalized [0..1] video-frame coords. */\n cx: number\n cy: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the default ramp ease. */\n ease?: string\n /**\n * Transition speed for THIS span's ramps (in, out, and the pan arriving\n * here from a chained neighbor). Absent = 'smooth', the camera style's\n * stock motion; 'instant' is a hard cut.\n */\n transition?: TransitionSpeed\n /**\n * 'auto' = the camera follows the cursor through the span (dead-zone\n * recenter, baked deterministically at lowering — see followFocusEvents);\n * absent/'manual' = the fixed cx/cy focus.\n */\n focusMode?: 'manual' | 'auto'\n /**\n * 'auto' = planner suggestion — regenerate replaces these freely, never\n * 'manual' ones. Any edit gesture promotes the span to 'manual' (OpenScreen's\n * contract: suggestions are disposable, user work is sacred).\n */\n source?: 'auto' | 'manual'\n}\n\n/** Zoom-level preset chips (OpenScreen-style picker). */\nexport const ZOOM_LEVELS = [1.25, 1.5, 1.8, 2.2, 3.5, 5] as const\nexport const ZOOM_LEVEL_MIN = 1\nexport const ZOOM_LEVEL_MAX = 5\n/** Default level for new/user-created zooms. */\nexport const DEFAULT_ZOOM_LEVEL = 1.8\n/**\n * Minimum zoom-span length in OUTPUT seconds (the lane clamps resizes,\n * converting through the rate in force so the floor is what the eye sees).\n */\nexport const ZOOM_SPAN_MIN = 0.3\n\n/** Clamp + quantize a zoom level for storage (2 decimals, like \"1.8×\"). */\nexport function clampZoomLevel(level: number): number {\n const l = Math.min(ZOOM_LEVEL_MAX, Math.max(ZOOM_LEVEL_MIN, level))\n return Math.round(l * 100) / 100\n}\n\n/**\n * A tilt region over a SOURCE-time span (seconds) — one adjustable clip on the\n * tilt lane. Footage-anchored\n * like ZoomSpan/SpeedSpan: it follows its content through trims/splits and\n * speed changes (a span whose footage is fully cut away renders nothing, and\n * comes back if the trim is undone). Non-overlapping (the lane clamps). While\n * active the card leans to this pose; between spans it returns to the RESTING\n * FLAT rest pose (there is no static card tilt) — expanded at lowering into an\n * OUTPUT-time [rx, ry] degree keyframe track (see tiltTrackFromDoc).\n */\nexport interface TiltSpan {\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n /** Stable identity for selection/editing (`t{n}` planner, `u{n}` user). */\n id: string\n in: number\n out: number\n /** Re-record tie to an actions.json step; `in`/`out` stay the truth. */\n anchor?: StepAnchor\n /**\n * Pose in DEGREES (the CardTilt convention): rx leans the card back/forward,\n * ry swings it left/right. Gentle values read best (±5..18°).\n */\n rx: number\n ry: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the house tilt ease. */\n ease?: string\n /**\n * Transition speed for this span's ramps. Absent = 'smooth' (the stock\n * tilt motion); 'instant' snaps the card to the pose.\n */\n transition?: TransitionSpeed\n /**\n * 'auto' = Dynamic-tilt wand suggestion — regenerate replaces these freely,\n * never 'manual' ones. Any edit gesture promotes the span to 'manual' (the\n * auto-zoom wand contract).\n */\n source?: 'auto' | 'manual'\n}\n\n/** Hard tilt bound in degrees (schema/lint); UI sliders stay within ±20. */\nexport const TILT_DEG_MAX = 45\n/** UI slider bound — matches the Card panel's static (rest) tilt sliders. */\nexport const TILT_UI_DEG_MAX = 20\n/**\n * Minimum tilt-span length in OUTPUT seconds (the lane clamps resizes). Bigger\n * than ZOOM_SPAN_MIN because tilt ramps are longer — a pose that can't settle\n * isn't a pose.\n */\nexport const TILT_SPAN_MIN = 0.8\n/** Default pose for user-created spans: a medium three-quarter \"showcase\" lean. */\nexport const DEFAULT_TILT_POSE = { rx: 6, ry: -9 }\n\n/** Clamp + quantize a tilt angle for storage (1 decimal, degrees). */\nexport function clampTiltDeg(deg: number): number {\n const d = Math.min(TILT_DEG_MAX, Math.max(-TILT_DEG_MAX, deg))\n return Math.round(d * 10) / 10\n}\n\n/**\n * Dynamic-tilt wand intensity ladder (the category convention — FocuSee ships\n * Subtle/Default/Strong): the max degrees planAutoTilt will lean per axis.\n */\nexport type TiltStyleName = 'off' | 'subtle' | 'medium' | 'strong'\nexport const TILT_INTENSITY_MAX: Record<\n Exclude<TiltStyleName, 'off'>,\n number\n> = {\n subtle: 5,\n medium: 9,\n strong: 14,\n}\n\nexport interface CursorStyle {\n /**\n * Draw the cursor dot. Off still keeps the track: auto-zoom cursor-follow\n * and click effects are independent of whether the dot is painted. Absent\n * reads as visible (pre-toggle docs).\n */\n visible: boolean\n /** 0..1 smoothing strength (lerp factor; higher = smoother/laggier). */\n smoothing: number\n /** rendered cursor size in px. */\n size: number\n /**\n * What the pointer is drawn as. `'arrow'` is an OS-style pointer (a\n * black arrow with a white edge, its tip on the recorded point), the\n * default for new takes: it is the pointer the viewer's own screen wears,\n * so the frame reads as a screen and not as a screen recorder's overlay.\n * `'default'`, `'dot'` and `'ring'` all draw the white dot every earlier\n * take was cut with. Click effects bloom under the tip either way.\n */\n style: 'default' | 'dot' | 'ring' | 'arrow'\n hideWhenIdle: boolean\n clickFx: ClickFxStyle\n}\n\n/**\n * Click-effect styling. Clicks are extracted\n * at lowering (OUTPUT-anchored — see extractClicks) and drawn by ON_FRAME as a\n * pure function of t; every field here is a live SET_DATA edit.\n */\nexport interface ClickFxStyle {\n /** ring drawn at the click point ('highlight' glows the clicked element's rect). */\n style: 'none' | 'ripple' | 'pulse' | 'highlight'\n /** cursor press dip on real down→up spans — independent of ring style. */\n press: boolean\n intensity: 'subtle' | 'medium' | 'strong'\n /** resolved hex for rings/glow; 'auto' = neutral white-over-dark-rim. */\n color: string | 'auto'\n}\n\n/**\n * Named intensity levels → resolved multipliers (size/alpha `k`, duration\n * `dur`), baked into ctx.data at lowering so ON_FRAME needs no registry —\n * the same pattern as MINIMAL_BAR_THEMES resolving to concrete colors.\n */\nexport const CLICK_FX_INTENSITY: Record<\n ClickFxStyle['intensity'],\n { k: number; dur: number }\n> = {\n subtle: { k: 0.7, dur: 0.9 },\n medium: { k: 1, dur: 1 },\n strong: { k: 1.35, dur: 1.1 },\n}\n\n/** Webcam bubble overlay — an editable layer composited over the frame. */\nexport interface CamStyle {\n visible: boolean\n /**\n * Free placement: the bubble CENTER as frame fractions (the overlay\n * and zoom cx/cy convention, so positions survive aspect switches). When\n * present they WIN over `position`; clearing them snaps back to the corner.\n */\n x?: number\n y?: number\n /** corner the bubble is anchored to when x/y are absent. */\n position: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n /** bubble diameter as a fraction of frame height (0..1). */\n size: number\n shape: 'circle' | 'rounded'\n /**\n * Corner radius in design px for the 'rounded' shape (absent = 18, the\n * house look; a circle ignores it). Scales with the canvas like every\n * frame-owned control.\n */\n radius?: number\n /**\n * Ring stroke over the bubble edge. Absent = the house ring (3px white at\n * 0.9 alpha — the pre-existing paint); `width: 0` = no ring.\n */\n border?: { width: number; color: string }\n /** Bubble shadow. Absent = 'soft', the pre-existing paint. */\n shadow?: 'none' | 'soft' | 'strong'\n /** mirror horizontally (selfie view). */\n mirror: boolean\n /**\n * Show the bubble only during this SOURCE-time span (trimmed on the cam\n * timeline lane; anchored to footage like zoom keyframes). Absent = always.\n */\n window?: Segment\n}\n\n/**\n * A cam pose region over a SOURCE-time span (seconds) — one adjustable clip on\n * the cam-move lane (MO track: animated cam layouts, the Screen Studio\n * signature). Footage-anchored like ZoomSpan/TiltSpan: it follows its content\n * through trims/splits and speed changes (a span whose footage is fully cut\n * away renders nothing, and comes back if the trim is undone). Non-overlapping\n * (the lane clamps). While active the bubble holds this pose; outside spans it\n * rests at the doc's cam style (doc.cam IS the rest pose); spans close together\n * in output time morph pose-to-pose without returning to rest. Absent pose\n * fields inherit the rest pose, so a span may move without resizing. Expanded\n * at lowering into an OUTPUT-time [x, y, size] fraction keyframe track\n * (camTrackFromDoc) — pure f(t), no springs.\n */\nexport interface CamPoseSpan {\n /** The media this span's source seconds belong to; absent = the primary. */\n media?: string\n /** Stable identity for selection/editing (`m{n}` user-created). */\n id: string\n in: number\n out: number\n /**\n * Bubble CENTER as frame fractions [0..1] (the cam.x/y and zoom cx/cy\n * convention — aspect-stable). Absent = the rest pose's center.\n */\n x?: number\n y?: number\n /** Bubble diameter as a fraction of frame height. Absent = the rest size. */\n size?: number\n /** arrival ease (@vosjs/timeline EASINGS name). Absent = the house cam ease. */\n ease?: string\n /**\n * Transition speed for this span's morphs. Absent = 'smooth' (~0.65s);\n * 'instant' jump-cuts the bubble to its pose — the Screen Studio layout-cut.\n */\n transition?: TransitionSpeed\n /**\n * Reserved for a future auto planner (the wand contract: 'auto' spans are\n * disposable suggestions). Every studio gesture writes 'manual'.\n */\n source?: 'auto' | 'manual'\n}\n\n/**\n * Minimum cam-move span length in OUTPUT seconds (the lane clamps resizes).\n * Between zoom's 0.3 and tilt's 0.8: a bubble move settles faster than a card\n * pose but still needs its ~0.65s ramp to read as a move, not a jump.\n */\nexport const CAM_SPAN_MIN = 0.5\n/** Bubble-size band for pose spans (fractions of frame height; UI + lint). */\nexport const CAM_SIZE_MIN = 0.08\nexport const CAM_SIZE_MAX = 0.6\n\n/** Clamp + quantize a pose size for storage (3 decimals, frame fraction). */\nexport function clampCamSize(size: number): number {\n const v = Math.min(CAM_SIZE_MAX, Math.max(CAM_SIZE_MIN, size))\n return Math.round(v * 1000) / 1000\n}\n\n/** Clamp + quantize a pose center coordinate for storage (3 decimals, [0..1]). */\nexport function clampCamFrac(v: number): number {\n const c = Math.min(1, Math.max(0, v))\n return Math.round(c * 1000) / 1000\n}\n\n/**\n * Default pose for user-created cam-move spans: front-and-center, large —\n * the \"talk to camera\" moment that is the feature's reason to exist (the\n * DEFAULT_TILT_POSE philosophy: a new span shows a visible, editable move,\n * never a no-op).\n */\nexport const DEFAULT_CAM_POSE = { x: 0.5, y: 0.55, size: 0.45 }\n\n/**\n * Mock browser chrome drawn as a strip above the video inside the frame card.\n * Drawn by the compositor (never captured) — the editor-frame pattern every\n * shot/recorder tool uses. All values travel in `ctx.data.frame` (live T2 edits).\n */\nexport interface BrowserBarStyle {\n kind:\n | 'none'\n | 'mac-light'\n | 'mac-dark'\n | 'windows-light'\n | 'windows-dark'\n | 'minimal'\n /** address-pill text (editable; seeded from the recorded page's URL). */\n url: string\n showUrl: boolean\n /** traffic lights (mac) / window buttons (windows). */\n showControls: boolean\n /** bar height in design px (1080-based, same space as padding/radius). */\n height: number\n /**\n * Minimal-bar color theme — RESOLVED colors (not a palette id) so ON_FRAME\n * needs no registry lookup (interpreter rule: everything in ctx.data is\n * self-contained). Absent = the built-in graphite look. Pick from\n * MINIMAL_BAR_THEMES in the inspector.\n */\n theme?: MinimalBarTheme\n}\n\n/** Resolved minimal-bar colors. `light` flips the hairline to dark-on-light. */\nexport interface MinimalBarTheme {\n id: string\n bar: string\n pill: string\n text: string\n light?: boolean\n}\n\n/**\n * Curated minimal-bar palette (Cursorful-style 12 swatches: 6 dark, 6 light).\n * The first entry matches the built-in default (theme absent).\n */\nexport const MINIMAL_BAR_THEMES: MinimalBarTheme[] = [\n { id: 'graphite', bar: '#141417', pill: '#26262b', text: '#9a9aa1' },\n { id: 'charcoal', bar: '#27272a', pill: '#3a3a3f', text: '#b0b0b6' },\n { id: 'ink', bar: '#0f172a', pill: '#1e293b', text: '#94a3b8' },\n { id: 'slate', bar: '#1e293b', pill: '#334155', text: '#a8b6c8' },\n { id: 'navy', bar: '#172033', pill: '#232e47', text: '#8fa0bd' },\n { id: 'steel', bar: '#2f3542', pill: '#414a5c', text: '#aab3c5' },\n { id: 'snow', bar: '#f8fafc', pill: '#ffffff', text: '#5f6368', light: true },\n {\n id: 'white',\n bar: '#ffffff',\n pill: '#f1f3f4',\n text: '#5f6368',\n light: true,\n },\n { id: 'mist', bar: '#e8ecf1', pill: '#f8fafc', text: '#566172', light: true },\n {\n id: 'lavender',\n bar: '#e7e9f8',\n pill: '#f6f7fe',\n text: '#5b5f79',\n light: true,\n },\n { id: 'sky', bar: '#dbe6f7', pill: '#f2f7ff', text: '#4d6079', light: true },\n {\n id: 'blush',\n bar: '#f7ecec',\n pill: '#fdf7f7',\n text: '#77595c',\n light: true,\n },\n]\n\n/**\n * A music/SFX clip. OUTPUT-anchored (`start` is final-cut seconds): music and\n * effects are authored against the cut that remains — unlike zoom keyframes /\n * cam window, they do NOT follow footage through trims. The mic track stays\n * source-anchored via the export's segment splice.\n */\nexport interface AudioClip {\n id: string\n /** blob URL (or asset URL) of the audio file. */\n key: string\n /** display name (file name or library track title). */\n name: string\n /** placement on the OUTPUT timeline, seconds. */\n start: number\n /** kept span within the source file, seconds (trim). */\n in: number\n out: number\n /** full source-file length, seconds — the trim ceiling (set on add). */\n duration: number\n /** linear gain 0..1. */\n gain: number\n /** fade durations, seconds. */\n fadeIn: number\n fadeOut: number\n /** loop the [in,out) span to fill `loopLen` output seconds. */\n loop?: boolean\n /** placed output length when looping (≥ span; defaults to the span). */\n loopLen?: number\n /** duck this clip under the mic while speech is detected. */\n duck?: boolean\n /** The template that placed this clip (see the overlay clip's `from`). */\n from?: string\n}\n\n/** Effective placed length of a clip on the output timeline, seconds. */\nexport function clipLength(\n clip: Pick<AudioClip, 'in' | 'out' | 'loop' | 'loopLen'>,\n): number {\n const span = Math.max(0, clip.out - clip.in)\n return clip.loop ? Math.max(span, clip.loopLen ?? span) : span\n}\n\n/**\n * Media layer drawn over the CSS `background` and under the card\n * The flagship option is a vos rendered\n * to a seamless loop (`vosId` provenance kept for re-bakes + a future live\n * tier). Video time is OUTPUT-anchored modulo the loop (`bgT = t % duration`)\n * — trims/speed never retime ambience. Fail-open: while the media loads (or if\n * it can't), the CSS background underneath still paints — never a black frame.\n */\nexport interface BackgroundMedia {\n kind: 'video' | 'image'\n /**\n * Source URL — blob URL (session), /api/assets/{id}/file (saved vos),\n * https://assets.vos.so/... (pre-baked official), or take-dir relative path\n * (CLI). Rides the same resolution plumbing as source.videoKey.\n */\n key: string\n /** Loop length in seconds (video; the bake duration). */\n duration?: number\n /** Provenance: the vos this media was rendered from. */\n vosId?: string\n versionId?: string\n /** Poster/thumbnail URL (picker display + reduced-motion; not drawn by the layer). */\n poster?: string\n /** Black scrim over the media, 0..1 — the one legibility dial. */\n dim: number\n /** Blur radius in design px — softens the media behind the card. */\n blur?: number\n}\n\nexport interface FrameStyle {\n /** CSS background (gradient/color): always painted — the media underlay/fallback. */\n background: string\n /** Optional media layer (vos loop / image) drawn over the CSS background. */\n backgroundMedia?: BackgroundMedia | null\n padding: number\n radius: number\n /**\n * Ambient shadow strength 0..1: the wide, soft layer under the card\n * (blur 60, offset 24 design px). The look presets set it with\n * `shadowContact`; every existing doc keeps its single layer.\n */\n shadow: number\n /**\n * Contact shadow strength 0..1: a second, tight layer (blur 10, offset 3\n * design px) drawn over the ambient one. It is what makes a light card sit\n * on a light ground, where a wide soft shadow alone reads as haze. Absent\n * = 0 (no second layer; byte-identical to before the field existed).\n */\n shadowContact?: number\n /**\n * Shadow colour as a `#rrggbb` hex; the strengths are its alpha. Absent =\n * black. A dark look lifts it toward its accent so the card glows instead\n * of sinking.\n */\n shadowColor?: string\n /**\n * Per-side card placement as FRACTIONS of the frame (left/right of its\n * width, top/bottom of its height), each overriding `padding` on its side\n * when present. A negative side BLEEDS the card past the frame edge (the\n * feature-clip grammar: headroom above, the window running off the\n * bottom). Absent sides keep `padding`, so every existing doc is untouched.\n * Under contain the card centres inside the inset area; under cover the\n * inset area IS the card.\n */\n inset?: { top?: number; right?: number; bottom?: number; left?: number }\n /** stroke around the card, 0..1 alpha (0 = off). The switch AND the opacity. */\n border: number\n /**\n * Stroke width in design px (scales with the canvas, like radius), drawn\n * OUTWARD from the card's edge (a CSS outline) so it never covers footage.\n * Absent = FRAME_BORDER_WIDTH_DEFAULT, the hairline every take shipped with.\n */\n borderWidth?: number\n /**\n * Stroke colour, any CSS colour string. Absent = FRAME_BORDER_COLOR_DEFAULT.\n * `border` is the alpha it is drawn at, so an opaque colour is correct here.\n */\n borderColor?: string\n /**\n * How footage meets an off-ratio frame. 'contain' (default) fits the\n * whole card inside the padded area, letterboxing onto the background;\n * 'cover' makes the padded area the card and cover-fills it with footage,\n * cropped around `focus` — what a 440x280 store tile or a 2.5:1 marquee\n * demands (\"fill the region\"). Absent = contain; every existing doc is\n * byte-identical.\n */\n fit?: 'contain' | 'cover'\n /**\n * Cover-crop anchor, normalized video-frame fractions (the zoom cx/cy\n * convention): which point of the footage stays visible when `fit:'cover'`\n * crops. Absent = center. Ignored under contain.\n */\n focus?: { cx: number; cy: number }\n /**\n * The zoom camera's model. `'card'` (absent; every existing document) is\n * the magnifier: the card scales about the focus point, which stays where\n * it is on screen, and the lowering clamps the focus so the zoomed card\n * always covers the canvas, which means a target near a card edge can\n * never sit near the frame's centre. `'stage'` is a camera: the card\n * scales AND slides so the focus lands at the frame's centre (blended in\n * over the first CAMERA_CENTRE_RAMP of level, so level 1 is the identity),\n * the focus is never clamped, and past the card's edge the frame shows\n * the ground, radius and shadow, the way a filmed card reads. New takes\n * open on `'stage'` (BASE_FRAME_STYLE); the layout helpers, the digest's\n * framing window and the studio's aiming rect all read this field.\n */\n camera?: 'card' | 'stage'\n aspectRatio: string\n browserBar: BrowserBarStyle\n /**\n * Background parallax 0..1: the background media counter-pans subtly\n * as the zoom camera moves (depth cue). 0/absent = static.\n */\n parallax?: number\n /**\n * How the card ENTERS at t = 0 (the feature-clip grammar: no clip opens\n * on a static, centred, flat window). `tilt-in` swings in from a\n * perspective pose while the card settles up and in; `pull-out` opens\n * zoomed in and pulls out; `rise` settles up and in flat. Lowered into\n * the tilt or zoom track's head and a card-pose track, so the card's only\n * rotation is still the tilt track. Absent = none, byte-identical.\n * @deprecated The card's `anim.enter`; migrated on read.\n */\n entrance?: FrameEntrance\n /**\n * How the card ENTERS at t = 0 and LEAVES when its footage ends (`recede`\n * settles it smaller and dimmer behind whatever plays after it; `fade`\n * takes it out). Past its footage the card holds its last frame at the\n * exit's end pose. Absent = neither, byte-identical.\n */\n anim?: Anim\n /**\n * Under `fit:'cover'`, follow the zoom track's focus with the crop\n * (`'camera'`), so a 9:16 cut of a 16:9 take keeps the affordance in\n * frame instead of the centre. Absent = `focus` (or the centre).\n */\n focusFollow?: 'camera'\n}\n\nexport interface FrameEntrance {\n kind: 'tilt-in' | 'pull-out' | 'rise' | 'none'\n /** Seconds to settle (default 1.2, clamped 0.2..3). */\n seconds?: number\n}\n\n/**\n * The end card: the last frame holds for `seconds` while the card recedes\n * and the words rise over it (the house title, caption and label presets,\n * so they lower like any overlay). The clip's last frame is its poster.\n * @deprecated A card `anim.exit` plus clips placed after the footage\n * (`from: 'endcard'`); migrated on read.\n */\nexport interface EndCard {\n seconds?: number\n headline?: string\n sub?: string\n wordmark?: string\n /**\n * The brand's MARK as an image (a take-dir path or a URL the render page\n * can load), with its aspect (width over height), drawn above the\n * wordmark; a wide mark (a stylised wordmark asset) stands alone.\n */\n mark?: { key: string; aspect: number }\n /**\n * The words' colour (any CSS colour). The house presets are white for\n * footage; over a light plate the brand's ink is what reads. Absent =\n * the presets' own.\n */\n ink?: string\n}\n\n/**\n * Overlay clips (compositor v2: \"elements-shaped data\"). Screen-\n * space clips drawn on the OVERLAY layer (above the card, never tilts, outside\n * the zoom transform). **OUTPUT-anchored** — trims/speed never retime a title.\n * The first slice ships `kind: 'text'`; image/video kinds are the next slice and extend this\n * union without changing the anchoring or transform model.\n */\nexport type OverlayKind = 'text' | 'image' | 'video' | 'html'\n\n/** Named house text styles — resolved to concrete font/size/color at lowering. */\nexport type TextOverlayPreset = 'title' | 'caption' | 'label'\n\n/** Enter/exit transition presets — pure f(t), evaluated in ON_FRAME. */\nexport type OverlayTransition = 'none' | 'fade' | 'rise'\n\n/**\n * THE animation vocabulary, one for every visual primitive. Which kinds a\n * thing accepts is its own (`anim.ts` holds the tables): the card enters by\n * `tilt-in`, `pull-out`, `rise`, `fade` or `slide` and leaves by `recede` or\n * `fade`; words enter by the block transitions or per unit (`pop`, `blur`,\n * `typewriter`, or a `fade`/`rise` that names its unit); an image or a\n * video clip enters and leaves by `fade` or `rise`; a prop idles by `spin`\n * or `float`; a FOOTAGE clip meets the clip beside it by `slide`, `fade` or\n * `scale` (a transition at the boundary). `none` is an explicit nothing;\n * absent is the house default.\n */\nexport type AnimKind =\n | 'none'\n | 'fade'\n | 'rise'\n | 'pop'\n | 'blur'\n | 'typewriter'\n | 'tilt-in'\n | 'pull-out'\n | 'recede'\n | 'slide'\n | 'scale'\n\n/**\n * The side a slide comes from (an enter) or leaves to (an exit), as seen on\n * screen: `right` enters from the right edge, `left` leaves off the left.\n */\nexport type AnimSide = 'left' | 'right' | 'up' | 'down'\n\n/** What a thing does while it stays. */\nexport type IdleKind = 'spin' | 'float'\n\n/**\n * One step of an animation: the kind, its seconds, and for words the\n * per-unit grammar. A bare kind is the same step with the house timing.\n */\nexport interface AnimStep {\n kind: AnimKind\n /** Seconds the step takes (the house length when absent). */\n seconds?: number\n /** Words only: the unit the step animates by (`block` = the whole clip). */\n unit?: TextFxUnit\n /** Words only: the order units animate in. */\n direction?: TextFxDirection\n /** Words only: seconds between one unit's start and the next. */\n stagger?: number\n /**\n * A slide's side: where an enter comes FROM, where an exit goes TO.\n * Absent: an enter comes from the right, an exit leaves to the left (the\n * page turns forward).\n */\n side?: AnimSide\n}\n\nexport interface Anim {\n enter?: AnimKind | AnimStep\n exit?: AnimKind | AnimStep\n idle?: IdleKind | null\n}\n\n/** Text animation vocabulary — entrance presets evaluated per unit. */\nexport type TextFxKind = 'fade' | 'rise' | 'pop' | 'blur' | 'typewriter'\nexport type TextFxUnit = 'block' | 'line' | 'word' | 'char'\nexport type TextFxDirection = 'forward' | 'reverse' | 'center'\n\n/**\n * Text entrance animation. When present it OWNS the entrance — the\n * clip's `enter` string is ignored (a spec with `unit: 'block'` is the\n * superset of the legacy presets); `exit` stays clip-level. Segmentation is\n * baked at lowering (deterministic doc-derived data), per-unit progress is\n * evaluated in ON_FRAME — pure f(t), so scrub/seek/chunk cold-seeks agree.\n */\nexport interface TextFxSpec {\n fx: TextFxKind\n /** What animates as one thing (default 'block' — the whole text). */\n unit?: TextFxUnit\n /** Unit start order (default 'forward'; 'center' ripples outward). */\n direction?: TextFxDirection\n /**\n * Seconds between unit starts. Defaults: typewriter 0.05, other kinds\n * 0.06 when unit ≠ block, else 0. Clamped at lowering so the whole\n * entrance fits the clip.\n */\n stagger?: number\n /** Per-unit seconds (default OVERLAY_TRANSITION_DUR); typewriter ignores it. */\n duration?: number\n}\n\n/**\n * A pose keyframe on an overlay/object clip (element motion).\n * `at` is CLIP-LOCAL OUTPUT seconds (0 = the clip's start), so poses ride\n * along when the clip moves. Values interpolate across the gap between poses\n * (ease-into per pose, the KeyframeTrack convention); a hold is two identical\n * poses. The clip's base transform is the value before the first pose, and\n * absent fields inherit it — a pose may move without resizing. Baked at\n * lowering into a clip-local keyframe track, sampled in ON_FRAME as pure\n * f(t): scrub, export and chunked server renders agree by construction.\n */\nexport interface MotionPose {\n /** Clip-local OUTPUT seconds. */\n at: number\n /** Anchor center as frame fractions (the transform.x/y convention). */\n x?: number\n y?: number\n /** Scale multiplier (the transform.scale convention). */\n scale?: number\n /** Degrees (the transform.rotation convention). */\n rotation?: number\n /** Opacity MULTIPLIER 0..1 on the clip's own alpha (default 1). */\n opacity?: number\n /** Arrival ease (@vosjs/timeline EASINGS name). Absent = the house motion ease. */\n ease?: string\n}\n\n/** Default pose-to-pose ease: a symmetric in-out (continuous motion between\n * poses, not a settle — the CapCut/keyframe convention). */\nexport const MOTION_EASE = 'power2.inOut'\n\nexport interface OverlayTransform {\n /**\n * Anchor CENTER as FRACTIONS of the output frame [0..1] (the zoom cx/cy\n * convention): 0.5/0.5 = frame center at ANY aspect ratio — positions\n * survive aspect switches (design px did not: the space's width changes\n * with the aspect, pushing clips off-frame).\n */\n x: number\n y: number\n /** Uniform scale multiplier on the preset size. */\n scale: number\n /** Rotation in degrees (screen-space, about the anchor). */\n rotation: number\n}\n\n/** Which side of its referent a pinned layer sits on. */\nexport type PinSide = 'auto' | 'right' | 'left' | 'below' | 'above'\n\n/** The standing mark a pinned layer leaves on its referent. */\nexport type PinMark = 'none' | 'ring' | 'underline'\n\n/**\n * A layer's REFERENT: the thing on the page it is about. A pinned layer is\n * placed beside that rect and follows it as the camera moves it on screen,\n * so the statement and its subject stay bound (proximity), optionally with\n * a hairline between them (a connector) and a standing mark on the subject.\n * One of `step`, `press` or `rect` names it. Resolved at lowering into the\n * clip's motion track through the frame's camera; `transform.x/y` stay the\n * author's fallback for a pin that cannot resolve. Not the re-record\n * `anchor` (a time tie the lowering never reads).\n */\nexport interface OverlayPin {\n /** A recorder step (`meta.steps[].id`, else its index) whose element\n * rect is the referent. */\n step?: string | number\n /** The press nearest this SOURCE second (a human recording has no\n * script): the referent is that click's element rect. */\n press?: number\n /** The referent itself, in normalised video fractions [0..1] (the zoom\n * cx/cy convention — never pixels). */\n rect?: Rect\n /** Absent = 'auto': the first of right, left, below, above whose box\n * fits inside the frame through the layer's life, else the roomiest. */\n side?: PinSide\n /** Design px between the referent and the layer's visible box (24). */\n gap?: number\n /** A standing mark on the referent for the layer's life (absent = none). */\n mark?: PinMark\n /** A hairline from the layer's near edge to the referent. */\n leader?: boolean\n /** The mark's and the leader's ink (CSS colour; absent = white over a\n * dark rim, legible on any ground). */\n color?: string\n}\n\ninterface OverlayClipBase {\n /** Stable identity for selection/editing in the timeline UI. */\n id: string\n /** OUTPUT-time span (seconds). */\n start: number\n duration: number\n transform: OverlayTransform\n /**\n * The layer's referent (see OverlayPin). A pinned layer's place is\n * computed from the pin; `motion` poses keep their scale, rotation and\n * opacity, and their x/y are ignored.\n */\n pin?: OverlayPin\n /**\n * How the clip enters and leaves (see Anim). Absent = the house motion:\n * a rise in, a fade out.\n */\n anim?: Anim\n /**\n * The template that placed this clip (a vos id, or a legacy marker),\n * provenance the lowering never reads: a loop drops a template's clips,\n * a re-apply replaces one template's and leaves the maker's.\n */\n from?: string\n /**\n * @deprecated `anim.enter` / `anim.exit`; migrated on read.\n */\n enter?: OverlayTransition\n exit?: OverlayTransition\n /**\n * Pose keyframes (see MotionPose): the clip's transform animated\n * over clip-local time, rendered as diamonds on the clip. Optional: absent\n * lowers byte-identically (no track in data).\n */\n motion?: MotionPose[]\n}\n\nexport interface TextOverlayClip extends OverlayClipBase {\n kind: 'text'\n /** Text content; '\\n' breaks lines. */\n text: string\n preset: TextOverlayPreset\n /** Font size override in design px (preset default when absent). */\n size?: number\n /** CSS color override (preset default when absent). */\n color?: string\n /**\n * Font family override — a catalog family name (GET /api/fonts). Unknown\n * names fail open: used verbatim with the preset stack as fallback.\n */\n family?: string\n /** Weight override — snapped to the nearest weight the catalog hosts. */\n weight?: number\n /** Synthesized oblique (no italic files are hosted). */\n italic?: boolean\n /** Multi-line alignment within the block (default center). */\n align?: 'left' | 'center' | 'right'\n /** Letter spacing in design px at the resolved size (default 0). */\n letterSpacing?: number\n /** Line height multiplier (default OVERLAY_LINE_HEIGHT). */\n lineHeight?: number\n /**\n * Legibility shadow strength 0..1 (the preset's when absent; 0 = none).\n * Words on a plate ground carry none: the shadow exists for words over\n * footage.\n */\n shadow?: number\n /** Text outline, drawn under the fill. */\n stroke?: TextOverlayStroke\n /** Background pill behind the text block (absent = none). */\n box?: TextOverlayBox\n /**\n * @deprecated A per-unit `anim.enter` step; migrated on read.\n */\n fx?: TextFxSpec\n /**\n * Wrap width as a FRACTION of the frame width [0.1..1] (the transform.x\n * convention — aspect-stable). Absent = no wrapping (lines break only on\n * explicit \\n). Wrapping is greedy over word tokens at measured widths; a\n * single token wider than the budget gets its own line (no intra-word\n * breaks). Tokens keep their trailing whitespace, so fx unit sequences\n * are IDENTICAL wrapped or not — entrances regroup, never recount.\n */\n maxWidth?: number\n}\n\nexport interface TextOverlayStroke {\n /** CSS stroke color. */\n color: string\n /** Stroke width in design px at the resolved size. */\n width: number\n}\n\n/**\n * Text background pill. Paddings and radius are EMs of the resolved font\n * size, so the pill scales with the text through size overrides, transform\n * scale and output resolution alike.\n */\nexport interface TextOverlayBox {\n /** CSS color of the pill. */\n color: string\n /** Extra opacity multiplier on top of the clip's fade alpha (default 1). */\n opacity?: number\n /** Horizontal padding in EMs (default 0.6). */\n paddingX?: number\n /** Vertical padding in EMs (default 0.35). */\n paddingY?: number\n /** Corner radius in EMs (default 0.25); clamped to half the pill height. */\n radius?: number\n}\n\n/**\n * Image/video overlay (V1b) — a media card on the overlay layer. `key` rides\n * the same resolution plumbing as source.videoKey / backgroundMedia.key\n * (blob URL in-session, /api/assets URL saved, take-dir path in CLI takes).\n * Sized by `width` (fraction of the FRAME width, aspect from the media) ×\n * transform.scale. Video time is clip-local (t − start), muted (soundtracks\n * belong to doc.audio), looping optional.\n */\nexport interface MediaOverlayClip extends OverlayClipBase {\n kind: 'image' | 'video'\n /**\n * The picture: an asset URL, a take-dir file, or a DOCUMENT MEDIA by\n * reference, `media:<id>` (`media:` alone is the primary), which brings\n * the media's facts with it (its cursor track, its recorded page) and\n * follows the media's kind. The lowering resolves the reference to the\n * media's own key; the document keeps the reference.\n */\n key: string\n /**\n * The layer as a CARD: drawn by the card painter on its own plane above\n * the primary card (the layered shadow, the media clipped to its corners,\n * a browser bar, the border, a lean, and for a document media its cursor\n * dot and click rings). Absent, the layer is the flat picture it always\n * was, byte-identically.\n */\n frame?: LayerCard\n /**\n * The card shadow. Absent = 'soft' — the baked look every\n * doc predating the field renders, so absence lowers byte-identically. 'strong' is the\n * hero float, 'none' the flat cutout.\n */\n shadow?: 'none' | 'soft' | 'strong'\n /**\n * An outline stroke drawn over the clipped media edge.\n * Absent = none. `width` in design px (scales with the canvas like\n * radius); any CSS color.\n */\n border?: { width: number; color: string }\n /** Base width as a fraction of the frame width [0..1]. Absent = 0.35. */\n width?: number\n /** Corner radius in design px. Absent = 12 (the house card radius). */\n radius?: number\n /** Opacity 0..1. Absent = 1. */\n opacity?: number\n /** Video only: loop while the clip is active. Absent = hold the last frame. */\n loop?: boolean\n}\n\n/**\n * A face an HTML layer sets type in that the CATALOG does not host, named by\n * URL. A catalog family needs no entry here: the lowering finds it from the\n * family the CSS names (`htmlLayerFaces`). The bytes are never stored: an SVG\n * loaded through `<img>` may fetch nothing, so the page fetches the face by\n * ordinary `fetch` at setup and inlines it into the picture.\n */\nexport interface HtmlOverlayFont {\n family: string\n url: string\n weight?: number\n style?: 'normal' | 'italic'\n}\n\n/**\n * A layer authored as DOM: markup and CSS, laid out and painted by the\n * browser itself, composited over the footage like any other picture.\n *\n * WHY IT IS SOURCE AND NOT A PICTURE. A product video points at a piece of\n * UI, and the camera is the only pointing device a recording has: a zoom\n * magnifies recorded pixels, so the thing the video is about becomes the\n * blurriest thing on screen. A layer rasterized FROM SOURCE at the size each\n * frame asks for has no such ceiling.\n *\n * WHY THE SOURCE CANNOT BE BAKED INTO A KEY. An SVG carrying a\n * `<foreignObject>` is origin-clean ONLY as a `data:` URI (served from a URL\n * it taints the canvas even same-origin, and a tainted frame cannot be\n * uploaded as a WebGL texture, so every layer disappears), and a `data:` URI\n * in the document costs tens of kilobytes per face per layer. Keeping the\n * SOURCE here and building the picture in the page is small in the\n * document, inline at draw time, and vector at any export size.\n *\n * It draws its OWN chrome: shadow, radius, border and any bar are the CSS's,\n * so the compositor adds none (`shadow`, `radius`, `border`, `frame` are\n * refused by the lint). It rides every other layer gesture unchanged: the\n * timing, the fractional transform, `anim`, `motion`, z-order.\n */\nexport interface HtmlOverlayClip extends OverlayClipBase {\n kind: 'html'\n /** The markup. Well-formed XML: `foreignObject` is XML, not HTML. */\n html: string\n /** The CSS. Scoped by the layer's own wrapper, so plain selectors are fine. */\n css?: string\n /** The design box the markup is laid out in, in 1080p design px. */\n box: { width: number; height: number }\n /**\n * Design px of room around the box for what paints OUTSIDE it: a shadow,\n * a glow, a blur. The SVG viewport clips hard, so without room a shadow is\n * sliced square at the edge. Absent = derived from the CSS at lowering\n * (`htmlLayerBleedFor`); stated, it wins.\n */\n bleed?: number\n /** Faces the CSS names that the catalog does not host, by URL. */\n fonts?: HtmlOverlayFont[]\n /**\n * Base width as a fraction of the FRAME width. Absent = the design size,\n * the picture box (the design box plus its bleed) over 1920, so a\n * component designed at 452 px lands at 452 px in a 1080p frame.\n */\n width?: number\n /**\n * A LIVE layer: the picture is a function of clip-local time. CSS\n * `@keyframes` inside it are scrubbed to t (paused, delayed by -t), and\n * `{{t}}` / `{{data.<name>}}` placeholders in the markup and CSS fill per\n * frame, so a counter, a progress bar or a typed line come out on the\n * timeline's clock in the preview and in every export chunk alike. One\n * rasterize per frame while on screen. Absent = a still, byte-identical\n * to before the field existed.\n */\n live?: boolean\n /** Values for a live layer's `{{data.<name>}}` placeholders. */\n data?: Record<string, string | number | boolean>\n /** Opacity 0..1. Absent = 1. */\n opacity?: number\n}\n\nexport type OverlayClip = TextOverlayClip | MediaOverlayClip | HtmlOverlayClip\n\n/**\n * Does this layer's picture come from a KEY? True for an asset, a take-dir\n * file or a document media; false for words and for an HTML layer, which\n * carry their own content.\n *\n * A predicate rather than `kind !== 'text'`, because that test silently\n * became wrong the moment a second contentful kind existed, and the way it\n * goes wrong is that a re-host tries to re-host something that has no key.\n */\nexport function isKeyedOverlay(c: OverlayClip): c is MediaOverlayClip {\n return c.kind === 'image' || c.kind === 'video'\n}\n\n/**\n * A layer's own card. The bar is ALLOWED on any card and opens off; its\n * address defaults to the media's recorded page when the layer shows a\n * document media. The lean is the tilt convention, in degrees.\n */\nexport interface LayerCard {\n browserBar?: Partial<BrowserBarStyle>\n lean?: { rx: number; ry: number }\n /** The layered shadow's strength; absent = LAYER_CARD_SHADOW. */\n shadow?: number\n shadowContact?: number\n shadowColor?: string\n /** Draw the media's cursor and clicks inside the card; absent = yes when it has a track. */\n cursor?: boolean\n}\nexport const LAYER_CARD_SHADOW = 0.32\n/** The prefix of a media overlay key that names a document media. */\nexport const MEDIA_KEY_PREFIX = 'media:'\n\n/**\n * Ceiling on `transform.scale` for text overlays, shared by the canvas box\n * and the panel so the two can never disagree about where growth stops (a\n * 64px title at 8× is a 512px hero word — past that it is a poster, not a\n * caption). The floor is 0.1 in both places.\n */\nexport const OVERLAY_SCALE_MAX = 8\nexport const OVERLAY_MEDIA_DEFAULT_WIDTH = 0.35\nexport const OVERLAY_MEDIA_DEFAULT_RADIUS = 12\n\n/**\n * World-space object clips (compositor v2). These shapes are\n * DRAFTED AS THE FUTURE ENGINE SPEC: field names, asset-ref shape, and transform\n * convention carry to `objects?: ObjectConfig[]` upstream unchanged — today they\n * run interpreter-side (ON_FRAME reconciles meshes from ctx.data; live\n * SET_DATA add/remove), and a later engine release swaps the construction site into the engine.\n *\n * Conventions (agent-facing units match the rest of the doc):\n * - position x/y = FRACTIONS of the frame [0..1] (the overlay/zoom\n * convention), z = world units TOWARD the camera from the card plane\n * (0 = on the card's depth; 0.5 floats clearly in front).\n * - scale = fraction of the FRAME HEIGHT the object's unit size occupies.\n * - span (OUTPUT seconds) gates visibility with soft edge fades; absent =\n * the whole timeline.\n */\nexport type ObjectPrimitiveShape = 'cube' | 'sphere' | 'torus' | 'knot'\n\n/**\n * 3D-text material presets — fleet-audited: everything single-sided,\n * no `dispersion`, transmission only single-sided (the documented\n * SwiftShader constraints). Resolved to plain material params at lowering.\n */\nexport type Text3dMaterial = 'standard' | 'metal' | 'glass' | 'neon'\n\nexport type ObjectAsset =\n /** Curated primitive props — fleet-safe, no asset fetch. */\n | { kind: 'primitive'; shape: ObjectPrimitiveShape; color?: string }\n /** GLB by key — accepted in the schema for forward compat with the engine spec; loads in a later slice. */\n | { kind: 'gltf'; key: string }\n /**\n * Extruded 3D text from a hosted typeface JSON. `typeface` is a\n * catalog slug or family name (GET the list from the typeface catalog;\n * unknown names fall back to the house face). `depth` is the extrusion as\n * a fraction of the glyph height (default 0.25); `bevel` defaults on.\n */\n | {\n kind: 'text3d'\n text: string\n typeface?: string\n material?: Text3dMaterial\n color?: string\n depth?: number\n bevel?: boolean\n }\n\n/** Curated motion presets — pure f(t), deterministic. */\nexport type ObjectAnimation = 'spin' | 'float'\n\n/**\n * A pose keyframe on a 3D object clip (the MotionPose model over\n * transform3d). `at` is CLIP-LOCAL OUTPUT seconds from the clip's span start\n * (0 when the clip has no span). Absent fields inherit the base transform3d;\n * `spin`/`float` presets compose ADDITIVELY on top of the sampled pose.\n */\nexport interface MotionPose3D {\n at: number\n /** Frame fractions (the transform3d.x/y convention). */\n x?: number\n y?: number\n /** World units toward the camera from the card plane. */\n z?: number\n /** Euler degrees. */\n rx?: number\n ry?: number\n rz?: number\n /** Fraction of the frame height. */\n scale?: number\n /** Arrival ease (@vosjs/timeline EASINGS name). Absent = the house motion ease. */\n ease?: string\n}\n\nexport interface ObjectClip {\n id: string\n asset: ObjectAsset\n /** OUTPUT-time visibility span; absent = always. */\n span?: { start: number; duration: number }\n transform3d: {\n x: number\n y: number\n /** World units toward the camera from the card plane. */\n z: number\n /** Euler degrees. */\n rx: number\n ry: number\n rz: number\n /** Fraction of the frame height. */\n scale: number\n }\n /** What the prop does while it stays (`anim.idle`), and how it comes and goes. */\n anim?: Anim\n /** The template that placed this prop (see the overlay clip's `from`). */\n from?: string\n /** @deprecated `anim.idle`; migrated on read. */\n animation?: ObjectAnimation | null\n /** Pose keyframes (see MotionPose3D). Absent lowers byte-identically. */\n motion?: MotionPose3D[]\n}\n\nexport const OBJECT_DEFAULT_SCALE = 0.18\n\n/** Enter/exit transition length in seconds (pure f(t) in ON_FRAME). */\nexport const OVERLAY_TRANSITION_DUR = 0.35\n/** Line height multiplier for multi-line text overlays. */\nexport const OVERLAY_LINE_HEIGHT = 1.25\nexport const OVERLAY_MIN_DURATION = 0.2\n\n/**\n * The editable project state. An app-level convention that *lowers to* a vos\n * Composition (it is NOT vos core). Fully serializable.\n */\nexport interface ProjectDoc {\n source: {\n videoKey: string\n cursor: CursorTrack\n meta: RecordingMeta\n /** object URL for a separately-recorded webcam track, if the take had a camera. */\n camKey?: string\n /**\n * object URL for the separately-recorded microphone sidecar (AT split).\n * When present the recording's own audio track (hasAudio) is SYSTEM/tab\n * audio and micGain governs this sidecar; absent on legacy takes, where\n * the recording's track is the old record-time mix.\n */\n micKey?: string\n /**\n * Decode strategy for the recording (vos VideoElement.frameSource):\n * 'webcodecs' (frame-accurate, MP4), 'html5' (robust, any format), 'auto'.\n * Defaults to 'auto' in lowering. Dev uploads use 'html5'; the B2 recorder\n * (known-good MP4) uses 'webcodecs'.\n */\n frameSource?: 'auto' | 'webcodecs' | 'html5'\n /**\n * 'image' = videoKey points at a still (screenshot) shown for the doc's\n * whole duration — the full editing stack (frame, browser bar, zoom, export)\n * applies unchanged. Defaults to 'video'.\n */\n sourceKind?: 'video' | 'image'\n /**\n * drawImage source rect (capture px) for window takes: the viewport's rect\n * inside the captured frame, derived once at doc build (normalizeCaptureSpace)\n * — crops the real browser chrome out of the footage so the synthetic\n * browser bar applies. When set, cursor track + meta dims are already in\n * crop space. Absent = draw the full frame.\n */\n crop?: Rect\n /**\n * The derived viewport crop + the full capture dims it was cut from — kept\n * even while the \"Original\" frame mode shows the uncropped window, so the\n * crop can be re-applied losslessly (docToCropSpace/docToFullSpace remap\n * cursor/zoom/meta between the two spaces; the footage itself always holds\n * the full frame). Present ⟺ crop derivation succeeded at ingest.\n */\n chromeCrop?: { rect: Rect; frameW: number; frameH: number }\n }\n /**\n * Kept SOURCE-time spans (@vosjs/timeline `Segment`s); the output timeline is\n * their concatenation — trim/split/cut are all segment edits. Canonical form\n * is one full-source segment; an empty list is tolerated and means \"untrimmed\".\n */\n segments: DocSegment[]\n /**\n * The OTHER media this take carries (concat, a layer's source): each the\n * `source` shape with an `id`, named by a segment's or a span's `media`.\n * The primary stays `source` (the sequence, the camera and the cut are\n * its). Absent = one media; a document with none lowers byte-identically.\n */\n media?: Media[]\n /**\n * The frame that stands for this document (OUTPUT seconds): the cover the\n * shelf shows, the still a kit leads with. Absent, `docStillTime` derives\n * it: the document's own last freeze (a poster's rest), else the hero\n * moment after the opening has entered. A template never sets it.\n */\n still?: number\n /**\n * Speed-change spans (SOURCE time, footage-anchored — see SpeedSpan).\n * Optional for backward compatibility with persisted docs; absent = all 1×.\n */\n speed?: SpeedSpan[]\n /**\n * Freezes (SOURCE moments, footage-anchored — see FreezeSpan), the\n * other retime primitive. Absent = none; a legacy segment `hold` migrates\n * here on read.\n */\n freeze?: FreezeSpan[]\n /** Zoom regions (SOURCE time, footage-anchored, non-overlapping — see ZoomSpan). */\n zoom: ZoomSpan[]\n /**\n * Camera style — one named strategy preset driving BOTH the auto-zoom\n * planner and the camera motion (ramps/eases/pans/follow; see zoomStyle.ts).\n * Absent = DEFAULT_ZOOM_STYLE.\n */\n zoomStyle?: ZoomStyleName\n /**\n * Per-doc overrides on top of the named style — the \"Custom\" seam for\n * agents/doc.json (the studio shows Custom while any override is present;\n * picking a named style clears them). Span edits do NOT set this: the style\n * describes camera dynamics, spans are content.\n */\n zoomParams?: Partial<ZoomStyleParams>\n /**\n * Per-doc overrides for the auto-speed planner: idle/typing/scroll\n * thresholds and rates. Absent = DEFAULT_SPEED_PARAMS.\n */\n speedParams?: Partial<SpeedParams>\n /**\n * Tilt regions (SOURCE time, footage-anchored, non-overlapping — see\n * TiltSpan). Optional: absent lowers byte-identically (no tiltTrack in data).\n */\n tilt?: TiltSpan[]\n /**\n * Dynamic-tilt wand intensity (planAutoTilt — the auto-zoom wand contract:\n * regenerate replaces only `source:'auto'` spans). 'off'/absent = the wand\n * is off; manual tilt spans work either way.\n */\n tiltStyle?: TiltStyleName\n /**\n * Deleted planner proposals (SOURCE time, one lane each — see\n * RejectedSpan): a re-plan never proposes a span that lands on one. Absent\n * = nothing rejected; lowers byte-identically (the renderer never reads it).\n */\n rejected?: RejectedSpan[]\n /** music/SFX clips on the output timeline (see AudioClip anchoring note). */\n audio: AudioClip[]\n /**\n * Master gain for the VOICE, 0..1. Absent = 1. With a mic sidecar\n * (source.micKey) this governs the sidecar; on legacy takes it governs the\n * recording's own (mixed) track.\n */\n micGain?: number\n /**\n * Master gain for the recording's own SYSTEM/tab audio track, 0..1. Absent\n * = 1. Only meaningful on split takes (source.micKey present) — legacy\n * takes have one track and one fader (micGain).\n */\n systemGain?: number\n cursor: CursorStyle\n cam: CamStyle\n /**\n * Cam pose regions (SOURCE time, footage-anchored, non-overlapping — see\n * CamPoseSpan). The bubble morphs to a span's pose and back to the rest\n * pose (doc.cam). Optional: absent lowers byte-identically (no camTrack\n * in data). Only renders when the take has a cam track (source.camKey).\n */\n camMotion?: CamPoseSpan[]\n frame: FrameStyle\n /**\n * Card presentation (tilt / entrance) — compositor v2. Optional: absent\n * lowers byte-identically to a pre-v2 doc and renders pixel-identically.\n */\n /**\n * The end card the clip closes on: a hold on the last frame, the card\n * receding, the words rising. Absent = the clip ends where the footage\n * ends.\n */\n endCard?: EndCard\n /**\n * Screen-space overlay clips (text; later image/video) — compositor v2.\n * OUTPUT-anchored spans on the overlay layer. Optional: absent lowers\n * byte-identically to a doc predating overlays.\n */\n overlays?: OverlayClip[]\n /**\n * World-space object clips (interpreter-side; the drafted engine spec).\n * Optional: absent lowers byte-identically.\n */\n objects?: ObjectClip[]\n export: {\n resolution: ExportResolution\n fps: 30 | 60\n format: 'mp4'\n }\n}\n\n/** Export quality presets — each names the SHORT edge of the output (see resolveExportSize). */\nexport type ExportResolution = '720p' | '1080p' | '2k' | '4k'\n\nexport const EXPORT_SHORT_EDGE: Record<ExportResolution, number> = {\n '720p': 720,\n '1080p': 1080,\n '2k': 1440,\n '4k': 2160,\n}\n\n/** Presets in ascending quality order (picker order; recommendedExportResolution walks it). */\nexport const EXPORT_RESOLUTION_OPTIONS: ExportResolution[] = [\n '720p',\n '1080p',\n '2k',\n '4k',\n]\n\n/**\n * Default ON (ripple + press, medium, neutral): click emphasis is the point of\n * the product, and the wrong-window/coverage gates already drop the cursor\n * track (and with it every click) on takes where effects would misfire.\n */\nexport const DEFAULT_CLICK_FX: ClickFxStyle = {\n style: 'ripple',\n press: true,\n intensity: 'medium',\n color: 'auto',\n}\n\nexport const DEFAULT_CURSOR_STYLE: CursorStyle = {\n visible: true,\n smoothing: 0.15,\n size: 24,\n // The dot every earlier take was cut with; a NEW take opens on the\n // arrow (projectFromArtifact sets it), so a document that stores\n // 'default' keeps its dot.\n style: 'default',\n hideWhenIdle: true,\n clickFx: DEFAULT_CLICK_FX,\n}\n\nexport const DEFAULT_CAM_STYLE: CamStyle = {\n visible: true,\n position: 'bottom-left',\n size: 0.25,\n shape: 'circle',\n mirror: true,\n}\n\nexport const DEFAULT_BROWSER_BAR: BrowserBarStyle = {\n kind: 'none',\n url: '',\n showUrl: true,\n showControls: true,\n height: 44,\n}\n\n/**\n * The frame with no backdrop: the card, its chrome and a flat ground. A\n * host that opens new takes on a backdrop builds its own opening frame\n * with `withBackdrop` (backdrop.ts) and hands it to `projectFromArtifact`,\n * so the pick never lives in the document model.\n */\nexport const BASE_FRAME_STYLE: FrameStyle = {\n // Brand default: signal red → amber (sunset warmth; deliberately not AI-purple).\n background: 'linear-gradient(135deg, #ff5148, #ffb03a)',\n padding: 48,\n radius: 12,\n shadow: 0.4,\n border: 0,\n // The stage camera: a new take's zoom centres its target and shows the\n // ground past the card's edge. A document without the field keeps the\n // clamped magnifier it was cut with (see FrameStyle.camera).\n camera: 'stage',\n // 'native' = the recording's own aspect ratio (meta.width/height). See ASPECT_RATIOS.\n aspectRatio: 'native',\n browserBar: DEFAULT_BROWSER_BAR,\n}\n\n/**\n * The frame a NEW take opens on when the host names none: the bare frame.\n * Every ingest path spreads the frame it is handed (or this one), so a\n * host's pick reaches the extension handoff, the in-page recorder, a\n * dropped file and `vos record` through one argument; a doc that already\n * carries a frame keeps it.\n */\nexport const DEFAULT_FRAME_STYLE: FrameStyle = BASE_FRAME_STYLE\n\n/** Border alpha applied when the Frame-border toggle turns on. */\nexport const FRAME_BORDER_DEFAULT = 0.35\n\n/**\n * The border a doc that names no width/colour is drawn with: the hairline\n * white stroke that was hard-coded in ON_FRAME before the two knobs existed,\n * so every take made before them renders byte-identically after.\n */\nexport const FRAME_BORDER_WIDTH_DEFAULT = 1.5\nexport const FRAME_BORDER_COLOR_DEFAULT = '#ffffff'\n\n/**\n * How far past the card's rounded clip the footage and the browser-bar fill\n * are drawn, in device pixels, so the clip is the ONE edge the eye sees.\n * A clipped drawImage whose destination rect lands exactly on the clip path\n * anti-aliases that edge twice wherever it falls on a fractional device\n * pixel (the studio canvas is the fitted box at device pixel ratio, and any\n * zoom rescales everything about its focus), leaving a partially covered\n * seam pixel along every straight edge that the arc, drawn by the clip\n * alone, never has. One pixel covers the seam and changes the footage scale\n * by less than a part in a thousand. Read by ON_FRAME; the layout tests\n * expect the destination rect to be the mirror's grown by this much.\n */\nexport const CARD_EDGE_OVERDRAW = 1\n\n/**\n * The realistic browser-bar kind matching the recorder's OS — seeds Default\n * mode so the synthetic chrome looks native to where the take was recorded\n * (light variants: browsers default light). Windows/Linux get the windows\n * chrome; when the platform is unknown — a direct upload with no browser\n * information — we default to macOS.\n */\nexport function platformBarKind(\n platform: RecordingMeta['platform'],\n): BrowserBarStyle['kind'] {\n return platform === 'windows' || platform === 'linux'\n ? 'windows-light'\n : 'mac-light'\n}\n\n/**\n * Address-pill display text for a recorded page URL: hostname (www. stripped)\n * plus a non-root path. Empty for non-http(s) or unparsable URLs.\n */\nexport function pageDisplayUrl(pageUrl: string | undefined): string {\n if (!pageUrl) return ''\n try {\n const u = new URL(pageUrl)\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return ''\n const host = u.hostname.replace(/^www\\./, '')\n return u.pathname && u.pathname !== '/' ? host + u.pathname : host\n } catch {\n return ''\n }\n}\n\n/** Output aspect-ratio presets (id used as FrameStyle.aspectRatio). Ordered for the picker. */\nexport interface AspectRatioOption {\n id: string\n label: string\n}\n\nexport const ASPECT_RATIOS: AspectRatioOption[] = [\n { id: 'native', label: 'Native' },\n { id: '21:9', label: '21:9' },\n { id: '16:9', label: '16:9' },\n { id: '16:10', label: '16:10' },\n { id: '3:2', label: '3:2' },\n { id: '4:3', label: '4:3' },\n { id: '1:1', label: '1:1' },\n { id: '3:4', label: '3:4' },\n { id: '2:3', label: '2:3' },\n { id: '10:16', label: '10:16' },\n { id: '9:16', label: '9:16' },\n]\n\n/** Numeric width/height ratio for an aspect-ratio id; 'native' resolves from the source meta. */\nexport function aspectRatioValue(\n id: string,\n meta: { width: number; height: number },\n): number {\n const nativeRatio = (meta.width || 16) / (meta.height || 9)\n if (!id || id === 'native') return nativeRatio\n const [w, h] = id.split(':').map(Number)\n return w > 0 && h > 0 ? w / h : nativeRatio\n}\n\n/**\n * Resolve the export pixel dimensions from the chosen aspect ratio + quality. The quality\n * (`export.resolution`) is the SHORT edge (720/1080/1440/2160), so 16:9 @ 1080p→1920×1080,\n * 9:16 @ 4k→2160×3840, 1:1 @ 1080p→1080×1080. Dimensions are rounded to even numbers\n * (H.264 requires it). Unknown values (hand-edited doc.json) fall back to 1080p.\n */\nexport function resolveExportSize(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'export'>,\n resolution: ExportResolution = doc.export.resolution,\n): { width: number; height: number } {\n return exportSizeFor(\n aspectRatioValue(doc.frame.aspectRatio, doc.source.meta),\n resolution,\n )\n}\n\n/**\n * The quality-preset → pixels math, free of any document. Every product's\n * export UI resolves its dimensions through this one function (the shared\n * ExportDialog included), so a preset name means the same thing everywhere:\n * before it existed the web app carried three private resolution tables that\n * disagreed about what \"2K\" was.\n */\nexport function exportSizeFor(\n ratio: number,\n resolution: ExportResolution,\n): { width: number; height: number } {\n // Widened index: hand-edited doc.json can carry values outside the union.\n const short =\n (EXPORT_SHORT_EDGE as Record<string, number | undefined>)[resolution] ??\n 1080\n const even = (n: number) => {\n const r = Math.round(n)\n return r % 2 ? r + 1 : r\n }\n const safe = ratio > 0 && Number.isFinite(ratio) ? ratio : 16 / 9\n return safe >= 1\n ? { width: even(short * safe), height: even(short) }\n : { width: even(short), height: even(short / safe) }\n}\n\n/**\n * One of a take's OTHER media (`doc.media`): the `source` shape (a media\n * plus the facts only a capture has) with the `id` a segment or a span\n * names. The primary media has no id; it is `doc.source`.\n */\n/**\n * The CARD-owned frame fields, what a media may carry of its own: its\n * placement and size (`inset`, per-side fractions of the frame), the bar,\n * the corner, the shadow, the border, the cover fit and its focus. The\n * frame-wide fields (the aspect, the padding, the ground, the backdrop, the\n * card's animation) stay the take's: one frame, many cards.\n */\nexport const MEDIA_FRAME_KEYS = [\n 'browserBar',\n 'radius',\n 'shadow',\n 'shadowContact',\n 'shadowColor',\n 'inset',\n 'border',\n 'borderWidth',\n 'borderColor',\n 'fit',\n 'focus',\n 'focusFollow',\n] as const\nexport type MediaFrameKey = (typeof MEDIA_FRAME_KEYS)[number]\n/** What a media says about its own card; absent fields fall to the take's frame. */\nexport type MediaFrame = Partial<\n Omit<Pick<FrameStyle, MediaFrameKey>, 'browserBar'>\n> & { browserBar?: Partial<BrowserBarStyle> }\nexport type Media = ProjectDoc['source'] & { id: string; frame?: MediaFrame }\n","/**\n * The callout grammar — three shapes a viewer learns once, composed from the\n * product's own register so the card is unmistakably the video's and still\n * the product's.\n *\n * The rule set (the distinguishability principle: share the product's hue\n * and type, contrast in value, elevation, scale and place):\n * - VALUE INVERSION: a light app gets a dark card in the product's hue, a\n * dark app a light one; the accent is kept for the kicker and the dot.\n * - ELEVATION: a real shadow plus a hairline of light on the edge.\n * - SCALE: the title is set for the render, CALLOUT_TYPE_RATIO times the\n * app's body as it appears on screen at the layer's start (the camera\n * scales the app, so the caller passes that scale), never the app's own\n * size.\n * - MOTION: a rise in, a fade out.\n * - PLACE is the pin's (lower/pin.ts), not the card's.\n *\n * Pure: hex in, markup and CSS out. The starters are this grammar in the\n * house register; `vos callout` is it in the take's BRAND.md.\n */\nimport type { HtmlOverlayClip, OverlayPin } from './types'\n\nexport type CalloutShape = 'tag' | 'note' | 'code'\nexport const CALLOUT_SHAPES: readonly CalloutShape[] = ['tag', 'note', 'code']\n\n/** The title's size over the app's body size as seen on screen. */\nexport const CALLOUT_TYPE_RATIO = 1.35\n/** The app's body size when nothing says otherwise (CSS px at the viewport). */\nexport const CALLOUT_DEFAULT_BODY_PX = 14\n\n/** The product's register: what a card shares with the app. */\nexport interface CalloutRegister {\n /** The app's ground (its page background), a hex. Decides the inversion. */\n ground: string\n /** The product's accent, a hex: the kicker, the dot, the bar. */\n accent: string\n /** The words face: a hosted catalog family or a system stack. */\n face?: string\n /** The code face. */\n mono?: string\n /** The app's body type size in CSS px at the recorded viewport. */\n body?: number\n}\n\nexport interface CalloutWords {\n /** A short label above the title, set in the accent (a tag is only this). */\n kicker?: string\n title?: string\n /** One or two lines under the title. */\n body?: string\n /** The payload, kept as typed (lines break on \\n). */\n code?: string\n}\n\n/** The resolved look: colours and sizes in design px. */\nexport interface CalloutLook {\n light: boolean\n cardGround: string\n cardInk: string\n cardMuted: string\n hairline: string\n accent: string\n shadow: string\n face: string\n mono: string\n kickerPx: number\n titlePx: number\n bodyPx: number\n codePx: number\n radius: number\n padX: number\n padY: number\n}\n\nexport const HOUSE_REGISTER: CalloutRegister = {\n ground: '#ffffff',\n accent: '#7c3aed',\n}\n\nconst DEFAULT_FACE = \"'Inter',-apple-system,system-ui,sans-serif\"\nconst DEFAULT_MONO = \"'JetBrains Mono',ui-monospace,monospace\"\n\n/**\n * Resolve a register into a look. `scale` is design px per CSS px of the\n * app as it appears at the layer's start (the card's footage scale times\n * the camera's level); 1 when unknown.\n */\nexport function calloutLook(register: CalloutRegister, scale = 1): CalloutLook {\n const ground = hexToRgb(register.ground) ?? [255, 255, 255]\n const accent = hexToRgb(register.accent) ?? [124, 58, 237]\n const light = relativeLuminance(ground) > 0.4\n const [h, s] = rgbToHsl(accent)\n // The card takes the product's hue at the opposite value: near-black or\n // near-white, tinted just enough to belong to the app.\n const cardGround = light\n ? hslToHex(h, Math.min(0.35, s * 0.6), 0.07)\n : hslToHex(h, Math.min(0.25, s * 0.35), 0.97)\n const bodyPx = register.body ?? CALLOUT_DEFAULT_BODY_PX\n const titlePx = Math.max(20, Math.round(CALLOUT_TYPE_RATIO * bodyPx * scale))\n return {\n light,\n cardGround,\n cardInk: light ? '#fafafa' : '#101012',\n cardMuted: light ? 'rgba(255,255,255,0.64)' : 'rgba(16,16,18,0.6)',\n hairline: light ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.1)',\n accent: rgbToHex(accent),\n shadow: '0 24px 60px rgba(0,0,0,0.35), 0 2px 6px rgba(0,0,0,0.16)',\n face: register.face ? quoteFamily(register.face) : DEFAULT_FACE,\n mono: register.mono ? quoteFamily(register.mono) : DEFAULT_MONO,\n kickerPx: Math.max(11, Math.round(titlePx * 0.55)),\n titlePx,\n bodyPx: Math.max(15, Math.round(titlePx * 0.75)),\n codePx: Math.max(14, Math.round(titlePx * 0.68)),\n radius: 16,\n padX: 24,\n padY: 20,\n }\n}\n\nexport interface CalloutBox {\n width: number\n height: number\n}\n\n/**\n * The design box a shape needs for its words: one line for a tag, the\n * kicker, title and wrapped body for a note, the lines for code. Widths\n * estimate glyphs at 0.56 em for words and 0.62 em for mono.\n */\nexport function calloutBox(\n shape: CalloutShape,\n look: CalloutLook,\n words: CalloutWords,\n): CalloutBox {\n const em = (px: number, chars: number, adv: number) =>\n Math.ceil(px * adv * chars)\n if (shape === 'tag') {\n const text = words.kicker ?? words.title ?? ''\n const w = em(look.kickerPx * 1.1, text.length, 0.62) + 14 + 18 * 2\n return {\n width: clamp(w, 120, 720),\n height: Math.max(36, Math.round(look.kickerPx * 1.1 * 2.6)),\n }\n }\n if (shape === 'code') {\n const lines = (words.code ?? '').split('\\n')\n const longest = Math.max(1, ...lines.map((l) => l.length))\n const lineH = Math.round(look.codePx * 1.7)\n const w = em(look.codePx, longest, 0.62) + look.padX * 2\n const h =\n lines.length * lineH +\n look.padY * 2 +\n (words.kicker ? look.kickerPx * 2 : 0)\n return { width: clamp(w, 360, 820), height: clamp(h, 72, 640) }\n }\n const title = words.title ?? ''\n const body = words.body ?? ''\n const titleW = em(look.titlePx, title.length, 0.56)\n const width = clamp(Math.max(titleW, 320) + look.padX * 2, 360, 640)\n const inner = width - look.padX * 2\n const bodyLines = body\n ? Math.max(1, Math.ceil(em(look.bodyPx, body.length, 0.54) / inner))\n : 0\n const titleLines = Math.max(1, Math.ceil(titleW / inner))\n const height =\n look.padY * 2 +\n (words.kicker ? Math.round(look.kickerPx * 1.6) : 0) +\n titleLines * Math.round(look.titlePx * 1.25) +\n (bodyLines ? 8 + bodyLines * Math.round(look.bodyPx * 1.5) : 0)\n return { width, height: clamp(height, 64, 480) }\n}\n\n/** The markup and CSS for a shape in a look, sized to a box. */\nexport function calloutSource(\n shape: CalloutShape,\n look: CalloutLook,\n words: CalloutWords,\n box: CalloutBox,\n): { html: string; css: string } {\n const base =\n `box-sizing:border-box;width:${box.width}px;height:${box.height}px;` +\n `background:${look.cardGround};color:${look.cardInk};` +\n `border:1px solid ${look.hairline};border-radius:${look.radius}px;` +\n `box-shadow:${look.shadow};-webkit-font-smoothing:antialiased;`\n if (shape === 'tag') {\n const text = esc(words.kicker ?? words.title ?? '')\n return {\n html: `<div class=\"tag\"><span class=\"dot\"></span><span class=\"t\">${text}</span></div>`,\n css:\n `.tag{font-family:${look.face};display:flex;align-items:center;gap:10px;${base}border-radius:999px;padding:0 18px;background:${look.accent};color:#fff;border-color:rgba(255,255,255,0.18);font-size:${Math.round(look.kickerPx * 1.1)}px;font-weight:600;letter-spacing:0.02em;text-transform:uppercase;white-space:nowrap}` +\n `.dot{width:8px;height:8px;border-radius:99px;background:#fff;opacity:0.9;flex:none}`,\n }\n }\n if (shape === 'code') {\n const lines = (words.code ?? '')\n .split('\\n')\n .map((l) =>\n /^\\s*(\\/\\/|#|\\$)/.test(l)\n ? `<span class=\"mut\">${esc(l)}</span>`\n : esc(l),\n )\n .join('\\n')\n const kicker = words.kicker\n ? `<div class=\"k\">${esc(words.kicker)}</div>`\n : ''\n return {\n html: `<div class=\"code\">${kicker}<pre class=\"p\">${lines}</pre></div>`,\n css:\n `.code{font-family:${look.mono};${base}padding:${look.padY}px ${look.padX}px;font-size:${look.codePx}px;line-height:1.7;letter-spacing:-0.01em}` +\n `.k{font-family:${look.face};font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin:0 0 ${Math.round(look.kickerPx * 0.9)}px}` +\n `.p{margin:0;white-space:pre;font:inherit}` +\n `.mut{color:${look.cardMuted}}`,\n }\n }\n const kicker = words.kicker\n ? `<div class=\"k\"><span class=\"dot\"></span>${esc(words.kicker)}</div>`\n : ''\n const body = words.body ? `<div class=\"b\">${esc(words.body)}</div>` : ''\n return {\n html: `<div class=\"note\">${kicker}<div class=\"t\">${esc(words.title ?? '')}</div>${body}</div>`,\n css:\n `.note{font-family:${look.face};${base}padding:${look.padY}px ${look.padX}px;display:flex;flex-direction:column;justify-content:center}` +\n `.k{display:flex;align-items:center;gap:8px;font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin-bottom:${Math.round(look.kickerPx * 0.6)}px}` +\n `.dot{width:7px;height:7px;border-radius:99px;background:${look.accent};box-shadow:0 0 10px ${look.accent}}` +\n `.t{font-size:${look.titlePx}px;font-weight:600;line-height:1.25;letter-spacing:-0.01em}` +\n `.b{margin-top:8px;font-size:${look.bodyPx}px;line-height:1.5;color:${look.cardMuted}}`,\n }\n}\n\nexport interface CalloutClipOptions {\n id: string\n /** OUTPUT seconds. */\n start: number\n duration: number\n /** Design px per CSS px of the app on screen at `start` (1 = unknown). */\n scale?: number\n pin?: OverlayPin\n /** The fallback place when the pin cannot resolve (frame fractions). */\n at?: { x: number; y: number }\n}\n\n/** A callout as an html overlay clip: the grammar applied to a register. */\nexport function calloutClip(\n shape: CalloutShape,\n register: CalloutRegister,\n words: CalloutWords,\n opts: CalloutClipOptions,\n): HtmlOverlayClip {\n const look = calloutLook(register, opts.scale ?? 1)\n const box = calloutBox(shape, look, words)\n const { html, css } = calloutSource(shape, look, words, box)\n return {\n id: opts.id,\n kind: 'html',\n start: opts.start,\n duration: opts.duration,\n html,\n css,\n box,\n transform: {\n x: opts.at?.x ?? 0.5,\n y: opts.at?.y ?? 0.5,\n scale: 1,\n rotation: 0,\n },\n anim: { enter: 'rise', exit: 'fade' },\n ...(opts.pin ? { pin: opts.pin } : {}),\n }\n}\n\n/**\n * The card's ground as its CSS declares it (the root rule's background),\n * for the picture check that compares it with the footage it covers. Null\n * when the CSS names none in a form a hex can be read from.\n */\nexport function calloutGroundOf(css: string): string | null {\n const m =\n /background(?:-color)?\\s*:\\s*(#[0-9a-fA-F]{3,8}|rgba?\\([^)]*\\))/.exec(css)\n if (!m) return null\n const rgb = parseCssColour(m[1])\n return rgb ? rgbToHex(rgb) : null\n}\n\n// --- colour ---------------------------------------------------------------\n\nexport type Rgb = [number, number, number]\n\nexport function hexToRgb(hex: string): Rgb | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim())\n if (!m) return null\n let h = m[1]\n if (h.length === 3)\n h = h\n .split('')\n .map((c) => c + c)\n .join('')\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ]\n}\n\nexport function rgbToHex([r, g, b]: Rgb): string {\n const c = (v: number) =>\n Math.round(clamp(v, 0, 255))\n .toString(16)\n .padStart(2, '0')\n return `#${c(r)}${c(g)}${c(b)}`\n}\n\nexport function parseCssColour(s: string): Rgb | null {\n const hex = hexToRgb(s)\n if (hex) return hex\n const m = /rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/.exec(s)\n if (!m) return null\n return [Number(m[1]), Number(m[2]), Number(m[3])]\n}\n\nexport function relativeLuminance([r, g, b]: Rgb): number {\n const lin = (v: number) => {\n const c = v / 255\n return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4\n }\n return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)\n}\n\nexport function rgbToHsl([r, g, b]: Rgb): [number, number, number] {\n const rr = r / 255\n const gg = g / 255\n const bb = b / 255\n const max = Math.max(rr, gg, bb)\n const min = Math.min(rr, gg, bb)\n const l = (max + min) / 2\n if (max === min) return [0, 0, l]\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n let h = 0\n if (max === rr) h = (gg - bb) / d + (gg < bb ? 6 : 0)\n else if (max === gg) h = (bb - rr) / d + 2\n else h = (rr - gg) / d + 4\n return [h / 6, s, l]\n}\n\nexport function hslToHex(h: number, s: number, l: number): string {\n const f = (n: number) => {\n const k = (n + h * 12) % 12\n const a = s * Math.min(l, 1 - l)\n return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))\n }\n return rgbToHex([f(0) * 255, f(8) * 255, f(4) * 255])\n}\n\n/** CIE76 colour difference between two sRGB colours. */\nexport function deltaE(a: Rgb, b: Rgb): number {\n const la = rgbToLab(a)\n const lb = rgbToLab(b)\n return Math.hypot(la[0] - lb[0], la[1] - lb[1], la[2] - lb[2])\n}\n\nexport function rgbToLab([r, g, b]: Rgb): [number, number, number] {\n const lin = (v: number) => {\n const c = v / 255\n return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4\n }\n const R = lin(r)\n const G = lin(g)\n const B = lin(b)\n const x = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047\n const y = R * 0.2126 + G * 0.7152 + B * 0.0722\n const z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 1.08883\n const f = (t: number) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116)\n return [116 * f(y) - 16, 500 * (f(x) - f(y)), 200 * (f(y) - f(z))]\n}\n\nfunction quoteFamily(family: string): string {\n const first = family\n .split(',')[0]\n .trim()\n .replace(/^['\"]|['\"]$/g, '')\n const rest = family.includes(',')\n ? family.slice(family.indexOf(',') + 1).trim()\n : /mono/i.test(first)\n ? 'ui-monospace,monospace'\n : '-apple-system,system-ui,sans-serif'\n return /^[a-z-]+$/i.test(first) &&\n /^(ui-|system-ui|sans-serif|serif|monospace)/.test(first)\n ? `${first},${rest}`\n : `'${first}',${rest}`\n}\n\nfunction esc(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\n","/**\n * Capture-space normalization — the single seam that makes window/monitor\n * recordings look like tab recordings to everything downstream.\n *\n * Tab captures record the viewport, so CursorEvent.x/y (viewport CSS px) IS the\n * cursor space and meta.width/height describes it. For window/monitor captures\n * the viewport is only part of the frame, so events are mapped into capture\n * pixels here — once, at doc-build time — using each event's screen coords\n * (sx/sy) and the geometry sampled at record start. After normalization the\n * planner, lowering, and composition consume the doc unchanged.\n *\n * Window takes additionally get a VIEWPORT CROP when the geometry is clean\n * (deriveViewportCrop): the real browser\n * chrome is cut out of the footage at the drawImage seam, the cursor/meta are\n * rewritten into crop space, and the synthetic browser bar becomes available\n * exactly as on tab takes. Fail-closed: a wrong crop (chrome sliver, cut page\n * edge) reads far worse than no crop, so any geometry doubt → no crop.\n */\nimport type {\n CursorEvent,\n CursorTrack,\n ProjectDoc,\n RecordingMeta,\n Rect,\n} from './types'\n\nexport interface CaptureNormalization {\n cursor: CursorTrack\n meta: RecordingMeta\n /**\n * Fraction of mapped events that landed inside the captured frame (1 for tab\n * captures). Low coverage means the user shared a different window/display\n * than the one hosting the recorded tab — the app should skip auto-zoom and\n * cursor overlay rather than render them at wrong positions.\n */\n coverage: number\n /**\n * Window takes with clean geometry: the viewport's rect inside the capture\n * frame (capture px) — the drawImage source crop that removes the real\n * browser chrome. When present, the returned cursor/meta are already in\n * crop space. Absent = render the full frame.\n */\n crop?: Rect\n}\n\n/** Plausible top-chrome height (tab strip + toolbar), CSS px. Outside → geometry is lying. */\nconst CHROME_TOP_MIN = 20\nconst CHROME_TOP_MAX = 220\n/** Event-offset vs window-geometry agreement tolerance, CSS px. */\nconst CROP_TOLERANCE = 16\n/** Minimum agreeing events before the event-derived viewport origin is trusted. */\nconst CROP_MIN_EVENTS = 3\n\n/**\n * Derive the viewport crop for a window take: where the page viewport sits\n * inside the captured window frame, in capture px.\n *\n * Two independent estimators cross-check each other:\n * 1. event-derived (primary): each event's own viewport→screen offset\n * (sx − x, sy − y) — exact wherever the chrome actually is, but needs events;\n * 2. window-derived: windowRect vs meta.viewport under the chrome-on-top\n * assumption (side insets split evenly) — no events needed, but wrong for\n * docked devtools / exotic decorations.\n *\n * Only offsets agreeing with (2) are kept — this simultaneously validates the\n * chrome-on-top assumption AND rejects cross-origin-iframe events, whose\n * offsets are the IFRAME's origin, not the top viewport's. Returns null\n * (no crop) on any doubt — see the fail-closed matrix in the analysis doc.\n */\nexport function deriveViewportCrop(\n cursor: CursorTrack,\n meta: RecordingMeta,\n): Rect | null {\n if ((meta.captureSurface ?? 'tab') !== 'window') return null\n const win = meta.windowRect\n const vp = meta.viewport\n const capW = meta.captureWidth ?? 0\n const capH = meta.captureHeight ?? 0\n if (\n !win ||\n !vp ||\n win.w <= 0 ||\n win.h <= 0 ||\n vp.w <= 0 ||\n vp.h <= 0 ||\n capW <= 0 ||\n capH <= 0\n )\n return null\n // Any mid-take geometry drift invalidates the single static crop.\n if (\n meta.windowMovedDuringTake ||\n meta.viewportChangedDuringTake ||\n meta.resizedDuringTake\n )\n return null\n // Browser window unfocused for most of the take ⇒ the user was driving a\n // different window — the SHARED surface is probably not this browser window,\n // and the crop would cut unrelated pixels (see windowFocusedFrac).\n if ((meta.windowFocusedFrac ?? 1) < WINDOW_FOCUS_MIN) return null\n // Page zoom breaks CSS px == DIPs for event x/y — deferred (a later fold-in).\n if (Math.abs((meta.zoom || 1) - 1) > 0.001) return null\n\n const scaleX = capW / win.w\n const scaleY = capH / win.h\n // A clean window capture scales both axes identically; disagreement means the\n // captured surface isn't this window (or the rect is stale).\n if (Math.abs(scaleX / scaleY - 1) > 0.05) return null\n\n // Window-derived estimate (chrome on top, side insets split evenly).\n const estX = win.x + (win.w - vp.w) / 2\n const estY = win.y + (win.h - vp.h)\n\n const xs: number[] = []\n const ys: number[] = []\n for (const e of cursor) {\n if (e.sx === undefined || e.sy === undefined) continue\n const ox = e.sx - e.x\n const oy = e.sy - e.y\n if (\n Math.abs(ox - estX) <= CROP_TOLERANCE &&\n Math.abs(oy - estY) <= CROP_TOLERANCE\n ) {\n xs.push(ox)\n ys.push(oy)\n }\n }\n if (xs.length < CROP_MIN_EVENTS) return null\n const vx = median(xs)\n const vy = median(ys)\n\n const topChrome = vy - win.y\n if (topChrome < CHROME_TOP_MIN || topChrome > CHROME_TOP_MAX) return null\n\n const crop: Rect = {\n x: Math.round((vx - win.x) * scaleX),\n y: Math.round((vy - win.y) * scaleY),\n w: Math.round(vp.w * scaleX),\n h: Math.round(vp.h * scaleY),\n }\n // Must sit inside the capture (rounding slack only) and be most of it.\n if (\n crop.x < -2 ||\n crop.y < 0 ||\n crop.x + crop.w > capW + 2 ||\n crop.y + crop.h > capH + 2\n )\n return null\n crop.x = Math.max(0, crop.x)\n crop.w = Math.min(crop.w, capW - crop.x)\n crop.h = Math.min(crop.h, capH - crop.y)\n if (crop.w * crop.h < 0.5 * capW * capH) return null\n return crop\n}\n\n/**\n * Map a cursor track into the capture's pixel space. Identity for tab captures\n * (or when geometry is missing). For window/monitor captures the returned meta\n * has width/height set to the capture pixel dimensions (the new cursor space)\n * and dpr/zoom reset to 1 — cursor space and video pixels now coincide. When a\n * window take yields a viewport crop, cursor space is the CROPPED frame and\n * meta dims are the crop dims (downstream layout/planner/zoom need no crop\n * awareness — only drawImage reads the rect).\n */\nexport function normalizeCaptureSpace(\n cursor: CursorTrack,\n meta: RecordingMeta,\n): CaptureNormalization {\n const surface = meta.captureSurface ?? 'tab'\n if (surface === 'tab') return { cursor, meta, coverage: 1 }\n const anchor = surface === 'window' ? meta.windowRect : meta.screenRect\n const capW = meta.captureWidth ?? 0\n const capH = meta.captureHeight ?? 0\n if (!anchor || anchor.w <= 0 || anchor.h <= 0 || capW <= 0 || capH <= 0) {\n // A display take we CANNOT map (missing geometry or capture dims — e.g. a\n // recorder that read track settings after the source ended). Never pass the\n // raw viewport-space track through as if it were frame space: the cursor\n // would render at unrelated positions. Report coverage 0 so the app drops\n // the track with its normal notice.\n return { cursor, meta, coverage: 0 }\n }\n\n // Independent axis scales: outerHeight vs captured height can disagree by a\n // title-bar's worth of chrome, so a single uniform scale would drift.\n const scaleX = capW / anchor.w\n const scaleY = capH / anchor.h\n\n const crop = deriveViewportCrop(cursor, meta) ?? undefined\n const cropX = crop?.x ?? 0\n const cropY = crop?.y ?? 0\n const frameW = crop?.w ?? capW\n const frameH = crop?.h ?? capH\n\n let inFrame = 0\n const mapped: CursorEvent[] = []\n for (const e of cursor) {\n if (e.sx === undefined || e.sy === undefined) continue // unmappable (old capture) — drop\n const x = (e.sx - anchor.x) * scaleX - cropX\n const y = (e.sy - anchor.y) * scaleY - cropY\n if (x >= 0 && x <= frameW && y >= 0 && y <= frameH) inFrame++\n // Element rects are viewport-relative; this event's own viewport→screen\n // offset transforms them without any window-geometry guesswork.\n let rect: Rect | undefined\n if (e.rect) {\n const dx = e.sx - e.x\n const dy = e.sy - e.y\n rect = {\n x: (e.rect.x + dx - anchor.x) * scaleX - cropX,\n y: (e.rect.y + dy - anchor.y) * scaleY - cropY,\n w: e.rect.w * scaleX,\n h: e.rect.h * scaleY,\n }\n }\n mapped.push({ ...e, x, y, rect })\n }\n\n return {\n cursor: mapped,\n meta: {\n ...meta,\n width: frameW,\n height: frameH,\n ...(crop ? { captureWidth: frameW, captureHeight: frameH } : {}),\n dpr: 1,\n zoom: 1,\n },\n coverage: mapped.length ? inFrame / mapped.length : 0,\n crop,\n }\n}\n\n/** Coverage below this → treat the cursor track as unusable (skip auto-zoom + overlay). */\nexport const CAPTURE_COVERAGE_MIN = 0.5\n\n/**\n * Window takes whose browser window was focused for less than this fraction of\n * the take are treated as wrong-window shares: cursor track dropped, no\n * viewport crop (see RecordingMeta.windowFocusedFrac).\n */\nexport const WINDOW_FOCUS_MIN = 0.5\n\n/**\n * Crop-space ↔ full-space doc remaps — the \"Original\" frame mode (show the\n * user's real browser chrome on a cropped window take). The footage always\n * holds the full frame, so the toggle is a LOSSLESS coordinate remap of the\n * doc (cursor events/rects, zoom focus points, meta dims) driven by the\n * chromeCrop record kept from ingest — one undoable patch-store edit, program\n * string untouched (everything involved lowers into ctx.data). Time-based\n * state (segments, speed, audio, cam window) is space-independent.\n *\n * Both helpers MUTATE a draft doc (call inside the store's edit()) and are\n * no-ops when the doc is already in the requested space or has no chromeCrop.\n */\n\n/** Remap a crop-space doc to full-capture space (show the original chrome). */\nexport function docToFullSpace(d: ProjectDoc): void {\n const cc = d.source.chromeCrop\n if (!cc || !d.source.crop) return\n const { rect, frameW, frameH } = cc\n d.source.crop = undefined\n d.source.cursor = d.source.cursor.map((e) => ({\n ...e,\n x: e.x + rect.x,\n y: e.y + rect.y,\n rect: e.rect\n ? { ...e.rect, x: e.rect.x + rect.x, y: e.rect.y + rect.y }\n : undefined,\n }))\n d.source.meta = {\n ...d.source.meta,\n width: frameW,\n height: frameH,\n captureWidth: frameW,\n captureHeight: frameH,\n }\n d.zoom = d.zoom.map((z) => ({\n ...z,\n cx: (rect.x + z.cx * rect.w) / frameW,\n cy: (rect.y + z.cy * rect.h) / frameH,\n }))\n}\n\n/** Remap a full-space doc back into crop space (hide the chrome again). */\nexport function docToCropSpace(d: ProjectDoc): void {\n const cc = d.source.chromeCrop\n if (!cc || d.source.crop) return\n const { rect, frameW, frameH } = cc\n d.source.crop = { ...rect }\n d.source.cursor = d.source.cursor.map((e) => ({\n ...e,\n x: e.x - rect.x,\n y: e.y - rect.y,\n rect: e.rect\n ? { ...e.rect, x: e.rect.x - rect.x, y: e.rect.y - rect.y }\n : undefined,\n }))\n d.source.meta = {\n ...d.source.meta,\n width: rect.w,\n height: rect.h,\n captureWidth: rect.w,\n captureHeight: rect.h,\n }\n // A focus aimed at chrome pixels clamps to the crop edge (the lowering's\n // clampFocus refines against the real card layout).\n d.zoom = d.zoom.map((z) => ({\n ...z,\n cx: clamp01((z.cx * frameW - rect.x) / rect.w),\n cy: clamp01((z.cy * frameH - rect.y) / rect.h),\n }))\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\n\nfunction median(values: number[]): number {\n const sorted = [...values].sort((a, b) => a - b)\n const mid = Math.floor(sorted.length / 2)\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2\n}\n","/**\n * Artifact → ProjectDoc ingest — the seam every recorder (extension, CLI)\n * funnels through. Moved from the web app so non-browser producers (the vos\n * CLI) run the exact same ingest as the studio.\n */\nimport {\n DEFAULT_CAM_STYLE,\n DEFAULT_CURSOR_STYLE,\n DEFAULT_FRAME_STYLE,\n pageDisplayUrl,\n platformBarKind,\n} from './types'\nimport {\n CAPTURE_COVERAGE_MIN,\n WINDOW_FOCUS_MIN,\n normalizeCaptureSpace,\n} from './capture'\nimport type { FrameStyle, ProjectDoc, RecordingArtifact } from './types'\n\nexport interface IngestOptions {\n /**\n * The frame the take opens on: a host's house backdrop on the bare frame,\n * say (`withBackdrop`). Its browser bar is still derived from the footage\n * below. Absent, `DEFAULT_FRAME_STYLE`.\n */\n frame?: FrameStyle\n}\n\n/** Build a ProjectDoc from a RecordingArtifact handed off by a recorder. */\nexport function projectFromArtifact(\n artifact: RecordingArtifact,\n videoUrl: string,\n opts: IngestOptions = {},\n): { doc: ProjectDoc; videoUrl: string } {\n const baseFrame = opts.frame ?? DEFAULT_FRAME_STYLE\n // Window/monitor takes: map cursor events into capture px (normalizeCaptureSpace);\n // low coverage means the user shared a surface other than the one hosting the\n // recorded tab (or the mapping anchors are unusable) — drop the track rather\n // than draw the cursor at wrong positions. A window that moved mid-take\n // invalidates its anchor the same way.\n const surface = artifact.meta.captureSurface ?? 'tab'\n const norm = normalizeCaptureSpace(artifact.cursor, artifact.meta)\n const { cursor, meta } = norm\n // Entire-screen takes: the cursor is only trackable INSIDE browser pages —\n // the moment the pointer leaves the browser (other apps, desktop, dock) the\n // track goes blind while the video keeps showing the real cursor, so the\n // synthetic cursor/zoom would be wrong for exactly the footage people record\n // screens for. Policy: no cursor effects for monitor takes (the record page\n // says so up front).\n // Wrong-window tell: cursor events come from the recorded tab, so their\n // geometry is self-consistent even when a DIFFERENT window was shared —\n // coverage can't catch it. A browser window that was unfocused for most of\n // the take means the user was driving another window (the one they shared).\n const unfocused =\n surface === 'window' &&\n (artifact.meta.windowFocusedFrac ?? 1) < WINDOW_FOCUS_MIN\n const coverage =\n surface === 'monitor' || artifact.meta.windowMovedDuringTake || unfocused\n ? 0\n : norm.coverage\n if (artifact.cursor.length > 0 && coverage < CAPTURE_COVERAGE_MIN) {\n console.warn(\n '[voila/studio] dropping cursor track —',\n surface === 'monitor'\n ? 'entire-screen takes have no cursor effects (untrackable outside browser pages)'\n : artifact.meta.windowMovedDuringTake\n ? 'window moved/resized during the take'\n : unfocused\n ? `browser window focused only ${Math.round((artifact.meta.windowFocusedFrac ?? 0) * 100)}% of the take — a different window was likely shared`\n : `coverage ${norm.coverage.toFixed(2)} (wrong surface shared, or unusable capture geometry)`,\n )\n }\n const doc: ProjectDoc = {\n source: {\n videoKey: videoUrl,\n cursor: coverage >= CAPTURE_COVERAGE_MIN ? cursor : [],\n meta,\n camKey: artifact.camKey,\n micKey: artifact.audioKey,\n // Tab takes are known-good MP4 → frame-accurate WebCodecs. Display takes\n // encode WebM (surface resizes break MP4) → robust HTMLVideoElement path.\n // CLI takes encode WebM too — same robust path.\n frameSource:\n surface === 'tab' && artifact.meta.producer !== 'cli'\n ? 'webcodecs'\n : 'html5',\n // Window takes with clean geometry: crop the real browser chrome out of the\n // footage (cursor/meta are already in crop space — see normalizeCaptureSpace).\n crop: norm.crop,\n // Keep the derivation + full capture dims so the \"Original\" frame mode can\n // remap the doc between crop/full space losslessly (docToFullSpace).\n chromeCrop: norm.crop\n ? {\n rect: norm.crop,\n frameW: artifact.meta.captureWidth ?? artifact.meta.width,\n frameH: artifact.meta.captureHeight ?? artifact.meta.height,\n }\n : undefined,\n },\n segments: [{ in: 0, out: artifact.meta.durationMs / 1000 }], // canonical full-source span\n zoom: [], // planner runs once the doc is loaded (editor) or planned (CLI)\n audio: [],\n // A new take wears the OS pointer; DEFAULT_CURSOR_STYLE keeps the dot\n // so a document that stores it renders as it was cut.\n cursor: { ...DEFAULT_CURSOR_STYLE, style: 'arrow' },\n cam: { ...DEFAULT_CAM_STYLE },\n frame: {\n ...baseFrame,\n browserBar: {\n ...baseFrame.browserBar,\n // Chrome-free footage (tab takes, cropped window takes) opens with the\n // OS-matched realistic frame — the Screen-Studio first render; Hidden is\n // one click away. Footage that still contains real chrome gets none.\n kind:\n surface === 'tab' || norm.crop\n ? platformBarKind(artifact.meta.platform)\n : 'none',\n // Pre-fill the address pill from the recorded page.\n url: pageDisplayUrl(artifact.meta.pageUrl),\n },\n },\n // 60 fps: the footage duplicates frames, the camera does not, and a\n // 0.5 s glide at 30 fps is fifteen visible steps.\n export: { resolution: '1080p', fps: 60, format: 'mp4' },\n }\n return { doc, videoUrl }\n}\n","/**\n * How the card MOVES and how a cut ENDS, as data the lowering already\n * understands: the card's `anim.enter` writes the head of the tilt or zoom\n * track and a card-pose track (scale, rise, opacity); its `anim.exit`\n * writes that track's tail; a FREEZE (`doc.freeze`, a source moment plus\n * output seconds; a legacy segment `hold` is one at the segment's end) is a\n * rated piece whose hair of source time plays for its seconds; the card is\n * on screen exactly while its clip (the footage, freezes included) runs and\n * gone after it, like every layer; and the output lasts until the last\n * visual clip ends, so an \"end card\" is a freeze of the last frame, a card\n * exit over it and clips placed over the freeze. The two older spellings\n * (`frame.entrance`, `doc.endCard`) migrate here, on read, into exactly\n * that. Pure; ON_FRAME reads the tracks.\n */\nimport { sortKeyframes } from '@vosjs/timeline'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type {\n Anim,\n AnimStep,\n DocSegment,\n EndCard,\n FrameStyle,\n FreezeSpan,\n MediaOverlayClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n TextOverlayClip,\n} from '../types'\nimport { resolveExportSize } from '../types'\nimport { animStep, enterOf, exitOf, lastLayerEnd } from '../anim'\nimport { splitBySpeed, totalDuration } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\n\n/**\n * THE REST of a take: the one output time its still is taken at, where\n * the LAST freeze begins — the composed frame before nothing moves (a\n * poster ends on one). Null when the take has no freeze on kept footage.\n * One convention for every still-taking surface (an export, a thumbnail,\n * a kit), so they agree. Reads a legacy segment `hold` as the freeze it\n * migrates to, and a raw hosted payload (no `segments`, no `source`) as\n * what it has. A legacy end card yields null: its words rise over a\n * receding card, which is footage under a fading title, not a poster.\n */\n/** The freezes the document owns: the ones no template laid (no `from`). */\nexport function ownFreezes(doc: ProjectDoc): FreezeSpan[] {\n return docFreezes(doc).filter((f) => !f.from)\n}\n\n/** The still a document with nothing to say gets: the old default. */\nexport const STILL_DEFAULT = 0.5\n/** A short beat after the opening has entered, so the hero frame is settled. */\nexport const HERO_BEAT = 0.25\n/** A clip starting within this many output seconds is part of the opening. */\nexport const OPENING_WINDOW = 3\n/** The house length of a clip's block enter, for clips that name none. */\nexport const OPENING_ENTER_SECONDS = 0.6\n\nconst r3 = (v: number) => Math.round(v * 1000) / 1000\n\n/**\n * The output moment that STANDS for a document: its cover on the shelf,\n * the still a kit leads with. One ladder, one derivation, read by the\n * fleet thumbnail, the stills export and the kit alike; a template can\n * never move it, because everything a template lays is stamped `from`.\n *\n * 1. `doc.still`, the author's own choice, clamped to the output.\n * 2. The last freeze the document OWNS (no `from`), where it begins: a\n * poster's rest, or a moment the author froze on purpose.\n * 3. The hero: the first moment at which the card and every clip present\n * at the cold open have finished entering, plus a beat, never past the\n * footage. Read from `anim` seconds, so it is the same everywhere.\n * 4. The old default, for a document with none of the above.\n */\nexport function docStillTime(doc: ProjectDoc): number {\n const segs = docSegmentsOf(doc)\n const rated = withFreezes(\n splitBySpeed(segs, doc.speed ?? []),\n docFreezes(doc),\n )\n const footage = totalDuration(rated)\n const end = outputEnd(doc, footage)\n const clampOut = (v: number) => Math.max(0, Math.min(end, v))\n if (typeof doc.still === 'number' && Number.isFinite(doc.still))\n return r3(clampOut(doc.still))\n if (!doc.endCard) {\n const own = ownFreezes(doc)\n if (own.length) {\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n own,\n )\n let rest: number | null = null\n for (const m of marks.values())\n if (rest === null || m.t > rest) rest = m.t\n if (rest !== null && rest >= 0) return r3(clampOut(rest))\n }\n }\n if (!(footage > 0)) return r3(clampOut(STILL_DEFAULT))\n let hero = entranceSeconds(cardEnter(doc.frame))\n for (const clip of doc.overlays ?? []) {\n if (clip.start > OPENING_WINDOW) continue\n const step = enterOf(clip.anim)\n const seconds =\n !step || step.kind === 'none'\n ? 0\n : Math.max(0, step.seconds ?? OPENING_ENTER_SECONDS)\n hero = Math.max(hero, clip.start + seconds)\n }\n hero = Math.max(STILL_DEFAULT, hero + HERO_BEAT)\n // Never past the footage: the card is what a cover shows.\n hero = Math.min(hero, Math.max(0, footage - 1 / 30))\n return r3(clampOut(hero))\n}\n\n/**\n * Where the LAST freeze of any origin begins, or null with none: the\n * poster's rest as first spelled. Prefer `docStillTime`, which ignores a\n * template's freezes and always answers.\n */\nexport function docRestTime(doc: ProjectDoc): number | null {\n if (doc.endCard) return null\n const freezes = docFreezes(doc)\n if (!freezes.length) return null\n const segs = docSegmentsOf(doc)\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n freezes,\n )\n let rest: number | null = null\n for (const m of marks.values()) if (rest === null || m.t > rest) rest = m.t\n if (rest === null || !(rest >= 0)) return null\n return Math.round(rest * 1000) / 1000\n}\n\n/** The doc's kept spans in explicit form; a raw payload with none is untrimmed. */\nfunction docSegmentsOf(doc: ProjectDoc): DocSegment[] {\n const segs = Array.isArray(doc.segments) ? doc.segments : []\n if (segs.length) return segs\n const ms = doc.source?.meta?.durationMs\n return [{ in: 0, out: typeof ms === 'number' ? ms / 1000 : 0 }]\n}\n\n/**\n * Every freeze the document carries, the spelled ones and the legacy\n * segment holds (as a freeze at that segment's `out`, id `h{i}`), sorted\n * by source moment. The one read every consumer takes, so an unmigrated\n * document freezes exactly where a migrated one does.\n */\nexport function docFreezes(\n doc: Pick<ProjectDoc, 'freeze' | 'segments'>,\n): FreezeSpan[] {\n const own = Array.isArray(doc.freeze) ? doc.freeze : []\n const legacy = legacyHoldFreezes(\n Array.isArray(doc.segments) ? doc.segments : [],\n )\n const all = [...own, ...legacy].filter(\n (f) =>\n typeof f.at === 'number' &&\n Number.isFinite(f.at) &&\n typeof f.seconds === 'number' &&\n f.seconds > 0,\n )\n return all.sort((a, b) => a.at - b.at)\n}\n\n/** A legacy segment `hold` as the freeze it migrates to. */\nfunction legacyHoldFreezes(segments: readonly DocSegment[]): FreezeSpan[] {\n const out: FreezeSpan[] = []\n segments.forEach((s, i) => {\n const hold = s.hold\n if (typeof hold === 'number' && hold > 0)\n out.push({ id: `h${i}`, at: s.out, seconds: hold })\n })\n return out\n}\n\n/** The card entrance's default length, seconds. */\nexport const ENTRANCE_SECONDS = 1.2\n/** The legacy end card's default length, seconds. */\nexport const END_CARD_SECONDS = 2.5\n/** How long the card takes to recede (the `recede` exit's house length). */\nexport const END_CARD_RECEDE = 0.7\n/** The `fade` exit's house length. */\nexport const CARD_FADE_SECONDS = 0.35\n/** The tilt-in's opening pose, degrees: top edge away, left edge toward. */\nexport const TILT_IN_POSE: [number, number] = [-9, 14]\n/** The pull-out's opening zoom level. */\nexport const PULL_OUT_LEVEL = 1.32\n/** The legacy marker a migrated end card's clips carry as `from`. */\nexport const END_CARD_FROM = 'endcard'\n\nconst HOLD_SOURCE_SPAN = 0.002\n\n/** A step with a kind and optional seconds: the card's, or a legacy entrance. */\ntype Step = { kind: string; seconds?: number } | null | undefined\n\n/** The card's enter step, from its `anim` (a migrated doc carries nothing else). */\nexport function cardEnter(frame: Pick<FrameStyle, 'anim'>): AnimStep | null {\n return enterOf(frame.anim)\n}\n\n/** The card's exit step, from its `anim`. */\nexport function cardExit(frame: Pick<FrameStyle, 'anim'>): AnimStep | null {\n return exitOf(frame.anim)\n}\n\nexport function entranceSeconds(e: Step): number {\n if (!e || e.kind === 'none') return 0\n return Math.max(0.2, Math.min(3, e.seconds ?? ENTRANCE_SECONDS))\n}\n\n/** The exit's length: its own seconds, else the house length of its kind. */\nexport function exitSeconds(e: Step): number {\n if (!e || e.kind === 'none') return 0\n const house = e.kind === 'fade' ? CARD_FADE_SECONDS : END_CARD_RECEDE\n return Math.max(0.1, Math.min(8, e.seconds ?? house))\n}\n\n/** A freeze's place on the output timeline (its start and its seconds). */\nexport interface FreezeExtent {\n t: number\n duration: number\n}\n\n/**\n * The rated pieces with every freeze placed: at a freeze's source moment\n * the piece holding it splits, and a freeze piece (a hair of source time\n * just before the moment, rated to play for the freeze's seconds) goes in\n * between; a freeze at a piece's end goes after it, one at the very start\n * before it. A freeze whose moment is on no kept footage is not placed\n * (it follows its frame, like a span). Every consumer of the rated list\n * (mapTime, the zoom remap, the audio splice, the duration) inherits the\n * freezes from this one seam. `marks` is where each placed freeze landed\n * in OUTPUT time, for the lane and the rest.\n */\nexport function placeFreezes(\n rated: readonly (Segment & { media?: string })[],\n freezes: readonly FreezeSpan[],\n): { pieces: Segment[]; marks: Map<string, FreezeExtent> } {\n const marks = new Map<string, FreezeExtent>()\n const placed = new Set<string>()\n const pieces: Segment[] = []\n let acc = 0\n const freezePiece = (f: FreezeSpan, media: string | undefined): void => {\n const at = Math.max(HOLD_SOURCE_SPAN, f.at)\n marks.set(f.id, { t: acc, duration: f.seconds })\n pieces.push({\n in: at - HOLD_SOURCE_SPAN,\n out: at,\n rate: HOLD_SOURCE_SPAN / f.seconds,\n ...(media !== undefined ? { media } : {}),\n } as Segment)\n placed.add(f.id)\n acc += f.seconds\n }\n const push = (p: Segment): void => {\n pieces.push(p)\n acc += Math.max(0, p.out - p.in) / (p.rate ?? 1)\n }\n for (let i = 0; i < rated.length; i++) {\n let piece = rated[i]\n const next = rated[i + 1]\n // The freezes on THIS piece's media whose moment it holds, in order: a\n // freeze inside it splits it; one at its very start goes before it (the\n // first frame, or a boundary the previous piece deferred); one exactly\n // at its end goes after it, unless the next piece continues the same\n // footage (a speed edge), which is the same frame either way — take the\n // later place so the freeze sits where the moment is.\n const continues =\n !!next &&\n sameMedia(next.media, piece.media) &&\n Math.abs(next.in - piece.out) < 1e-9 &&\n next.out > piece.out\n const mine = freezes\n .filter(\n (f) =>\n f.seconds > 0 &&\n !placed.has(f.id) &&\n sameMedia(f.media, piece.media) &&\n f.at >= piece.in - 1e-9 &&\n (continues ? f.at < piece.out - 1e-9 : f.at <= piece.out + 1e-9),\n )\n .sort((a, b) => a.at - b.at)\n for (const f of mine) {\n if (f.at <= piece.in + 1e-9) {\n freezePiece(f, piece.media)\n } else if (f.at < piece.out - 1e-9) {\n push({ ...piece, out: f.at })\n freezePiece(f, piece.media)\n piece = { ...piece, in: f.at }\n } else {\n const media = piece.media\n push(piece)\n piece = null as unknown as Segment\n freezePiece(f, media)\n }\n }\n if (piece) push(piece)\n }\n return { pieces, marks }\n}\n\n/** The rated segments with every freeze placed (see `placeFreezes`). */\nexport function withFreezes(\n rated: readonly Segment[],\n freezes: readonly FreezeSpan[],\n): Segment[] {\n return placeFreezes(rated, freezes).pieces\n}\n\n/** Where each freeze lands on the output timeline (unplaced ones absent). */\nexport function freezeOutputExtents(\n rated: readonly Segment[],\n freezes: readonly FreezeSpan[],\n): Map<string, FreezeExtent> {\n return placeFreezes(rated, freezes).marks\n}\n\n/**\n * The rated segments with every legacy segment `hold` placed as the freeze\n * it migrates to.\n * @deprecated Read `docFreezes` and place them with `withFreezes`.\n */\nexport function withHolds(\n docSegments: readonly DocSegment[],\n rated: Segment[],\n): Segment[] {\n return withFreezes(rated, legacyHoldFreezes(docSegments))\n}\n\n/** Keyframes the card's entrance prepends to the tilt track, or none. */\nexport function entranceTiltKeyframes(e: Step): Keyframe<number[]>[] {\n const s = entranceSeconds(e)\n if (!s || e?.kind !== 'tilt-in') return []\n return [\n { t: 0, value: [...TILT_IN_POSE], ease: 'none' },\n { t: s, value: [0, 0], ease: 'power3.out' },\n ]\n}\n\n/**\n * Keyframes the card's entrance prepends to the zoom track ([level, cx,\n * cy]): the pull-out's own head, or, for the other kinds, a REST head,\n * because the camera must not zoom into a card that is still arriving (a\n * planner's cold-open span would otherwise fight the entrance). None for\n * none.\n */\nexport function entranceZoomKeyframes(e: Step): Keyframe<number[]>[] {\n const s = entranceSeconds(e)\n if (!s) return []\n if (e?.kind === 'pull-out')\n return [\n { t: 0, value: [PULL_OUT_LEVEL, 0.5, 0.42], ease: 'none' },\n { t: s, value: [1, 0.5, 0.5], ease: 'power3.out' },\n ]\n return [\n { t: 0, value: [1, 0.5, 0.5], ease: 'none' },\n { t: s, value: [1, 0.5, 0.5], ease: 'none' },\n ]\n}\n\n/**\n * Prepend an entrance's keyframes to a track: keyframes the entrance\n * would overlap (earlier than its end plus a beat) give way, so a span\n * that starts at the cold open loses its head to the entrance rather\n * than fighting it. An empty entrance returns the track untouched.\n */\nexport function prependEntrance(\n track: KeyframeTrack<number[]> | undefined,\n head: Keyframe<number[]>[],\n): KeyframeTrack<number[]> | undefined {\n if (!head.length) return track\n const end = head[head.length - 1].t\n const rest = (track?.keyframes ?? []).filter((k) => k.t > end + 0.1)\n return { keyframes: sortKeyframes([...head, ...rest]) }\n}\n\n/** The pose a card exit ENDS on, gone: [scale, dy, opacity]. */\nfunction exitPose(e: Step): number[] {\n return e?.kind === 'fade' ? [1, 0, 0] : [0.9, -0.02, 0]\n}\n\n/** A recede's midway pose: stepped back and still mostly there; the fade comes last. */\nconst RECEDE_MID = [0.91, -0.018, 0.8]\n\n/**\n * The card-pose track [scale, dy, opacity] in OUTPUT seconds: the entrance\n * settles the card in from a slightly smaller, lower, transparent pose;\n * the exit takes it out as its footage ends. The exit runs FROM the\n * footage's end when something plays after it (the card recedes under the\n * words), and ENDS at the footage's end when nothing does (the card leaves\n * as the cut closes). Undefined when the card carries neither, so a doc\n * without them lowers byte-identically.\n */\nexport function cardPoseTrack(\n enter: Step,\n exit: Step,\n footageEnd: number,\n outputEnd: number = footageEnd,\n): KeyframeTrack<number[]> | undefined {\n const keyframes: Keyframe<number[]>[] = []\n const s = entranceSeconds(enter)\n if (s) {\n // The first frame is what a feed shows: the card is VISIBLE at t = 0\n // (the references open in perspective, never on nothing), so the pose\n // settles from a smaller, lower, softened card, not from a blank.\n // A slide moves the plane, not the pose (the transitions table carries\n // it), so its track holds the rest.\n const from =\n enter?.kind === 'rise'\n ? [0.96, 0.08, 0.7]\n : enter?.kind === 'pull-out' ||\n enter?.kind === 'fade' ||\n enter?.kind === 'slide'\n ? enter.kind === 'fade'\n ? [1, 0, 0]\n : [1, 0, 1]\n : [0.94, 0.05, 0.7]\n keyframes.push({ t: 0, value: from, ease: 'none' })\n keyframes.push({ t: s, value: [1, 0, 1], ease: 'power3.out' })\n }\n // The exit plays over the last seconds of the card's clip and ends gone\n // at the clip's end (the footage, freezes included), the contract every\n // clip's exit keeps. A recede steps back first and fades last.\n const x = exitSeconds(exit)\n let last = [1, 0, 1]\n if (x > 0 && footageEnd > 0) {\n const t0 = round3(Math.max(s, footageEnd - x))\n const t1 = round3(Math.max(t0 + 0.05, footageEnd))\n last = exitPose(exit)\n keyframes.push({ t: t0, value: [1, 0, 1], ease: 'none' })\n if (exit?.kind !== 'fade') {\n keyframes.push({\n t: round3(t0 + (t1 - t0) * 0.45),\n value: [...RECEDE_MID],\n ease: 'power2.out',\n })\n keyframes.push({ t: t1, value: last, ease: 'power1.in' })\n } else {\n keyframes.push({ t: t1, value: last, ease: 'power2.out' })\n }\n }\n // Past its clip the card is gone: a cut to nothing, so clips placed\n // after the footage play over the ground alone. A document that ends\n // on its footage never reaches this frame, so it carries no track.\n if (outputEnd > footageEnd + 1e-6 && footageEnd > 0) {\n if (!keyframes.some((k) => Math.abs(k.t - footageEnd) < 1e-9))\n keyframes.push({ t: footageEnd, value: [...last], ease: 'none' })\n keyframes.push({\n t: round3(footageEnd + 0.001),\n value: [last[0], last[1], 0],\n ease: 'none',\n })\n }\n if (!keyframes.length) return undefined\n return { keyframes: sortKeyframes(keyframes) }\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n\n/**\n * The clips a legacy end card places after the footage: the house title,\n * caption and label presets over the receding card, the mark above the\n * wordmark, every one stamped `from: 'endcard'` so a loop drops them and\n * a re-apply replaces them. Pure; the words are the card's.\n */\nexport function endCardClips(\n card: EndCard,\n doc: Pick<ProjectDoc, 'export' | 'frame' | 'source'>,\n endStart: number,\n): OverlayClip[] {\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n const anim: Anim = { enter: 'rise', exit: 'none' }\n const clips: OverlayClip[] = []\n const text = (\n id: string,\n body: string,\n preset: TextOverlayClip['preset'],\n y: number,\n delay: number,\n ): TextOverlayClip => ({\n id,\n kind: 'text',\n text: body,\n preset,\n start: endStart + delay,\n duration: Math.max(0.3, seconds - delay),\n transform: { x: 0.5, y, scale: 1, rotation: 0 },\n anim,\n from: END_CARD_FROM,\n align: 'center',\n ...(card.ink ? { color: card.ink } : {}),\n shadow: 0,\n })\n if (card.headline?.trim())\n clips.push(text('endcard-title', card.headline.trim(), 'title', 0.44, 0.35))\n if (card.sub?.trim())\n clips.push(text('endcard-sub', card.sub.trim(), 'caption', 0.57, 0.5))\n // The mark: a square-ish one sits above the wordmark at 1.3 times the\n // label's cap height; a wide one (a stylised wordmark asset) is the\n // wordmark. Width is a fraction of the FRAME width, so it needs the\n // frame's aspect; height is design px over 1080.\n const mark = card.mark\n const wide = !!mark && mark.aspect > 2.2\n if (mark && mark.key) {\n const size = resolveExportSize(doc as ProjectDoc)\n const designW = (1080 * size.width) / Math.max(1, size.height)\n const hPx = wide ? 44 : 30\n const clip: MediaOverlayClip = {\n id: 'endcard-markimg',\n kind: 'image',\n key: mark.key,\n width: Math.min(0.9, (hPx * Math.max(0.2, mark.aspect)) / designW),\n radius: 0,\n shadow: 'none',\n start: endStart + 0.65,\n duration: Math.max(0.3, seconds - 0.65),\n transform: { x: 0.5, y: wide ? 0.88 : 0.835, scale: 1, rotation: 0 },\n anim,\n from: END_CARD_FROM,\n }\n clips.push(clip)\n }\n if (card.wordmark?.trim() && !wide)\n clips.push(\n text(\n 'endcard-mark',\n card.wordmark.trim(),\n 'label',\n mark && mark.key ? 0.895 : 0.88,\n 0.65,\n ),\n )\n return clips\n}\n\n/**\n * A doc with its legacy end card expanded the OLD way: the last segment\n * holds for the card's seconds and the words ride over the frozen footage.\n * Kept for a caller that still wants the hold shape; the lowering migrates\n * instead (clips after the footage, a card exit, no hold), which paints the\n * same picture without a scenario field.\n * @deprecated Use `migrateMotion`.\n */\nexport function expandEndCard(\n doc: ProjectDoc,\n outputDuration: number,\n): { doc: ProjectDoc; endStart: number | null; seconds: number } {\n const card = doc.endCard\n if (!card || !doc.segments.length) return { doc, endStart: null, seconds: 0 }\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n const segments = doc.segments.map((s, i) =>\n i === doc.segments.length - 1\n ? { ...s, hold: ((s as { hold?: number }).hold ?? 0) + seconds }\n : s,\n )\n const endStart = outputDuration\n const overlays: OverlayClip[] = [\n ...(doc.overlays ?? []),\n ...endCardClips(card, doc, endStart),\n ]\n return { doc: { ...doc, segments, overlays }, endStart, seconds }\n}\n\nconst hasLegacyClipMotion = (o: OverlayClip): boolean =>\n o.enter !== undefined ||\n o.exit !== undefined ||\n (o.kind === 'text' && o.fx !== undefined)\n\n/** The one `anim` a legacy clip's spellings describe. */\nfunction migrateClipAnim(o: OverlayClip): Anim {\n const anim: Anim = { ...(o.anim ?? {}) }\n if (o.kind === 'text' && o.fx) {\n const fx = o.fx\n anim.enter = {\n kind: fx.fx,\n unit: fx.unit ?? 'block',\n ...(fx.direction !== undefined ? { direction: fx.direction } : {}),\n ...(fx.stagger !== undefined ? { stagger: fx.stagger } : {}),\n ...(fx.duration !== undefined ? { seconds: fx.duration } : {}),\n }\n } else if (o.enter !== undefined && anim.enter === undefined) {\n anim.enter = o.enter\n }\n if (o.exit !== undefined && anim.exit === undefined) anim.exit = o.exit\n return anim\n}\n\n/**\n * A document in the one vocabulary: `frame.entrance` becomes the card's\n * `anim.enter`; a clip's `enter`, `exit` and `fx` become its `anim`; a\n * prop's `animation` becomes its `anim.idle`; and the end card becomes a\n * card `anim.exit` of `recede` plus its clips placed after the footage\n * (no hold: the output now lasts until the last clip, and the card holds\n * its last frame at the exit's end pose, which is the picture the end card\n * painted). Returns the SAME object when there is nothing to migrate, so a\n * document already in the vocabulary lowers byte-identically and identity\n * checks hold.\n */\nexport function migrateMotion(doc: ProjectDoc): ProjectDoc {\n // Read over UNTRUSTED shapes too (a hosted payload on its way in): a\n // document with no frame or no segments migrates what it has.\n const frame: FrameStyle = doc.frame ?? ({} as FrameStyle)\n const segments = Array.isArray(doc.segments) ? doc.segments : []\n const overlays = Array.isArray(doc.overlays) ? doc.overlays : []\n const objects = Array.isArray(doc.objects) ? doc.objects : []\n const heldSegments = segments.some((s) => s.hold !== undefined)\n const legacy =\n frame.entrance !== undefined ||\n doc.endCard !== undefined ||\n heldSegments ||\n overlays.some(hasLegacyClipMotion) ||\n objects.some((o) => o.animation !== undefined)\n if (!legacy) return doc\n\n const out: ProjectDoc = { ...doc }\n if (heldSegments) {\n // A segment's hold is a freeze at its end: spelled once, on the lane\n // every retime lives on, and the segment loses the field.\n const holds = legacyHoldFreezes(segments)\n const own = Array.isArray(doc.freeze) ? doc.freeze : []\n const taken = new Set(own.map((f) => f.id))\n let n = own.length\n const migrated = holds.map((h) => {\n let id = `f${n++}`\n while (taken.has(id)) id = `f${n++}`\n taken.add(id)\n return { id, at: h.at, seconds: h.seconds }\n })\n out.freeze = [...own, ...migrated].sort((a, b) => a.at - b.at)\n out.segments = segments.map((s) => {\n if (s.hold === undefined) return s\n const next = { ...s }\n delete next.hold\n return next\n })\n }\n const nextFrame: FrameStyle = { ...frame }\n if (frame.entrance !== undefined) {\n const e = frame.entrance\n if (e && e.kind !== 'none') {\n nextFrame.anim = {\n ...(frame.anim ?? {}),\n enter:\n e.seconds !== undefined\n ? { kind: e.kind, seconds: e.seconds }\n : e.kind,\n }\n }\n delete nextFrame.entrance\n }\n\n let nextOverlays: OverlayClip[] = overlays.map((o) => {\n if (!hasLegacyClipMotion(o)) return o\n const next = { ...o, anim: migrateClipAnim(o) } as OverlayClip\n delete next.enter\n delete next.exit\n if (next.kind === 'text') delete (next as TextOverlayClip).fx\n return next\n })\n\n if (doc.endCard !== undefined) {\n const card = doc.endCard\n if (card && segments.length) {\n const seconds = Math.max(1, Math.min(8, card.seconds ?? END_CARD_SECONDS))\n // The card stays under the words as a FREEZE of its last frame (the\n // card is gone past its clip), receding over those seconds; the\n // words play over the freeze.\n const segsNow = (out.segments ?? segments) as DocSegment[]\n const lastSeg = segsNow.at(-1)!\n const freezesNow = docFreezes({ segments: segsNow, freeze: out.freeze })\n const rated = withFreezes(\n splitBySpeed(segsNow, doc.speed ?? []),\n freezesNow,\n )\n const endStart = totalDuration(rated)\n const taken = new Set(freezesNow.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n out.freeze = [\n ...freezesNow,\n { id: `f${n}`, at: lastSeg.out, seconds, from: END_CARD_FROM },\n ].sort((a, b) => a.at - b.at)\n nextOverlays = [...nextOverlays, ...endCardClips(card, doc, endStart)]\n nextFrame.anim = {\n ...(nextFrame.anim ?? {}),\n exit: { kind: 'recede', seconds },\n }\n }\n delete out.endCard\n }\n\n const nextObjects: ObjectClip[] = objects.map((o) => {\n if (o.animation === undefined) return o\n const next: ObjectClip = {\n ...o,\n anim: {\n ...(o.anim ?? {}),\n ...(o.anim?.idle === undefined ? { idle: o.animation ?? null } : {}),\n },\n }\n delete next.animation\n return next\n })\n\n if (doc.frame !== undefined || nextFrame.anim !== undefined)\n out.frame = nextFrame\n if (doc.overlays !== undefined || nextOverlays.length)\n out.overlays = nextOverlays\n if (doc.objects !== undefined) out.objects = nextObjects\n return out\n}\n\n/**\n * Where the OUTPUT ends: the footage's end (its rated segments, freezes\n * included) or the last visual clip's end, whichever is later. Past its\n * footage the card is gone and the clips play over the ground. A document\n * that ends on its footage answers the footage's end, byte-identically to\n * before the rule.\n */\nexport function outputEnd(\n doc: Pick<ProjectDoc, 'overlays' | 'objects'>,\n footageEnd: number,\n): number {\n return Math.max(footageEnd, lastLayerEnd(doc))\n}\n\n/** Re-export for callers that read the step shape through this module. */\nexport { animStep }\n","/**\n * ONE animation vocabulary for every visual primitive: the card, a text,\n * image or video clip, a prop. `anim.enter` and `anim.exit` are how a thing\n * arrives and leaves; `anim.idle` is what it does while it stays. A kind\n * alone is the house motion; a step object adds its seconds and, for words,\n * the per-unit grammar (unit, direction, stagger). Pure helpers over the\n * type; the lowering bakes them into the same ON_FRAME data the older\n * spellings produced (`enter`, `exit`, `fx`, `anim`), so a migrated document\n * paints the same picture.\n */\nimport type {\n Anim,\n AnimKind,\n AnimSide,\n AnimStep,\n IdleKind,\n ObjectClip,\n OverlayClip,\n TextFxSpec,\n TextOverlayClip,\n} from './types'\n\n/** What the CARD can do. */\nexport const CARD_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'fade',\n 'rise',\n 'tilt-in',\n 'pull-out',\n 'slide',\n]\nexport const CARD_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'recede']\n/**\n * What a FOOTAGE clip can do at a boundary with the clip beside it: the\n * incoming slides, fades or scales in while the outgoing, frozen on its\n * last frame, does the same out. `none` (or absent) is a hard cut.\n */\nexport const SEGMENT_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'slide',\n 'fade',\n 'scale',\n]\nexport const SEGMENT_EXIT_KINDS: readonly AnimKind[] = [\n 'none',\n 'slide',\n 'fade',\n 'scale',\n]\n/** A transition's house length, seconds. */\nexport const TRANSITION_SECONDS = 0.6\n/** A transition's bounds, seconds (the lint's and the lowering's). */\nexport const TRANSITION_SECONDS_MIN = 0.15\nexport const TRANSITION_SECONDS_MAX = 2\n\n/**\n * A transition step's seconds: its own, else the house length, clamped.\n * Zero for none or absent.\n */\nexport function transitionSeconds(step: AnimStep | null | undefined): number {\n if (!step || step.kind === 'none') return 0\n return Math.max(\n TRANSITION_SECONDS_MIN,\n Math.min(TRANSITION_SECONDS_MAX, step.seconds ?? TRANSITION_SECONDS),\n )\n}\n\n/**\n * A slide's side: where an enter comes from, where an exit goes to. The\n * default turns the page forward: in from the right, out to the left.\n */\nexport function slideSide(\n step: AnimStep | null | undefined,\n which: 'enter' | 'exit',\n): AnimSide {\n return step?.side ?? (which === 'enter' ? 'right' : 'left')\n}\n/** What WORDS can do (the per-unit kinds are text-only). */\nexport const TEXT_ENTER_KINDS: readonly AnimKind[] = [\n 'none',\n 'fade',\n 'rise',\n 'pop',\n 'blur',\n 'typewriter',\n]\nexport const TEXT_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\n/** What an image or a video clip can do. */\nexport const MEDIA_ENTER_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\nexport const MEDIA_EXIT_KINDS: readonly AnimKind[] = ['none', 'fade', 'rise']\n/** What a prop does while it stays. */\nexport const IDLE_KINDS: readonly IdleKind[] = ['spin', 'float']\n\n/** The kinds that animate PER UNIT (word by word, letter by letter). */\nconst UNIT_KINDS: ReadonlySet<AnimKind> = new Set(['pop', 'blur', 'typewriter'])\n\n/**\n * A step as an object: a bare kind becomes `{ kind }`; absent stays null so\n * a caller can tell \"the house default\" from \"none\".\n */\nexport function animStep(\n step: AnimKind | AnimStep | null | undefined,\n): AnimStep | null {\n if (step == null) return null\n if (typeof step === 'string') return { kind: step }\n return step\n}\n\n/** The enter step of a thing, or null when it carries none. */\nexport function enterOf(anim: Anim | null | undefined): AnimStep | null {\n return animStep(anim?.enter)\n}\n\n/** The exit step of a thing, or null when it carries none. */\nexport function exitOf(anim: Anim | null | undefined): AnimStep | null {\n return animStep(anim?.exit)\n}\n\n/**\n * Whether a text clip's enter step is a PER-UNIT animation (the baked `fx`\n * payload ON_FRAME reads) rather than the block transition: a per-unit\n * kind, or a fade/rise that names its unit, stagger or seconds. A bare\n * `'rise'` stays the block transition, which is what a migrated `enter`\n * was; a migrated `fx` always names its unit, so it stays per-unit.\n */\nexport function isUnitStep(step: AnimStep | null): boolean {\n if (!step) return false\n if (UNIT_KINDS.has(step.kind)) return true\n if (step.kind !== 'fade' && step.kind !== 'rise') return false\n return (\n step.unit !== undefined ||\n step.stagger !== undefined ||\n step.seconds !== undefined ||\n step.direction !== undefined\n )\n}\n\n/**\n * The text-fx spec a clip's enter step describes, in the shape the fx\n * normalizer reads, or null when the clip enters by the block transition.\n */\nexport function overlayFxSpec(clip: TextOverlayClip): TextFxSpec | null {\n const step = enterOf(clip.anim)\n if (!isUnitStep(step) || !step || step.kind === 'none') return null\n const kind = step.kind\n const fx: TextFxSpec['fx'] =\n kind === 'fade' ||\n kind === 'rise' ||\n kind === 'pop' ||\n kind === 'blur' ||\n kind === 'typewriter'\n ? kind\n : 'rise'\n return {\n fx,\n ...(step.unit !== undefined ? { unit: step.unit } : {}),\n ...(step.direction !== undefined ? { direction: step.direction } : {}),\n ...(step.stagger !== undefined ? { stagger: step.stagger } : {}),\n ...(step.seconds !== undefined ? { duration: step.seconds } : {}),\n }\n}\n\nconst BLOCK_KINDS: ReadonlySet<string> = new Set(['none', 'fade', 'rise'])\n\n/**\n * The block transition ON_FRAME reads as `enter`: the step's kind when it\n * is one of the three transitions, the house `rise` otherwise (a per-unit\n * step bakes `fx` beside it, and ON_FRAME ignores `enter` while `fx` is\n * set, exactly as before).\n */\nexport function enterKey(clip: OverlayClip): 'none' | 'fade' | 'rise' {\n const step = enterOf(clip.anim)\n if (!step) return 'rise'\n return BLOCK_KINDS.has(step.kind)\n ? (step.kind as 'none' | 'fade' | 'rise')\n : 'rise'\n}\n\n/** The block transition ON_FRAME reads as `exit`. */\nexport function exitKey(clip: OverlayClip): 'none' | 'fade' | 'rise' {\n const step = exitOf(clip.anim)\n if (!step) return 'fade'\n return BLOCK_KINDS.has(step.kind)\n ? (step.kind as 'none' | 'fade' | 'rise')\n : 'fade'\n}\n\n/** A prop's idle motion as ON_FRAME reads it (`anim`). */\nexport function idleKey(clip: ObjectClip): IdleKind | null {\n const idle = clip.anim?.idle\n return idle === 'spin' || idle === 'float' ? idle : null\n}\n\n/**\n * Where the last VISUAL layer ends, in output seconds: a text, image or\n * video clip's end, a prop's span end. The output lasts at least this\n * long, so an end card is nothing but clips placed after the footage.\n * Audio never extends the output (a sound after the last picture is not a\n * thing to watch); a bed is trimmed to the output instead. A prop with no\n * span is always on and extends nothing.\n */\nexport function lastLayerEnd(\n doc: Pick<\n Partial<{ overlays: OverlayClip[]; objects: ObjectClip[] }>,\n 'overlays' | 'objects'\n >,\n): number {\n let end = 0\n for (const o of doc.overlays ?? []) end = Math.max(end, o.start + o.duration)\n for (const o of doc.objects ?? [])\n if (o.span) end = Math.max(end, o.span.start + o.span.duration)\n return Number.isFinite(end) && end > 0 ? end : 0\n}\n","/**\n * Many media in one take, one rule: a recording is media plus the FACTS\n * only a capture has, and `doc.source` is the PRIMARY media (the subject:\n * the sequence, the camera and the cut belong to it). `doc.media` holds\n * the others, each the same shape with an `id`; a footage clip (a segment)\n * and every source-anchored span (zoom, tilt, speed, freeze, cam move, a\n * rejected proposal) name the media they belong to by that id, and an\n * absent `media` is the primary. Nothing else moves: `mapTime` still\n * answers source seconds, and the piece under the playhead says whose.\n */\nimport type { Segment } from '@vosjs/timeline'\nimport { MEDIA_FRAME_KEYS, MEDIA_KEY_PREFIX, pageDisplayUrl } from './types'\nimport type { FrameStyle, Media, MediaFrameKey, ProjectDoc } from './types'\n\n/** The primary media's key: the absent `media` on a clip or a span. */\nexport const PRIMARY_MEDIA = ''\n\n/** The key a `media` field carries, the primary's when absent. */\nexport const mediaKey = (id: string | undefined | null): string =>\n id ?? PRIMARY_MEDIA\n\n/** Do two `media` fields name the same media (absent = the primary)? */\nexport const sameMedia = (\n a: string | undefined | null,\n b: string | undefined | null,\n): boolean => mediaKey(a) === mediaKey(b)\n\n/** A piece of the rated list, carrying the media it plays. */\nexport type MediaSegment = Segment & { media?: string }\n\n/**\n * Every media the document carries, the primary first (its key is the\n * empty string), then `doc.media` in order.\n */\nexport function docMediaList(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n): { id: string; source: ProjectDoc['source'] }[] {\n return [\n { id: PRIMARY_MEDIA, source: doc.source },\n ...(doc.media ?? []).map((m) => ({ id: m.id, source: m })),\n ]\n}\n\n/** The media a key names, the primary for the empty key, null for a stranger. */\nexport function mediaSource(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n id: string | undefined | null,\n): ProjectDoc['source'] | null {\n const key = mediaKey(id)\n if (key === PRIMARY_MEDIA) return doc.source\n return (doc.media ?? []).find((m) => m.id === key) ?? null\n}\n\n/** A media's source length in seconds (its capture meta). */\nexport function mediaDuration(\n source: Pick<ProjectDoc['source'], 'meta'>,\n): number {\n return (source.meta.durationMs || 0) / 1000\n}\n\n/**\n * The media the output plays at `t`: the rated piece under it, the last\n * piece past the footage's end (the frame the card froze on), the primary\n * for an empty list.\n */\nexport function mediaAtOutput(\n rated: readonly MediaSegment[],\n t: number,\n): string {\n let acc = 0\n let last: MediaSegment | null = null\n for (const p of rated) {\n const len = Math.max(0, p.out - p.in) / (p.rate && p.rate > 0 ? p.rate : 1)\n if (t < acc + len) return mediaKey(p.media)\n acc += len\n last = p\n }\n return last ? mediaKey(last.media) : PRIMARY_MEDIA\n}\n\n/**\n * The card a media wears: the take's frame with the media's own card\n * fields over it. Facts first: a media with a recorded page names it in\n * the bar, and one with no page (an upload) wears no bar, unless its own\n * `frame.browserBar` says otherwise. The primary wears the take's frame.\n */\nexport function mediaFrame(\n doc: Pick<ProjectDoc, 'source' | 'media' | 'frame'>,\n id: string | undefined | null,\n): FrameStyle {\n const key = mediaKey(id)\n if (key === PRIMARY_MEDIA) return doc.frame\n const m = (doc.media ?? []).find((x) => x.id === key)\n if (!m) return doc.frame\n const over = m.frame ?? {}\n const out: FrameStyle = { ...doc.frame }\n for (const k of MEDIA_FRAME_KEYS) {\n if (k === 'browserBar') continue\n const v = over[k]\n if (v !== undefined) (out as Record<MediaFrameKey, unknown>)[k] = v\n }\n const bar = { ...doc.frame.browserBar }\n const page = pageDisplayUrl(m.meta.pageUrl)\n if (page) bar.url = page\n if (!m.meta.pageUrl) bar.kind = 'none'\n out.browserBar = { ...bar, ...(over.browserBar ?? {}) }\n return out\n}\n\n/** The card-owned fields of a frame, alone (what a media's card is made of). */\nexport function cardFields(frame: FrameStyle): Pick<FrameStyle, MediaFrameKey> {\n const out: Partial<Pick<FrameStyle, MediaFrameKey>> = {}\n for (const k of MEDIA_FRAME_KEYS) {\n const v = frame[k]\n if (v !== undefined) (out as Record<MediaFrameKey, unknown>)[k] = v\n }\n return out as Pick<FrameStyle, MediaFrameKey>\n}\n\n/** The overlay key that shows a document media (`''` = the primary). */\nexport const mediaRef = (id: string): string => MEDIA_KEY_PREFIX + id\n\n/** The media id an overlay key names, `''` for the primary, null for a plain key. */\nexport function mediaRefId(key: string | undefined | null): string | null {\n if (typeof key !== 'string' || !key.startsWith(MEDIA_KEY_PREFIX)) return null\n return key.slice(MEDIA_KEY_PREFIX.length)\n}\n\n/** The document media a layer's key shows, or null for a plain key or a stranger. */\nexport function layerMedia(\n doc: Pick<ProjectDoc, 'source' | 'media'>,\n key: string | undefined | null,\n): ProjectDoc['source'] | null {\n const id = mediaRefId(key)\n return id === null ? null : mediaSource(doc, id)\n}\n\n/** The next free media id (`m1`, `m2`, …). */\nexport function nextMediaId(doc: Pick<ProjectDoc, 'media'>): string {\n const taken = new Set((doc.media ?? []).map((m) => m.id))\n let n = 1\n while (taken.has(`m${n}`)) n++\n return `m${n}`\n}\n\n/** A document's media as `Media` rows (the primary is not one: it has no id). */\nexport type MediaRow = Media\n","/**\n * Zoom/pan camera styles — named strategy\n * presets covering the whole auto-zoom pipeline: how the planner turns clicks\n * into spans (cluster vs session merging, level clamps, follow default) AND\n * how the lowering animates the camera (ramp durations, eases, connected-pan\n * gap, dead-zone follow tuning, and the tilt track's tempo). One name → one\n * coherent feel.\n *\n * The presets were grounded (2026-07) in a measured comparison of the four\n * shipping strategies (frame-by-frame optical-flow tracking of real exports +\n * source audits of Recordly/OpenScreen + Cursorful bundle/behavior research),\n * and re-founded (2026-09) on the stage camera at 60 fps, where every style\n * is timed against the default's traced arrival and obeys two structural\n * rules (see `pumpFreeChainGap` and the lowering's ramp fitting):\n *\n * - Cursorful (\"glide\"): ONE modest zoom (~1.5×) per activity session —\n * clicks within a rolling window keep the zoom alive — and the camera\n * TRAVELS by panning between focus points while zoomed. Its arrival was\n * traced at 60 fps and fitted by sum of differences (see the entry).\n * - Screen Studio \"Focused\" / Recordly (\"focus\"): a zoom block per click\n * cluster, spring-settled ramps, direct pans across short gaps, no cursor\n * follow. The measured OpenScreen/Recordly ramp (their bezier filtered\n * through the spring) fits css-bezier(0.28, 0.03, 0.09, 1) — that curve IS\n * the family feel; it now runs at the default's tempo instead of 1.4 s.\n * - Screen Studio \"Smooth\" (\"cinema\"): the long-HOLD style, slower and more\n * fluid — for content that is watched, not read.\n * - \"snappy\": the studio's original fast cycles, with the defect fixed — the\n * raw css-bezier(0.16, 1, 0.3, 1) arrival had an initial velocity 6.25×\n * the ramp average (the \"jump cut\" complaint); competitors always filter\n * that curve through a spring. This preset keeps the pace but caps the\n * onset.\n * - \"cut\": Screen Studio's \"instant zoom\" option — hard cut-in, no glide.\n *\n * `keynote` and `drift` (2026-08) were another row plus a tilt intensity the\n * panel already exposes as its own control; they are retired names that\n * resolve to `glide` + medium and `cinema` + subtle (RETIRED_ZOOM_STYLES;\n * migrated on read by docVersion). What they had right survives in every\n * style: the tilt track moves at the style's own zoom tempo, so a chosen\n * lean lands WITH its zoom.\n *\n * Style changes are live SET_DATA (the zoom track is data); regenerating\n * auto spans on a style switch re-plans with the style's planner params while\n * preserving user-touched ('manual') spans, per the wand contract.\n */\n\nimport type { TiltStyleName } from './types'\n\nexport type ZoomStyleName =\n | 'glide'\n | 'focus'\n | 'cinema'\n | 'snappy'\n | 'cut'\n | 'none'\n\n/**\n * Retired style names and what they meant: a live style plus the tilt\n * intensity the name carried. `resolveZoomStyle` honours the name at runtime\n * (a hand-edited doc.json never falls silently to the default) and\n * `migrateHostedDoc` rewrites a stored document onto the pair.\n */\nexport const RETIRED_ZOOM_STYLES: Record<\n string,\n { style: ZoomStyleName; tilt: TiltStyleName }\n> = {\n keynote: { style: 'glide', tilt: 'medium' },\n drift: { style: 'cinema', tilt: 'subtle' },\n}\n\n/** The live style a name resolves to, retired names included; null = unknown. */\nexport function liveZoomStyleName(\n name: string | undefined,\n): ZoomStyleName | null {\n if (!name) return null\n if (name in ZOOM_STYLES) return name as ZoomStyleName\n return RETIRED_ZOOM_STYLES[name]?.style ?? null\n}\n\n/**\n * The tilt half of a camera style: the Dynamic-tilt intensity the style\n * ships with (every live style ships 'off' — intensity is the Dynamic tilt\n * dial's, never a name's) plus the tilt track's motion, which is the\n * style's OWN zoom tempo so a lean lands with its zoom. A style pick stamps\n * `doc.tiltStyle` with `intensity` and re-plans auto tilt spans alongside\n * the auto zooms — one name, one coherent camera sentence (zoom AND lean).\n */\nexport interface TiltPersonality {\n /** Dynamic-tilt intensity this style ships with ('off' = flat card). */\n intensity: TiltStyleName\n /** tilt ramp overrides (seconds); absent = the TILT_RAMP_* constants. */\n rampIn?: number\n rampOut?: number\n /** output-time gap ≤ this → swing pose-to-pose (absent = TILT_CHAIN_GAP). */\n chainGap?: number\n /** connected-swing duration (absent = TILT_PAN). */\n pan?: number\n}\n\nexport interface ZoomStyleParams {\n // ── planner (planAutoZoom) ───────────────────────────────────────────────\n /** false = the planner emits nothing (the 'none' style — manual zooms only). */\n autoZoom: boolean\n /** clicks within this many seconds merge into one span (session merge). */\n clusterGap: number\n /**\n * Minimum clicks for a cluster to earn a zoom. Every style is 1: the\n * Cursorful ≥2 rule glide once carried was never in effect on a recorded\n * take (the recorder counted each press twice, so a lone click was a pair\n * on most pages and a stray on the rest), and the lone click on a button\n * is the zoom people miss when it goes.\n */\n minClusterClicks: number\n /** zoom so the clicked element fills ~this fraction of the frame. */\n targetFill: number\n minLevel: number\n maxLevel: number\n /**\n * The level a DRAG is followed at (a press that travels before its\n * release: a slider thumb, a scrubber, a thing moved across a canvas).\n * The camera pans WITH the pointer for the whole press (a path follow,\n * not a dead zone), so the level stays mild: the thumb and what it drives\n * both stay in the window. By rule minLevel + 0.2.\n */\n dragLevel: number\n /** span lead-in before the first click / hold after the last (seconds). */\n lead: number\n hold: number\n /** planner emits spans with focusMode 'auto' (cursor-follow camera). */\n followByDefault: boolean\n // ── typing (`key` activity pings → typing-session spans) ─────────────\n /** false = typing sessions plan no spans (clicks/dwells still do). */\n typingZoom: boolean\n /** max silence between pings before the typing session ends (seconds). */\n typingGap: number\n /** hold after the last keystroke — the read-what-you-typed beat (seconds). */\n typingHold: number\n /**\n * Level FLOOR for typing spans, above the style's minLevel: a wide field\n * (URL bar, dialog search input) fit-clamps to this instead — typing is the\n * moment being narrated, so it reads a notch punchier than a wide click.\n * By rule minLevel + 0.1.\n */\n typingMinLevel: number\n // ── camera (zoomTrackFromDoc) ────────────────────────────────────────────\n /** zoom-in ramp duration; arrival lands rampInOverlap into the span. */\n rampIn: number\n rampInOverlap: number\n rampOut: number\n /**\n * output-time gap ≤ this → pan straight to the next span (no zoom-out).\n * Never below `pumpFreeChainGap`: a gap just past it would zoom out and\n * straight back in (the pump), which is arithmetic, not taste.\n */\n chainGap: number\n /** connected-pan duration. */\n pan: number\n /** arrival ease (zoom-in AND zoom-out). */\n ease: string\n /** connected-pan + follow-recenter ease. */\n panEase: string\n // ── cursor follow (followFocusEvents) ────────────────────────────────────\n /** recenter when the cursor exits this central fraction of the crop. */\n followSafeRatio: number\n /** seconds the camera takes to glide to a recentered focus. */\n followRecenter: number\n /**\n * Recenter targets the cursor this many seconds AHEAD of the exit moment\n * (Cursorful's look-ahead: the camera leads the pointer instead of chasing\n * a stale position). Sampled from the real track — still deterministic.\n */\n followLookahead: number\n // ── tilt (planAutoTilt + tiltTrackFromDoc) ───────────────────────────────\n /** The style's tilt personality — see TiltPersonality. */\n tilt: TiltPersonality\n}\n\n/**\n * The shortest rest between a zoom-out and the next zoom-in that reads as\n * a rest rather than a pump (seconds).\n */\nexport const PUMP_REST = 1.0\n\n/**\n * The chain gap below which a style PUMPS: two spans whose gap lies in\n * (chainGap, chainGap + rampOut + rampIn − overlap + PUMP_REST] zoom out and\n * straight back in with no rest between. Every ZOOM_STYLES entry clears\n * this floor by construction (a test pins it) and `resolveZoomStyle`\n * raises a `zoomParams` override that does not.\n */\nexport function pumpFreeChainGap(\n p: Pick<ZoomStyleParams, 'rampIn' | 'rampInOverlap' | 'rampOut'>,\n): number {\n return p.rampOut + (p.rampIn - p.rampInOverlap) + PUMP_REST\n}\n\n// Cursorful strategy — one steady zoom per activity session, travel by pans.\n// The camera's timing was measured against a Cursorful take of the same\n// steps: a zoom-in of ~0.55 s on a heavy ease-out (most of the move in\n// the first quarter second, a long settle), a zoom-out of ~0.45 s, and\n// holds of four to five seconds per beat. The old 1.1 s ramp started\n// 0.75 s BEFORE the click and landed 0.35 s after it, so the frame was\n// moving more of the time than it was resting.\nconst glide: ZoomStyleParams = {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 1,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n dragLevel: 1.5,\n lead: 0.35,\n hold: 1.2,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 0.6,\n rampInOverlap: 0.2,\n rampOut: 0.5,\n chainGap: 3.0,\n pan: 0.55,\n // The reference's corner trace at 60 fps: a quarter of the move by a\n // fifth of the time, two thirds by half, nine tenths by three\n // quarters, over ~0.6 s. Fitted against those three points,\n // (0.36, 0, 0.4, 1) is the closest cubic (15 / 64 / 93 %; half the\n // move at ~245 ms and 90 % at ~425 ms at 0.6 s): a gentle start and a\n // long settle. CSS ease (0.25, 0.1, 0.25, 1) reached the midpoint\n // 70 ms earlier and read a touch fast; (0.16, 1, 0.3, 1) put half the\n // move in the first sixty milliseconds and read as a twitch.\n ease: 'css-bezier(0.36, 0, 0.4, 1)',\n panEase: 'css-bezier(0.36, 0, 0.4, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.55,\n followLookahead: 0.4,\n tilt: {\n intensity: 'off',\n rampIn: 0.6,\n rampOut: 0.5,\n chainGap: 3.0,\n pan: 0.55,\n },\n}\n\nexport const ZOOM_STYLES: Record<ZoomStyleName, ZoomStyleParams> = {\n glide,\n // Screen Studio \"Focused\" / Recordly — a block per click, framed and\n // released, no travel. The spring-settled family ease (near-linear onset,\n // long settle) at the default's tempo: 0.8 s in, 0.65 s out, and a beat\n // of 1.5 s after the click so the block is read before it goes. At its\n // 2026-07 timing (1.4 s in, 1.0 s out around a 1.35 s span) a lone click\n // spent 74% of its beat moving and every dwell or sped-up click was\n // shorter than the ramp, which the lowering compressed into a cut.\n focus: {\n autoZoom: true,\n clusterGap: 1.5,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.0,\n dragLevel: 1.6,\n lead: 0.35,\n hold: 1.5,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.0,\n typingHold: 1.3,\n typingMinLevel: 1.5,\n rampIn: 0.8,\n rampInOverlap: 0.25,\n rampOut: 0.65,\n chainGap: 2.4,\n pan: 0.7,\n ease: 'css-bezier(0.28, 0.03, 0.09, 1)',\n panEase: 'css-bezier(0.26, 0.08, 0.2, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.55,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.8,\n rampOut: 0.65,\n chainGap: 2.4,\n pan: 0.7,\n },\n },\n // Screen Studio \"Smooth\" — the long-HOLD style: 1.2 s in, 1.0 s out,\n // two-second holds, chains across 3.5 s so a slow clip travels rather\n // than pumps, and the default's level range, because a watched clip\n // wants the frame, not the magnifier. Its ramps still exceed a dwell or\n // a sped-up click; the lowering fits them to the room.\n cinema: {\n autoZoom: true,\n clusterGap: 2.4,\n minClusterClicks: 1,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n dragLevel: 1.5,\n lead: 0.6,\n hold: 2.0,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.8,\n typingMinLevel: 1.4,\n rampIn: 1.2,\n rampInOverlap: 0.4,\n rampOut: 1.0,\n chainGap: 3.5,\n pan: 1.1,\n ease: 'css-bezier(0.33, 0, 0.15, 1)',\n panEase: 'css-bezier(0.33, 0, 0.22, 1)',\n followSafeRatio: 0.6,\n followRecenter: 0.9,\n followLookahead: 0.5,\n tilt: {\n intensity: 'off',\n rampIn: 1.2,\n rampOut: 1.0,\n chainGap: 3.5,\n pan: 1.1,\n },\n },\n // The studio's original pace with the instant-velocity onset defect fixed:\n // 0.4 s cycles on the onset-capped ease, the 2.5× ceiling of the 2026-07\n // comparison's defect two brought to 2.2, a chain gap past its pump band.\n snappy: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.2,\n dragLevel: 1.6,\n lead: 0.25,\n hold: 1.2,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.6,\n typingHold: 0.9,\n typingMinLevel: 1.5,\n rampIn: 0.4,\n rampInOverlap: 0.15,\n rampOut: 0.4,\n chainGap: 2.0,\n pan: 0.5,\n ease: 'css-bezier(0.3, 0.55, 0.2, 1)',\n panEase: 'css-bezier(0.25, 0.1, 0.25, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.45,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.4,\n rampOut: 0.4,\n chainGap: 2.0,\n pan: 0.5,\n },\n },\n // Screen Studio's \"instant zoom\" — a cut-in (eight frames at 60 fps),\n // tutorial tempo. Untouched in motion (the one style that never pumped\n // and never left a ramp unsettled); the ceiling comes down with the\n // others and the hold gains the beat a cut-in needs to be read.\n cut: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.0,\n dragLevel: 1.6,\n lead: 0.2,\n hold: 1.2,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.2,\n typingHold: 0.6,\n typingMinLevel: 1.5,\n rampIn: 0.14,\n rampInOverlap: 0.07,\n rampOut: 0.14,\n chainGap: 1.3,\n pan: 0.35,\n ease: 'css-bezier(0.2, 0, 0.4, 1)',\n panEase: 'css-bezier(0.2, 0, 0.4, 1)',\n followSafeRatio: 0.5,\n followRecenter: 0.35,\n followLookahead: 0,\n tilt: {\n intensity: 'off',\n rampIn: 0.3,\n rampOut: 0.3,\n chainGap: 1.3,\n pan: 0.35,\n },\n },\n // Auto-zoom off (every competitor ships this switch). Camera params still\n // apply to MANUAL spans — they move the way the default camera moves\n // TODAY (spelled as a spread, never a copy that fossilises).\n none: { ...glide, autoZoom: false },\n}\n\n/** The default camera style for new projects (the Cursorful-family strategy). */\nexport const DEFAULT_ZOOM_STYLE: ZoomStyleName = 'glide'\n\n/**\n * Resolve a style name (+ optional per-doc overrides, `doc.zoomParams`) into a\n * full parameter bundle. Overrides are the \"Custom\" seam: agents/doc.json can\n * tune individual params on top of a named preset; the studio shows Custom\n * while any override is present. A retired name resolves to its live style;\n * an unknown name from a hand-edited doc.json falls back to the default.\n * The chain gap never rests below its pump-free floor, whatever an override\n * asked for.\n */\nexport function resolveZoomStyle(\n name?: string,\n overrides?: Partial<ZoomStyleParams>,\n): ZoomStyleParams {\n const live = liveZoomStyleName(name) ?? DEFAULT_ZOOM_STYLE\n const params = { ...ZOOM_STYLES[live], ...overrides }\n const floor = pumpFreeChainGap(params)\n if (params.chainGap < floor) params.chainGap = floor\n return params\n}\n\n/** Picker order + copy for the studio's Camera style control. */\nexport const ZOOM_STYLE_OPTIONS: {\n name: ZoomStyleName\n label: string\n hint: string\n}[] = [\n {\n name: 'glide',\n label: 'Glide',\n hint: 'One steady zoom that travels between clicks',\n },\n {\n name: 'focus',\n label: 'Focus',\n hint: 'A zoom per click, framed and released',\n },\n {\n name: 'cinema',\n label: 'Cinema',\n hint: 'Slow moves and long holds, for content that is watched',\n },\n { name: 'snappy', label: 'Snappy', hint: 'Quick, energetic zoom cycles' },\n { name: 'cut', label: 'Cut', hint: 'Instant zooms, no glide' },\n {\n name: 'none',\n label: 'None',\n hint: 'No automatic zooms, add your own on the timeline',\n },\n]\n","/**\n * Hosted-doc schema versioning: the scoped reversal of \"ProjectDocs\n * are never persisted\" is hosted versions only, and every persisted doc is\n * stamped `docSchemaVersion` from day one so the migration obligation the\n * old rule avoided stays bounded to one seam — migrate-on-read, here.\n *\n * Local studio sessions still never persist docs; CLI take dirs carry\n * doc.json under `schema/doc.schema.json` (which tolerates the stamp via\n * additionalProperties). Both hydration paths (studio handback, `vos\n * pull`) run through migrateHostedDoc before trusting a hosted doc.\n */\nimport { migrateMotion } from './lower/motion'\nimport { RETIRED_ZOOM_STYLES } from './zoomStyle'\nimport type { ProjectDoc } from './types'\n\n/**\n * 5 = the six-style era: the retired camera styles `keynote` and `drift`\n * are read into their live style plus the tilt intensity the name carried\n * (`glide` + medium, `cinema` + subtle; an explicit `tiltStyle` wins). The\n * spans are untouched: a migration never re-plans.\n * 4 = the freeze era: a segment's `hold` is read into `doc.freeze` (a\n * freeze at that segment's end), the retime primitive beside speed spans.\n * 3 = the one-vocabulary era: a recording document's `frame.entrance`,\n * `endCard`, clip `enter`/`exit`/`fx` and prop `animation` are read into\n * `anim` (and the end card into clips after the footage plus a card exit).\n * 2 = the document FAMILY era (a recording document, or a program\n * document); a v1 doc IS a recording document, so 1 → 2 was a stamp, and\n * 0 → 1 was a stamp too.\n */\nexport const DOC_SCHEMA_VERSION = 5\n\n/**\n * Upgrade a hosted doc.json payload to the current schema version.\n * Unstamped docs are v0 — the pre-stamp era. v0 → v2 were stamps; v2 → v3\n * rewrites a recording document's motion spellings into the vocabulary,\n * and v3 → v4 its segment holds into freezes (one rewrite, `migrateMotion`,\n * idempotent, so both steps are one call); v4 → v5 rewrites a retired\n * camera style name (`migrateZoomStyle`); a program document is left as\n * it was (its layers migrate the same way when they carry the old\n * spellings). A real shape change chains its step here.\n */\nexport function migrateHostedDoc(\n raw: Record<string, unknown>,\n): Record<string, unknown> {\n const version =\n typeof raw.docSchemaVersion === 'number' ? raw.docSchemaVersion : 0\n if (version >= DOC_SCHEMA_VERSION) return raw\n let doc = raw\n if (version < 4 && doc.source && typeof doc.source === 'object') {\n doc = migrateMotion(doc as unknown as ProjectDoc) as unknown as Record<\n string,\n unknown\n >\n }\n if (version < 5) doc = migrateZoomStyle(doc)\n return { ...doc, docSchemaVersion: DOC_SCHEMA_VERSION }\n}\n\n/**\n * A retired camera style name becomes its live style plus the tilt\n * intensity the name carried, unless the document already says how far it\n * leans. Idempotent; a document on a live name is returned as it was.\n */\nexport function migrateZoomStyle(\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const name = doc.zoomStyle\n if (typeof name !== 'string') return doc\n const retired = RETIRED_ZOOM_STYLES[name]\n if (!retired) return doc\n return {\n ...doc,\n zoomStyle: retired.style,\n tiltStyle: doc.tiltStyle ?? retired.tilt,\n }\n}\n","import type {\n AudioClip,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n SpeedSpan,\n} from '../types'\n\n/**\n * The studio document family.\n *\n * A document is an ANCHOR plus the layers every anchor shares. `ProjectDoc`\n * is the recording-anchored member, field for field what it always was (its\n * wire, `doc.json`, does not move). `ProgramAnchorDoc` is the program-anchored\n * member: its anchor IS the user's config — the execution IR, untouched\n * — plus the tween-timing overlay that used to live only in a\n * hook. The shared layers are optional on it until the shared modules activate them.\n *\n * Discriminated on `source`: every recording doc carries one, no program doc\n * may.\n */\n\n/** One entry of the tween-timing overlay — `@vosjs/tween`'s `TweenEdit`, structurally. */\nexport interface ProgramTweenEdit {\n index: number\n startTime?: number\n duration?: number\n ease?: string\n to?: Record<string, number>\n from?: Record<string, number>\n}\n\nexport interface ProgramAnchorDoc {\n program: {\n /** THE user's config, as authored (functions as strings). Never composed here. */\n config: Record<string, unknown>\n /** Retimes over the config's recorded tweens, by spec index. */\n tweenEdits?: Record<number, ProgramTweenEdit>\n /** The anchor's own length when the config's is a placeholder. */\n duration?: number\n }\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n /** Required, like the recording's: the audio module and its lane read it without a guard. Minted `[]`. */\n audio: AudioClip[]\n /** Retime spans over the ANCHOR's clock: the recording's type, `in`/`out` in program seconds. */\n speed?: SpeedSpan[]\n export?: ProjectDoc['export']\n}\n\nexport type StudioDoc = ProjectDoc | ProgramAnchorDoc\n\nexport type AnchorKind = 'recording' | 'program'\n\nexport const anchorKindOf = (doc: StudioDoc): AnchorKind =>\n 'source' in doc ? 'recording' : 'program'\n\nexport const isRecordingDoc = (doc: StudioDoc): doc is ProjectDoc =>\n 'source' in doc\n\nexport const isProgramDoc = (doc: StudioDoc): doc is ProgramAnchorDoc =>\n !('source' in doc)\n\n/** A program anchor's own length in seconds: `program.duration`, else the config's. */\nexport function programDuration(doc: ProgramAnchorDoc): number {\n const own = doc.program.duration\n if (typeof own === 'number' && own > 0) return own\n const cfg = doc.program.config.duration\n return typeof cfg === 'number' && cfg > 0 ? cfg : 0\n}\n\n/**\n * The anchor's SOURCE length: the footage's for a recording, the\n * program's own for a program. Speed spans, segments and every source-time\n * floor measure against it.\n */\nexport function anchorSourceDuration(doc: StudioDoc): number {\n return isRecordingDoc(doc)\n ? doc.source.meta.durationMs / 1000\n : programDuration(doc)\n}\n","/**\n * Cursor smoothing.\n *\n * Raw pointer samples are jittery and irregularly spaced. We resample to a fixed\n * cadence and apply an exponential lerp — counterintuitively, linear smoothing\n * beats ease-in-out for cursors (easing stutters between samples). Pure and\n * deterministic: same input → same output.\n */\nimport type { CursorTrack } from '../types'\n\nexport interface SmoothPoint {\n /** seconds. */\n t: number\n x: number\n y: number\n}\n\nexport interface SmoothOptions {\n /** lerp factor per step, 0..1 (higher = smoother/laggier). Default 0.15. */\n factor?: number\n /** resample cadence in fps. Default 60. */\n fps?: number\n /**\n * Pull the smoothed path onto each click's true position around the click\n * instant: click effects anchor at the\n * click point, so the (laggy) smoothed cursor must arrive on time or the\n * ring blooms away from the dot. The pull feeds back into the lerp state,\n * so the path continues from the click point afterwards. Deterministic.\n */\n clickSnap?: boolean\n}\n\n/** Snap window: the pull ramps in over this many seconds before the click… */\nconst SNAP_BEFORE = 0.12\n/** …and holds through this many seconds after it (covers the press dip). */\nconst SNAP_AFTER = 0.18\n/** Per-step pull gain at full envelope (60 fps steps → arrives by click time). */\nconst SNAP_GAIN = 0.5\n\n/** Linear interpolation of raw (move) samples at an arbitrary time. */\nfunction sampleAt(points: SmoothPoint[], t: number): { x: number; y: number } {\n if (points.length === 0) return { x: 0, y: 0 }\n if (t <= points[0].t) return { x: points[0].x, y: points[0].y }\n const last = points[points.length - 1]\n if (t >= last.t) return { x: last.x, y: last.y }\n // linear scan is fine for studio-length tracks; binary search if needed later\n for (let i = 1; i < points.length; i++) {\n if (points[i].t >= t) {\n const a = points[i - 1]\n const b = points[i]\n const f = (t - a.t) / (b.t - a.t || 1)\n return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f }\n }\n }\n return { x: last.x, y: last.y }\n}\n\n/**\n * Produce a smoothed, fixed-cadence cursor path (in seconds) from a raw track.\n * Only `move`/`down`/`up` events carry positions; others are ignored here —\n * `scroll` re-emits a stale point, and `focus`/`key` synthesize element\n * centers the cursor never visited (letting those in would teleport the dot).\n */\nexport function smoothCursor(\n track: CursorTrack,\n options: SmoothOptions = {},\n): SmoothPoint[] {\n const factor = clamp(options.factor ?? 0.15, 0.01, 1)\n const fps = options.fps ?? 60\n const raw: SmoothPoint[] = track\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n if (raw.length === 0) return []\n\n const clicks: SmoothPoint[] = options.clickSnap\n ? track\n .filter((e) => e.type === 'down')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n : []\n\n const start = raw[0].t\n const end = raw[raw.length - 1].t\n const step = 1 / fps\n const out: SmoothPoint[] = []\n let cx = raw[0].x\n let cy = raw[0].y\n let ci = 0\n for (let t = start; t <= end + 1e-9; t += step) {\n const target = sampleAt(raw, t)\n cx += (target.x - cx) * factor\n cy += (target.y - cy) * factor\n if (clicks.length) {\n // smoothstep envelope rising into the click, held through SNAP_AFTER\n while (ci < clicks.length - 1 && t > clicks[ci].t + SNAP_AFTER) ci++\n const ck = clicks[ci]\n const u = clamp(\n (t - (ck.t - SNAP_BEFORE)) / SNAP_BEFORE,\n 0,\n t <= ck.t + SNAP_AFTER ? 1 : 0,\n )\n const w = u * u * (3 - 2 * u) * SNAP_GAIN\n if (w > 0) {\n cx += (ck.x - cx) * w\n cy += (ck.y - cy) * w\n }\n }\n out.push({ t, x: cx, y: cy })\n }\n return out\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\n","/**\n * Element-aware auto-zoom planner — the differentiator.\n *\n * Because we capture inside the browser, a click is not a guessed pixel point\n * but a known element (`rect`). We frame that element's bounding box, merge\n * clustered clicks into one sustained zoom span, and pick an adaptive level\n * that fits the element (unlike fixed-level recorders). Clicks are the PRIMARY\n * signal (Recordly's stance); TYPING SESSIONS are their peer (nobody\n * ships this): `key` activity pings group into sessions that frame the field\n * being typed into, absorb the click that focused it (the camera commits as\n * the field is clicked), and hold until typing stops; DWELLS augment last\n * (OpenScreen's stance) only where no other span exists — the cursor parking\n * on the thing being narrated is worth framing even without a click. Pure &\n * deterministic: same track → same spans. The editor edits this *output*, not\n * the planner — every span is tagged `source: 'auto'`, so a regenerate can\n * replace planner suggestions while leaving user-touched ('manual') spans\n * alone. Ids by origin: `z{n}` clicks, `k{n}` typing, `d{n}` dwells.\n */\nimport { clampZoomLevel } from '../types'\nimport { resolveZoomStyle } from '../zoomStyle'\nimport type { CursorTrack, Rect, ZoomSpan } from '../types'\nimport type { ZoomStyleName, ZoomStyleParams } from '../zoomStyle'\n\n/** Normalized step distance that ends a dwell run (OpenScreen's 0.02). */\nconst DWELL_MOVE_FRAC = 0.02\n/** A run qualifies as a dwell when it lasts this long (seconds). */\nconst DWELL_MIN = 0.45\nconst DWELL_MAX = 2.6\n/** Min gap between accepted dwell centers (longest dwell wins). */\nconst DWELL_SPACING = 1.8\n/**\n * A click cluster's targets, taken together, must sit within this fraction\n * of the zoom window (each axis), so the camera lands with room around the\n * union rather than an edge on a target. It is what makes a cluster: presses\n * that cannot share one window at a level worth zooming to are two beats,\n * however close in time.\n */\nexport const CLUSTER_FILL = 0.85\n/**\n * A press whose element FITS the frame at less than this level is not a\n * target: it is a drag (aiming, scrubbing, moving a thing across the canvas)\n * or a frame-sized surface (a canvas, a panel, a modal clicked to focus or\n * dismiss it), and a zoom on it says nothing. Five real takes (2026-08-25)\n * each carried 1-4 clusters of these, planned at the floor level for 10-25s;\n * every one was dropped by hand. Now they plan nothing.\n *\n * The rule is PER PRESS, never per cluster: a target cluster's level came\n * from its LARGEST element, so one press on a panel inside a chain of button\n * clicks read the whole chain as a drag and threw every zoom away — two real\n * takes (2026-09-11) lost 5 of 8 and 4 of 4 clicks that way. A surface press\n * is set aside before clustering; the targets around it keep their zoom.\n */\nexport const DRAG_FIT_LEVEL = 1.15\n/**\n * Two `down`s this close in time and space are ONE press seen twice: a\n * recorder listening to pointer AND mouse events records each click as a\n * pair 0-1 ms apart (the extension did for a year). Counting the echo made\n * every lone click a two-click cluster and doubled the digest's counts.\n */\nconst PRESS_ECHO_MS = 4\nconst PRESS_ECHO_PX = 2\n/**\n * A press that TRAVELS before its release is a drag (a slider thumb, a\n * scrubber, a thing moved across a canvas): the pointer's path is the\n * content, so the camera follows it for the whole press at the style's\n * dragLevel instead of framing the press as a click. Travel is the farthest\n * the pointer gets from the press point, as a fraction of the frame width;\n * a press held still (a long click) stays a click.\n */\nexport const DRAG_MIN_TRAVEL_FRAC = 0.015\nexport const DRAG_MIN_S = 0.2\n\n/** A press that travelled before its release, in seconds and frame fractions. */\nexport interface Drag {\n /** the press and the release, seconds */\n t0: number\n t1: number\n /** the press point, normalized */\n nx: number\n ny: number\n /** the pointer's farthest excursion from the press point, frame widths */\n travel: number\n}\n\n/**\n * The drags in a cursor track: each `down` paired with the next `up`, kept\n * when the pointer travelled ≥ DRAG_MIN_TRAVEL_FRAC between them over\n * ≥ DRAG_MIN_S. Shared by the planner (a drag plans a follow span, and its\n * press is not a click) and the lowering's cursor follow (a drag inside any\n * follow span is tracked as a path).\n */\nexport function dragsFromTrack(\n track: CursorTrack,\n width: number,\n height: number,\n): Drag[] {\n if (!(width > 0) || !(height > 0)) return []\n const downs = dedupePresses(track.filter((e) => e.type === 'down'))\n const out: Drag[] = []\n for (const d of downs) {\n const up = track.find((e) => e.type === 'up' && e.t > d.t)\n if (!up) continue\n const t0 = d.t / 1000\n const t1 = up.t / 1000\n if (t1 - t0 < DRAG_MIN_S) continue\n let travel = 0\n for (const e of track) {\n if (e.t < d.t) continue\n if (e.t > up.t) break\n if (e.type !== 'move' && e.type !== 'up') continue\n const dist = Math.hypot(e.x - d.x, e.y - d.y) / width\n if (dist > travel) travel = dist\n }\n if (travel < DRAG_MIN_TRAVEL_FRAC) continue\n out.push({\n t0,\n t1,\n nx: clamp01(d.x / width),\n ny: clamp01(d.y / height),\n travel,\n })\n }\n return out\n}\n\n// ── typing sessions ────────────────────────────────────────────────────\n/** A session needs at least this many `key` pings (a lone Enter never zooms). */\nconst TYPING_MIN_PINGS = 2\n/** …spanning at least this long (seconds) — sub-half-second typing is a blip. */\nconst TYPING_MIN_DUR = 0.5\n/**\n * A `down` this close before the first ping, on the same field, is absorbed:\n * the span enters at the click so the camera commits as the field is clicked,\n * not after the fact (the anticipatory beat).\n */\nconst TYPING_CLICK_ABSORB = 1.5\n/**\n * Normalized field-center distance that means \"a different field\" — splits a\n * session (form-filling becomes a field-to-field pan chain via the lowering's\n * chainGap) and gates click absorption / same-field span merging.\n */\nconst TYPING_REFOCUS_FRAC = 0.08\n\nexport interface PlanOptions {\n /** captured frame size (for normalizing rects → [0..1] focus points). */\n width: number\n height: number\n /**\n * Camera style whose planner params seed every default below (zoomStyle.ts).\n * Explicit options still win. Absent = DEFAULT_ZOOM_STYLE.\n */\n style?: ZoomStyleName\n /** per-doc overrides on top of the style (doc.zoomParams — the Custom seam). */\n params?: Partial<ZoomStyleParams>\n /** target: zoom so the element fills ~this fraction of the frame. */\n targetFill?: number\n /** clamp zoom level. */\n minLevel?: number\n maxLevel?: number\n /** clicks within this many seconds merge into one zoom (session merge). */\n clusterGap?: number\n /** span lead-in before the first click + hold after the last. */\n lead?: number\n hold?: number\n /** minimum clicks for a cluster to earn a zoom (Cursorful's ≥2 rule). */\n minClusterClicks?: number\n /** the level a drag is followed at (see ZoomStyleParams.dragLevel). */\n dragLevel?: number\n /** emit spans with focusMode 'auto' (cursor-follow camera). */\n followByDefault?: boolean\n /** typing sessions plan spans. */\n typingZoom?: boolean\n /** max silence between `key` pings before the typing session ends. */\n typingGap?: number\n /** hold after the last keystroke (the read-what-you-typed beat). */\n typingHold?: number\n /** level floor for typing spans (a wide field still reads punchier). */\n typingMinLevel?: number\n}\n\n/** One press (or ping) in seconds, with the target element when known. */\nexport interface Click {\n t: number // seconds\n rect?: Rect\n x: number\n y: number\n}\n\n/**\n * The planner's first two passes, exported so the take DIGEST lists\n * the same click clusters and typing sessions the planner zooms on — one\n * grouping, never a second implementation that drifts. Typing sessions come\n * FIRST: a session may ABSORB the click that focused its field, and that\n * click must then not seed a click cluster — the two passes never compete\n * for the same press.\n */\nexport function groupTrack(\n track: CursorTrack,\n opts: {\n width: number\n height: number\n clusterGap: number\n typingGap: number\n typingZoom: boolean\n /** the style's fill target — what decides a press is on a SURFACE. */\n targetFill: number\n /**\n * the style's level floor: a press joins a cluster only while the\n * cluster's targets still share one window at this level or above.\n * Absent = the default style's.\n */\n minLevel?: number\n },\n): { sessions: TypingSession[]; clusters: Click[][]; surfaces: Click[][] } {\n const { width, height, clusterGap, typingGap, typingZoom, targetFill } = opts\n const minLevel = opts.minLevel ?? resolveZoomStyle().minLevel\n const clicks: Click[] = dedupePresses(\n track.filter((e) => e.type === 'down'),\n ).map((e) => ({ t: e.t / 1000, rect: e.rect, x: e.x, y: e.y }))\n\n const sessions = typingZoom\n ? typingSessions(track, width, height, typingGap)\n : []\n const absorbed = new Set<Click>()\n for (const s of sessions) {\n let best: Click | null = null\n for (const c of clicks) {\n if (absorbed.has(c)) continue\n if (c.t >= s.first || s.first - c.t > TYPING_CLICK_ABSORB) continue\n const [nx, ny] = fieldCenter(c, width, height)\n if (Math.hypot(nx - s.cx, ny - s.cy) > TYPING_REFOCUS_FRAC) continue\n if (!best || c.t > best.t) best = c\n }\n if (best) {\n absorbed.add(best)\n // Anticipatory entry: the span opens on the click into the field, so the\n // camera is already moving when the first character appears.\n s.start = best.t\n s.events = [best, ...s.events]\n }\n }\n\n // Surface presses (DRAG_FIT_LEVEL) are set aside BEFORE clustering, each\n // press on its own: the targets around one keep their chain. A press that\n // a typing session absorbed is its field, never a surface.\n const targets: Click[] = []\n const surfaces: Click[] = []\n for (const c of clicks) {\n if (absorbed.has(c)) continue\n if (isSurfacePress(c, width, height, targetFill)) surfaces.push(c)\n else targets.push(c)\n }\n // Merge clicks that are close in time into one sustained zoom, while the\n // cluster's targets still share one window: a press that would push the\n // union past what the camera can show at the level floor starts a new\n // cluster, and the chain gap pans between them. Time alone once merged a\n // corner press with one at the top of the frame into a span aimed at\n // their midpoint, which framed neither. Surface presses reserve their\n // window and plan no zoom, so for them time is the whole test.\n const chain = (list: Click[], spatial: boolean): Click[][] => {\n const clusters: Click[][] = []\n for (const c of list) {\n const last = clusters.at(-1) // Click[] | undefined\n const prev = last?.at(-1)\n const joins =\n last &&\n prev &&\n c.t - prev.t <= clusterGap &&\n (!spatial ||\n unionFitLevel(unionRect([...last, c]), width, height) >= minLevel)\n if (joins) last!.push(c)\n else clusters.push([c])\n }\n return clusters\n }\n return {\n sessions,\n clusters: chain(targets, true),\n surfaces: chain(surfaces, false),\n }\n}\n\n/** Drop a `down` that echoes the previous one (PRESS_ECHO_MS/PX). */\nexport function dedupePresses(downs: CursorTrack): CursorTrack {\n const kept: CursorTrack = []\n for (const e of downs) {\n const prev = kept.at(-1)\n if (\n prev &&\n e.t - prev.t <= PRESS_ECHO_MS &&\n Math.abs(e.x - prev.x) <= PRESS_ECHO_PX &&\n Math.abs(e.y - prev.y) <= PRESS_ECHO_PX\n ) {\n continue\n }\n kept.push(e)\n }\n return kept\n}\n\n/** The rect that holds every press: its element when known, else the point. */\nexport function unionRect(clicks: Click[]): Rect {\n let x0 = Infinity\n let y0 = Infinity\n let x1 = -Infinity\n let y1 = -Infinity\n for (const c of clicks) {\n const r = c.rect ?? { x: c.x, y: c.y, w: 0, h: 0 }\n x0 = Math.min(x0, r.x)\n y0 = Math.min(y0, r.y)\n x1 = Math.max(x1, r.x + r.w)\n y1 = Math.max(y1, r.y + r.h)\n }\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }\n}\n\n/**\n * The highest level at which `rect` sits within CLUSTER_FILL of the zoom\n * window on both axes, in the video's own frame (the window shows 1/level\n * of it; a padded frame shows a little more, so this errs toward splitting,\n * whose cost is one pan). Infinity for a point.\n */\nexport function unionFitLevel(\n rect: Rect,\n width: number,\n height: number,\n): number {\n const fx = rect.w > 0 ? (width * CLUSTER_FILL) / rect.w : Infinity\n const fy = rect.h > 0 ? (height * CLUSTER_FILL) / rect.h : Infinity\n return Math.min(fx, fy)\n}\n\n/**\n * The level at which the element fills `targetFill` of the frame — the\n * planner's zoom level before clamping, and the drag test's measure. Null\n * without a usable rect (a point press zooms at the ceiling).\n */\nexport function fitLevel(\n rect: Rect | undefined,\n width: number,\n height: number,\n targetFill: number,\n): number | null {\n if (!rect || rect.w <= 0 || rect.h <= 0) return null\n return Math.min((width * targetFill) / rect.w, (height * targetFill) / rect.h)\n}\n\n/** A press on a frame-sized element (a drag, a canvas, a panel): no target. */\nexport function isSurfacePress(\n c: Click,\n width: number,\n height: number,\n targetFill: number,\n): boolean {\n const fit = fitLevel(c.rect, width, height, targetFill)\n return fit !== null && fit < DRAG_FIT_LEVEL\n}\n\nexport function planAutoZoom(\n track: CursorTrack,\n options: PlanOptions,\n): ZoomSpan[] {\n const style = resolveZoomStyle(options.style, options.params)\n // The 'none' style (or an autoZoom:false override): manual zooms only.\n if (!style.autoZoom) return []\n const {\n width,\n height,\n targetFill = style.targetFill,\n minLevel = style.minLevel,\n maxLevel = style.maxLevel,\n clusterGap = style.clusterGap,\n lead = style.lead,\n hold = style.hold,\n minClusterClicks = style.minClusterClicks,\n dragLevel = style.dragLevel,\n followByDefault = style.followByDefault,\n typingZoom = style.typingZoom,\n typingGap = style.typingGap,\n typingHold = style.typingHold,\n typingMinLevel = style.typingMinLevel,\n } = options\n\n const grouped = groupTrack(track, {\n width,\n height,\n clusterGap,\n typingGap,\n typingZoom,\n targetFill,\n minLevel,\n })\n const { sessions } = grouped\n\n // A drag's press is the drag's, never a click: it leaves its cluster (a\n // target's or a surface's) before the clusters plan anything.\n const drags = dragsFromTrack(track, width, height)\n const dragPress = new Set(drags.map((d) => round(d.t0)))\n const withoutDrags = (list: Click[][]): Click[][] =>\n list\n .map((cl) => cl.filter((c) => !dragPress.has(round(c.t))))\n .filter((cl) => cl.length > 0)\n const clusters = withoutDrags(grouped.clusters)\n const surfaces = withoutDrags(grouped.surfaces)\n\n interface Working {\n in: number\n out: number\n cx: number\n cy: number\n level: number\n dead?: boolean\n }\n\n // One follow span per drag, at the drag level, from the press to the\n // release: the lowering's cursor follow tracks the pointer's path for\n // the whole press.\n const dragSpans: ZoomSpan[] = drags.map((d, i) => ({\n id: `g${i}`,\n in: round(Math.max(0, d.t0 - lead)),\n out: round(d.t1 + hold),\n level: clampZoomLevel(Math.min(Math.max(dragLevel, 1.1), maxLevel)),\n cx: round(d.nx),\n cy: round(d.ny),\n focusMode: 'auto' as const,\n source: 'auto',\n }))\n\n // Lone clicks below the cluster minimum earn no zoom (the Cursorful rule:\n // one stray click isn't worth a camera move — dwells may still cover it).\n const eligible = clusters.filter((c) => c.length >= minClusterClicks)\n const clickSpans: Working[] = []\n // Surface clusters (drags, canvas and panel presses) plan no zoom but still\n // RESERVE their window: the cursor was working there, and the pauses\n // between drags are not dwells.\n const dragReserved: ZoomSpan[] = surfaces.map((cluster, i) => {\n const f = clusterFocus(cluster, width, height)\n return {\n id: `drag${i}`,\n in: Math.max(0, cluster[0].t - lead),\n out: cluster[cluster.length - 1].t + hold,\n level: 1,\n cx: f.cx,\n cy: f.cy,\n }\n })\n for (const cluster of eligible) {\n const first = cluster[0]\n const last = cluster[cluster.length - 1]\n // focus point + level from the element rect when present, else the point\n const f = focusFor(cluster, width, height, targetFill, minLevel, maxLevel)\n clickSpans.push({\n in: Math.max(0, first.t - lead),\n out: last.t + hold,\n cx: f.cx,\n cy: f.cy,\n level: f.level,\n })\n }\n\n // Typing spans clamp to their own floor: the field is the moment being\n // narrated, so a wide input still reads a notch punchier than a wide click.\n const typingFloor = Math.min(Math.max(minLevel, typingMinLevel), maxLevel)\n let typing: Working[] = sessions.map((s) => {\n const f = focusFor(\n s.events,\n width,\n height,\n targetFill,\n typingFloor,\n maxLevel,\n )\n return {\n in: Math.max(0, s.start - lead),\n out: s.last + typingHold,\n cx: f.cx,\n cy: f.cy,\n level: f.level,\n }\n })\n\n // Resolve typing↔click overlaps. Same field → MERGE (union extents, the\n // typing focus wins: the field is the payload). Different field → the click\n // beat keeps its span and the typing span CEDES the overlap — the camera\n // moves off the field for the click and the chainGap pan carries the travel.\n for (const t of typing) {\n for (const z of clickSpans) {\n if (z.dead || t.dead) continue\n if (t.in >= z.out || t.out <= z.in) continue\n if (Math.hypot(t.cx - z.cx, t.cy - z.cy) <= TYPING_REFOCUS_FRAC) {\n t.in = Math.min(t.in, z.in)\n t.out = Math.max(t.out, z.out)\n z.dead = true\n } else if (z.in <= t.in && z.out >= t.out) {\n t.dead = true\n } else if (z.in > t.in) {\n t.out = z.in\n } else {\n t.in = z.out\n }\n }\n }\n // A field switch splits sessions faster than lead+hold shrink — trim the\n // earlier span to the later one's entry (adjacent spans chain into a pan).\n typing = typing.filter((t) => !t.dead).sort((a, b) => a.in - b.in)\n for (let i = 0; i + 1 < typing.length; i++) {\n if (typing[i].out > typing[i + 1].in) typing[i].out = typing[i + 1].in\n }\n typing = typing.filter((t) => t.out - t.in >= 0.3)\n\n // Deterministic ids (pure planner: same track → same spans, same ids),\n // re-numbered in TIME order per origin so ids stay stable under replans.\n const zSpans: ZoomSpan[] = clickSpans\n .filter((s) => !s.dead)\n .sort((a, b) => a.in - b.in)\n .map((s, i) => ({\n id: `z${i}`,\n in: round(s.in),\n out: round(s.out),\n level: clampZoomLevel(s.level),\n cx: round(s.cx),\n cy: round(s.cy),\n // Follow styles ride the cursor through the span (entry + dead-zone\n // recenters baked at lowering); typing/dwell spans stay fixed-focus.\n ...(followByDefault ? { focusMode: 'auto' as const } : {}),\n source: 'auto',\n }))\n const kSpans: ZoomSpan[] = typing.map((s, i) => ({\n id: `k${i}`,\n in: round(s.in),\n out: round(s.out),\n level: clampZoomLevel(s.level),\n cx: round(s.cx),\n cy: round(s.cy),\n // Never focusMode:'auto': the dot is parked (and fading) while typing —\n // the FIELD is the anchor, and a follow would be a no-op at best.\n source: 'auto',\n }))\n const spans = [...zSpans, ...kSpans, ...dragSpans].sort((a, b) => a.in - b.in)\n\n // Dwell augmentation: sustained cursor rests no click/typing/drag span\n // covers.\n const dwells = dwellSpans(track, width, height, maxLevel, [\n ...spans,\n ...dragReserved,\n ])\n return [...spans, ...dwells].sort((a, b) => a.in - b.in)\n}\n\nexport interface TypingSession {\n /** pings (+ the absorbed focusing click) — fed to focusFor unchanged. */\n events: Click[]\n first: number\n last: number\n /** span anchor: the absorbed click's time, else the first ping's. */\n start: number\n /** normalized field center of the FIRST ping — the session's identity. */\n cx: number\n cy: number\n}\n\n/**\n * Group `key` pings into typing sessions: a ping joins the current session\n * while the silence stays ≤ typingGap AND it is still the same field (a ping\n * whose field center moved > TYPING_REFOCUS_FRAC starts a new session — that\n * split is what turns form-filling into a field-to-field pan chain). Sessions\n * below TYPING_MIN_PINGS/TYPING_MIN_DUR are noise (a lone Enter, a shortcut\n * chord) and plan nothing.\n */\nexport function typingSessions(\n track: CursorTrack,\n width: number,\n height: number,\n typingGap: number,\n): TypingSession[] {\n const pings: Click[] = track\n .filter((e) => e.type === 'key')\n .map((e) => ({ t: e.t / 1000, rect: e.rect, x: e.x, y: e.y }))\n const all: TypingSession[] = []\n let cur: TypingSession | null = null\n for (const p of pings) {\n const [nx, ny] = fieldCenter(p, width, height)\n if (\n cur &&\n p.t - cur.last <= typingGap &&\n Math.hypot(nx - cur.cx, ny - cur.cy) <= TYPING_REFOCUS_FRAC\n ) {\n cur.events.push(p)\n cur.last = p.t\n } else {\n cur = { events: [p], first: p.t, last: p.t, start: p.t, cx: nx, cy: ny }\n all.push(cur)\n }\n }\n return all.filter(\n (s) =>\n s.events.length >= TYPING_MIN_PINGS && s.last - s.first >= TYPING_MIN_DUR,\n )\n}\n\n/** Normalized center of the event's element rect (or its point). */\nexport function fieldCenter(\n c: Click,\n width: number,\n height: number,\n): [number, number] {\n const px = c.rect ? c.rect.x + c.rect.w / 2 : c.x\n const py = c.rect ? c.rect.y + c.rect.h / 2 : c.y\n return [clamp01(px / width), clamp01(py / height)]\n}\n\n/**\n * Dwell detection over move samples. One capture subtlety drives the shape:\n * the recorder is event-driven with a distance gate, so a PARKED cursor emits\n * NO samples — stillness is the time gap between a run's last sample and the\n * sample that finally breaks the distance threshold. A run therefore extends\n * to its breaking sample's time (or the track end). Candidates are ranked by\n * duration (longest wins), deduped by DWELL_SPACING between centers, and any\n * span that would overlap an existing (click) span is dropped.\n */\nexport function dwellSpans(\n track: CursorTrack,\n width: number,\n height: number,\n maxLevel: number,\n reserved: ZoomSpan[],\n): ZoomSpan[] {\n const moves = track\n .filter((e) => e.type === 'move')\n .map((e) => ({ t: e.t / 1000, nx: e.x / width, ny: e.y / height }))\n if (moves.length < 2) return []\n const trackEnd = track[track.length - 1].t / 1000\n\n interface Candidate {\n center: number\n cx: number\n cy: number\n strength: number\n }\n const candidates: Candidate[] = []\n let start = 0\n for (let i = 1; i <= moves.length; i++) {\n const breaks =\n i === moves.length ||\n Math.hypot(moves[i].nx - moves[i - 1].nx, moves[i].ny - moves[i - 1].ny) >\n DWELL_MOVE_FRAC\n if (!breaks) continue\n const endT = i < moves.length ? moves[i].t : trackEnd\n const dur = endT - moves[start].t\n // The run that starts at the track's FIRST sample is the cursor before\n // it has done anything: on a CLI take it is parked at the frame's\n // corner until the first step, and six of eight real takes opened on a\n // point zoom at the ceiling onto that corner. Not a moment.\n const opening = start === 0\n if (!opening && dur >= DWELL_MIN && dur <= DWELL_MAX) {\n const run = moves.slice(start, i)\n candidates.push({\n center: (moves[start].t + endT) / 2,\n cx: run.reduce((s, p) => s + p.nx, 0) / run.length,\n cy: run.reduce((s, p) => s + p.ny, 0) / run.length,\n strength: dur,\n })\n }\n start = i\n }\n\n // Longest dwell wins; enforce center spacing; drop-on-overlap vs everything\n // already accepted (click spans + earlier dwells — adjacency is fine).\n const sorted = [...candidates].sort((a, b) => b.strength - a.strength)\n const sourceDuration = trackEnd\n const len = Math.max(1, sourceDuration * 0.05)\n const taken: ZoomSpan[] = [...reserved]\n const accepted: ZoomSpan[] = []\n const centers: number[] = []\n for (const c of sorted) {\n if (centers.some((t) => Math.abs(t - c.center) < DWELL_SPACING)) continue\n const spanIn = Math.max(\n 0,\n Math.min(c.center - len / 2, sourceDuration - len),\n )\n const spanOut = Math.min(sourceDuration, spanIn + len)\n if (spanOut - spanIn < 0.3) continue\n if (taken.some((z) => spanIn < z.out && spanOut > z.in)) continue\n const span: ZoomSpan = {\n id: `d${accepted.length}`,\n in: round(spanIn),\n out: round(spanOut),\n // No element rect on moves → point zoom at the planner ceiling.\n level: clampZoomLevel(maxLevel),\n cx: round(clamp01(c.cx)),\n cy: round(clamp01(c.cy)),\n source: 'auto',\n }\n accepted.push(span)\n taken.push(span)\n centers.push(c.center)\n }\n // Deterministic ids in TIME order (rank order depends on durations, which\n // would make ids unstable under small edits) — re-id after sorting.\n return accepted\n .sort((a, b) => a.in - b.in)\n .map((z, i) => ({ ...z, id: `d${i}` }))\n}\n\nfunction focusFor(\n cluster: Click[],\n width: number,\n height: number,\n targetFill: number,\n minLevel: number,\n maxLevel: number,\n): { cx: number; cy: number; level: number } {\n // Average the rect centers (or points) in the cluster.\n let sx = 0\n let sy = 0\n let maxW = 0\n let maxH = 0\n for (const c of cluster) {\n if (c.rect) {\n sx += c.rect.x + c.rect.w / 2\n sy += c.rect.y + c.rect.h / 2\n maxW = Math.max(maxW, c.rect.w)\n maxH = Math.max(maxH, c.rect.h)\n } else {\n sx += c.x\n sy += c.y\n }\n }\n const n = cluster.length\n const cx = clamp01(sx / n / width)\n const cy = clamp01(sy / n / height)\n\n // Level: zoom so the LARGEST element fills ~targetFill of the frame\n // (element-aware). No rect → the max level (point zoom). Every member\n // cleared DRAG_FIT_LEVEL on its own (groupTrack), so the level is a\n // target's, never a surface's.\n // …and no closer than the whole cluster allows: two targets that share a\n // window at 1.4× but not at 1.8× get 1.4×, so both stay in frame.\n const own =\n fitLevel({ x: 0, y: 0, w: maxW, h: maxH }, width, height, targetFill) ??\n maxLevel\n const together = unionFitLevel(unionRect(cluster), width, height)\n return { cx, cy, level: clamp(Math.min(own, together), minLevel, maxLevel) }\n}\n\n/**\n * Normalized focus + union rect of a click cluster or typing session (the\n * digest's per-moment `focus`/`rect`, in the doc's [0..1] units) — the same\n * averaging `focusFor` zooms on, minus the level.\n */\nexport function clusterFocus(\n events: readonly Click[],\n width: number,\n height: number,\n): { cx: number; cy: number; rect: Rect | null } {\n let sx = 0\n let sy = 0\n let x0 = Infinity\n let y0 = Infinity\n let x1 = -Infinity\n let y1 = -Infinity\n let rects = 0\n for (const c of events) {\n if (c.rect) {\n sx += c.rect.x + c.rect.w / 2\n sy += c.rect.y + c.rect.h / 2\n x0 = Math.min(x0, c.rect.x)\n y0 = Math.min(y0, c.rect.y)\n x1 = Math.max(x1, c.rect.x + c.rect.w)\n y1 = Math.max(y1, c.rect.y + c.rect.h)\n rects++\n } else {\n sx += c.x\n sy += c.y\n }\n }\n const n = Math.max(1, events.length)\n const rect =\n rects > 0\n ? {\n x: clamp01(x0 / width),\n y: clamp01(y0 / height),\n w: clamp01((x1 - x0) / width),\n h: clamp01((y1 - y0) / height),\n }\n : null\n return { cx: clamp01(sx / n / width), cy: clamp01(sy / n / height), rect }\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return Math.max(lo, Math.min(hi, v))\n}\nfunction clamp01(v: number): number {\n return clamp(v, 0, 1)\n}\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * lowerToComposition — the IR bridge.\n *\n * Turns the app-level `ProjectDoc` into a vos `VosConfigJson` (+ a `data` value).\n * The editor's \"opinion\" lowers into vos core's function-string IR; vos core never\n * sees `ProjectDoc`.\n *\n * The studio's output is 2D compositing (gradient background, the video inset with\n * padding + rounded corners + shadow, zoom/pan, a cursor on top). Compositor v2\n * draws it as a **three-layer mesh stack** under one perspective camera —\n * a background quad (CSS fill + vos loop), the CARD (its own 2D canvas, on a\n * plane that can tilt), and an overlay quad (cam bubble + future overlays). Each\n * layer is a 2D canvas that `onFrame` paints from `ctx.data` (deterministic — a\n * pure function of ctx.time); at tilt = 0 the card fills the frustum and renders\n * pixel-identically to the pre-v2 fullscreen quad. See stage.ts for the geometry.\n *\n * THE INTERPRETER PATTERN: all four\n * function strings are module-level CONSTANTS — the program is a fixed\n * interpreter and the entire editable state travels in `ctx.data`. The compiled\n * program string is therefore a structural hash: every edit is a live SET_DATA\n * (T2), a trim is SET_DATA + SET_DURATION (T2.5, via the `vosCarrier` opt-in),\n * and the program only changes when this module changes. Even `duration` is\n * data: the config bakes a placeholder and the carrier timeline reads\n * `ctx.data.duration` (delivered with LOAD / deps.data).\n *\n * Time model: `t` (ctx.time) is OUTPUT-timeline seconds; the source moment on\n * screen is `srcT = mapTime(segments, t)` (@vosjs/timeline, inlined into the\n * program via `timelineRuntimeCode` so host and sandbox evaluate identically).\n * Cursor samples stay SOURCE-anchored and are read at `srcT`; zoom spans are\n * source-anchored in the doc and expanded to an OUTPUT-time keyframe track here\n * (ramps must run in output time so they never straddle a cut).\n *\n * NOTE: uses an HTMLVideoElement for the source (robust, any format). Frame-accurate\n * WebCodecs export is a later layer; this nails the *look* + makes every control work.\n */\nimport {\n EASINGS,\n lerpArray,\n sample,\n segmentRate,\n sortKeyframes,\n sourceToTimeline,\n splitBySpeed,\n totalDuration,\n} from '@vosjs/timeline'\nimport { timelineRuntimeCode } from '@vosjs/timeline/bundle'\nimport {\n CAMERA_CENTRE_RAMP,\n camBubbleRect,\n cameraModel,\n clampFocus,\n docCardLayout,\n} from '../layout'\nimport { smoothCursor } from '../planner/smoothing'\nimport {\n BACKGROUND_Z,\n CAMERA_FAR,\n CAMERA_NEAR,\n CARD_FOV,\n CARD_OVERSCAN_MAX,\n CARD_OVERSCAN_STEP,\n CARD_Z,\n OVERLAY_Z,\n} from '../stage'\nimport {\n OVERLAY_FONT_FACES,\n overlayFaceFor,\n overlayFontFaces,\n overlayLines,\n resolveOverlayBox,\n resolveOverlayFx,\n resolveOverlayStyle,\n} from '../overlayText'\nimport { resolveText3dAsset } from '../text3d'\nimport {\n CARD_EDGE_OVERDRAW,\n CLICK_FX_INTENSITY,\n FRAME_BORDER_COLOR_DEFAULT,\n FRAME_BORDER_WIDTH_DEFAULT,\n LAYER_CARD_SHADOW,\n MOTION_EASE,\n OBJECT_DEFAULT_SCALE,\n OVERLAY_LINE_HEIGHT,\n OVERLAY_MEDIA_DEFAULT_RADIUS,\n OVERLAY_MEDIA_DEFAULT_WIDTH,\n OVERLAY_MIN_DURATION,\n clampCamSize,\n clampTiltDeg,\n clampZoomLevel,\n clipLength,\n pageDisplayUrl,\n transitionMult,\n TILT_SPAN_MIN,\n ZOOM_SPAN_MIN,\n} from '../types'\nimport {\n GHOST_RENDER_ORDER,\n GHOST_Z_LIFT,\n TRANSITION_SCALE_STEP,\n TRANSITION_TRAVEL,\n docTransitions,\n restSpansThroughTransitions,\n} from './transitions'\nimport { DEFAULT_ZOOM_STYLE, ZOOM_STYLES, resolveZoomStyle } from '../zoomStyle'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport { clipEnvelope } from './audioEnvelope'\nimport { followFocusEvents } from './cursorFollow'\nimport { pinPlacement, pinReferent } from './pin'\nimport { cursorIdleFade } from './cursorIdle'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\nimport {\n cardEnter,\n cardExit,\n cardPoseTrack,\n docFreezes,\n entranceTiltKeyframes,\n entranceZoomKeyframes,\n migrateMotion,\n outputEnd,\n prependEntrance,\n withFreezes,\n} from './motion'\nimport { enterKey, exitKey, idleKey } from '../anim'\nimport {\n CLICK_FX_PRE,\n CLICK_HIGHLIGHT_FADE,\n CLICK_PULSE_DUR,\n CLICK_RIPPLE_DUR,\n extractClicks,\n hexToRgbTriplet,\n} from './extractClicks'\nimport { cardFields, layerMedia, mediaFrame, sameMedia } from '../media'\nimport { htmlLayerPayload } from '../htmlLayer'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { TimelineEdit } from '@vosjs/shared/timelineEdits'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type { ZoomStyleParams } from '../zoomStyle'\nimport type { CamBubbleRect, CameraModel, CardLayout } from '../layout'\nimport type { FollowEvent } from './cursorFollow'\nimport type { PinCandidate, PinPlacement } from './pin'\nimport type {\n AudioClip,\n CamPoseSpan,\n CamStyle,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n TiltSpan,\n ZoomSpan,\n} from '../types'\n\nexport interface LoweredComposition {\n /** The composed config: the anchor's program plus the studio stack entry. */\n config: Record<string, unknown>\n /** The MAIN program's ctx.data. */\n data: Record<string, unknown>\n /** Each stack entry's own ctx.data, by entry id (`deps.stack` / `SET_DATA { target }`). */\n stack: Record<string, Record<string, unknown>>\n /**\n * A program anchor's tween-timing overlay: delivered to the player\n * LIVE (`SET_TWEEN_EDITS`, bridge protocol 8) so a retime never changes\n * the program string. Absent on a recording, and on a stored (baked)\n * program config.\n */\n tweenEdits?: readonly TimelineEdit[]\n /** Output duration in seconds (drives the carrier + SET_DURATION). */\n duration: number\n}\n\n/**\n * Zoom transition shape — deterministic pure keyframes evaluated by TL.sample\n * (no stateful springs, seek stays a pure function of t). The zoom-in ramp\n * starts BEFORE the span and lands rampInOverlap into it (the camera arrives\n * just after the moment it frames); the zoom-out starts at the span's end.\n * All timing/ease constants come from the doc's zoom STYLE (zoomStyle.ts —\n * named strategy presets grounded in the measured competitor comparison).\n * Eases are `css-bezier(…)` curves\n * (@vosjs/timeline >=0.4.0 — parsed identically by host + runtime bundle).\n *\n * Legacy constant names = the DEFAULT style's values, kept for scripts/tests\n * that reason about \"the default ramp\" symbolically.\n */\nexport const ZOOM_RAMP_IN = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampIn\nexport const ZOOM_RAMP_IN_OVERLAP =\n ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampInOverlap\nexport const ZOOM_RAMP_OUT = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].rampOut\n/** Output-time gap ≤ this → pan straight to the next span (no zoom-out). */\nexport const ZOOM_CHAIN_GAP = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].chainGap\n/** Connected-zoom pan duration (compressed into short gaps). */\nexport const ZOOM_PAN = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].pan\nexport const ZOOM_EASE = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].ease\nexport const ZOOM_PAN_EASE = ZOOM_STYLES[DEFAULT_ZOOM_STYLE].panEase\n\n/**\n * Tilt transition shape (tilt spans): the FALLBACK for a bare\n * tiltTrackFromDoc call. The lowering hands the track the camera style's\n * own tilt motion (`ZoomStyleParams.tilt`, the style's zoom tempo), so a\n * lean lands WITH its zoom in every style — the one thing the retired\n * keynote style had right, generalized. The eases are the same measured\n * css-bezier family the default zoom style uses. Unlike zoom, the pose is\n * SETTLED at span.in (the ramp starts TILT_RAMP_IN before, with no\n * overlap-into-span): tilt frames a moment, it doesn't chase content.\n */\nexport const TILT_RAMP_IN = 0.9\nexport const TILT_RAMP_OUT = 0.8\n/** Output-time gap ≤ this → swing straight to the next pose (no flatten between). */\nexport const TILT_CHAIN_GAP = 1.35\n/** Connected-tilt swing duration (compressed into short gaps). */\nexport const TILT_PAN = 0.9\nexport const TILT_EASE = ZOOM_EASE\nexport const TILT_PAN_EASE = ZOOM_PAN_EASE\n\n/**\n * Cam-move transition shape (animated cam layouts). Own constants,\n * deliberately NOT the zoom style's or tilt's (switching another\n * subsystem's personality must never silently change the bubble's feel). The\n * premium band for an in-video layout morph is 0.6–1.0s (Descript's Smart\n * Transition defaults to 0.8s); the bubble is small chrome, so it sits at the\n * fast end. Like tilt, the pose is SETTLED at span.in (the ramp starts before,\n * no overlap-into-span): a cam move frames what follows.\n */\nexport const CAM_RAMP_IN = 0.65\nexport const CAM_RAMP_OUT = 0.65\n/** Output-time gap ≤ this → morph straight to the next pose (no return to rest). */\nexport const CAM_CHAIN_GAP = 1.2\n/** Connected-move morph duration (compressed into short gaps). */\nexport const CAM_PAN = 0.7\nexport const CAM_EASE = ZOOM_EASE\nexport const CAM_PAN_EASE = ZOOM_PAN_EASE\n\n/**\n * The config `duration` placeholder. The REAL duration lives in `ctx.data`\n * (the carrier timeline reads it), so trims never change the program string.\n */\nconst PROGRAM_DURATION = 1\n\n/**\n * The doc's kept spans with speed spans applied — the OUTPUT-time truth every\n * downstream consumer evaluates (mapTime in ON_FRAME, duration, zoom remap,\n * the export's audio splice). An empty segment list means \"untrimmed\", so\n * speed spans still apply over one synthesized full-source segment.\n */\nexport function ratedSegments(doc: StudioDoc): Segment[] {\n // A program is ONE source span, its own length: its speed spans\n // rate it exactly as a recording's rate the footage.\n const segs = isRecordingDoc(doc)\n ? doc.segments.length\n ? doc.segments\n : [{ in: 0, out: doc.source.meta.durationMs / 1000 }]\n : [{ in: 0, out: programDuration(doc) }]\n // A freeze is a rated piece placed at its source moment, so every reader\n // of this list inherits it (a legacy segment hold is one at its end).\n // Every span is its media's: a segment on another media is rated by that\n // media's speed spans, and the pieces keep the media they play.\n const freezes = isRecordingDoc(doc) ? docFreezes(doc) : []\n const speeds = doc.speed ?? []\n const pieces = segs.flatMap((seg) =>\n splitBySpeed(\n [seg],\n speeds.filter((sp) =>\n sameMedia(sp.media, (seg as { media?: string }).media),\n ),\n ),\n )\n return withFreezes(pieces, freezes)\n}\n\nfunction durationSec(doc: ProjectDoc, rated: Segment[]): number {\n const trimmed = totalDuration(rated)\n return trimmed > 0 ? trimmed : doc.source.meta.durationMs / 1000\n}\n\n/**\n * Map a SOURCE-time span onto the output timeline through the RATED segment\n * list: the output extent of its KEPT footage (a partially-cut span snaps its\n * edges into kept footage; a fully-cut span returns null — it follows its\n * footage, like every source-anchored feature). Rate-aware: output positions\n * accumulate each piece's (out − in) / rate.\n */\nexport function spanOutputExtent(\n segments: readonly (Segment & { media?: string })[],\n sIn: number,\n sOut: number,\n media?: string,\n): { start: number; end: number } | null {\n let acc = 0\n let start: number | null = null\n let end: number | null = null\n for (const p of segments) {\n const rate = segmentRate(p)\n const len = Math.max(0, p.out - p.in) / rate\n // Source seconds are one media's: a piece of another media is output\n // time this span never covers, however its numbers overlap.\n if (!sameMedia(p.media, media)) {\n acc += len\n continue\n }\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) {\n if (start === null) start = acc + (ovIn - p.in) / rate\n end = acc + (ovOut - p.in) / rate\n }\n acc += len\n }\n return start !== null && end !== null && end > start ? { start, end } : null\n}\n\n/** A span's arrival ease, validated against the shared ease set. */\nfunction spanEase(\n ease: string | undefined,\n fallback: string,\n): NonNullable<Keyframe['ease']> {\n return (ease && ease in EASINGS ? ease : fallback) as NonNullable<\n Keyframe['ease']\n >\n}\n\n/**\n * The shortest ramp the tracks ever emit (seconds): the cut style's own\n * arrival, eight frames at 60 fps. A ramp that collides with the keyframe\n * before it shrinks to the room it has, never below this, instead of the\n * emitter's 1 ms nudge, which turned every ramp longer than its span (a\n * dwell of a second under a 1.8 s cinema ramp, a click under a 3× speed\n * span) into a hard cut inside a style sold as smooth.\n */\nexport const RAMP_FLOOR = 0.14\n\n/**\n * Where a ramp lands. Unclamped (the emitter accepted the wanted start),\n * the landing is exactly start + duration, float-identical to the old\n * expression so a fitting track stays byte-identical. Clamped (the ramp\n * would have begun before t = 0, or before the previous keyframe), the\n * ramp keeps its full duration when the span has the room (a clip that\n * opens mid-zoom-in reads as the camera already moving, the way it\n * always did), lands at the span's end when it does not, and never lands\n * before its designed point (`startWanted + duration`, rampInOverlap into\n * the span) or less than RAMP_FLOOR after the actual start. The hold pin\n * and the exit take over from there.\n */\nfunction rampLanding(\n start: number,\n startWanted: number,\n duration: number,\n spanEnd: number,\n): number {\n if (Math.abs(start - startWanted) < 1e-9) return start + duration\n const landWanted = startWanted + duration\n const landFull = Math.min(start + duration, spanEnd)\n return Math.max(start + RAMP_FLOOR, landWanted, landFull)\n}\n\n/**\n * Monotonic keyframe emitter shared by every span→track expansion (zoom,\n * tilt): clamps into strictly-increasing time, skips exact no-op repeats,\n * nudges 1ms on time collisions. Extracted so the tracks can never drift on\n * these rules — zoom's emitted keyframes are byte-identical to the previous\n * in-closure version (verify-zoom-spans pins it).\n */\nfunction trackEmitter(): {\n keyframes: Keyframe<number[]>[]\n push: (\n t: number,\n value: number[],\n ease: NonNullable<Keyframe['ease']>,\n ) => number\n} {\n const keyframes: Keyframe<number[]>[] = []\n const push = (\n t: number,\n value: number[],\n ease: NonNullable<Keyframe['ease']>,\n ): number => {\n const prev = keyframes.at(-1)\n let tt = Math.max(0, t)\n if (prev) {\n if (tt <= prev.t + 1e-6 && sameVec(prev.value, value)) return prev.t\n if (tt <= prev.t + 1e-6) tt = prev.t + 0.001\n }\n keyframes.push({ t: round(tt), value: value.map(round), ease })\n return tt\n }\n return { keyframes, push }\n}\n\n/**\n * Expand the doc's source-anchored zoom spans into a standard @vosjs/timeline\n * keyframe track in OUTPUT time (values are [level, cx, cy] vectors):\n *\n * rest ──ramp-in──▶ [level,cx,cy] ──hold──▶ span end ──ramp-out──▶ rest\n *\n * with one twist: when the output gap to the NEXT span is ≤ ZOOM_CHAIN_GAP,\n * the camera never returns to rest — it pans straight to the next span's\n * state over ZOOM_PAN and holds it through the gap (OpenScreen's connected\n * zooms: the camera glides from focus to focus). Transitions run in output\n * time, so they never straddle a cut; keyframe times are strictly increasing\n * (dense spans compress rather than reorder).\n */\n/** A span enriched by the lowering with baked cursor-follow recenters. */\nexport interface LoweredZoomSpan extends ZoomSpan {\n followEvents?: FollowEvent[]\n}\n\nexport function zoomTrackFromDoc(\n zoom: LoweredZoomSpan[],\n segments: Segment[],\n style: ZoomStyleParams = ZOOM_STYLES[DEFAULT_ZOOM_STYLE],\n): KeyframeTrack<number[]> {\n const panEase = style.panEase as NonNullable<Keyframe['ease']>\n const mapped = [...zoom]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n // Monotonic emit: clamp into strictly-increasing time (skip exact no-ops).\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n // [level, cx, cy, centring]: the fourth component is the stage camera's\n // pull of the focus toward the frame's centre, 0 at rest and 1 at the\n // apex, interpolated by the SAME ease as the level so the card's slide\n // and its scale are one motion. Keyed to the level over a fixed band it\n // was a jump: any ease reaches level 1.3 in its first frames, so the\n // whole slide landed at once and the corner drifted back out as the\n // scale caught up (the twitch measured on the tweakcn take).\n const entry = [clampZoomLevel(z.level), z.cx, z.cy, 1]\n // The camera's current state within the span — advanced by follow recenters.\n let cur = entry\n // Per-span transition speed: a multiplier on the style's ramps.\n // ×1 (absent/'smooth') is float-exact, so legacy tracks stay byte-identical.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; scale in place around this span's focus\n // (level 1 renders identically for any focus, so the rest focus is free).\n const startWanted = tIn - (style.rampIn - style.rampInOverlap) * m\n const start = push(startWanted, [1, z.cx, z.cy, 0], 'none')\n push(\n rampLanding(start, startWanted, style.rampIn * m, tOut),\n entry,\n spanEase(z.ease, style.ease),\n )\n }\n\n // Cursor-follow recenters (focusMode 'auto', baked by the lowering): hold\n // at the current focus, glide to the recentered one over the style's\n // recenter duration.\n let inPath = false\n for (const e of z.followEvents ?? []) {\n const eOut = sourceToTimeline(segments, e.t)\n if (eOut === null || eOut <= tIn || eOut >= tOut) continue\n // A recenter with less than RAMP_FLOOR of room after the arrival or\n // before the exit would compress into a jump (the focus freezes for\n // the zoom-out anyway); it is dropped, and the next one glides.\n if (eOut - tIn < RAMP_FLOOR || tOut - eOut < RAMP_FLOOR) continue\n const next = [cur[0], e.cx, e.cy, 1]\n if (e.path) {\n // A path sample: the camera is HERE at this time. The first sample\n // of a run is reached by a glide of the recenter length from the\n // held focus (a hold keyframe pins the start), every later one by\n // a linear segment, so the pan reads as the pointer's own motion.\n if (!inPath) {\n push(Math.max(tIn, eOut - style.followRecenter), cur, 'none')\n push(eOut, next, panEase)\n inPath = true\n } else {\n push(eOut, next, 'linear')\n }\n } else {\n inPath = false\n push(eOut, cur, 'none')\n push(Math.min(eOut + style.followRecenter, tOut), next, panEase)\n }\n cur = next\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, cur, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= style.chainGap) {\n // Connected zooms: pan straight to the next state. Adjacent spans still\n // get a real pan by letting it land up to rampInOverlap into the next.\n // The pan is the NEXT span's arrival, so its transition speed governs.\n const mNext = transitionMult(next.z.transition)\n const nextValue = [clampZoomLevel(next.z.level), next.z.cx, next.z.cy, 1]\n push(\n Math.max(\n tOut + RAMP_FLOOR,\n Math.min(\n tOut + style.pan * mNext,\n next.tIn + style.rampInOverlap * mNext,\n ),\n ),\n nextValue,\n panEase,\n )\n chained = true\n } else {\n // Focus FREEZES for the zoom-out (Recordly's rule): the camera pulls\n // back from wherever the follow left it, no parting pan.\n push(\n tOut + style.rampOut * m,\n [1, cur[1], cur[2], 0],\n spanEase(z.ease, style.ease),\n )\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\nfunction sameVec(a: number[], b: number[]): boolean {\n return a.length === b.length && a.every((v, i) => Math.abs(v - b[i]) < 1e-6)\n}\n\n/**\n * Expand the doc's source-anchored tilt spans into an OUTPUT-time keyframe\n * track of [rx, ry] DEGREES (ON_FRAME converts to radians at the mesh):\n *\n * flat ──ramp-in──▶ [rx,ry] ──hold──▶ span end ──ramp-out──▶ flat\n *\n * Rest is FLAT: there is no static card pose to return to (decided\n * 2026-08-03 — a lean is a moment on the timeline), which makes this the\n * exact analog of zoom's level-1 rest. Deliberate\n * differences from zoomTrackFromDoc: the pose is SETTLED at\n * span.in (ramp starts TILT_RAMP_IN before, no overlap-into-span), there are\n * no follow events, and ramps are fixed constants rather than the zoom\n * style's. Spans ≤ TILT_CHAIN_GAP apart in output time swing pose-to-pose\n * without flattening between (the connected-zoom rule). Transitions run in\n * output time so they never straddle a cut; keyframe times are strictly\n * increasing (dense spans compress rather than reorder).\n */\nexport function tiltTrackFromDoc(\n tilt: TiltSpan[],\n segments: Segment[],\n // Motion overrides from the camera style's tilt personality —\n // absent fields fall back to the TILT_* constants, so a bare call keeps\n // the house motion and a style like 'drift' can slow its leans down.\n motion: {\n rampIn?: number\n rampOut?: number\n chainGap?: number\n pan?: number\n } = {},\n): KeyframeTrack<number[]> {\n const rampInDur = motion.rampIn ?? TILT_RAMP_IN\n const rampOutDur = motion.rampOut ?? TILT_RAMP_OUT\n const chainGap = motion.chainGap ?? TILT_CHAIN_GAP\n const panDur = motion.pan ?? TILT_PAN\n const panEase = TILT_PAN_EASE as NonNullable<Keyframe['ease']>\n const rest = [0, 0]\n const mapped = [...tilt]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n const pose = [clampTiltDeg(z.rx), clampTiltDeg(z.ry)]\n // Per-span transition speed — ×1 when absent, float-exact.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; arrive settled exactly at the span start.\n const startWanted = tIn - rampInDur * m\n const start = push(startWanted, rest, 'none')\n push(\n rampLanding(start, startWanted, rampInDur * m, tOut),\n pose,\n spanEase(z.ease, TILT_EASE),\n )\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, pose, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= chainGap) {\n // Connected tilts: swing straight to the next pose, landing by its\n // start — the next span's arrival, so its transition speed governs.\n const nextPose = [clampTiltDeg(next.z.rx), clampTiltDeg(next.z.ry)]\n push(\n Math.max(\n tOut + RAMP_FLOOR,\n Math.min(tOut + panDur * transitionMult(next.z.transition), next.tIn),\n ),\n nextPose,\n panEase,\n )\n chained = true\n } else {\n push(tOut + rampOutDur * m, rest, spanEase(z.ease, TILT_EASE))\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/**\n * The bubble's rest pose as [x, y, size] frame fractions, resolved through the\n * SAME oracle the picking layer uses (camBubbleRect) so the corner math can\n * never fork a third way (draw / pick / lowering). Fractions are resolution-\n * stable at a fixed aspect: the margin (24·s) and diameter (size·H) both\n * scale with s = H/1080, so the fraction depends only on the aspect ratio.\n */\nexport function camRestPose(cam: CamStyle, W: number, H = 1080): number[] {\n const r = camBubbleRect(cam, W, H)\n return [(r.x + r.size / 2) / W, (r.y + r.size / 2) / H, r.size / H]\n}\n\n/**\n * Expand the doc's source-anchored cam pose spans into an OUTPUT-time keyframe\n * track of [x, y, size] frame fractions (the third consumer of the\n * span→track seam):\n *\n * rest ──ramp-in──▶ [x,y,size] ──hold──▶ span end ──ramp-out──▶ rest\n *\n * Rest is the doc's cam style resolved to fractions (camRestPose) — doc.cam IS\n * the rest pose, exactly as tilt's rest is flat. The pose is SETTLED at\n * span.in (ramp starts CAM_RAMP_IN before): a cam move frames what follows.\n * Spans ≤ CAM_CHAIN_GAP apart in output time morph pose-to-pose without\n * returning to rest (the connected-zoom rule). Absent pose fields inherit the\n * rest pose. Transitions run in output time so they never straddle a cut.\n */\nexport function camTrackFromDoc(\n cam: CamStyle,\n spans: CamPoseSpan[],\n segments: Segment[],\n W: number,\n H = 1080,\n): KeyframeTrack<number[]> {\n const rest = camRestPose(cam, W, H)\n const poseOf = (z: CamPoseSpan): number[] => [\n z.x != null ? Math.min(1, Math.max(0, z.x)) : rest[0],\n z.y != null ? Math.min(1, Math.max(0, z.y)) : rest[1],\n z.size != null ? clampCamSize(z.size) : rest[2],\n ]\n const panEase = CAM_PAN_EASE as NonNullable<Keyframe['ease']>\n const mapped = [...spans]\n .sort((a, b) => a.in - b.in)\n .flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext ? [{ z, tIn: ext.start, tOut: ext.end }] : []\n })\n\n const { keyframes, push } = trackEmitter()\n\n let chained = false\n for (let i = 0; i < mapped.length; i++) {\n const { z, tIn, tOut } = mapped[i]\n const pose = poseOf(z)\n // Per-span transition speed — 'instant' is the layout jump-cut.\n const m = transitionMult(z.transition)\n\n if (!chained) {\n // Rest until the ramp starts; arrive settled exactly at the span start.\n const start = push(tIn - CAM_RAMP_IN * m, rest, 'none')\n push(start + CAM_RAMP_IN * m, pose, spanEase(z.ease, CAM_EASE))\n }\n\n // Pin the hold to the span's end — the exit transition starts here.\n push(tOut, pose, 'none')\n\n const next = mapped.at(i + 1)\n if (next && next.tIn - tOut <= CAM_CHAIN_GAP) {\n // Connected moves: morph straight to the next pose, landing by its\n // start — the next span's arrival, so its transition speed governs.\n push(\n Math.min(tOut + CAM_PAN * transitionMult(next.z.transition), next.tIn),\n poseOf(next.z),\n panEase,\n )\n chained = true\n } else {\n push(tOut + CAM_RAMP_OUT * m, rest, spanEase(z.ease, CAM_EASE))\n chained = false\n }\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/**\n * Pose fractions → the bubble square, mirroring ON_FRAME's pose branch (the\n * 40px floor and the rounded radius ride s, exactly like the static path).\n */\nexport function camRectFromPose(\n pose: readonly number[],\n cam: CamStyle,\n W: number,\n H = 1080,\n): CamBubbleRect {\n const s = H / 1080\n const size = Math.max(40, pose[2] * H)\n return {\n x: pose[0] * W - size / 2,\n y: pose[1] * H - size / 2,\n size,\n radius: cam.shape === 'rounded' ? (cam.radius ?? 18) * s : size / 2,\n }\n}\n\n/**\n * The bubble rect at OUTPUT time t — the time-aware picking oracle.\n * With no motion spans it is exactly camBubbleRect at the doc's design layout;\n * with spans it samples the SAME track the lowering ships, so picking can\n * never drift from the paint (camDraw.test.ts pins both paths). Design space\n * is docCardLayout's (H = 1080, W from the output aspect).\n */\nexport function camBubbleRectAt(doc: ProjectDoc, t: number): CamBubbleRect {\n const { W, H } = docCardLayout(doc)\n if (!doc.camMotion || !doc.camMotion.length) {\n return camBubbleRect(doc.cam, W, H)\n }\n const track = camTrackFromDoc(\n doc.cam,\n doc.camMotion,\n ratedSegments(doc),\n W,\n H,\n )\n if (!track.keyframes.length) return camBubbleRect(doc.cam, W, H)\n return camRectFromPose(sample(track, t, lerpArray), doc.cam, W, H)\n}\n\n/** A resolved pose keyframe: clip-local time + full value vector. */\nexport interface MotionKey {\n at: number\n value: number[]\n ease?: string\n}\n\n/**\n * Bake resolved pose keyframes into a CLIP-LOCAL keyframe track.\n * The base vector holds until the first pose (a leading keyframe at 0 pins\n * it), values interpolate across each gap (ease-into per pose), and the last\n * pose holds to the clip's end (sample clamps). Same emitter, same\n * interpolator, same purity as the zoom/tilt/cam tracks.\n */\nexport function motionTrack(\n base: readonly number[],\n keys: MotionKey[],\n dur: number,\n): KeyframeTrack<number[]> {\n const sorted = keys\n .filter((k) => Number.isFinite(k.at))\n .sort((a, b) => a.at - b.at)\n const first = sorted.at(0)\n if (!first) return { keyframes: [] }\n const { keyframes, push } = trackEmitter()\n if (first.at > 0.001) push(0, [...base], 'none')\n for (const k of sorted) {\n push(\n Math.min(Math.max(0, k.at), dur),\n k.value,\n spanEase(k.ease, MOTION_EASE),\n )\n }\n return { keyframes: sortKeyframes(keyframes) }\n}\n\n/** An overlay clip's base vector: [x, y, scale, rotation, opacityMul]. */\nexport function overlayMotionBase(o: OverlayClip): number[] {\n return [\n o.transform.x,\n o.transform.y,\n o.transform.scale || 1,\n o.transform.rotation || 0,\n 1,\n ]\n}\n\nfunction overlayMotionKeys(o: OverlayClip, base: readonly number[]) {\n return (o.motion ?? []).map((p) => ({\n at: p.at,\n ease: p.ease,\n value: [\n p.x ?? base[0],\n p.y ?? base[1],\n p.scale ?? base[2],\n p.rotation ?? base[3],\n p.opacity ?? base[4],\n ],\n }))\n}\n\n/**\n * Effective [x, y, scale, rotation, opacityMul] of an overlay clip at\n * CLIP-LOCAL time t — the host-side mirror of ON_FRAME's sampling (the\n * picking layer substitutes it into the clip's transform so hit rects track\n * the animated element). Null = the clip has no motion.\n */\nexport function overlayMotionPoseAt(\n o: OverlayClip,\n t: number,\n): number[] | null {\n if (!o.motion || !o.motion.length) return null\n const base = overlayMotionBase(o)\n const track = motionTrack(\n base,\n overlayMotionKeys(o, base),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n if (!track.keyframes.length) return null\n return [...sample(track, t, lerpArray)]\n}\n\n/** An object clip's base vector: [x, y, z, rx, ry, rz, scale]. */\nexport function objectMotionBase(o: ObjectClip): number[] {\n const t = o.transform3d\n return [t.x, t.y, t.z, t.rx, t.ry, t.rz, t.scale || OBJECT_DEFAULT_SCALE]\n}\n\nfunction objectMotionKeys(o: ObjectClip, base: readonly number[]) {\n return (o.motion ?? []).map((p) => ({\n at: p.at,\n ease: p.ease,\n value: [\n p.x ?? base[0],\n p.y ?? base[1],\n p.z ?? base[2],\n p.rx ?? base[3],\n p.ry ?? base[4],\n p.rz ?? base[5],\n p.scale ?? base[6],\n ],\n }))\n}\n\n/**\n * Effective [x, y, z, rx, ry, rz, scale] of an object clip at CLIP-LOCAL\n * time t (from the span start; 0 when span-less over `clipDur`). Null = the\n * clip has no motion. The 3D mirror of overlayMotionPoseAt.\n */\nexport function objectMotionPoseAt(\n o: ObjectClip,\n t: number,\n clipDur: number,\n): number[] | null {\n if (!o.motion || !o.motion.length) return null\n const base = objectMotionBase(o)\n const track = motionTrack(\n base,\n objectMotionKeys(o, base),\n o.span?.duration ?? clipDur,\n )\n if (!track.keyframes.length) return null\n return [...sample(track, t, lerpArray)]\n}\n\n// The paused-frame steps: ONE source, inlined into SETUP (where the frame-prep\n// hook runs it) and into ON_FRAME (where syncVid runs it), so a capture\n// harness and the paint can never disagree on a target. `stepPaused` asks a\n// WebCodecs provider (capture pages) by PTS at decode speed, else seeks the\n// element and puts its 'seeked' promise (250ms fallback) on pendingDecodes for\n// the export loop to await. `stepBg` is the background loop's seek, COALESCED:\n// a scrub moves t every frame and re-assigning currentTime ABORTS the in-flight\n// seek — on a remote (assets.vos.so) source that keeps the element mid-seek for\n// the whole drag, so no frame ever decodes and the background pops in seconds\n// late — so a seek is issued only when none is in flight; the frame after\n// 'seeked' fires corrects toward the latest target, so seeks run serially and\n// converge on the release point. Both read `ns` from the enclosing scope.\nexport const FRAME_STEP_SRC = `\n function stepPaused(vid, srcT) {\n if (!vid.paused) vid.pause()\n // A WebCodecs provider (capture pages) services the frame by PTS at\n // decode speed — no element seek, no 250ms settle cap. The element stays\n // paused as the dimension source and fallback.\n var wcp = vid.__voilaWc\n if (wcp) {\n var wcT = Math.min(srcT, wcp.duration || srcT)\n if (wcp.req !== wcT) {\n var wdp = wcp.seek(wcT)\n if (ns.pendingDecodes) {\n ns.pendingDecodes.add(wdp)\n wdp.finally(function () { ns.pendingDecodes.delete(wdp) })\n }\n }\n return\n }\n var target = Math.min(srcT, vid.duration || srcT)\n if (vid.readyState >= 1 && Math.abs(vid.currentTime - target) > 0.02) {\n if (ns.pendingDecodes) {\n var dp = new Promise(function (resolve) {\n var done = function () { vid.removeEventListener('seeked', done); resolve() }\n vid.addEventListener('seeked', done)\n setTimeout(done, 250) // fallback so a missed 'seeked' can't hang the export\n })\n ns.pendingDecodes.add(dp)\n dp.finally(function () { ns.pendingDecodes.delete(dp) })\n }\n vid.currentTime = target\n }\n }\n function stepBg(bgEl, bgT) {\n if (!bgEl.paused) bgEl.pause()\n var bgTarget = Math.min(bgT, bgEl.duration || bgT)\n if (bgEl.readyState >= 1 && !bgEl.seeking && Math.abs(bgEl.currentTime - bgTarget) > 0.02) {\n if (ns.pendingDecodes) {\n var bgDp = new Promise(function (resolve) {\n var bgDone = function () { bgEl.removeEventListener('seeked', bgDone); resolve() }\n bgEl.addEventListener('seeked', bgDone)\n setTimeout(bgDone, 250) // fallback so a missed 'seeked' can't hang the export\n })\n ns.pendingDecodes.add(bgDp)\n bgDp.finally(function () { ns.pendingDecodes.delete(bgDp) })\n }\n bgEl.currentTime = bgTarget\n }\n }\n // A GHOST element: a second element on the same bytes, held on ONE frame\n // (a transition's outgoing snapshot), so one recording can be both cards\n // at a boundary. Cached by source. Its readiness rides pendingDecodes,\n // so an export's first paint inside a window waits for it, and a frame\n // asked before it could seek is sought the moment it can.\n function ghostEl(el) {\n var gc = ns.ghostCache || (ns.ghostCache = new Map())\n var src = el.currentSrc || el.src\n var g = gc.get(src)\n if (g) return g\n g = document.createElement('video')\n g.crossOrigin = 'anonymous'\n g.muted = true\n g.playsInline = true\n g.preload = 'auto'\n var gp = new Promise(function (resolve) {\n var fired = false\n var done = function () {\n if (fired) return\n fired = true\n g.removeEventListener('loadeddata', done)\n if (g.__voilaWant != null && g.readyState >= 1) {\n var want = g.__voilaWant\n g.__voilaWant = null\n var seeked = function () { g.removeEventListener('seeked', seeked); resolve() }\n g.addEventListener('seeked', seeked)\n setTimeout(seeked, 250)\n g.currentTime = Math.min(want, g.duration || want)\n } else resolve()\n }\n g.addEventListener('loadeddata', done)\n setTimeout(done, 2000)\n })\n if (ns.pendingDecodes) {\n ns.pendingDecodes.add(gp)\n gp.finally(function () { ns.pendingDecodes.delete(gp) })\n }\n g.src = src\n g.load()\n gc.set(src, g)\n return g\n }\n function stepGhost(g, at) {\n if (g.readyState >= 1) { g.__voilaWant = null; stepPaused(g, at); return }\n g.__voilaWant = at\n }`\n\n// Load the recording as an HTMLVideoElement (any container/codec the browser plays).\n// Warm-swap asset reuse: cache the decoded <video> by src on window.__vos__ so a\n// program swap reuses the already-decoded element instead of reloading it — no\n// flash. The cached element deliberately survives cleanup (it is not appended to the\n// scene/DOM and content has no dispose), so it persists across warm LOADs.\n//\n// The @vosjs/timeline runtime IIFE is inlined first: it defines\n// globalThis.__vosTimeline (sample/mapTime/easings) with EXACTLY the code the host\n// evaluates, so keyframes/segments in ctx.data render identically on both sides.\nconst SETUP = `async (ctx) => {\n ;${timelineRuntimeCode}\n const ns = (window.__vos__ = window.__vos__ || {})\n // Paused/decode machinery (the engine's video-renderer contract). The studio has no element\n // renderers, so set it up here: the player bridge toggles isPaused on play/pause, and the\n // deterministic export loop awaits pendingDecodes via waitForVideosReady before capturing.\n if (ns.isPaused === undefined) ns.isPaused = true\n if (!ns.setGlobalPaused) ns.setGlobalPaused = (p) => { ns.isPaused = p }\n ns.pendingDecodes = ns.pendingDecodes || new Set()\n if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {\n if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])\n }\n ${FRAME_STEP_SRC}\n const cache = ns.videoCache || (ns.videoCache = new Map())\n // Server capture pages opt into BLOB-backed elements (data.videoFetchMode,\n // merged in by the render queue — never stored in a doc or config): a\n // detached, paused, network-backed video gets SUSPENDED by Chrome within\n // seconds (readyState drops to 0) and every later seek pays a ranged\n // re-fetch — the background-media rationale, applied to the\n // recording an export chunk seeks a hundred-plus times. Size-capped (a\n // page has a memory budget) and FAIL-OPEN: any fetch trouble degrades to\n // the plain network element, never a dead LOAD.\n const BLOB_FETCH_MAX = 400 * 1024 * 1024\n const toBlobUrl = async (src) => {\n const resp = await fetch(src)\n if (!resp.ok) throw new Error('[voila] blob fetch HTTP ' + resp.status)\n const len = Number(resp.headers.get('content-length') || 0)\n if (len > BLOB_FETCH_MAX) {\n try { if (resp.body) await resp.body.cancel() } catch (e) { void e }\n throw new Error('[voila] blob fetch over size cap')\n }\n const fetched = await resp.blob()\n // Keep the Blob itself: the WebCodecs provider demuxes the SAME\n // bytes (BlobSource) instead of paying a second network read.\n ;(ns.videoBlobs || (ns.videoBlobs = new Map())).set(src, fetched)\n return URL.createObjectURL(fetched)\n }\n const load = async (src, muted) => {\n let v = cache.get(src)\n if (v) return v\n let url = src\n if (ctx.data.videoFetchMode === 'blob') {\n try { url = await toBlobUrl(src) }\n catch (e) { console.warn('[voila] blob fetch failed, using network src', e) }\n }\n v = document.createElement('video')\n v.src = url\n v.crossOrigin = 'anonymous'\n v.muted = muted\n v.playsInline = true\n v.preload = 'auto'\n await new Promise((res, rej) => {\n v.oncanplay = () => res()\n // The MediaError rides along: code 4 is an unreadable/unsupported source\n // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network\n // stall. A bare \"failed to load\" gave the fleet log nothing to act on.\n v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))\n v.load()\n })\n cache.set(src, v)\n return v\n }\n // Shots: the \"video\" is a still image — drawImage accepts it directly and the\n // whole compositor (frame, browser bar, zoom) applies unchanged.\n const loadImage = (src) => {\n const hit = cache.get(src)\n if (hit) return Promise.resolve(hit)\n return new Promise((res, rej) => {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.onload = () => { cache.set(src, img); res(img) }\n img.onerror = () => rej(new Error('[voila] image failed to load'))\n img.src = src\n })\n }\n // Capture pages only: a WebCodecs sequential frame provider for the\n // screen recording. Element seeks cost up to 250ms/frame under the settle\n // cap and currentTime is audio-clock-backed (not frame-accurate by spec);\n // sink-driven sequential decode delivers frames at decode speed and BY\n // PTS. The pull queue feeds canvasesAtTimestamps so the capture walk's\n // monotonic timestamps keep mediabunny's decode-each-packet-once fast\n // path. FAIL-OPEN at every step: a null provider leaves the element path\n // exactly as it was; preview/scrub never sets the flag.\n const makeWcProvider = async (src) => {\n if (!window.VideoDecoder) return null\n const MB = await import('https://esm.sh/mediabunny@1.55.7?target=es2022')\n const wcBlob = ns.videoBlobs && ns.videoBlobs.get(src)\n const input = new MB.Input({\n formats: MB.ALL_FORMATS,\n source: wcBlob ? new MB.BlobSource(wcBlob) : new MB.UrlSource(src),\n })\n try {\n const track = await input.getPrimaryVideoTrack()\n if (!track || !(await track.canDecode())) { input.dispose(); return null }\n // VideoSampleSink, NOT CanvasSink — CanvasSink converts EVERY\n // decoded source frame to a canvas, and that per-source-frame paint\n // lost 41% on the SwiftShader fleet (job 4f1e99ab), multiplied by\n // rate-N spans (N source frames decoded per output frame). Samples\n // skipped in rated spans now CLOSE unconverted; only the DISPLAYED\n // frame draws, straight into the card via sample.draw (crop-capable).\n //\n // Sequential walk: drive samples() (pre-decoding, each packet decoded\n // once) and advance to the frame CONTAINING each requested timestamp.\n // NOT samplesAtTimestamps — its pipeline prefetches the timestamp\n // iterable ahead of yielding frames, which deadlocks a demand-driven\n // feed. The iterator starts AT THE FIRST REQUESTED TIMESTAMP and\n // RE-SEEKS on any jump beyond WC_JUMP — samples(t) begins at the\n // preceding keyframe, which is chunk cold-seek semantics; starting at\n // 0 and grinding forward decoded the whole source prefix before a\n // mid-timeline chunk's first frame (job 639dd24c: startup grew\n // linearly with chunk index).\n const sink = new MB.VideoSampleSink(track)\n const WC_JUMP = 3\n let iter = null\n let cur = null\n const wcClose = (s) => { if (s) { try { s.close() } catch (e) { void e } } }\n const advanceTo = async (t) => {\n if (\n !iter ||\n (cur && t < cur.timestamp) ||\n (cur && t > cur.timestamp + cur.duration + WC_JUMP)\n ) {\n // Dispose the old walk so its in-flight pre-decoded samples close\n // (an abandoned iterator leaks VideoSamples — pool stall risk).\n if (iter && iter.return) { try { void iter.return() } catch (e) { void e } }\n wcClose(cur)\n iter = sink.samples(t)\n cur = null\n }\n for (;;) {\n if (cur && cur.timestamp + cur.duration > t) return\n const nx = await iter.next()\n if (nx.done || !nx.value) return\n wcClose(cur) // skipped (rated spans) or superseded — never converted\n cur = nx.value\n }\n }\n let chain = Promise.resolve()\n const provider = {\n req: -1,\n width: track.displayWidth,\n height: track.displayHeight,\n duration: await input.computeDuration(),\n seek(t) {\n provider.req = t\n chain = chain\n .then(() => advanceTo(t))\n .catch((e) => { console.warn('[voila] webcodecs frame failed', e) })\n return chain\n },\n // Draw the CURRENT frame into the card. Returns false (element path\n // draws instead) until the first seek resolves or after any failure.\n draw(c2, crp2, dx2, dy2, dw2, dh2) {\n if (!cur) return false\n if (crp2) cur.draw(c2, crp2.x, crp2.y, crp2.w, crp2.h, dx2, dy2, dw2, dh2)\n else cur.draw(c2, dx2, dy2, dw2, dh2)\n return true\n },\n }\n return provider\n } catch (e) {\n input.dispose()\n throw e\n }\n }\n // Mic recordings carry audio → unmute the screen video so it plays back during native\n // playback. (Export pulls audio from the source blob separately; the seek path is silent.)\n const video = ctx.data.isImage\n ? await loadImage(ctx.data.videoSrc)\n : await load(ctx.data.videoSrc, !ctx.data.hasAudio)\n if (!ctx.data.isImage && ctx.data.videoDecodeMode === 'webcodecs') {\n try {\n const wcProvider = await makeWcProvider(ctx.data.videoSrc)\n // The provider's canvases must be drop-in for the element at the draw\n // sites (crop rects are capture-pixel space) — dimension mismatch\n // (rotation metadata, anamorphic) keeps the element path.\n if (\n wcProvider &&\n (!video.videoWidth ||\n (wcProvider.width === video.videoWidth &&\n wcProvider.height === video.videoHeight))\n ) {\n video.__voilaWc = wcProvider\n } else if (wcProvider) {\n console.warn('[voila] webcodecs dims mismatch — element path keeps the frame')\n }\n } catch (e) {\n console.warn('[voila] webcodecs provider unavailable, seeks stay html5', e)\n }\n }\n // The webcam is a separate recording (video-only) drawn as an editable bubble overlay.\n const cam = ctx.data.camSrc ? await load(ctx.data.camSrc, true) : null\n // The mic is a separate AUDIO sidecar (AT split): an off-DOM element driven by\n // the same sync as the screen video (source-anchored, so element time == the\n // video's). Unmuted — syncVid's autoplay net re-mutes on policy rejection.\n const mic = ctx.data.micSrc ? await load(ctx.data.micSrc, false) : null\n // The take's OTHER media (concat): one element each, decoded the way the\n // primary is; ON_FRAME drives the one under the playhead and pauses the\n // rest. Absent = one media, byte-identical.\n const mediaEls = {}\n for (const m of ctx.data.media || []) {\n try {\n const el = m.isImage ? await loadImage(m.src) : await load(m.src, !m.hasAudio)\n if (!m.isImage && ctx.data.videoDecodeMode === 'webcodecs') {\n try {\n const wp = await makeWcProvider(m.src)\n if (wp && (!el.videoWidth || (wp.width === el.videoWidth && wp.height === el.videoHeight))) el.__voilaWc = wp\n } catch (e) {\n console.warn('[voila] webcodecs provider unavailable for a media, seeks stay html5', e)\n }\n }\n mediaEls[m.id] = el\n } catch (e) {\n console.warn('[voila] a media failed to load', m.id, e)\n }\n }\n // Background media (frame.backgroundMedia): warm-load so the first paint is\n // complete. FAIL-OPEN — a bad key degrades to the CSS fill underneath, never\n // a dead LOAD (unlike the recording, which is the comp's reason to exist).\n const bgm = ctx.data.frame && ctx.data.frame.backgroundMedia\n if (bgm && bgm.key) {\n try {\n if (bgm.kind === 'image') await loadImage(bgm.key)\n else (await load(bgm.key, true)).loop = true\n } catch (e) { console.warn('[voila] background media failed to load', e) }\n }\n // Frame prep: a capture harness calls every ns.framePrep hook right after\n // seeking the timeline and before the first paint, then awaits\n // waitForVideosReady. The footage for the frame is requested HERE, so the\n // paint that follows draws the right frame the first time. Without the\n // hook the request could only be issued from inside ON_FRAME (the only\n // place that knew the source time), so every take frame painted twice:\n // once with the previous footage, then again after the decode. The steps\n // are the SAME functions ON_FRAME's syncVid runs (one source, inlined in\n // both scopes), so the paint finds its target already met and registers\n // nothing. Keyed by program so a warm LOAD re-registers instead of\n // chaining; playback never calls it; a harness without it keeps the\n // two-phase loop and stays correct.\n ns.framePrep = ns.framePrep || new Map()\n ns.framePrep.set('voila.card', (t) => {\n if (ns.isPaused === false) return\n const d = ctx.data\n const TL = globalThis.__vosTimeline\n const srcT = TL.mapTime(d.segments || [], t)\n // The media under the playhead (concat): step its element; the\n // primary's sidecars belong to the primary's moments only.\n let am = ''\n {\n let acc = 0\n const sg = d.segments || []\n for (let i = 0; i < sg.length; i++) {\n const s0 = sg[i]\n const ln = Math.max(0, s0.out - s0.in) / (s0.rate || 1)\n if (t < acc + ln || i === sg.length - 1) { am = s0.media || ''; break }\n acc += ln\n }\n }\n const amEl = am ? mediaEls[am] : null\n if (amEl) {\n if (amEl.play) stepPaused(amEl, srcT)\n } else {\n if (video.play) stepPaused(video, srcT)\n if (cam) stepPaused(cam, srcT)\n if (mic) stepPaused(mic, srcT)\n }\n // A boundary's outgoing snapshot: the ghost element on the clip's last\n // frame, stepped like the footage, and warmed a second ahead.\n const trs = d.transitions || []\n for (let i = 0; i < trs.length; i++) {\n const tr = trs[i]\n if (!tr.out || t < tr.t - 1 || t >= tr.t + tr.d) continue\n const live = tr.out.media ? mediaEls[tr.out.media] : video\n if (live && live.play) stepGhost(ghostEl(live), tr.out.at)\n }\n const bg2 = d.frame && d.frame.backgroundMedia\n if (bg2 && bg2.key && bg2.kind !== 'image') {\n const bgEl2 = cache.get(bg2.key)\n if (bgEl2 && bgEl2.play) {\n const bgDur2 = bg2.duration || bgEl2.duration || 0\n stepBg(bgEl2, bgDur2 > 0 ? t % bgDur2 : 0)\n }\n }\n })\n return { video: video, cam: cam, mic: mic, media: mediaEls }\n}`\n\n// Compositor v2 — a three-layer mesh stack under ONE perspective camera.\n// Each layer is a plane\n// perpendicular to the camera axis, centered on it, sized to exactly FILL the\n// frustum at its depth (stage.ts planeSizeAtDepth), so it projects to the whole\n// viewport regardless of depth:\n//\n// overlay quad (z ${OVERLAY_Z}) screen-space cam bubble + overlays\n// card mesh (z ${CARD_Z}) world-space the card painting — TILTS\n// background (z ${BACKGROUND_Z}) screen-space CSS fill + vos loop\n//\n// Painter's order is renderOrder (0/1/2) with depthTest off, so depth is free —\n// the card's tilt never z-fights. At tilt = 0 the card plane fills the viewport\n// exactly like the pre-v2 ortho quad ⇒ pixel-identical. LinearFilter/no-mipmaps\n// matches the old quad (1:1 texel↔pixel at tilt 0); mipmaps + anisotropy switch\n// on only when the card tilts (ON_FRAME), where minification would shimmer.\nconst CREATE_CONTENT = `(ctx, setupData) => {\n const THREE = ctx.THREE\n const gl = ctx.renderer && ctx.renderer.domElement\n const res = ctx.resolution\n const W0 = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || 1280))\n const H0 = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || 720))\n const aspect = W0 / H0\n // Own the perspective camera's aspect (ON_FRAME keeps it in sync on resize),\n // so the frustum-filling planes never distort regardless of what the engine\n // initialised. Camera sits at the origin looking down −z; the planes are in\n // front at negative z.\n const cam = ctx.camera\n if (cam && cam.isPerspectiveCamera) { cam.aspect = aspect; cam.updateProjectionMatrix() }\n const planeH = (z) => 2 * Math.abs(z) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n const makeLayer = (z, order) => {\n const canvas = document.createElement('canvas')\n canvas.width = W0\n canvas.height = H0\n const c2d = canvas.getContext('2d')\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.generateMipmaps = false\n const h = planeH(z)\n const mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(h * aspect, h),\n // transparent so each layer alpha-composites over the one behind (the card\n // padding, the overlay's blank area). depthTest off + renderOrder = the\n // painter's order that keeps a tilted card a single coherent layer.\n new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })\n )\n mesh.position.set(0, 0, z)\n mesh.frustumCulled = false\n mesh.renderOrder = order\n ctx.scene.add(mesh)\n return { canvas: canvas, c2d: c2d, texture: texture, mesh: mesh }\n }\n const bg = makeLayer(${BACKGROUND_Z}, 0)\n const card = makeLayer(${CARD_Z}, 1)\n const ov = makeLayer(${OVERLAY_Z}, 2)\n return {\n objects: [bg.mesh, card.mesh, ov.mesh],\n refs: {\n bg: bg, card: card, ov: ov,\n // flat aliases = the card layer, so stub-context tests (which build only\n // { c2d, canvas, texture }) drive the card and let bg/overlay fall back.\n canvas: card.canvas, c2d: card.c2d, texture: card.texture,\n video: setupData.video, cam: setupData.cam, mic: setupData.mic,\n media: setupData.media,\n },\n }\n}`\n\n// Pure duration carrier (the interpreter pattern): the timeline exists only to\n// define duration and drive ctx.time — per-frame state derives from\n// ctx.time + ctx.data in onFrame. Duration comes from ctx.data so trims are\n// data edits; vosCarrier opts into the engine's setDuration (T2.5) capability.\nconst CREATE_TIMELINE = `(ctx, content, duration) => {\n const tl = ctx.gsap.timeline()\n tl.to({}, { duration: (ctx.data && ctx.data.duration) || duration || 1, ease: 'none' })\n tl.data = { vosCarrier: true }\n return tl\n}`\n\n// The compositor. Deterministic: a pure function of ctx.time + ctx.data.\nconst ON_FRAME = `(ctx, content, dt) => {\n var r = content.refs\n // Three layers (compositor v2). Stub-context tests build only the flat\n // { c2d, canvas, texture } — those fall back to the card layer, so bg/overlay\n // draw to the SAME c2d and the merged call log stays in draw order.\n var card = r.card || r, bg = r.bg || r, ov = r.ov || r\n var c = card.c2d, cv = card.canvas, video = r.video\n if (!c || !video) return\n var bgC = bg.c2d || c, ovC = ov.c2d || c\n var res = ctx.resolution\n // Track the LIVE renderer canvas size (resize-aware); ctx.resolution is stale.\n var gl = ctx.renderer && ctx.renderer.domElement\n var W = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || cv.width))\n var H = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || cv.height))\n // Resizing a backing canvas requires disposing its CanvasTexture (THREE keeps\n // the GPU texture allocated at the original dims and re-uploads the new canvas\n // against stale dims → stretch/duplicate; dispose() forces a full realloc) and\n // rebuilding each frustum-filling plane at the new aspect + syncing the camera.\n // Card OVERSCAN: the card's pose (the tilt track, the card-pose track)\n // lets the frame see MORE plane than the frame is wide on a receding\n // side, and the card's content (zoomed footage, a bled card, the shadow)\n // runs past the frame, so a plane that ends at the frame edge shows its\n // texture's edge, and the backdrop through it, as soon as it leans. The\n // card layer's canvas and plane therefore grow about the frame's centre\n // by the extent the two tracks can reach at the LIVE aspect: each frame\n // corner's viewing ray meets the posed plane and reads back in plane\n // units; the extremes of both tracks (largest lean per axis, smallest\n // scale, largest rise, every sign) bound every interpolated frame. This\n // is the twin of stage.ts cardVisibleExtent/cardOverscanFor (pinned by\n // stage.test.ts + cardTilt.test.ts; change them together). A card that\n // never moves, and the flat stub (no card layer), stay at 1: byte- and\n // pixel-identical. The factor is quantised up in steps so a scrubbed\n // pose grows the canvas a few times, not per tick.\n var d = ctx.data || {}\n var okx = 1, oky = 1\n if (r.card) {\n var okT = d.tiltTrack, okP = d.cardPoseTrack, okKf, oki, okv\n var okRx = 0, okRy = 0, okSc = 1, okDy = 0\n okKf = okT && okT.keyframes || []\n for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okRx = Math.max(okRx, Math.abs(okv[0] || 0)); okRy = Math.max(okRy, Math.abs(okv[1] || 0)) }\n okKf = okP && okP.keyframes || []\n for (oki = 0; oki < okKf.length; oki++) { okv = okKf[oki].value || []; okSc = Math.min(okSc, okv[0] == null ? 1 : okv[0]); okDy = Math.max(okDy, Math.abs(okv[1] || 0)) }\n if (okRx || okRy || okSc < 1 || okDy) {\n var okD = ${Math.abs(CARD_Z)}, okMax = ${CARD_OVERSCAN_MAX}, okStep = ${CARD_OVERSCAN_STEP}\n var okHh = okD * Math.tan(${CARD_FOV} * Math.PI / 180 / 2), okHw = okHh * W / H\n // [max |x|, max |y|] of the frame corners on the posed plane, in\n // half-plane units (R = Rx · Ry, three.js Euler XYZ with rz = 0).\n var okE = function (rxd, ryd, sc, dy) {\n var rx = rxd * Math.PI / 180, ry = ryd * Math.PI / 180\n var cx = Math.cos(rx), sx = Math.sin(rx), cy = Math.cos(ry), sy = Math.sin(ry)\n var scl = Math.max(0.05, sc), py = dy * 2 * okHh\n var n0 = sy, n1 = -sx * cy, n2 = cx * cy\n var nPos = n1 * py - n2 * okD\n var ex = 0, ey = 0\n for (var kx = -1; kx <= 1; kx += 2) for (var ky = -1; ky <= 1; ky += 2) {\n var d0 = kx * okHw, d1 = ky * okHh, d2 = -okD\n var nd = n0 * d0 + n1 * d1 + n2 * d2\n var lam = nd === 0 ? -1 : nPos / nd\n if (!(lam > 0)) { ex = okMax; ey = okMax; continue }\n var q0 = lam * d0, q1 = lam * d1 - py, q2 = lam * d2 + okD\n ex = Math.max(ex, Math.abs((cy * q0 + sx * sy * q1 - cx * sy * q2) / scl / okHw))\n ey = Math.max(ey, Math.abs((cx * q1 + sx * q2) / scl / okHh))\n }\n return [Math.min(okMax, ex), Math.min(okMax, ey)]\n }\n for (var okA = -1; okA <= 1; okA += 2) for (var okB = -1; okB <= 1; okB += 2) for (var okC = -1; okC <= 1; okC += 2) {\n var okR = okE(okA * okRx, okB * okRy, okSc, okC * okDy)\n okx = Math.max(okx, okR[0]); oky = Math.max(oky, okR[1])\n }\n var okQ = function (k) { if (!(k > 1)) return 1; var q = Math.ceil(Math.round((k + okStep / 10) * 1000) / (okStep * 1000)) * okStep; return Math.min(okMax, Math.round(q * 1000) / 1000) }\n okx = okQ(okx); oky = okQ(oky)\n }\n }\n // The canvas grows by an EVEN number of pixels on each axis so the frame\n // region sits on whole pixels inside it (a half-pixel base translation\n // would resample every draw).\n var cW = W + 2 * Math.round(W * (okx - 1) / 2), cH = H + 2 * Math.round(H * (oky - 1) / 2)\n if (cv.width !== cW || cv.height !== cH) {\n var THREE = ctx.THREE\n var aspect = W / H\n var cm = ctx.camera\n if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }\n // Each layer's canvas size and its plane's per-axis growth; the card's\n // plane grows by exactly its canvas's ratio, so a texel stays a pixel.\n var lyr = [[bg, ${BACKGROUND_Z}, W, H], [card, ${CARD_Z}, cW, cH], [ov, ${OVERLAY_Z}, W, H]]\n for (var Li = 0; Li < lyr.length; Li++) {\n var Ly = lyr[Li][0], Lz = lyr[Li][1], Lw = lyr[Li][2], Lhp = lyr[Li][3]\n if (Ly.canvas) { Ly.canvas.width = Lw; Ly.canvas.height = Lhp }\n if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()\n if (Ly.mesh && THREE) {\n if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()\n var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect * (Lw / W), Lh * (Lhp / H))\n }\n }\n }\n var frame = d.frame || {}\n var TL = globalThis.__vosTimeline\n // Output-timeline seconds (engine-fed master clock) → source seconds on screen.\n var t = ctx.time || 0\n var srcT = TL.mapTime(d.segments || [], t)\n // The media under the playhead (concat): its element, crop, cursor and\n // space stand in for the primary's below, and every other media's\n // element rests. An absent media list is the primary alone.\n var amId = ''\n { var amAcc = 0, amSg = d.segments || []\n for (var amI = 0; amI < amSg.length; amI++) {\n var amS = amSg[amI], amLn = Math.max(0, amS.out - amS.in) / (amS.rate || 1)\n if (t < amAcc + amLn || amI === amSg.length - 1) { amId = amS.media || ''; break }\n amAcc += amLn\n } }\n var actM = null\n if (amId && d.media) for (var amJ = 0; amJ < d.media.length; amJ++) if (d.media[amJ].id === amId) actM = d.media[amJ]\n if (actM && r.media && r.media[amId]) video = r.media[amId]\n else amId = ''\n // The card under the playhead: another media's own card fields over the\n // take's frame (resolved at lowering), the take's frame for the primary.\n var cfr = amId && actM && actM.frame ? actM.frame : frame\n if (r.media) for (var amK in r.media) { var amE = r.media[amK]; if (amK !== amId && amE && amE.pause && amE.paused === false) amE.pause() }\n if (amId) {\n if (r.video && r.video.pause && r.video.paused === false) r.video.pause()\n if (r.cam && r.cam.pause && r.cam.paused === false) r.cam.pause()\n if (r.mic && r.mic.pause && r.mic.paused === false) r.mic.pause()\n }\n var s = H / 1080 // scale design-px controls to comp px\n\n // Play natively while playing (smooth); seek precisely otherwise (paused, scrubbing,\n // export). isPaused is the source of truth (bridge toggles it; export forces it true);\n // playing => let the video advance, else step to the exact frame. During a seek we\n // register a decode promise so the deterministic export loop can await the exact frame.\n var ns = window.__vos__ || {}\n var playing = ns.isPaused === false\n // Speed spans: the rate of the segment under the playhead. Natural playback\n // mirrors the remap with playbackRate (clamped to the browser's supported\n // range); the paused/seek path needs nothing — mapTime already lands srcT.\n var srcRate = TL.rateAt ? TL.rateAt(d.segments || [], t) : 1\n var playRate = Math.min(16, Math.max(0.0625, srcRate))\n // Where the FOOTAGE ends in output seconds. The output may outlast it\n // (clips placed after the footage), and a hold is a freeze piece whose\n // rate is tiny: in both the element must stay PAUSED on its frame. An\n // ended element's play() rewinds it to zero and the drift guard then\n // seeks it back, which reads as frames jumping back and forth; a hold\n // crawling at the clamped 1/16 speed toward that same end was the same\n // bug in slow motion.\n var footEnd = TL.totalDuration ? TL.totalDuration(d.segments || []) : Infinity\n var frozen = srcRate < 0.01 || t >= footEnd - 1e-3\n // Drive a <video> to the on-screen SOURCE moment: play natively while playing (drift\n // correction covers cut-boundary jumps), else step to the exact frame (registering a\n // decode promise so the deterministic export awaits it). Shared by the screen video\n // and the webcam so both stay frame-accurate and in sync.\n ${FRAME_STEP_SRC}\n function syncVid(vid) {\n try {\n if (playing && !frozen) {\n if (vid.playbackRate !== playRate) {\n vid.playbackRate = playRate\n // Resampled (tape-style) speed, matching the export's offline mix.\n if (vid.preservesPitch !== false) vid.preservesPitch = false\n // Rate switches land LATE on the media clock — worst with an audio\n // track, where the element resyncs on the audio clock (~300ms of\n // source error at 4×, measured) — so a span's END plays the wrong\n // content moment. Resync position at the switch when it has already\n // drifted (an unconditional seek costs more than it fixes when the\n // media clock is tight, e.g. muted video-only playback).\n if (Math.abs(vid.currentTime - srcT) > 0.06) vid.currentTime = srcT\n } else if (Math.abs(vid.currentTime - srcT) > (srcRate !== 1 ? 0.12 : 0.3)) {\n // Tighter leash inside rated spans: at N× a given source drift is N×\n // more visible in content terms; a rare micro-seek there is not.\n vid.currentTime = srcT\n }\n if (vid.paused) {\n var p = vid.play()\n if (p && p.catch) p.catch(function (err) {\n // Autoplay policy: an UNMUTED play() without user activation\n // rejects (the studio tab opens programmatically, so there is no\n // gesture yet). Without this fallback the element never plays and\n // \"playback\" degrades to drift-correction seeks — a silent ~3fps\n // slideshow until the user's first scrub. Muted playback is always\n // allowed: play muted now, unmute below once the host reports a\n // user gesture (d.audioUnlocked; the player iframe carries\n // allow=\"autoplay\" so the top frame's activation counts here).\n if (!vid.muted && err && err.name === 'NotAllowedError') {\n vid.muted = true\n vid.__voilaAutoMuted = true\n var p2 = vid.play()\n if (p2 && p2.catch) p2.catch(function () {})\n }\n })\n } else if (vid.__voilaAutoMuted && d.audioUnlocked) {\n // First user gesture happened — lift the policy fallback. If the\n // browser still objects it pauses the element, and the paused branch\n // above self-heals (re-mute + resume) on the next frame.\n vid.muted = false\n vid.__voilaAutoMuted = false\n }\n } else {\n // The paused step (FRAME_STEP_SRC): the provider by PTS, else the\n // element seek. A capture harness that ran the frame-prep hook has\n // already met this target, so nothing registers and the paint is\n // the one that counts.\n stepPaused(vid, srcT)\n }\n } catch (e) {}\n }\n if (video.play) syncVid(video) // stills (HTMLImageElement) have nothing to sync\n if (r.cam && !amId) syncVid(r.cam)\n if (r.mic && !amId) syncVid(r.mic)\n\n // --- transitions at a boundary (data.transitions, output seconds): the\n // record whose window holds t, else the next one within a second (its\n // snapshot warms ahead, so playback never shows a blank ghost). Locals\n // are tr-prefixed (one var scope).\n var trs = d.transitions || [], trA = null, trW = null\n for (var trI = 0; trI < trs.length; trI++) {\n var trR = trs[trI]\n if (t >= trR.t && t < trR.t + trR.d) { trA = trR; break }\n if (trR.out && trR.t > t && trR.t <= t + 1) { trW = trR; break }\n }\n function trEase(p) { p = p < 0 ? 0 : p > 1 ? 1 : p; var q = 1 - p; return 1 - q * q * q }\n function trSide(side) { return side === 'left' ? [-1, 0] : side === 'right' ? [1, 0] : side === 'up' ? [0, 1] : [0, -1] }\n // A record's outgoing media: its live element, and the GHOST held on the\n // clip's last frame (a second element on the same bytes, so one\n // recording can be both cards: a page change inside a single take). A\n // still needs no ghost.\n function trLiveEl(o) { return o.media ? (r.media ? r.media[o.media] : null) : r.video }\n function trGhostEl(o) {\n var el = trLiveEl(o)\n if (!el) return null\n if (!el.play) return el\n var g = ghostEl(el)\n stepGhost(g, o.at)\n return g\n }\n if (trW) trGhostEl(trW.out)\n var trIn = trA && trA.in ? trA.in : null, trOut = trA && trA.out ? trA.out : null\n var trGEl = trOut ? trGhostEl(trOut) : null\n if (!trGEl) trOut = null\n // The live card's enter and the ghost's exit as plane-fraction offsets\n // (x in plane widths, y in plane heights; +y up), a scale and an opacity.\n var trOx = 0, trOy = 0, trSc = 1, trOp = 1\n if (trIn) {\n var trP = 1 - trEase((t - trA.t) / trIn.d)\n if (trIn.kind === 'slide') { var trV = trSide(trIn.side); trOx = trV[0] * trP * ${TRANSITION_TRAVEL}; trOy = trV[1] * trP * ${TRANSITION_TRAVEL} }\n else if (trIn.kind === 'fade') trOp = 1 - trP\n else { trSc = 1 - ${TRANSITION_SCALE_STEP} * trP; trOp = 1 - trP }\n }\n var trGox = 0, trGoy = 0, trGsc = 1, trGop = 1\n if (trOut) {\n var trQ = trEase((t - trA.t) / trOut.d)\n if (trOut.kind === 'slide') { var trV2 = trSide(trOut.side); trGox = trV2[0] * trQ * ${TRANSITION_TRAVEL}; trGoy = trV2[1] * trQ * ${TRANSITION_TRAVEL} }\n else if (trOut.kind === 'fade') trGop = 1 - trQ\n else { trGsc = 1 - ${TRANSITION_SCALE_STEP} * trQ; trGop = 1 - trQ }\n }\n var trGM = null\n if (trOut && trOut.media && d.media) for (var trJ = 0; trJ < d.media.length; trJ++) if (d.media[trJ].id === trOut.media) trGM = d.media[trJ]\n var trGFr = trOut && trOut.media && trGM && trGM.frame ? trGM.frame : frame\n // The ghost layer: made on first need (most documents never cross a\n // boundary), sized with the card layer, above it and below the stack's\n // card planes. The flat stub paints the ghost on the card's own canvas.\n var gh = null\n if (trOut) {\n if (!r.ghost && ctx.THREE && ctx.scene && card.mesh) {\n var gT = ctx.THREE\n var gCv = document.createElement('canvas')\n gCv.width = cW; gCv.height = cH\n var gTex = new gT.CanvasTexture(gCv)\n gTex.colorSpace = gT.SRGBColorSpace\n gTex.minFilter = gT.LinearFilter; gTex.magFilter = gT.LinearFilter; gTex.generateMipmaps = false\n var gPh = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var gMesh = new gT.Mesh(new gT.PlaneGeometry(gPh * (W / H) * (cW / W), gPh * (cH / H)), new gT.MeshBasicMaterial({ map: gTex, transparent: true, depthTest: false, depthWrite: false }))\n gMesh.position.set(0, 0, ${CARD_Z + GHOST_Z_LIFT})\n gMesh.frustumCulled = false\n gMesh.renderOrder = ${GHOST_RENDER_ORDER}\n ctx.scene.add(gMesh)\n r.ghost = { canvas: gCv, c2d: gCv.getContext('2d'), texture: gTex, mesh: gMesh, w: cW, h: cH }\n }\n gh = r.ghost || { c2d: c, canvas: cv }\n if (r.ghost && (r.ghost.w !== cW || r.ghost.h !== cH)) {\n r.ghost.canvas.width = cW; r.ghost.canvas.height = cH; r.ghost.w = cW; r.ghost.h = cH\n if (r.ghost.texture.dispose) r.ghost.texture.dispose()\n if (r.ghost.mesh.geometry && r.ghost.mesh.geometry.dispose) r.ghost.mesh.geometry.dispose()\n var gPh2 = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n r.ghost.mesh.geometry = new ctx.THREE.PlaneGeometry(gPh2 * (W / H) * (cW / W), gPh2 * (cH / H))\n }\n }\n // The two cursor points a boundary lerps between (frame space), set by\n // the passes below: the ghost's last, the live card's current.\n var trQa = null, trQb = null, trQs = 0, trQw = 0\n // Gain routing (live via SET_DATA). With a mic sidecar (AT split) the\n // recording <video> carries SYSTEM audio — its volume is the system fader —\n // and the sidecar element is the voice (micGain). Legacy takes have one\n // mixed track on the <video>, governed by micGain as before.\n if (video.play && video.volume !== undefined) {\n var vidG = r.mic\n ? (d.sysGain != null ? d.sysGain : 1)\n : (d.micGain != null ? d.micGain : 1)\n if (Math.abs(video.volume - vidG) > 0.001) video.volume = vidG\n }\n if (r.mic && r.mic.volume !== undefined) {\n var micG = d.micGain != null ? d.micGain : 1\n if (Math.abs(r.mic.volume - micG) > 0.001) r.mic.volume = micG\n }\n\n // background — the BACKGROUND layer (screen-space plane, never tilts). Painted\n // to bgC (its own canvas at runtime; the card c2d under stub tests). Redraw +\n // re-upload ONLY when the signature changed or the media layer can paint a\n // FRESH frame (a ready video advances every frame). A static gradient thus\n // uploads once, so the common case steady-states at the card texture ALONE —\n // SwiftShader-fleet perf, compositor v2 risk #1.\n var bgSigM = frame.backgroundMedia\n var bgSig = (frame.background || '') + '|' + (bgSigM && bgSigM.key ? bgSigM.kind + ':' + bgSigM.key + ':' + (bgSigM.dim || 0) + ':' + (bgSigM.blur || 0) + ':' + (frame.parallax || 0) : '') + '|' + W + 'x' + H\n\n // --- background media: a baked vos loop\n // or still, cover-fit over the CSS fill, under the card, OUTSIDE the zoom\n // transform. Video time is OUTPUT-anchored modulo the loop (bgT = t % dur) —\n // pure f(t), so chunk cold-seeks land correctly and trims/speed never retime\n // ambience. Locals bg-prefixed (one var scope). Lazy element acquisition\n // keeps background SWAPS live SET_DATA edits (no LOAD): the element is\n // created + cached on first sight, and until it can paint the CSS fill shows\n // through (fail-open, never black). Acquisition + time-sync run EVERY frame\n // (even when the repaint below is skipped) so scrub seeks land and playback\n // stays locked to the modulo clock.\n var bgm = frame.backgroundMedia\n var bgEl = null, bgIsImg = !!(bgm && bgm.kind === 'image'), bgReady = false\n if (bgm && bgm.key && ns.videoCache) {\n bgEl = ns.videoCache.get(bgm.key)\n if (!bgEl) {\n if (bgIsImg) {\n bgEl = new Image()\n bgEl.crossOrigin = 'anonymous'\n bgEl.src = bgm.key\n } else {\n bgEl = document.createElement('video')\n bgEl.crossOrigin = 'anonymous'\n bgEl.muted = true\n bgEl.playsInline = true\n bgEl.preload = 'auto'\n bgEl.loop = true\n if (bgm.key.indexOf('blob:') === 0 || bgm.key.indexOf('data:') === 0) {\n bgEl.src = bgm.key\n bgEl.load()\n } else {\n // URL-backed loops (assets.vos.so bakes, /api proxies, take-dir\n // keys): fetch to a BLOB first — the render-page pattern. A\n // detached, paused, network-backed video gets SUSPENDED by Chrome\n // within seconds (readyState drops to 0, media resources released),\n // and the next seek then needs a full network reload — that's the\n // \"official-vos background vanishes on scrub and pops in seconds\n // late\" bug. Blob-backed elements seek instantly and never suspend.\n // Fail-open to the direct URL if the fetch dies; baked loops are\n // ≤~200KB so the buffer cost is trivial.\n ;(function (el, url) {\n fetch(url).then(function (r) { return r.ok ? r.blob() : Promise.reject(new Error('' + r.status)) })\n .then(function (b) { el.src = URL.createObjectURL(b); el.load() })\n .catch(function () { el.src = url; el.load() })\n })(bgEl, bgm.key)\n }\n }\n // Cache immediately (readiness gates drawing): the export settle guards\n // scan videoCache, so a still-loading background is waited on, not raced.\n ns.videoCache.set(bgm.key, bgEl)\n }\n if (!bgIsImg && bgEl.play) {\n var bgDur = bgm.duration || bgEl.duration || 0\n var bgT = bgDur > 0 ? t % bgDur : 0\n try {\n if (playing) {\n if (bgEl.playbackRate !== 1) bgEl.playbackRate = 1\n // Free-run on the element's native loop; drift-correct against the\n // modulo clock. Near the wrap the raw delta spans ~bgDur — treat\n // wrap-adjacent as in sync so every loop boundary isn't a seek.\n var bgDrift = Math.abs(bgEl.currentTime - bgT)\n if (bgDur > 0 && !bgEl.seeking && bgDrift > 0.3 && bgDur - bgDrift > 0.3) bgEl.currentTime = bgT\n if (bgEl.paused) { var bgP = bgEl.play(); if (bgP && bgP.catch) bgP.catch(function () {}) }\n } else {\n // The background step (FRAME_STEP_SRC): a coalesced seek, met\n // already when a capture harness ran the frame-prep hook.\n stepBg(bgEl, bgT)\n }\n } catch (e) {}\n }\n bgReady = bgIsImg ? !!(bgEl.complete && bgEl.naturalWidth) : bgEl.readyState >= 2\n }\n\n // Repaint on signature change, or when the media can paint a FRESH frame.\n // A video's readyState drops below HAVE_CURRENT_DATA while a seek is in\n // flight, and a scrub issues a new seek every frame — repainting then would\n // flash the CSS fill through until the drag ends (the background-vanishes-\n // while-scrubbing bug). Skipping the repaint keeps the LAST uploaded frame,\n // matching the card video's retained frame mid-seek (and the cam bubble's\n // sticky-readiness fix below). A sig change still repaints immediately —\n // fail-open to the CSS fill until the new medium decodes.\n var bgDirty = bg.sig !== bgSig || bgReady\n if (bgDirty) {\n bgC.clearRect(0, 0, W, H)\n // A KNOWN ground first: assigning an unpaintable string to fillStyle is a\n // silent no-op in canvas, so the layer would keep whatever colour the last\n // draw happened to leave — a backdrop the document never asked for and\n // nothing on screen explains.\n bgC.fillStyle = '#0b0b0c'\n bgC.fillStyle = (function () {\n var bgcss = frame.background || '#0b0b0c'\n if (typeof bgcss === 'string' && bgcss.indexOf('linear-gradient') === 0) {\n var inner = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))\n var parts = inner.split(',').map(function (x) { return x.trim() })\n var ang = 135, cols = []\n for (var i = 0; i < parts.length; i++) {\n if (parts[i].indexOf('deg') >= 0) ang = parseFloat(parts[i])\n else cols.push(parts[i])\n }\n if (cols.length < 2) cols = [cols[0] || '#000', cols[0] || '#000']\n var rad = (ang - 90) * Math.PI / 180\n var ux = Math.cos(rad), uy = Math.sin(rad)\n var g = bgC.createLinearGradient(W / 2 - ux * W / 2, H / 2 - uy * H / 2, W / 2 + ux * W / 2, H / 2 + uy * H / 2)\n g.addColorStop(0, cols[0]); g.addColorStop(1, cols[cols.length - 1])\n return g\n }\n // Radial: 'radial-gradient([circle|ellipse] [at X% Y%,] A, B)'. Canvas\n // cannot take the string, so it is built here like the linear one, with\n // CSS's own default extent (farthest corner) so the second colour lands\n // exactly where a browser would put it.\n if (typeof bgcss === 'string' && bgcss.indexOf('radial-gradient') === 0) {\n var rin = bgcss.substring(bgcss.indexOf('(') + 1, bgcss.lastIndexOf(')'))\n var rps = rin.split(',').map(function (x) { return x.trim() })\n var rcx = 0.5, rcy = 0.5, rcols = []\n for (var ri = 0; ri < rps.length; ri++) {\n var rp = rps[ri]\n if (rp.indexOf('circle') === 0 || rp.indexOf('ellipse') === 0 || rp.indexOf('at ') === 0) {\n var rat = /at\\\\s+([\\\\d.]+)%\\\\s+([\\\\d.]+)%/.exec(rp)\n if (rat) { rcx = parseFloat(rat[1]) / 100; rcy = parseFloat(rat[2]) / 100 }\n } else rcols.push(rp)\n }\n if (rcols.length < 2) rcols = [rcols[0] || '#000', rcols[0] || '#000']\n var rpx = rcx * W, rpy = rcy * H\n var rr = Math.max(\n Math.sqrt(rpx * rpx + rpy * rpy),\n Math.sqrt((W - rpx) * (W - rpx) + rpy * rpy),\n Math.sqrt(rpx * rpx + (H - rpy) * (H - rpy)),\n Math.sqrt((W - rpx) * (W - rpx) + (H - rpy) * (H - rpy))\n )\n var rg = bgC.createRadialGradient(rpx, rpy, 0, rpx, rpy, rr)\n rg.addColorStop(0, rcols[0]); rg.addColorStop(1, rcols[rcols.length - 1])\n return rg\n }\n return bgcss\n })()\n bgC.fillRect(0, 0, W, H)\n\n if (bgEl && bgReady) {\n var bgW = (bgIsImg ? bgEl.naturalWidth : bgEl.videoWidth) || 16\n var bgH = (bgIsImg ? bgEl.naturalHeight : bgEl.videoHeight) || 9\n // Parallax: the media counter-pans a touch as the zoom camera moves —\n // a depth cue. Pure f(t): offset from the SAME zoom-track sample the card\n // uses; over-scan the cover fit so the pan never reveals an edge.\n var bgPar = Math.min(1, Math.max(0, frame.parallax || 0))\n var bgOx = 0, bgOy = 0\n if (bgPar > 0 && d.zoomTrack && d.zoomTrack.keyframes && d.zoomTrack.keyframes.length) {\n var bgZ = TL.sample(d.zoomTrack, t, TL.lerpArray)\n var bgAmp = bgPar * (bgZ[0] - 1) * 0.08\n bgOx = -(bgZ[1] - 0.5) * bgAmp * W\n bgOy = -(bgZ[2] - 0.5) * bgAmp * H\n }\n var bgOver = 1 + (bgPar > 0 ? 0.1 : 0)\n var bgS = Math.max(W / bgW, H / bgH) * bgOver // cover-fit (+ parallax slack)\n var bgDw = bgW * bgS, bgDh = bgH * bgS\n // Clamp the pan into the cover slack so edges never show.\n var bgSlackX = (bgDw - W) / 2, bgSlackY = (bgDh - H) / 2\n bgOx = Math.max(-bgSlackX, Math.min(bgSlackX, bgOx))\n bgOy = Math.max(-bgSlackY, Math.min(bgSlackY, bgOy))\n // Blur: softens the media behind the card (design px × s).\n var bgBlur = bgm.blur || 0\n if (bgBlur > 0 && bgC.filter !== undefined) bgC.filter = 'blur(' + bgBlur * s + 'px)'\n try { bgC.drawImage(bgEl, (W - bgDw) / 2 + bgOx, (H - bgDh) / 2 + bgOy, bgDw, bgDh) } catch (e) {}\n if (bgBlur > 0 && bgC.filter !== undefined) bgC.filter = 'none'\n var bgDim = bgm.dim || 0\n if (bgDim > 0) {\n bgC.fillStyle = 'rgba(0,0,0,' + Math.min(1, bgDim) + ')'\n bgC.fillRect(0, 0, W, H)\n }\n }\n bg.sig = bgSig\n if (bg.texture) bg.texture.needsUpdate = true\n }\n\n // The card paints in PASSES: the ghost first (the outgoing clip's last\n // frame on its own canvas, while a boundary is crossing), then the live\n // card. A pass swaps the canvas, the element, the media, the source\n // moment and the card fields it reads; the ghost reads the camera as it\n // stood at the boundary and paints no clicks and no dot. The live pass\n // runs last, so everything after the loop reads the live card.\n var lvVideo = video, lvActM = actM, lvAmId = amId, lvSrcT = srcT, lvCfr = cfr, lvC = c, lvCv = cv\n var trPasses = trOut ? [true, false] : [false]\n for (var tpI = 0; tpI < trPasses.length; tpI++) {\n var tpG = trPasses[tpI]\n var tpT = t\n if (tpG) { c = gh.c2d; cv = gh.canvas; video = trGEl; actM = trGM; amId = trOut.media; srcT = trOut.at; cfr = trGFr; tpT = trA.t - 0.001 }\n else { c = lvC; cv = lvCv; video = lvVideo; actM = lvActM; amId = lvAmId; srcT = lvSrcT; cfr = lvCfr }\n // The CARD layer canvas starts transparent each frame — the padding around\n // the contain-fit card shows the background layer through the plane's alpha.\n // Under overscan the canvas is larger than the frame and every frame-space\n // draw below rides a base translation to its centre.\n var okOn = cW !== W || cH !== H\n if (okOn) c.setTransform(1, 0, 0, 1, 0, 0)\n c.clearRect(0, 0, cv.width, cv.height)\n if (okOn) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)\n // Footage is nearly always drawn SMALLER than it was recorded (a 1920\n // recording in a padded 1080 card, or a 440-wide tile): the default\n // bilinear tap aliases and blurs a large downscale; 'high' resamples.\n c.imageSmoothingQuality = 'high'\n\n // video destination rect (contain within the padded area). The optional browser-bar\n // strip is part of the card: it takes barH from the available height and the video\n // sits below it — bar + video share the rounded clip and zoom together.\n var pad = (frame.padding || 0) * s\n // Per-side placement (cfr.inset: fractions of the frame, a negative side\n // bleeds the card past the edge) overrides the symmetric padding on the\n // sides it names; absent sides keep pad, so the old math falls out.\n var ipIns = cfr.inset || {}\n var ipL = ipIns.left == null ? pad : ipIns.left * W\n var ipR = ipIns.right == null ? pad : ipIns.right * W\n var ipT = ipIns.top == null ? pad : ipIns.top * H\n var ipB = ipIns.bottom == null ? pad : ipIns.bottom * H\n var bar = cfr.browserBar || {}\n // Window takes carry a viewport crop (drawImage source rect, capture px) that\n // removes the real browser chrome — the card's source dims are then the CROP\n // dims (meta/cursor were rewritten into crop space at doc build).\n var crp = actM ? (actM.crop || null) : (d.crop || null)\n var vw = crp ? crp.w : (video.videoWidth || video.naturalWidth || 16)\n var vh = crp ? crp.h : (video.videoHeight || video.naturalHeight || 9)\n // Card-chrome scale: everything that belongs to the CARD (browser bar +\n // internals, corner radius, border, card shadow, cursor dot, click effects)\n // scales with the CARD, not the frame. When the frame is NARROWER than the\n // footage the card is width-limited and shrinks by frameAspect/videoAspect;\n // frame-relative sizing (s alone) was calibrated for native aspect and made\n // the chrome read giant on a small card (\"the 9:16 huge browser bar\" bug).\n // At native/wider aspects cf = 1 exactly, so nothing changes. MIRRORED by\n // computeCardLayout — change them together.\n // Cover fit: the CARD rect and the VIDEO rect separate. Under\n // contain (default, byte-identical for every existing doc) the card IS the\n // fitted video; under cover the card is the padded area itself and the\n // footage cover-fills it, cropped around cfr.focus (normalized video\n // fractions, the zoom cx/cy convention; clamped so no gap ever shows).\n // Chrome under cover scales by s alone (cf = 1): the card is as wide as\n // the frame allows, which is the case cf existed to protect against.\n var fitCover = cfr.fit === 'cover'\n var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))\n var s2 = s * cf\n // current zoom — a standard keyframe track in OUTPUT time (hold + arrival pairs\n // expanded by the lowering), sampled with the shared deterministic interpolator.\n // Sampled here, before the rect math, so a cover crop can follow it.\n var lvl = 1, zx = 0.5, zy = 0.5, zc = -1\n var zt = d.zoomTrack\n if (zt && zt.keyframes && zt.keyframes.length) {\n var z = TL.sample(zt, tpT, TL.lerpArray)\n lvl = z[0]; zx = z[1]; zy = z[2]\n // The stage camera's centring rides the track (0 rest, 1 apex, eased\n // with the level); a three-component track (a document lowered before\n // the component existed) falls back to the level band below.\n if (z.length > 3) zc = z[3]\n }\n var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0\n var availW = Math.max(1, W - ipL - ipR), availH = Math.max(1, H - ipT - ipB - barH)\n var sc, dw, dh, dx, dy, cardX, cardY, cardW, cardH\n if (fitCover) {\n sc = Math.max(availW / vw, availH / vh)\n dw = vw * sc; dh = vh * sc\n cardX = ipL; cardY = ipT; cardW = availW; cardH = availH + barH\n var fcv = cfr.focus || {}\n var fcx = fcv.cx == null ? 0.5 : Math.max(0, Math.min(1, fcv.cx))\n var fcy = fcv.cy == null ? 0.5 : Math.max(0, Math.min(1, fcv.cy))\n // focusFollow: the crop keeps the camera's focus in frame (a 9:16 cut).\n if (cfr.focusFollow === 'camera' && zt && zt.keyframes && zt.keyframes.length) { fcx = zx; fcy = zy }\n var vTop = ipT + barH\n dx = Math.min(cardX, Math.max(cardX + availW - dw, cardX + availW / 2 - fcx * dw))\n dy = Math.min(vTop, Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh))\n } else {\n sc = Math.min(availW / vw, availH / vh)\n dw = vw * sc; dh = vh * sc\n // Centred inside the inset area (equal to (W - dw) / 2 when no side is\n // inset, the old formula).\n dx = ipL + (availW - dw) / 2; dy = ipT + barH + (availH - dh) / 2\n cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH\n }\n var radius = (cfr.radius || 0) * s2\n var shadow = cfr.shadow || 0\n // The shadow's colour (a #rrggbb; the strengths are its alpha) and the\n // optional tight CONTACT layer that makes a light card sit on a light\n // ground.\n var shC = cfr.shadowContact || 0\n var shRgb = '0,0,0'\n var shHex = cfr.shadowColor\n if (typeof shHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(shHex)) {\n shRgb = parseInt(shHex.slice(1, 3), 16) + ',' + parseInt(shHex.slice(3, 5), 16) + ',' + parseInt(shHex.slice(5, 7), 16)\n }\n\n function rr(x, y, w, h, rd, cx) {\n var cc = cx || c\n if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }\n else { cc.beginPath(); cc.rect(x, y, w, h) }\n }\n\n c.save()\n // d.zoomSuppressed = editor aiming mode: the host merges it into ctx.data\n // while the focus overlay is up so the full frame renders. Never persisted.\n // ceZs = the zoom transform's device scale, for the two values below that\n // are sized in DEVICE pixels inside a scaled user space.\n var ceZs = 1\n // The camera model (cfr.camera; MIRRORS layout.ts zoomView — change them\n // together). The magnifier scales the card about the focus, which stays\n // where it is on screen; the stage camera also slides the card so the\n // focus lands at the frame's centre, blended in over CAMERA_CENTRE_RAMP\n // of level (a smoothstep, 0 at level 1) so a ramp through the band slides\n // without a kink, and past the card's edge the ground shows. wcx/wcy is\n // the content point at the frame's centre under either model; the two\n // branches are the same map, the magnifier's kept in its old float order\n // so every existing document renders byte-identically.\n var camStage = cfr.camera === 'stage'\n var fx = dx + zx * dw, fy = dy + zy * dh\n var wcx = fx, wcy = fy\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) {\n var ctt = zc\n if (ctt < 0) {\n var ctu = Math.max(0, Math.min(1, (lvl - 1) / ${CAMERA_CENTRE_RAMP}))\n ctt = ctu * ctu * (3 - 2 * ctu)\n }\n wcx = fx + ((W / 2 - fx) / lvl) * (1 - ctt)\n wcy = fy + ((H / 2 - fy) / lvl) * (1 - ctt)\n c.translate(W / 2, H / 2); c.scale(lvl, lvl); c.translate(-wcx, -wcy)\n } else {\n c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)\n }\n ceZs = lvl\n }\n // The card's shadows are cast by a body drawn OFF-CANVAS and brought back\n // by shadowOffsetX alone. Shadow offsets are device space, untouched by\n // the transform, so the body moves by shD / ceZs in user space and the\n // offset is shD; shD clears the canvas by the card's own device size, so\n // no part of the body lands on it at any zoom. Painting the body in place\n // (opaque black under the footage) left a 1px dark seam along every\n // straight card edge at a fractional coordinate: the fill's anti-aliased\n // rim showed through where the clipped footage did not cover it, and the\n // corners, drawn by the clip alone, hid theirs inside the arc.\n var shD = ceZs * (cardW + cardH) + 2 * (W + H)\n var shX = cardX - shD / ceZs\n // The card shadow is LAYERED: four shadows whose blur and offset grow\n // while each stays at a low alpha (shadow is the strength they share):\n // a hairline that defines the edge, two that ground it, and a WIDE,\n // faint ambient bloom (120 design px of blur, dropped well below the\n // card) that is what lets a large card read as lifted off a light\n // ground instead of outlined on it. One wide layer at the full strength\n // was the old paint, and at the default 0.4 it read as a dark pool\n // under the card; three tight layers read as a rim.\n if (shadow > 0) {\n var shL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]\n for (var shI = 0; shI < shL.length; shI++) {\n c.save()\n c.shadowColor = 'rgba(' + shRgb + ',' + +(shadow * shL[shI][2]).toFixed(3) + ')'\n c.shadowBlur = shL[shI][0] * s2; c.shadowOffsetX = shD; c.shadowOffsetY = shL[shI][1] * s2\n c.fillStyle = '#000'\n rr(shX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\n }\n // contact shadow: tight and close, over the ambient layer\n if (shC > 0) {\n c.save()\n c.shadowColor = 'rgba(' + shRgb + ',' + shC + ')'\n c.shadowBlur = 10 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 3 * s2\n c.fillStyle = '#000'\n rr(shX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\n // The footage and the bar fill overdraw the clip by one device pixel\n // (CARD_EDGE_OVERDRAW) so the clip is the only edge: a destination rect\n // that lands exactly on the clip path anti-aliases the edge twice at a\n // fractional coordinate and leaves a half-covered seam pixel.\n var ceOv = ${CARD_EDGE_OVERDRAW} / ceZs\n var ceX = dx - ceOv, ceY = dy - ceOv, ceW = dw + 2 * ceOv, ceH = dh + 2 * ceOv\n // video, then bar, clipped to the card's rounded corners. The bar draws\n // AFTER the footage: under cover the video rect can overflow ABOVE the\n // bar strip (a vertical crop), and bar-first let the footage paint over\n // it (found by eye on the padded marquee check — the stub geometry tests\n // have no z-order). Contain never overlaps, so the order is free there.\n c.save()\n rr(cardX, cardY, cardW, cardH, radius); c.clip()\n try {\n // The provider draws the current sample directly (crop-capable,\n // dimension-checked at attach); false ⇒ element path (pre-first-seek,\n // or the provider never attached).\n var wcp3 = video.__voilaWc\n if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {\n if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)\n else c.drawImage(video, ceX, ceY, ceW, ceH)\n }\n } catch (e) {}\n if (barH > 0) {\n var dark = bar.kind.indexOf('dark') >= 0\n var minimal = bar.kind === 'minimal'\n // minimal-bar theme: resolved colors from ctx.data (MINIMAL_BAR_THEMES);\n // absent = the built-in graphite look\n var thm = (minimal && bar.theme) || null\n c.fillStyle = minimal ? (thm ? thm.bar : '#141417') : dark ? '#2a2a2e' : '#e9e9eb'\n c.fillRect(cardX - ceOv, cardY - ceOv, cardW + 2 * ceOv, barH + ceOv)\n c.fillStyle = dark || (minimal && !(thm && thm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'\n c.fillRect(cardX, cardY + barH - s2, cardW, s2) // hairline above the video\n var midY = cardY + barH / 2\n if (bar.showControls !== false && !minimal) {\n if (bar.kind.indexOf('mac') === 0) {\n var lights = ['#ff5f57', '#febc2e', '#28c840']\n for (var li = 0; li < 3; li++) {\n c.fillStyle = lights[li]\n c.beginPath(); c.arc(cardX + (20 + li * 20) * s2, midY, 6 * s2, 0, Math.PI * 2); c.fill()\n }\n } else {\n c.strokeStyle = dark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'\n c.lineWidth = 1.5 * s2\n var g = 4.5 * s2, gx = cardX + cardW - 22 * s2 // close ✕, then ▢, then — leftward\n c.beginPath()\n c.moveTo(gx - g, midY - g); c.lineTo(gx + g, midY + g)\n c.moveTo(gx + g, midY - g); c.lineTo(gx - g, midY + g)\n c.stroke()\n c.strokeRect(gx - 28 * s2 - g, midY - g, g * 2, g * 2)\n c.beginPath()\n c.moveTo(gx - 56 * s2 - g, midY); c.lineTo(gx - 56 * s2 + g, midY)\n c.stroke()\n }\n }\n if (bar.showUrl !== false && bar.url) {\n var pillW = Math.min(cardW * 0.5, Math.max(200 * s2, cardW * 0.34))\n var pillH = barH - 16 * s2\n var px0 = cardX + (cardW - pillW) / 2, py0 = cardY + 8 * s2\n c.fillStyle = minimal ? (thm ? thm.pill : '#26262b') : dark ? '#1d1d20' : '#ffffff'\n rr(px0, py0, pillW, pillH, pillH / 2); c.fill()\n c.fillStyle = minimal ? (thm ? thm.text : '#9a9aa1') : dark ? '#a1a1a6' : '#5f5f64'\n c.font = 13 * s2 + 'px -apple-system, system-ui, sans-serif'\n c.textAlign = 'center'; c.textBaseline = 'middle'\n var label = String(bar.url)\n var maxTextW = pillW - 28 * s2\n if (c.measureText(label).width > maxTextW) {\n while (label.length > 1 && c.measureText(label + '\\\\u2026').width > maxTextW) label = label.slice(0, -1)\n label += '\\\\u2026'\n }\n c.fillText(label, px0 + pillW / 2, py0 + pillH / 2)\n c.textAlign = 'start'; c.textBaseline = 'alphabetic'\n }\n }\n c.restore()\n // border around the card, drawn OUTWARD like a CSS outline: the path is\n // expanded by half the width so the stroke's inner edge lands on the card's\n // own edge and never covers footage (inset, a 24px width ate 24px of the\n // recording). Outer corner radius grows with the width, the CSS-border rule;\n // a square card stays square. cfr.border is the ALPHA (0 = off) and rides\n // globalAlpha, so borderColor takes any CSS colour notation without this\n // having to parse one. Card chrome, so it scales by s2.\n if (cfr.border) {\n var bdW = (cfr.borderWidth > 0 ? cfr.borderWidth : ${FRAME_BORDER_WIDTH_DEFAULT}) * s2\n var bdH = bdW / 2\n c.save()\n c.globalAlpha = Math.min(1, cfr.border)\n c.strokeStyle = cfr.borderColor || '${FRAME_BORDER_COLOR_DEFAULT}'\n c.lineWidth = bdW\n rr(cardX - bdH, cardY - bdH, cardW + bdW, cardH + bdW, radius > 0 ? radius + bdH : 0)\n c.stroke()\n c.restore()\n }\n // Cover crop: click effects and the cursor dot are video-anchored, so a\n // cropped-out moment must not paint over the padding — clip them to the\n // card. Contain never needs it (the video rect IS the card).\n if (fitCover) { c.save(); rr(cardX, cardY, cardW, cardH, radius); c.clip() }\n // cursor coordinate space + drawn radius (shared by click effects + the dot)\n var space = (actM ? actM.cursorSpace : d.cursorSpace) || { w: vw, h: vh }\n var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5\n // The pointer's shape: an OS-style arrow (tip on the recorded point, a\n // black body with a white edge, the shape of the pointer on the viewer's\n // own screen) or the white dot every earlier take was cut with. One\n // painter for both draw sites (in the card, and the ghost across a\n // boundary); r is the dot's radius, so an arrow stands 2r tall.\n var curArrow = !!(d.cursorStyle && d.cursorStyle.style === 'arrow')\n function drawCur(cc, x, y, r, a, lw) {\n cc.save()\n if (curArrow) {\n var h = 2 * r\n var P = [[0, 0], [0, 0.72], [0.19, 0.55], [0.31, 0.83], [0.43, 0.78], [0.31, 0.5], [0.55, 0.5]]\n cc.beginPath()\n cc.moveTo(x, y)\n for (var pi = 1; pi < P.length; pi++) cc.lineTo(x + P[pi][0] * h, y + P[pi][1] * h)\n cc.closePath()\n cc.lineJoin = 'round'\n cc.fillStyle = 'rgba(0,0,0,' + (0.92 * a) + ')'\n cc.strokeStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'\n cc.lineWidth = Math.max(1, lw * 0.75)\n cc.fill(); cc.stroke()\n } else {\n cc.fillStyle = 'rgba(255,255,255,' + (0.95 * a) + ')'\n cc.strokeStyle = 'rgba(0,0,0,' + (0.4 * a) + ')'\n cc.lineWidth = lw\n cc.beginPath(); cc.arc(x, y, r, 0, Math.PI * 2); cc.fill(); cc.stroke()\n }\n cc.restore()\n }\n\n // click effects — pure f(t): d.clicks are\n // OUTPUT-anchored records baked at lowering (sorted by ot; re-baked on every\n // edit, so they can't go stale), drawn UNDER the cursor and inside the zoom\n // transform so they scale with the camera. Locals are ck-prefixed (one var\n // scope). Effects anchor at the click point, never the smoothed cursor.\n var cks = d.clicks || []\n var ckF = d.clickFx || {}\n var ckPress = 1\n if (cks.length && !tpG) {\n var ckK = ckF.k || 1\n var ckPre = ${CLICK_FX_PRE}\n var ckRD = ${CLICK_RIPPLE_DUR} * (ckF.dur || 1)\n var ckPD = ${CLICK_PULSE_DUR} * (ckF.dur || 1)\n // 'highlight' keeps the ripple window too: clicks whose element rect\n // failed the lowering gates (huge/absent) fall back to a ripple per click\n var ckWin = ckF.style === 'pulse' ? ckPD : ckF.style === 'none' ? 0 : ckRD\n var ckCol = ckF.col || 0\n var ckDip = Math.min(0.3, 0.18 * ckK)\n for (var ci = 0; ci < cks.length; ci++) {\n var ck = cks[ci]\n if (ck.ot - 0.08 > t) break // sorted: nothing later is active yet\n var ckEnd = ck.ot - ckPre + ckWin\n if (ckF.style === 'highlight' && ck.r && ck.up + ${CLICK_HIGHLIGHT_FADE} > ckEnd) ckEnd = ck.up + ${CLICK_HIGHLIGHT_FADE}\n if (ckF.press && ck.up + 0.16 > ckEnd) ckEnd = ck.up + 0.16\n if (t > ckEnd) continue\n // cross-cut guard: the on-screen source moment must still be near the\n // click's source moment, or an effect near a cut would keep painting\n // over the NEXT segment's unrelated footage\n if (Math.abs(srcT - ck.st) > 2) continue\n var ckSp = ck.sp || space\n var ckAx = dx + (ck.x / (ckSp.w || vw)) * dw\n var ckAy = dy + (ck.y / (ckSp.h || vh)) * dh\n // press dip: smoothstep down around the real mousedown (80 ms lead —\n // anticipation is what makes effects feel synced), hold through the real\n // down→up span (drags dip long), easeOutBack rebound with a slight\n // overshoot. Feeds the cursor dot's radius below.\n if (ckF.press) {\n var ckPs = 1\n if (t < ck.ot + 0.05) {\n var ckU = (t - (ck.ot - 0.08)) / 0.13\n if (ckU > 0) {\n if (ckU > 1) ckU = 1\n ckU = ckU * ckU * (3 - 2 * ckU)\n ckPs = 1 - ckDip * ckU\n }\n } else if (t <= ck.up) {\n ckPs = 1 - ckDip\n } else {\n var ckV = (t - ck.up) / 0.16\n if (ckV < 1) {\n var ckW2 = ckV - 1\n ckPs = 1 - ckDip + ckDip * (1 + 4 * ckW2 * ckW2 * ckW2 + 3 * ckW2 * ckW2)\n }\n }\n ckPress = ckPs\n }\n var ckStyle = ckF.style === 'highlight' && !ck.r ? 'ripple' : ckF.style\n if (ckStyle === 'ripple') {\n // expanding ring: cubic-out expansion, cubic fade, thinning stroke\n var ckU2 = (t - (ck.ot - ckPre)) / ckRD\n if (ckU2 >= 0 && ckU2 <= 1) {\n var ckFade = (1 - ckU2) * (1 - ckU2) * (1 - ckU2)\n var ckR = curSize * 0.8 + (1 - ckFade) * 44 * s2 * ckK\n var ckA = Math.min(1, ckFade * 0.65 * ckK)\n var ckLw = Math.max(1.5 * s2, 3 * s2 * (1 - ckU2))\n c.save()\n if (ckCol) {\n c.strokeStyle = 'rgba(' + ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] + ',' + ckA + ')'\n c.lineWidth = ckLw\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n // thin white outline so accent colors read on dark content too\n c.strokeStyle = 'rgba(255,255,255,' + ckA * 0.5 + ')'\n c.lineWidth = Math.max(1, s2)\n c.beginPath(); c.arc(ckAx, ckAy, ckR + ckLw * 0.8, 0, Math.PI * 2); c.stroke()\n } else {\n // auto: the cursor dot's dual-stroke trick — dark rim under a\n // white ring, legible on any content\n c.strokeStyle = 'rgba(0,0,0,' + ckA * 0.35 + ')'\n c.lineWidth = ckLw + 2 * s2\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n c.strokeStyle = 'rgba(255,255,255,' + ckA + ')'\n c.lineWidth = ckLw\n c.beginPath(); c.arc(ckAx, ckAy, ckR, 0, Math.PI * 2); c.stroke()\n }\n c.restore()\n }\n } else if (ckStyle === 'pulse') {\n // soft filled orb: parabola opacity (in fast, out soft), cubic-out bloom\n var ckU3 = (t - (ck.ot - ckPre)) / ckPD\n if (ckU3 >= 0 && ckU3 <= 1) {\n var ckE3 = 1 - (1 - ckU3) * (1 - ckU3) * (1 - ckU3)\n var ckR3 = Math.max(1, 34 * s2 * ckK * (0.35 + 0.65 * ckE3))\n var ckA3 = Math.min(1, 4 * ckU3 * (1 - ckU3) * 0.45 * ckK)\n var ckC3 = ckCol ? ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] : '255,255,255'\n var ckG = c.createRadialGradient(ckAx, ckAy, 0, ckAx, ckAy, ckR3)\n ckG.addColorStop(0, 'rgba(' + ckC3 + ',' + ckA3 + ')')\n ckG.addColorStop(1, 'rgba(' + ckC3 + ',0)')\n c.save()\n c.fillStyle = ckG\n c.beginPath(); c.arc(ckAx, ckAy, ckR3, 0, Math.PI * 2); c.fill()\n c.restore()\n }\n } else if (ckStyle === 'highlight') {\n // element glow (unique to DOM capture): rounded-rect around the\n // clicked element's rect — fast smoothstep in, hold while pressed,\n // quadratic fade after release\n var ckIn = (t - (ck.ot - ckPre)) / 0.08\n if (ckIn > 0) {\n if (ckIn > 1) ckIn = 1\n ckIn = ckIn * ckIn * (3 - 2 * ckIn)\n var ckOut = 1\n if (t > ck.up) {\n var ckV2 = (t - ck.up) / ${CLICK_HIGHLIGHT_FADE}\n ckOut = ckV2 >= 1 ? 0 : (1 - ckV2) * (1 - ckV2)\n }\n var ckA4 = Math.min(1, ckIn * ckOut * 0.9 * ckK)\n if (ckA4 > 0.004) {\n var ckRX = dx + (ck.r[0] / (space.w || vw)) * dw\n var ckRY = dy + (ck.r[1] / (space.h || vh)) * dh\n var ckRW = (ck.r[2] / (space.w || vw)) * dw\n var ckRH = (ck.r[3] / (space.h || vh)) * dh\n var ckRad = Math.min(10 * s2, ckRH / 2)\n var ckC4 = ckCol ? ckCol[0] + ',' + ckCol[1] + ',' + ckCol[2] : '255,255,255'\n c.save()\n // dark rim under the glowing stroke — legible on light content too\n c.strokeStyle = 'rgba(0,0,0,' + ckA4 * 0.35 + ')'\n c.lineWidth = 4 * s2\n rr(ckRX, ckRY, ckRW, ckRH, ckRad); c.stroke()\n c.shadowColor = 'rgba(' + ckC4 + ',' + ckA4 * 0.4 + ')'\n c.shadowBlur = 12 * s2\n c.strokeStyle = 'rgba(' + ckC4 + ',' + ckA4 + ')'\n c.lineWidth = 2 * s2\n rr(ckRX, ckRY, ckRW, ckRH, ckRad); c.stroke()\n c.restore()\n }\n }\n }\n }\n }\n\n // pinned layers' marks — a standing ring or underline on a layer's\n // REFERENT for the layer's life (d.pins: OUTPUT-anchored records baked at\n // lowering, the referent in normalised video fractions). Painted inside the\n // zoom transform so the mark sits on the element under any level or pose,\n // at a constant on-screen weight (÷ ceZs), with the layer's own entrance\n // and exit. Locals are pn-prefixed (one var scope). Skipped on the ghost\n // pass like the effects.\n var pns = d.pins || []\n if (pns.length && !tpG) {\n for (var pi = 0; pi < pns.length; pi++) {\n var pn = pns[pi]\n if (!pn.mark || pn.mark === 'none') continue\n var pnT = t - pn.start\n if (pnT < 0 || pnT > pn.dur) continue\n var pnA = 1\n if (pnT < 0.35) { var pnU = pnT / 0.35; pnA = 1 - Math.pow(1 - pnU, 3) }\n if (pn.dur - pnT < 0.35) { var pnV = (pn.dur - pnT) / 0.35; pnA = Math.min(pnA, 1 - Math.pow(1 - pnV, 3)) }\n if (pnA <= 0.004) continue\n var pnX = dx + pn.x * dw, pnY = dy + pn.y * dh, pnW = pn.w * dw, pnH = pn.h * dh\n // a carried referent (a drag): moved by where the pointer has taken\n // it at the on-screen source moment, held at the release\n if (pn.carry) {\n var pcC = (actM ? actM.cursor : d.cursor) || []\n var pcT = Math.max(pn.carry.t0, Math.min(pn.carry.t1, srcT))\n var pcx = -1, pcy = -1\n for (var pk = 0; pk < pcC.length; pk++) { if (pcC[pk].t <= pcT) { pcx = pcC[pk].x; pcy = pcC[pk].y } }\n if (pcx >= 0) { pnX += (pcx / (space.w || vw) - pn.carry.x0) * dw; pnY += (pcy / (space.h || vh) - pn.carry.y0) * dh }\n }\n var pnLw = 2 * s2 / ceZs, pnPad = 6 * s2 / ceZs\n var pnCol = pn.color || 'rgba(255,255,255,0.95)'\n c.save()\n c.globalAlpha = pnA\n if (pn.mark === 'underline') {\n // a bar under the referent: dark rim, then the ink\n var pnBy = pnY + pnH + pnPad * 0.5\n c.fillStyle = 'rgba(0,0,0,0.35)'\n c.fillRect(pnX - pnLw, pnBy - pnLw, pnW + pnLw * 2, pnLw * 1.5 + pnLw * 2)\n c.fillStyle = pnCol\n c.fillRect(pnX, pnBy, pnW, pnLw * 1.5)\n } else {\n // a ring around the referent: the cursor's dual stroke, a dark rim\n // under the ink, legible on any content\n c.lineWidth = pnLw + 2 * s2 / ceZs\n c.strokeStyle = 'rgba(0,0,0,0.35)'\n rr(pnX - pnPad, pnY - pnPad, pnW + pnPad * 2, pnH + pnPad * 2, pnPad, c); c.stroke()\n c.lineWidth = pnLw\n c.strokeStyle = pnCol\n rr(pnX - pnPad, pnY - pnPad, pnW + pnPad * 2, pnH + pnPad * 2, pnPad, c); c.stroke()\n }\n c.restore()\n }\n }\n\n // cursor (SOURCE-anchored samples, read at the on-screen source moment).\n // The dot is the only thing cursorStyle.visible hides — the track still drives\n // cursor-follow zoom, and click effects draw above on their own switch.\n // Undefined reads as visible so pre-toggle docs are unchanged.\n var cur = (actM ? actM.cursor : d.cursor) || []\n if (cur.length && !(d.cursorStyle && d.cursorStyle.visible === false)) {\n var px = cur[0].x, py = cur[0].y\n for (var j = 0; j < cur.length; j++) { if (cur[j].t <= srcT) { px = cur[j].x; py = cur[j].y } }\n var ax = dx + (px / (space.w || vw)) * dw\n var ay = dy + (py / (space.h || vh)) * dh\n // Idle fade: a sparse SOURCE-time opacity curve baked by cursorIdleFade.\n // Linear between keys — opacity needs no easing, and the ramps are already\n // shaped by where the keys sit. Absent/empty = the cursor never dwells.\n var cuA = 1, cuK = actM ? actM.cursorFade : d.cursorFade\n if (cuK && cuK.length) {\n if (srcT <= cuK[0].t) cuA = cuK[0].a\n else if (srcT >= cuK[cuK.length - 1].t) cuA = cuK[cuK.length - 1].a\n else {\n for (var cuJ = 1; cuJ < cuK.length; cuJ++) {\n if (cuK[cuJ].t >= srcT) {\n var cuB = cuK[cuJ - 1], cuC = cuK[cuJ]\n cuA = cuB.a + (cuC.a - cuB.a) * ((srcT - cuB.t) / ((cuC.t - cuB.t) || 1))\n break\n }\n }\n }\n }\n // Across a boundary the dot is ONE, drawn in frame space on the\n // overlay layer (the lerp after the passes), never on a card that is\n // moving: each pass leaves its point, mapped out of the zoom.\n if (trOut) {\n var tqX = ax, tqY = ay\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) { tqX = W / 2 + (ax - wcx) * lvl; tqY = H / 2 + (ay - wcy) * lvl }\n else { tqX = fx + (ax - fx) * lvl; tqY = fy + (ay - fy) * lvl }\n }\n if (tpG) trQa = [tqX, tqY]\n else { trQb = [tqX, tqY]; trQs = curSize * ckPress; trQw = s2 }\n } else if (cuA > 0.01) {\n drawCur(c, ax, ay, curSize * ckPress, cuA, 2 * s2)\n }\n }\n if (fitCover) c.restore()\n c.restore()\n if (tpG && gh.texture) gh.texture.needsUpdate = true\n }\n\n // --- OVERLAY layer (screen-space plane, never tilts): the cam bubble, the\n // recording's own footage. Text/image/video overlay CLIPS are the studio\n // stack entry's (studioEntry.ts): they paint on their own layer in\n // ctx.overlayScene, above this one. Painted to ovC (its own canvas at\n // runtime; the card c2d under stubs). Redraw + re-upload only while the\n // bubble is active (a video → every frame), on resize, or once when it turns\n // off (to clear) — so a cam-less take never uploads the overlay after frame 1.\n var camV = amId ? null : r.cam, camS = d.cam || {}\n var camOn = !camS.window || (srcT >= camS.window.in && srcT <= camS.window.out)\n // Readiness is STICKY: readyState drops to HAVE_METADATA while a seek is in\n // flight, so gating each frame on it makes the bubble vanish on every scrub\n // step (the screen video is drawn ungated and just shows its retained frame).\n // Wait only for the FIRST decoded frame, then keep drawing through seeks.\n if (camV && camV.readyState >= 2) r.camHasFrame = true\n var camActiveNow = !!(camV && camOn && camS.visible !== false && r.camHasFrame)\n // A pinned layer's leader is live for the layer's life: the overlay layer\n // repaints while one is up (the referent moves with the camera).\n var pqs = d.pins || [], pqLive = false\n for (var pl = 0; pl < pqs.length; pl++) {\n var pq0 = pqs[pl]\n if (pq0.leader && t >= pq0.start && t <= pq0.start + pq0.dur) { pqLive = true; break }\n }\n var ovSig = W + 'x' + H\n var ovDirty = ov.sig !== ovSig || camActiveNow || ov.active || !!trOut || pqLive\n if (ovDirty) {\n ovC.clearRect(0, 0, W, H)\n // webcam bubble — pinned to the frame corner regardless of card tilt/zoom.\n if (camActiveNow) {\n // Cam pose track: [x, y, size] frame fractions sampled at t — wins\n // over the static pose while spans exist. camPoseOverride is ephemeral\n // editor state (the zoomSuppressed seam): the selected span's settled\n // pose while paused, merged by the host, never persisted.\n var camP = d.camPoseOverride || null\n var camTk = d.camTrack\n if (!camP && camTk && camTk.keyframes && camTk.keyframes.length) camP = TL.sample(camTk, t, TL.lerpArray)\n var diam = Math.max(40, (camP ? camP[2] : (camS.size || 0.25)) * H)\n var mg = 24 * s\n var pos = camS.position || 'bottom-left'\n // Free placement: x/y are the bubble CENTER as frame fractions and\n // win over the corner anchor when present; a sampled pose wins over both.\n var bx = camP ? camP[0] * W - diam / 2 : camS.x != null ? camS.x * W - diam / 2 : pos.indexOf('right') >= 0 ? W - mg - diam : mg\n var by = camP ? camP[1] * H - diam / 2 : camS.y != null ? camS.y * H - diam / 2 : pos.indexOf('top') >= 0 ? mg : H - mg - diam\n // The bubble's look is three knobs with the old paint as every default\n // (decided 2026-08-24: the defaults must stay editable): radius\n // (rounded only, 18), shadow ('soft'), border (3px white at 0.9).\n var rd = camS.shape === 'rounded' ? (camS.radius != null ? camS.radius : 18) * s : diam / 2\n var cw = camV.videoWidth || 16, ch = camV.videoHeight || 9\n var sc2 = Math.max(diam / cw, diam / ch)\n var sw = cw * sc2, sh = ch * sc2\n var sx = bx + (diam - sw) / 2, sy = by + (diam - sh) / 2\n var camShadow = camS.shadow || 'soft'\n if (camShadow !== 'none') {\n ovC.save()\n ovC.shadowColor = camShadow === 'strong' ? 'rgba(0,0,0,0.55)' : 'rgba(0,0,0,0.4)'\n ovC.shadowBlur = (camShadow === 'strong' ? 60 : 30) * s\n ovC.shadowOffsetY = (camShadow === 'strong' ? 20 : 10) * s\n ovC.fillStyle = '#000'\n rr(bx, by, diam, diam, rd, ovC); ovC.fill()\n ovC.restore()\n }\n ovC.save()\n rr(bx, by, diam, diam, rd, ovC); ovC.clip()\n if (camS.mirror) { ovC.translate(bx * 2 + diam, 0); ovC.scale(-1, 1) } // mirror about bubble center\n try { ovC.drawImage(camV, sx, sy, sw, sh) } catch (e) {}\n ovC.restore()\n var camBW = camS.border ? camS.border.width : 3\n if (camBW > 0) {\n ovC.save()\n ovC.strokeStyle = (camS.border && camS.border.color) || 'rgba(255,255,255,0.9)'; ovC.lineWidth = camBW * s\n rr(bx, by, diam, diam, rd, ovC); ovC.stroke()\n ovC.restore()\n }\n }\n // pinned layers' leaders — a hairline from the layer's near edge (the\n // baked tip track, clip-local frame fractions) to the referent's near\n // edge, the referent mapped through the camera the way the boundary dot\n // is, with a dot at the referent. Fades with the layer; rides the rise\n // entrance so the line meets the card as it lands.\n if (pqLive) {\n for (var pj = 0; pj < pqs.length; pj++) {\n var pq = pqs[pj]\n if (!pq.leader) continue\n var pqT = t - pq.start\n if (pqT < 0 || pqT > pq.dur) continue\n var pqA = 1, pqRise = 0\n if (pqT < 0.35) {\n var pqU = 1 - Math.pow(1 - pqT / 0.35, 3)\n pqA = pqU\n if (pq.enter === 'rise') pqRise = (1 - pqU) * 24 * s\n }\n if (pq.dur - pqT < 0.35) { var pqV = (pq.dur - pqT) / 0.35; pqA = Math.min(pqA, 1 - Math.pow(1 - pqV, 3)) }\n if (pqA <= 0.004 || !pq.tip || !pq.tip.keyframes || !pq.tip.keyframes.length) continue\n var pqTip = TL.sample(pq.tip, pqT, TL.lerpArray)\n var pqTx = pqTip[0] * W, pqTy = pqTip[1] * H + pqRise\n // the referent's corners in frame space under the live camera\n var pqAx = dx + pq.x * dw, pqAy = dy + pq.y * dh\n var pqBx = dx + (pq.x + pq.w) * dw, pqBy = dy + (pq.y + pq.h) * dh\n if (pq.carry) {\n var pqcT = Math.max(pq.carry.t0, Math.min(pq.carry.t1, srcT))\n var pqcx = -1, pqcy = -1\n for (var pqk = 0; pqk < cur.length; pqk++) { if (cur[pqk].t <= pqcT) { pqcx = cur[pqk].x; pqcy = cur[pqk].y } }\n if (pqcx >= 0) {\n var pqOx = (pqcx / (space.w || vw) - pq.carry.x0) * dw, pqOy = (pqcy / (space.h || vh) - pq.carry.y0) * dh\n pqAx += pqOx; pqBx += pqOx; pqAy += pqOy; pqBy += pqOy\n }\n }\n if (lvl > 1.001 && !d.zoomSuppressed) {\n if (camStage) {\n pqAx = W / 2 + (pqAx - wcx) * lvl; pqAy = H / 2 + (pqAy - wcy) * lvl\n pqBx = W / 2 + (pqBx - wcx) * lvl; pqBy = H / 2 + (pqBy - wcy) * lvl\n } else {\n pqAx = fx + (pqAx - fx) * lvl; pqAy = fy + (pqAy - fy) * lvl\n pqBx = fx + (pqBx - fx) * lvl; pqBy = fy + (pqBy - fy) * lvl\n }\n }\n var pqRx = pq.side === 'right' ? pqBx : pq.side === 'left' ? pqAx : (pqAx + pqBx) / 2\n var pqRy = pq.side === 'below' ? pqBy : pq.side === 'above' ? pqAy : (pqAy + pqBy) / 2\n var pqCol = pq.color || 'rgba(255,255,255,0.95)'\n var pqLw = 2 * s\n ovC.save()\n ovC.globalAlpha = pqA\n ovC.lineCap = 'round'\n ovC.strokeStyle = 'rgba(0,0,0,0.35)'; ovC.lineWidth = pqLw + 2 * s\n ovC.beginPath(); ovC.moveTo(pqTx, pqTy); ovC.lineTo(pqRx, pqRy); ovC.stroke()\n ovC.strokeStyle = pqCol; ovC.lineWidth = pqLw\n ovC.beginPath(); ovC.moveTo(pqTx, pqTy); ovC.lineTo(pqRx, pqRy); ovC.stroke()\n ovC.fillStyle = 'rgba(0,0,0,0.35)'\n ovC.beginPath(); ovC.arc(pqRx, pqRy, 5 * s, 0, Math.PI * 2); ovC.fill()\n ovC.fillStyle = pqCol\n ovC.beginPath(); ovC.arc(pqRx, pqRy, 4 * s, 0, Math.PI * 2); ovC.fill()\n ovC.restore()\n }\n }\n // The boundary dot: the outgoing card's last cursor point and the\n // incoming's current one, each carried by its card's move, one dot on\n // the eased lerp between them (the idle fade never fires across a page\n // change). It lands on the incoming's own dot as the window closes.\n if (trOut && (trQa || trQb) && !(d.cursorStyle && d.cursorStyle.visible === false)) {\n var tqA = trQa || trQb, tqB = trQb || trQa\n var tqP = trEase((t - trA.t) / trA.d)\n var tqAx = (tqA[0] - W / 2) * trGsc + W / 2 + trGox * W, tqAy = (tqA[1] - H / 2) * trGsc + H / 2 - trGoy * H\n var tqBx = (tqB[0] - W / 2) * trSc + W / 2 + trOx * W, tqBy = (tqB[1] - H / 2) * trSc + H / 2 - trOy * H\n var tqX2 = tqAx + (tqBx - tqAx) * tqP, tqY2 = tqAy + (tqBy - tqAy) * tqP\n var tqR = trQs || (((d.cursorStyle && d.cursorStyle.size) || 24) * s * 0.5)\n drawCur(ovC, tqX2, tqY2, tqR, 1, 2 * (trQw || s))\n }\n ov.sig = ovSig\n ov.active = camActiveNow || !!trOut || pqLive\n if (ov.texture) ov.texture.needsUpdate = true\n }\n\n // --- card presentation (compositor v2): the card's pose is the TILT\n // TRACK and nothing else (decided 2026-08-03 — a lean is a moment in time,\n // so it lives on the timeline; the static rest pose, entrance, exit, float\n // and glow are gone with the Card panel). No spans ⇒ no track ⇒ identity,\n // which is pixel-identical to the pre-v2 fullscreen quad. Mipmaps and\n // anisotropy switch on when the card actually tilts (minification would\n // otherwise shimmer). d.tiltSuppressed is pure editor ui state (the\n // zoomSuppressed seam): on-canvas edit overlays mirror UNtilted card\n // geometry, so edit views need the flat card.\n if (card.mesh) {\n var rx = 0, ry = 0\n var tk = d.tiltTrack\n if (tk && tk.keyframes && tk.keyframes.length && !d.tiltSuppressed) {\n var tkv = TL.sample(tk, t, TL.lerpArray)\n rx = tkv[0] * Math.PI / 180\n ry = tkv[1] * Math.PI / 180\n }\n card.mesh.rotation.x = rx\n card.mesh.rotation.y = ry\n // The card's pose through an entrance or an end card: [scale, dy,\n // opacity]; absent = the rest pose, exactly as before the track existed.\n // dy is a fraction of the FRAME plane's height (not the geometry's,\n // which the overscan grows).\n var cpk = d.cardPoseTrack\n var cpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var cpv = null\n if (cpk && cpk.keyframes && cpk.keyframes.length) cpv = TL.sample(cpk, t, TL.lerpArray)\n if ((cpv || trIn) && card.mesh.scale && card.mesh.position) {\n // The pose track, then a boundary's enter on top: a slide is an\n // offset in plane widths and heights, a fade an opacity, a scale both.\n var cpS = (cpv ? cpv[0] : 1) * trSc\n var cpO = (cpv ? cpv[2] : 1) * trOp\n card.mesh.scale.x = cpS; card.mesh.scale.y = cpS\n card.mesh.position.x = trOx * cpH * (W / H)\n card.mesh.position.y = ((cpv ? cpv[1] : 0) + trOy) * cpH\n if (card.mesh.material) card.mesh.material.opacity = cpO\n // Gone past its clip: the mesh leaves the scene, not just its paint.\n card.mesh.visible = cpO > 0.001\n } else if (card.mesh.scale && card.mesh.position && ((card.mesh.scale.x !== undefined && card.mesh.scale.x !== 1) || (card.mesh.position.y !== undefined && card.mesh.position.y !== 0) || (card.mesh.position.x !== undefined && card.mesh.position.x !== 0) || card.mesh.visible === false)) {\n card.mesh.scale.x = 1; card.mesh.scale.y = 1\n card.mesh.position.x = 0\n card.mesh.position.y = 0\n if (card.mesh.material) card.mesh.material.opacity = 1\n card.mesh.visible = true\n }\n var tilted = rx * rx + ry * ry > 1e-6\n if (card.texture && card.texture.generateMipmaps !== tilted) {\n var THREE2 = ctx.THREE\n card.texture.generateMipmaps = tilted\n card.texture.minFilter = tilted && THREE2 ? THREE2.LinearMipmapLinearFilter : (THREE2 ? THREE2.LinearFilter : card.texture.minFilter)\n if (tilted && ctx.renderer && ctx.renderer.capabilities && card.texture.anisotropy !== undefined) {\n card.texture.anisotropy = ctx.renderer.capabilities.getMaxAnisotropy ? ctx.renderer.capabilities.getMaxAnisotropy() : 1\n }\n card.texture.needsUpdate = true\n }\n }\n\n // The ghost plane: posed as the card stood at the boundary (its lean, its\n // pose), then moved by its exit; hidden while no boundary is crossing.\n if (r.ghost && r.ghost.mesh) {\n var gm = r.ghost.mesh\n if (trOut && gh === r.ghost) {\n var gtT = trA.t - 0.001\n var grx = 0, gry = 0\n var gtk = d.tiltTrack\n if (gtk && gtk.keyframes && gtk.keyframes.length && !d.tiltSuppressed) { var gtv = TL.sample(gtk, gtT, TL.lerpArray); grx = gtv[0] * Math.PI / 180; gry = gtv[1] * Math.PI / 180 }\n gm.rotation.x = grx; gm.rotation.y = gry\n var gpv = [1, 0, 1]\n var gpk = d.cardPoseTrack\n if (gpk && gpk.keyframes && gpk.keyframes.length) gpv = TL.sample(gpk, gtT, TL.lerpArray)\n var gpH = 2 * ${Math.abs(CARD_Z)} * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var gS = gpv[0] * trGsc, gO = gpv[2] * trGop\n gm.scale.x = gS; gm.scale.y = gS\n gm.position.x = trGox * gpH * (W / H)\n gm.position.y = (gpv[1] + trGoy) * gpH\n if (gm.material) gm.material.opacity = gO\n gm.visible = gO > 0.001\n } else if (gm.visible !== false) gm.visible = false\n }\n\n // The card layer redraws every frame (dynamic content); bg/overlay uploads are\n // gated inside their blocks (dirty-tracking above).\n if (card.texture) card.texture.needsUpdate = true\n\n // verification hook (no-op unless the harness sets window.__VOILA_DEBUG__).\n // cv = the CARD 2D canvas; bgCv/ovCv are the background/overlay layers (v2).\n if (typeof window !== 'undefined' && window.__VOILA_DEBUG__) {\n window.__voilaDebug = { cv: cv, bgCv: bg.canvas, ovCv: ov.canvas, W: W, H: H, glW: gl && gl.width, glH: gl && gl.height, t: t, canvases: document.querySelectorAll('canvas').length }\n }\n}`\n\n/**\n * The shared layers as the studio entry's data: overlay clips (presets resolved\n * to plain values HERE, ON_FRAME reads no registry), 3D props (numbers resolved\n * HERE), the extra font faces SETUP awaits. Every key is omitted when its layer\n * is absent — data byte parity for docs that never touched it. Both anchors\n * call this with their own output duration.\n */\nexport function studioLayerData(\n layers: {\n overlays?: OverlayClip[]\n objects?: ObjectClip[]\n audio?: AudioClip[]\n },\n duration: number,\n /**\n * Pinned layers' placements by clip id (resolvePins): a pinned clip's\n * track is its place beside its referent through the camera, in the\n * same clip-local shape a `motion` pose bakes to. Absent for a program\n * document, which has no referents.\n */\n pins?: ReadonlyMap<string, PinPlacement>,\n): Record<string, unknown> {\n // A media layer resolves against the RECORDING it rides (a program\n // document has no media to show).\n const rec = 'source' in layers ? (layers as unknown as ProjectDoc) : null\n return {\n // Music/SFX clips with their gain envelopes baked (shared truth for the\n // preview scheduler and the export's offline mix). `duckEnv` (the mic-derived\n // duck multiplier curve) is merged in asynchronously by useComposition — it\n // needs a decoded recording, which a sync lowering can't produce.\n audio: (layers.audio ?? []).map((c) => ({\n key: c.key,\n start: round(c.start),\n in: round(c.in),\n out: round(c.out),\n gain: round(c.gain),\n loop: !!c.loop,\n len: round(clipLength(c)),\n duck: !!c.duck,\n env: clipEnvelope(c).map((p) => ({ t: round(p.t), g: round(p.g) })),\n })),\n // Object clips: numbers resolved HERE; shapes are the drafted engine spec.\n ...(layers.objects && layers.objects.length\n ? {\n objects: layers.objects.map((o) => ({\n id: o.id,\n asset:\n o.asset.kind === 'primitive'\n ? {\n kind: 'primitive',\n shape: o.asset.shape,\n color: o.asset.color ?? '#e4e4e7',\n }\n : o.asset.kind === 'text3d'\n ? resolveText3dAsset(o.asset)\n : { kind: 'gltf', key: o.asset.key },\n ...(o.span\n ? {\n span: {\n start: round(o.span.start),\n duration: round(o.span.duration),\n },\n }\n : {}),\n x: round(o.transform3d.x),\n y: round(o.transform3d.y),\n z: round(o.transform3d.z),\n rx: round(o.transform3d.rx),\n ry: round(o.transform3d.ry),\n rz: round(o.transform3d.rz),\n scale: round(o.transform3d.scale || OBJECT_DEFAULT_SCALE),\n anim: idleKey(o),\n // Pose keyframes: clip-local [x,y,z,rx,ry,rz,scale] track,\n // sampled at t − span.start. Omitted when absent — parity.\n ...(() => {\n if (!o.motion || !o.motion.length) return {}\n const mb = objectMotionBase(o)\n const track = motionTrack(\n mb,\n objectMotionKeys(o, mb),\n o.span?.duration ?? duration,\n )\n return track.keyframes.length ? { track } : {}\n })(),\n })),\n }\n : {}),\n // Text overlays: presets resolved to plain values HERE (ON_FRAME reads\n // no registry). Omitted when absent/empty — byte parity for docs without them.\n // Full face list for SETUP's cold-load await (export parity). Baked only\n // when overrides add faces beyond the base three — old-doc data parity;\n // SETUP falls back to the base literal.\n ...(layers.overlays &&\n overlayFontFaces(layers).length > OVERLAY_FONT_FACES.length\n ? { overlayFonts: overlayFontFaces(layers) }\n : {}),\n ...(layers.overlays && layers.overlays.length\n ? {\n overlays: layers.overlays.map((o) => {\n const base = {\n id: o.id,\n kind: o.kind,\n start: round(o.start),\n dur: round(Math.max(OVERLAY_MIN_DURATION, o.duration)),\n x: round(o.transform.x),\n y: round(o.transform.y),\n scale: round(o.transform.scale || 1),\n rot: round(o.transform.rotation || 0),\n enter: enterKey(o),\n exit: exitKey(o),\n // Pose keyframes: a CLIP-LOCAL [x, y, scale, rot, opacity]\n // track, sampled in ON_FRAME at t − start. Omitted when the clip\n // has no motion — data byte parity.\n ...(() => {\n const pinned = pins?.get(o.id)\n if (pinned) return { track: pinned.track }\n if (!o.motion || !o.motion.length) return {}\n const mb = overlayMotionBase(o)\n const track = motionTrack(\n mb,\n overlayMotionKeys(o, mb),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n return track.keyframes.length ? { track } : {}\n })(),\n }\n if (o.kind === 'html') {\n // An HTML layer lowers to an IMAGE clip carrying its SOURCE:\n // setup builds the picture into the cache under a\n // content-addressed key and every paint path downstream is the\n // one a picture already takes. The key is a cache handle, never\n // a URL; nothing must ever fetch it. The compositor adds no\n // chrome (the painter casts a clip's shadow from its BOX, and a\n // layer is mostly transparent bleed, so a 'soft' default came\n // out as a black rectangle): the CSS is the chrome.\n const hl = htmlLayerPayload(o)\n return {\n ...base,\n kind: 'image',\n key: hl.key,\n w: round(hl.width),\n radius: 0,\n shadow: 'none',\n opacity: round(o.opacity ?? 1),\n html: hl.html,\n }\n }\n if (o.kind !== 'text') {\n // Media overlay: sized by frame-width fraction; corners in design\n // px; video time is clip-local (ON_FRAME seeks el to t − start).\n // A `media:<id>` key shows a DOCUMENT media: the reference\n // resolves to the media's own key here, the media's kind wins,\n // and its facts (the cursor track in its space, the clicks,\n // the recorded page) ride the clip for the card painter.\n const lm = rec ? layerMedia(rec, o.key) : null\n const lmImage = lm\n ? lm.sourceKind === 'image'\n : o.kind === 'image'\n const lmCard = o.frame\n ? {\n bar: {\n kind: o.frame.browserBar?.kind ?? 'none',\n url:\n o.frame.browserBar?.url ??\n (lm ? pageDisplayUrl(lm.meta.pageUrl) : ''),\n showUrl: o.frame.browserBar?.showUrl ?? true,\n showControls: o.frame.browserBar?.showControls ?? true,\n height: o.frame.browserBar?.height ?? 44,\n ...(o.frame.browserBar?.theme\n ? { theme: o.frame.browserBar.theme }\n : {}),\n },\n lean: [\n round(o.frame.lean?.rx ?? 0),\n round(o.frame.lean?.ry ?? 0),\n ],\n shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,\n shadowContact: o.frame.shadowContact ?? 0,\n ...(o.frame.shadowColor\n ? { shadowColor: o.frame.shadowColor }\n : {}),\n }\n : null\n const lmFacts =\n lm && o.frame && o.frame.cursor !== false && lm.cursor.length\n ? (() => {\n const lfx = rec!.cursor.clickFx\n const sm = smoothCursor(lm.cursor, {\n factor: rec!.cursor.smoothing,\n clickSnap: lfx.style !== 'none' || lfx.press,\n })\n const clicks: { t: number; x: number; y: number }[] = []\n for (const ev of lm.cursor) {\n if (ev.type === 'down')\n clicks.push({\n t: round(ev.t / 1000),\n x: round(ev.x),\n y: round(ev.y),\n })\n }\n return {\n cur: {\n pts: sm.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n space: { w: lm.meta.width, h: lm.meta.height },\n size: rec!.cursor.size,\n clicks,\n },\n }\n })()\n : {}\n return {\n ...base,\n ...(lm ? { kind: lmImage ? 'image' : 'video' } : {}),\n key: lm ? lm.videoKey : o.key,\n ...(lm && lm.crop ? { crop: lm.crop } : {}),\n ...(lmCard ? { card: lmCard } : {}),\n ...lmFacts,\n w: round(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),\n radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,\n opacity: o.opacity ?? 1,\n loop: !!o.loop,\n // Emitted only when SET, so a doc without the\n // fields lowers byte-identically (ON_FRAME defaults absent\n // shadow to 'soft' — the baked look docs predating the field render).\n ...(o.shadow ? { shadow: o.shadow } : {}),\n ...(o.border && o.border.width > 0\n ? {\n border: {\n width: round(o.border.width),\n color: o.border.color,\n },\n }\n : {}),\n }\n }\n const st = resolveOverlayStyle(o)\n const bx = resolveOverlayBox(o)\n return {\n ...base,\n text: o.text,\n lines: overlayLines(o.text),\n fs: st.size,\n weight: st.weight,\n stack: st.stack,\n color: st.color,\n shadow: st.shadow,\n // Style-v2 fields bake only when non-default (byte parity for\n // older docs); ON_FRAME reads them unconditionally.\n ...(st.fontStyle === 'italic' ? { sty: 'italic' } : {}),\n ...(o.maxWidth ? { mw: round(o.maxWidth) } : {}),\n ...(st.letterSpacing ? { ls: round(st.letterSpacing) } : {}),\n ...(st.lineHeight !== OVERLAY_LINE_HEIGHT\n ? { lh: round(st.lineHeight) }\n : {}),\n ...(st.align !== 'center' ? { align: st.align } : {}),\n ...(st.stroke\n ? { stroke: { c: st.stroke.color, w: round(st.stroke.width) } }\n : {}),\n // Hosted face behind an override: ON_FRAME lazy-loads it so a\n // live family/weight edit paints without a LOAD (SETUP only\n // runs on cold load).\n ...(() => {\n const face = overlayFaceFor(o)\n return face\n ? { face: { f: face.family, w: face.weight, u: face.url } }\n : {}\n })(),\n // Background pill, resolved to design px at fs (absent = none;\n // conditional spread keeps box-less docs' data byte-identical).\n ...(bx\n ? {\n box: {\n c: bx.color,\n o: round(bx.opacity),\n px: round(bx.padX),\n py: round(bx.padY),\n r: round(bx.radius),\n },\n }\n : {}),\n // Entrance animation: segmentation + timing normalized\n // HERE (deterministic doc-derived data) — ON_FRAME interprets\n // per-unit progress as pure f(t). Absent = data byte parity.\n ...(() => {\n const olFx = resolveOverlayFx(\n o,\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n return olFx ? { fx: olFx } : {}\n })(),\n }\n }),\n }\n : {}),\n }\n}\n\nexport function lowerToComposition(input: ProjectDoc): LoweredComposition {\n // One vocabulary first: a legacy entrance, end card or clip spelling is\n // read into `anim` (the end card into clips after the footage plus a\n // card exit), so every track below is laid out against one shape.\n const doc = migrateMotion(input)\n const rated = ratedSegments(doc)\n // The boundaries that move (a clip's enter or exit, the card's slide at\n // the open), in output seconds; the camera rests through each window.\n const transitions = docTransitions(doc)\n // The footage's end, holds included; the output lasts until the last\n // visual clip ends, and past its footage the card holds its last frame.\n const footageEnd = durationSec(doc, rated)\n const duration = outputEnd(doc, footageEnd)\n // clickSnap only when effects are on, so an effects-off doc's path (and its\n // lowered data) stays byte-identical to the pre-click-effects lowering.\n const fx = doc.cursor.clickFx\n const smoothed = smoothCursor(doc.source.cursor, {\n factor: doc.cursor.smoothing,\n clickSnap: fx.style !== 'none' || fx.press,\n })\n // Idle fade (SOURCE-anchored, so trims/cuts/speed inherit it). Skipped when\n // the dot is hidden outright, and empty when nothing dwells long enough —\n // either way no `cursorFade` key is emitted and the data stays as it was.\n const cursorFade =\n doc.cursor.hideWhenIdle !== false && doc.cursor.visible !== false\n ? cursorIdleFade(doc.source.cursor, {\n space: { w: doc.source.meta.width, h: doc.source.meta.height },\n sourceDuration: (doc.source.meta.durationMs || 0) / 1000,\n })\n : []\n // Clamp each span's focus so the zoomed card always covers the canvas —\n // focusBounds is the same function the aiming overlay/inspector uses, so what\n // the editor shows and what renders can't disagree. Auto-focus spans get\n // their entry focus + dead-zone recenters baked from the cursor track\n // (followFocusEvents clamps internally).\n const layout = docCardLayout(doc)\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n // The frame's camera model: the stage camera never clamps (the window may\n // show the ground past the card), the magnifier always does.\n const camera = cameraModel(doc.frame)\n const zoomSpans = loweredZoomSpans(\n doc,\n rated,\n transitions,\n layout,\n camera,\n zoomStyle,\n )\n // The OUTPUT-time zoom track; a pull-out entrance writes its head.\n const zoomTrack = prependEntrance(\n zoomTrackFromDoc(zoomSpans, rated, zoomStyle),\n entranceZoomKeyframes(cardEnter(doc.frame)),\n )\n // Pinned layers: each resolved beside its referent through this camera.\n const pins = resolvePins(doc, layout, camera, zoomTrack)\n const pinsData = pinsDataOf(doc, pins)\n\n const data = {\n videoSrc: doc.source.videoKey,\n isImage: doc.source.sourceKind === 'image',\n // Viewport crop for window takes (drawImage source rect; null = full frame).\n crop: doc.source.crop ?? null,\n camSrc: doc.source.camKey ?? null,\n cam: doc.cam,\n // Cam pose spans: OUTPUT-time [x, y, size] fraction track built at\n // the doc's design layout (fractions are aspect-stable, so the track holds\n // at any render size). Omitted when the doc has no spans or no cam track —\n // byte parity with pre-MO docs.\n ...(doc.camMotion && doc.camMotion.length && doc.source.camKey\n ? {\n camTrack: camTrackFromDoc(doc.cam, doc.camMotion, rated, layout.W),\n }\n : {}),\n // Mic sidecar (AT split) — conditional spread keeps legacy docs' data\n // byte-identical; ON_FRAME reads both keys guarded.\n ...(doc.source.micKey\n ? { micSrc: doc.source.micKey, sysGain: doc.systemGain ?? 1 }\n : {}),\n hasAudio: !!doc.source.meta.hasAudio,\n duration,\n // Rated segments (speed spans pre-intersected): ON_FRAME's mapTime and the\n // export's audio splice read the rate straight off each segment.\n segments: rated.map((seg) => ({\n in: round(seg.in),\n out: round(seg.out),\n ...(seg.rate !== undefined && seg.rate !== 1\n ? { rate: round(seg.rate) }\n : {}),\n ...((seg as { media?: string }).media\n ? { media: (seg as { media?: string }).media }\n : {}),\n })),\n frame: doc.frame,\n micGain: doc.micGain ?? 1,\n cursor: smoothed.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n cursorStyle: doc.cursor,\n cursorSpace: { w: doc.source.meta.width, h: doc.source.meta.height },\n // The other media (concat): each with its element's source, its crop\n // and its own cursor in its own capture space; absent = one media.\n ...(doc.media && doc.media.length\n ? {\n media: doc.media.map((m) => {\n const sm = smoothCursor(m.cursor, {\n factor: doc.cursor.smoothing,\n clickSnap: fx.style !== 'none' || fx.press,\n })\n const fade =\n doc.cursor.hideWhenIdle !== false && doc.cursor.visible !== false\n ? cursorIdleFade(m.cursor, {\n space: { w: m.meta.width, h: m.meta.height },\n sourceDuration: (m.meta.durationMs || 0) / 1000,\n })\n : []\n return {\n id: m.id,\n src: m.videoKey,\n isImage: m.sourceKind === 'image',\n crop: m.crop ?? null,\n hasAudio: !!m.meta.hasAudio,\n cursor: sm.map((p) => ({\n t: round(p.t),\n x: round(p.x),\n y: round(p.y),\n })),\n cursorSpace: { w: m.meta.width, h: m.meta.height },\n // The card this media wears (its own placement, bar, corner,\n // shadow, border, fit and focus over the take's), resolved\n // here so ON_FRAME swaps one object and reads no document.\n frame: cardFields(mediaFrame(doc, m.id)),\n ...(fade.length ? { cursorFade: fade } : {}),\n }\n }),\n }\n : {}),\n ...(cursorFade.length ? { cursorFade } : {}),\n // The boundaries that move: ON_FRAME reads them by output time and\n // needs no document. Absent when none moves.\n ...(transitions.length ? { transitions } : {}),\n // Click effects: OUTPUT-anchored click records + resolved styling (named\n // intensities/colors become numbers HERE — ON_FRAME reads no registry).\n ...clickFxData(doc, rated),\n // Rated segments so zoom spans land at their speed-adjusted output times.\n zoomTrack,\n // Pinned layers' marks and leaders: the referent (normalised video\n // fractions) plus the leader's tip track, painted by ON_FRAME beside the\n // camera. Absent when no pinned layer asks for either — byte parity.\n ...(pinsData.length ? { pins: pinsData } : {}),\n // Tilt spans: OUTPUT-time [rx, ry] degree track. The rest pose is\n // FLAT — there is no static card tilt any more — and the motion constants\n // come from the camera style's tilt personality ('drift' slows its\n // leans, 'keynote' matches the zoom ramps). Omitted when the doc has no\n // spans and no tilt-in entrance — byte parity. The entrance is a\n // producer of this track, never a second pose.\n ...(() => {\n const head = entranceTiltKeyframes(cardEnter(doc.frame))\n const tilts = doc.tilt\n ? restSpansThroughTransitions(\n doc.tilt,\n rated,\n transitions,\n TILT_SPAN_MIN,\n )\n : []\n const spans = tilts.length\n ? tiltTrackFromDoc(tilts, rated, zoomStyle.tilt)\n : undefined\n const track = prependEntrance(spans, head)\n return track ? { tiltTrack: track } : {}\n })(),\n // The card's pose through its enter and its exit: [scale, dy, opacity]\n // in output seconds. Absent when the card carries neither.\n ...(() => {\n const track = cardPoseTrack(\n cardEnter(doc.frame),\n cardExit(doc.frame),\n footageEnd,\n duration,\n )\n return track ? { cardPoseTrack: track } : {}\n })(),\n }\n\n // The studio stack entry's OWN ctx.data (E0): the shared layers. The\n // recording anchor lights its props itself (`lights`); a program anchor's\n // entry carries no lights, its scene has its own.\n const entryData: Record<string, unknown> = {\n lights: true,\n ...studioLayerData(doc, duration, pins),\n }\n\n const config: Record<string, unknown> = {\n version: 2,\n // Placeholder — the carrier timeline reads ctx.data.duration, so the program\n // string stays constant across trims (see the interpreter-pattern note above).\n duration: PROGRAM_DURATION,\n // Compositor v2: a perspective camera so the world-space card plane can\n // TILT with real foreshortening. Every layer plane is sized to fill this\n // frustum (stage.ts), so tilt = 0 projects pixel-identically to the pre-v2\n // ortho 'fullscreen' quad. Camera sits at the origin looking down −z.\n camera: {\n preset: 'perspective',\n fov: CARD_FOV,\n near: CAMERA_NEAR,\n far: CAMERA_FAR,\n },\n data,\n setup: SETUP,\n createContent: CREATE_CONTENT,\n createTimeline: CREATE_TIMELINE,\n onFrame: ON_FRAME,\n stack: [studioEntry(entryData)],\n }\n\n return { config, data, stack: { [STUDIO_ENTRY_ID]: entryData }, duration }\n}\n\n/**\n * The zoom spans as the camera reads them: rested through the transition\n * windows, focus clamped under the frame's camera model, an auto-focus\n * span's entry and dead-zone recenters baked from the cursor track.\n */\nfunction loweredZoomSpans(\n doc: ProjectDoc,\n rated: Segment[],\n transitions: ReturnType<typeof docTransitions>,\n layout: CardLayout,\n camera: CameraModel,\n zoomStyle: ZoomStyleParams,\n): LoweredZoomSpan[] {\n const meta = doc.source.meta\n return restSpansThroughTransitions(\n doc.zoom,\n rated,\n transitions,\n ZOOM_SPAN_MIN,\n ).map((z) => {\n if (z.focusMode === 'auto') {\n const f = followFocusEvents(\n z,\n doc.source.cursor,\n { w: meta.width, h: meta.height },\n layout,\n {\n safeRatio: zoomStyle.followSafeRatio,\n recenter: zoomStyle.followRecenter,\n lookahead: zoomStyle.followLookahead,\n camera,\n },\n )\n if (f.entry)\n return { ...z, cx: f.entry.cx, cy: f.entry.cy, followEvents: f.events }\n }\n return {\n ...z,\n ...clampFocus(z.cx, z.cy, clampZoomLevel(z.level), layout, camera),\n }\n })\n}\n\n/**\n * The document's OUTPUT-time zoom track — the camera as ON_FRAME samples\n * it, for a host that needs the camera without lowering everything (a\n * pinned layer's on-canvas rect).\n */\nexport function docZoomTrack(\n input: ProjectDoc,\n): KeyframeTrack<number[]> | undefined {\n const doc = migrateMotion(input)\n const rated = ratedSegments(doc)\n const transitions = docTransitions(doc)\n const layout = docCardLayout(doc)\n const camera = cameraModel(doc.frame)\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const spans = loweredZoomSpans(\n doc,\n rated,\n transitions,\n layout,\n camera,\n zoomStyle,\n )\n return prependEntrance(\n zoomTrackFromDoc(spans, rated, zoomStyle),\n entranceZoomKeyframes(cardEnter(doc.frame)),\n )\n}\n\n/**\n * Every pinned layer's placement, by clip id. A pin that names nothing\n * (an unknown step, a step without a rect or a press) resolves to nothing\n * and the layer keeps its `transform` — the lint says why.\n */\nexport function resolvePins(\n doc: ProjectDoc,\n layout: CardLayout,\n camera: CameraModel,\n zoomTrack: KeyframeTrack<number[]> | undefined,\n): Map<string, PinPlacement> {\n const out = new Map<string, PinPlacement>()\n const rated = ratedSegments(doc)\n const space = { w: doc.source.meta.width, h: doc.source.meta.height }\n for (const o of doc.overlays ?? []) {\n if (!o.pin) continue\n const ref = pinReferent(doc, o.pin)\n if (!ref) continue\n const base = overlayMotionBase(o)\n const motion =\n o.motion && o.motion.length\n ? motionTrack(\n base,\n overlayMotionKeys(o, base),\n Math.max(OVERLAY_MIN_DURATION, o.duration),\n )\n : null\n out.set(\n o.id,\n pinPlacement({\n clip: o,\n referent: ref.rect,\n layout,\n camera,\n zoomTrack,\n base,\n motion,\n carry: ref.carry ?? null,\n cursor: doc.source.cursor,\n space,\n rated,\n }),\n )\n }\n return out\n}\n\n/**\n * The steps an unpinned layer could name: every step with an element (a\n * rect, or presses inside its window) whose output window overlaps the\n * layer's, most overlap first. The studio's Pin-to select and the lint's\n * advice read this.\n */\nexport function pinCandidates(\n doc: ProjectDoc,\n clip: { start: number; duration: number },\n): PinCandidate[] {\n const rated = ratedSegments(doc)\n const out: PinCandidate[] = []\n const a0 = clip.start\n const a1 = clip.start + clip.duration\n for (const s of doc.source.meta.steps ?? []) {\n if (s.skipped) continue\n const ref = pinReferent(doc, { step: s.id ?? s.step })\n if (!ref) continue\n const ext = spanOutputExtent(rated, s.tStart, s.tEnd)\n if (!ext) continue\n const overlap = Math.min(a1, ext.end) - Math.max(a0, ext.start)\n if (overlap <= 0) continue\n out.push({\n step: s.id ?? s.step,\n do: s.do,\n ...(s.selector ? { selector: s.selector } : {}),\n output: { start: round(ext.start), end: round(ext.end) },\n overlap: round(overlap),\n })\n }\n return out.sort((x, y) => y.overlap - x.overlap)\n}\n\n/**\n * A pinned layer's effective [x, y, scale, rotation, opacity] at CLIP-LOCAL\n * time t — the host-side mirror the picking layer substitutes into the\n * clip's transform, the way overlayMotionPoseAt does for a `motion` clip.\n * Null when the clip is not pinned or its pin resolves to nothing.\n */\nexport function overlayPinPoseAt(\n doc: ProjectDoc,\n o: OverlayClip,\n t: number,\n): number[] | null {\n if (!o.pin) return null\n const layout = docCardLayout(doc)\n const camera = cameraModel(doc.frame)\n const pins = resolvePins(\n { ...doc, overlays: [o] },\n layout,\n camera,\n docZoomTrack(doc),\n )\n const p = pins.get(o.id)\n if (!p) return null\n return [...sample(p.track, t, lerpArray)]\n}\n\n/** The `pins` slice of ctx.data: one record per pinned layer that asks for a mark or a leader. */\nfunction pinsDataOf(\n doc: ProjectDoc,\n pins: ReadonlyMap<string, PinPlacement>,\n): Record<string, unknown>[] {\n const out: Record<string, unknown>[] = []\n for (const o of doc.overlays ?? []) {\n const p = o.pin\n const placed = p ? pins.get(o.id) : undefined\n if (!p || !placed) continue\n const mark = p.mark ?? 'none'\n if (mark === 'none' && !p.leader) continue\n const ref = pinReferent(doc, p)\n if (!ref) continue\n out.push({\n id: o.id,\n start: round(o.start),\n dur: round(Math.max(OVERLAY_MIN_DURATION, o.duration)),\n x: round4(ref.rect.x),\n y: round4(ref.rect.y),\n w: round4(ref.rect.w),\n h: round4(ref.rect.h),\n side: placed.side,\n mark,\n leader: !!p.leader,\n ...(p.color ? { color: p.color } : {}),\n // A carried referent (a drag): ON_FRAME moves the mark and the\n // leader's end with the pointer through [t0, t1], source seconds.\n ...(ref.carry\n ? {\n carry: {\n t0: round(ref.carry.t0),\n t1: round(ref.carry.t1),\n x0: round4(ref.carry.x0),\n y0: round4(ref.carry.y0),\n },\n }\n : {}),\n enter: enterKey(o),\n tip: placed.tip,\n })\n }\n return out\n}\n\nfunction round4(v: number): number {\n return Math.round(v * 100000) / 100000\n}\n\n/**\n * Click-effect slice of ctx.data: extracted OUTPUT-anchored clicks + the\n * doc's named style resolved to numbers (k/dur multipliers, [r,g,b] color).\n * With effects fully off the click list is skipped so the doc lowers as light\n * as before the feature.\n */\nfunction clickFxData(doc: ProjectDoc, rated: Segment[]) {\n const fx = doc.cursor.clickFx\n const on = fx.style !== 'none' || fx.press\n const meta = doc.source.meta\n const level = CLICK_FX_INTENSITY[fx.intensity]\n return {\n clicks: on\n ? [\n ...extractClicks(doc.source.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: meta.width, h: meta.height },\n }),\n // The other media's clicks map through their own pieces and carry\n // their own capture space, so the ring lands where the press was.\n ...(doc.media ?? []).flatMap((m) =>\n extractClicks(m.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: m.meta.width, h: m.meta.height },\n media: m.id,\n }).map((ck) => ({\n ...ck,\n sp: { w: m.meta.width, h: m.meta.height },\n })),\n ),\n ].sort((a, b) => a.ot - b.ot)\n : [],\n clickFx: {\n style: fx.style,\n press: fx.press,\n k: level.k,\n dur: level.dur,\n col: fx.color === 'auto' ? 0 : (hexToRgbTriplet(fx.color) ?? 0),\n },\n }\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Host-side mirror of ON_FRAME's card layout + the focus-space zoom clamp.\n *\n * ON_FRAME (lowerToComposition) is generated code — it computes the card rect\n * (padding, browser-bar strip, contain-fit) inline each frame. These pure\n * helpers duplicate that math for host consumers: focus clamping at lowering\n * time, and the preview focus-region overlay + the inspector's X/Y %\n * mapping. `layout.test.ts` pins the two implementations together — change\n * them TOGETHER or the overlay drifts from the rendered zoom.\n *\n * Clamping lives in normalized focus space (the OpenScreen trick): one bounds\n * function serves the camera, the overlay rect, and the % inputs, so \"0%\"\n * always means \"camera flush against the card edge\" at any zoom level.\n */\nimport {\n EXPORT_RESOLUTION_OPTIONS,\n aspectRatioValue,\n clampZoomLevel,\n resolveExportSize,\n} from './types'\nimport type {\n CamStyle,\n ExportResolution,\n FrameStyle,\n ProjectDoc,\n} from './types'\nimport { mediaFrame, mediaSource } from './media'\n\nexport interface CardLayout {\n /** canvas size the layout was computed for (comp px). */\n W: number\n H: number\n /** video destination rect, comp px. Contain: the fitted video. Cover\n * the cover-scaled video, positioned by frame.focus — it can\n * overflow the card, which crops it. */\n dx: number\n dy: number\n dw: number\n dh: number\n /** card rect = browser-bar strip + footage area — what the zoom must keep\n * covering. Contain: cardX/cardW equal dx/dw. Cover: the padded area\n * itself, which the video rect overflows. */\n cardX: number\n cardY: number\n cardW: number\n cardH: number\n}\n\n/**\n * Mirror of ON_FRAME's destination-rect math (see the \"video destination rect\"\n * block in lowerToComposition's ON_FRAME string). `video` is the source's\n * pixel size — only its aspect matters (contain-fit scales it).\n */\nexport function computeCardLayout(\n frame: FrameStyle,\n video: { width: number; height: number },\n W: number,\n H: number,\n /**\n * The zoom track's current focus, for a frame whose cover crop FOLLOWS\n * the camera (`focusFollow: 'camera'`); absent = the frame's own focus.\n */\n zoomFocus?: { cx: number; cy: number },\n): CardLayout {\n const s = H / 1080 // scale design-px controls to comp px (same rule as ON_FRAME)\n const pad = (frame.padding || 0) * s\n // Per-side placement (MIRRORS ON_FRAME): frame.inset fractions override\n // the symmetric padding on the sides they name; a negative side bleeds.\n const ins = frame.inset ?? {}\n const padL = ins.left == null ? pad : ins.left * W\n const padR = ins.right == null ? pad : ins.right * W\n const padT = ins.top == null ? pad : ins.top * H\n const padB = ins.bottom == null ? pad : ins.bottom * H\n const bar = frame.browserBar\n const vw = video.width || 16\n const vh = video.height || 9\n // Card-chrome scale (MIRRORS ON_FRAME — change together): card-owned sizes\n // (the browser bar here) shrink with the card when the frame is narrower\n // than the footage; exactly 1 at native/wider aspects, so native layouts are\n // untouched. Under cover the card is as wide as the frame allows, so cf = 1.\n const fitCover = frame.fit === 'cover'\n const cf = fitCover ? 1 : Math.min(1, W / H / (vw / vh))\n const barH = bar.kind !== 'none' ? (bar.height || 44) * s * cf : 0\n const availW = Math.max(1, W - padL - padR)\n const availH = Math.max(1, H - padT - padB - barH)\n if (fitCover) {\n // Cover (MIRRORS ON_FRAME): the padded area is the card; the video\n // cover-fills it, positioned by frame.focus and clamped gap-free.\n const sc = Math.max(availW / vw, availH / vh)\n const dw = vw * sc\n const dh = vh * sc\n const follow =\n frame.focusFollow === 'camera' && zoomFocus ? zoomFocus : null\n const fcx = clamp01(follow ? follow.cx : (frame.focus?.cx ?? 0.5))\n const fcy = clamp01(follow ? follow.cy : (frame.focus?.cy ?? 0.5))\n const vTop = padT + barH\n const dx = Math.min(\n padL,\n Math.max(padL + availW - dw, padL + availW / 2 - fcx * dw),\n )\n const dy = Math.min(\n vTop,\n Math.max(vTop + availH - dh, vTop + availH / 2 - fcy * dh),\n )\n return {\n W,\n H,\n dx,\n dy,\n dw,\n dh,\n cardX: padL,\n cardY: padT,\n cardW: availW,\n cardH: availH + barH,\n }\n }\n const sc = Math.min(availW / vw, availH / vh)\n const dw = vw * sc\n const dh = vh * sc\n // Centred inside the inset area: (W - dw) / 2 when no side is inset.\n const dx = padL + (availW - dw) / 2\n const dy = padT + barH + (availH - dh) / 2\n return {\n W,\n H,\n dx,\n dy,\n dw,\n dh,\n cardX: dx,\n cardY: dy - barH,\n cardW: dw,\n cardH: dh + barH,\n }\n}\n\n/**\n * The doc's card layout at design size (H = 1080, W from the output aspect).\n * All layout terms scale linearly with the canvas at a fixed aspect, so the\n * normalized focus bounds computed from this layout hold at ANY render size.\n *\n * Viewport-cropped window takes need no special casing here: normalization\n * rewrote meta.captureWidth/Height to the CROP dims, which is exactly what\n * ON_FRAME uses as source dims when `d.crop` is set (crp.w/crp.h) — the\n * golden contract holds through the crop.\n */\nexport function docCardLayout(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'media'>,\n /** The media whose card to lay out; absent or `''` = the primary. */\n media?: string | null,\n): CardLayout {\n const meta = doc.source.meta\n const H = 1080\n const W = Math.max(\n 2,\n Math.round(H * aspectRatioValue(doc.frame.aspectRatio, meta)),\n )\n // A media wears its own card on the take's frame (mediaFrame), and its\n // own footage dimensions size it.\n const src = mediaSource(doc, media) ?? doc.source\n const sm = src.meta\n return computeCardLayout(\n mediaFrame(doc, media),\n {\n width: sm.captureWidth ?? sm.width,\n height: sm.captureHeight ?? sm.height,\n },\n W,\n H,\n )\n}\n\n/**\n * Largest export preset whose footage card still gets ≥1 captured px per output\n * px. The composited chrome (background, bar, cursor, effects) is vector-drawn\n * and real at any size; the footage layer is bounded by capture pixels, so\n * presets above this one upscale the footage (the picker labels them, never\n * hides them). Judged on the width of\n * the contain-fitted card at each preset's output size vs meta.captureWidth\n * (crop-space when a viewport crop applies — ingest rewrote meta to crop dims).\n * Floors at the smallest preset for tiny captures.\n */\nexport function recommendedExportResolution(\n doc: Pick<ProjectDoc, 'frame' | 'source' | 'export'>,\n): ExportResolution {\n const meta = doc.source.meta\n const capW = meta.captureWidth ?? meta.width\n const video = { width: capW, height: meta.captureHeight ?? meta.height }\n let best = EXPORT_RESOLUTION_OPTIONS[0]\n for (const r of EXPORT_RESOLUTION_OPTIONS) {\n const { width, height } = resolveExportSize(doc, r)\n // Card size grows linearly with output size at fixed aspect → first fail ends it.\n if (computeCardLayout(doc.frame, video, width, height).dw > capW) break\n best = r\n }\n return best\n}\n\nexport interface CamBubbleRect {\n /** top-left corner of the bubble's square, design px (docCardLayout space). */\n x: number\n y: number\n /** the square's side — the bubble diameter. */\n size: number\n /** corner radius (size/2 for circles, the fixed rounded radius otherwise). */\n radius: number\n}\n\n/**\n * Host-side mirror of ON_FRAME's webcam-bubble geometry (the \"webcam bubble\"\n * block in lowerToComposition) — the picking oracle for the on-canvas cam\n * layer. Same design space as docCardLayout (H = 1080), and like ON_FRAME the\n * bubble is FRAME-owned chrome: everything scales by s = H/1080, never the\n * card-chrome cf. The bubble ignores card tilt/zoom (it paints on the\n * screen-space overlay plane), so this rect is valid under any camera pose.\n * camDraw.test.ts pins this to the painted geometry — change them TOGETHER.\n */\nexport function camBubbleRect(\n cam: CamStyle,\n W: number,\n H = 1080,\n): CamBubbleRect {\n const s = H / 1080\n const size = Math.max(40, (cam.size || 0.25) * H)\n const mg = 24 * s\n // Free placement wins over the corner anchor — mirrors ON_FRAME.\n const x =\n cam.x != null\n ? cam.x * W - size / 2\n : cam.position.includes('right')\n ? W - mg - size\n : mg\n const y =\n cam.y != null\n ? cam.y * H - size / 2\n : cam.position.includes('top')\n ? mg\n : H - mg - size\n return {\n x,\n y,\n size,\n radius: cam.shape === 'rounded' ? (cam.radius ?? 18) * s : size / 2,\n }\n}\n\nexport interface FocusBounds {\n minX: number\n maxX: number\n minY: number\n maxY: number\n}\n\n/**\n * Focus bounds (normalized video coords) so the zoomed CARD covers the whole\n * canvas — the crop never reveals background past a card edge at full zoom.\n *\n * Derivation: ON_FRAME's transform maps content point p → f + (p − f)·L around\n * the focus anchor f (fx = dx + zx·dw), so the visible canvas [0, V] shows\n * content [f − f/L, f + (V − f)/L]. Requiring that window ⊆ the cover range\n * [o, o + c] and solving for the anchor (k = 1 − 1/L):\n *\n * f ≥ o / k and f ≤ (o + c − V/L) / k\n *\n * When the zoomed card is too small to cover the canvas (low level + padding),\n * the bounds cross — collapse to their midpoint (the least-uncovered focus;\n * 0.5 for a centered card, matching OpenScreen's margin collapse).\n */\nexport function focusBounds(\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n): FocusBounds {\n if (level <= 1.001) {\n // Identity transform — focus is irrelevant; pin to center like OpenScreen\n // (margin = min(0.5, ratio/2L) also collapses to [0.5, 0.5] at L = 1).\n return { minX: 0.5, maxX: 0.5, minY: 0.5, maxY: 0.5 }\n }\n if (camera === 'stage') {\n // The stage camera clamps only to the cover band: the card must still\n // cover the central CAMERA_COVER_MIN of the frame at the apex, so a\n // corner target sits near the centre with a strip of ground beside it\n // rather than half a frame of it (a centred corner would put the card's\n // edge at the centre).\n const x = stageAxisBounds(\n layout.dx,\n layout.dw,\n layout.cardX,\n layout.cardW,\n layout.W,\n level,\n )\n const y = stageAxisBounds(\n layout.dy,\n layout.dh,\n layout.cardY,\n layout.cardH,\n layout.H,\n level,\n )\n return { minX: x.min, maxX: x.max, minY: y.min, maxY: y.max }\n }\n const x = axisBounds(\n layout.dx,\n layout.dw,\n layout.cardX,\n layout.cardW,\n layout.W,\n level,\n )\n const y = axisBounds(\n layout.dy,\n layout.dh,\n layout.cardY,\n layout.cardH,\n layout.H,\n level,\n )\n return { minX: x.min, maxX: x.max, minY: y.min, maxY: y.max }\n}\n\n/**\n * One axis of focusBounds. The focus is normalized over the ANCHOR rect (the\n * video: cx/cy ∈ [0,1] of it) while coverage is demanded of the COVER rect\n * (the card — bar included, since bar + video zoom together).\n */\nfunction axisBounds(\n anchorOff: number,\n anchorSize: number,\n coverOff: number,\n coverSize: number,\n viewport: number,\n level: number,\n): { min: number; max: number } {\n const k = 1 - 1 / level\n let lo = (coverOff / k - anchorOff) / anchorSize\n let hi =\n ((coverOff + coverSize - viewport / level) / k - anchorOff) / anchorSize\n if (lo > hi) {\n const mid = (lo + hi) / 2\n lo = mid\n hi = mid\n }\n return { min: clamp01(lo), max: clamp01(hi) }\n}\n\n/**\n * The stage camera's axis bounds. With the focus centred, the frame's\n * central band of half-width `b = CAMERA_COVER_MIN / 2 · viewport` shows\n * content [f − b/L, f + b/L]; requiring that inside the cover range gives\n * f ∈ [o + b/L, o + c − b/L]. Bounds that cross (a level too low for the\n * band) collapse to their midpoint, like the magnifier's.\n */\nfunction stageAxisBounds(\n anchorOff: number,\n anchorSize: number,\n coverOff: number,\n coverSize: number,\n viewport: number,\n level: number,\n): { min: number; max: number } {\n const b = (CAMERA_COVER_MIN / 2) * viewport\n let lo = (coverOff + b / level - anchorOff) / anchorSize\n let hi = (coverOff + coverSize - b / level - anchorOff) / anchorSize\n if (lo > hi) {\n const mid = (lo + hi) / 2\n lo = mid\n hi = mid\n }\n return { min: clamp01(lo), max: clamp01(hi) }\n}\n\n/** Clamp a focus point into the bounds for its zoom level. */\nexport function clampFocus(\n cx: number,\n cy: number,\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n): { cx: number; cy: number } {\n const b = focusBounds(level, layout, camera)\n return {\n cx: Math.min(b.maxX, Math.max(b.minX, cx)),\n cy: Math.min(b.maxY, Math.max(b.minY, cy)),\n }\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\n\n/**\n * The zoom camera's model (FrameStyle.camera): the clamped magnifier every\n * existing document was cut with, or the stage camera a new take opens on.\n */\nexport type CameraModel = 'card' | 'stage'\n\n/** The frame's camera model; absent = the magnifier. */\nexport function cameraModel(\n frame: { camera?: string | null } | null | undefined,\n): CameraModel {\n return frame?.camera === 'stage' ? 'stage' : 'card'\n}\n\n/**\n * The stage camera's centring is a component of the zoom TRACK (0 at rest,\n * 1 at an apex, eased with the level), so the card's slide and its scale\n * are one motion. This band is the FALLBACK for a track lowered before the\n * component existed: centring as a smoothstep of the level over\n * 1 .. 1 + CAMERA_CENTRE_RAMP. Keyed to the level it front-loaded the\n * slide, since any ease crosses the band in its first frames (the twitch).\n */\nexport const CAMERA_CENTRE_RAMP = 0.3\n\n/**\n * The fraction of the frame (each axis, centred) the card must still cover\n * at a stage-camera apex. 0.8 leaves a strip of ground a tenth of the\n * frame wide beside a corner target, the composition a filmed card reads\n * as; 1 would be the magnifier's rule, 0 no clamp at all.\n */\nexport const CAMERA_COVER_MIN = 0.8\n\n/** 0..1: the stage camera's centring at `level` (MIRRORS ON_FRAME). */\nexport function cameraCentring(level: number): number {\n const u = clamp01((level - 1) / CAMERA_CENTRE_RAMP)\n return u * u * (3 - 2 * u)\n}\n\n/**\n * The zoom transform at one instant, in canvas px: content point p maps to\n * `(ox + (p.x − wcx)·level, oy + (p.y − wcy)·level)`. `fx/fy` is the focus\n * in canvas px; `wcx/wcy` is the content point that lands at the frame's\n * centre `ox/oy`. Under the magnifier `wc = f + (o − f) / L`, which is the\n * old \"scale about the focus\" transform written the long way (the focus\n * stays where it is on screen); under the stage camera that pull toward\n * the focus fades out with cameraCentring, so `wc → f` and the target sits\n * at the centre. ON_FRAME's zoom block mirrors this — change them together\n * (layout.test.ts pins the pair).\n */\nexport interface ZoomView {\n level: number\n fx: number\n fy: number\n wcx: number\n wcy: number\n ox: number\n oy: number\n}\n\nexport function zoomView(\n level: number,\n cx: number,\n cy: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n /**\n * The stage camera's centring at this instant, 0..1: the zoom track's\n * fourth component (0 at rest, 1 at an apex, eased with the level).\n * Absent = the apex (1) under the stage camera, which is what a span's\n * stored focus describes; always 0 under the magnifier.\n */\n centring?: number,\n): ZoomView {\n const L = Math.max(1, level)\n const fx = layout.dx + cx * layout.dw\n const fy = layout.dy + cy * layout.dh\n const ox = layout.W / 2\n const oy = layout.H / 2\n const t = camera === 'stage' ? clamp01(centring ?? 1) : 0\n const wcx = fx + ((ox - fx) / L) * (1 - t)\n const wcy = fy + ((oy - fy) / L) * (1 - t)\n return { level: L, fx, fy, wcx, wcy, ox, oy }\n}\n\n/**\n * The visible window as fractions of the canvas (x/y its top-left, w/h its\n * size, both 1/level): what the studio's aiming rect draws and what the\n * framing lint tests a click rect against. The window may leave the card\n * under the stage camera; it never does under the magnifier once the focus\n * is clamped.\n */\nexport function zoomViewport(\n level: number,\n cx: number,\n cy: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n centring?: number,\n): { x: number; y: number; w: number; h: number } {\n const v = zoomView(level, cx, cy, layout, camera, centring)\n const size = 1 / v.level\n return {\n x: (v.wcx - layout.W / (2 * v.level)) / layout.W,\n y: (v.wcy - layout.H / (2 * v.level)) / layout.H,\n w: size,\n h: size,\n }\n}\n\n/**\n * The inverse of zoomViewport's centre: the focus (normalized video coords)\n * that puts the visible window's centre at canvas fractions `cX/cY` at\n * `level`. The aiming rect's corner drag re-derives its focus through this\n * so the rect stays under the pointer in either camera model.\n */\nexport function focusForViewportCentre(\n cX: number,\n cY: number,\n level: number,\n layout: CardLayout,\n camera: CameraModel = 'card',\n centring?: number,\n): { cx: number; cy: number } {\n const L = Math.max(1, level)\n const t = camera === 'stage' ? clamp01(centring ?? 1) : 0\n const a = 1 - (1 - t) / L\n if (a <= 1e-6) return { cx: 0.5, cy: 0.5 }\n const fx = (cX * layout.W - ((layout.W / 2) * (1 - t)) / L) / a\n const fy = (cY * layout.H - ((layout.H / 2) * (1 - t)) / L) / a\n return { cx: (fx - layout.dx) / layout.dw, cy: (fy - layout.dy) / layout.dh }\n}\n\n/**\n * The zoom level a focus rect of this canvas-fraction size\n * means — the aiming rect's size is purely 1/level, so a corner drag IS a\n * level drag, and this is the inverse. Floored a hair above the identity so\n * a drag can never reach level ≈ 1 and dismiss the aiming rect mid-gesture;\n * clamped and quantized like every stored level (clampZoomLevel).\n */\nexport function levelForFocusFraction(frac: number): number {\n if (!(frac > 0)) return clampZoomLevel(Number.POSITIVE_INFINITY)\n return clampZoomLevel(Math.max(1.1, 1 / frac))\n}\n","/**\n * Compositor v2 — the layer stage geometry.\n *\n * The stage turns the studio's single fullscreen-ortho quad into a three-layer mesh stack\n * under ONE perspective camera:\n *\n * overlay quad screen-space cam bubble + text/image/video overlays\n * card mesh world-space the existing 2D card painting, on a plane\n * that can TILT (doc.tilt spans)\n * background quad screen-space CSS fill + vos background loop\n *\n * Every layer is a plane placed perpendicular to the camera axis and centered\n * on it, sized to exactly fill the camera frustum at its depth. Perpendicular +\n * centered + frustum-filling ⇒ it projects to the full viewport regardless of\n * depth, so the background/overlay read as flat screen-space and the CARD, at\n * `tilt = 0`, projects PIXEL-IDENTICALLY to today's ortho fullscreen quad. Only\n * the card ever rotates; the perspective camera then gives it real\n * foreshortening (an ortho camera would only skew it).\n *\n * These are pure helpers (no THREE dependency) so the host can mirror the exact\n * projection the runtime draws with — the world-unit basis that\n * on-canvas picking builds on (host picks / instance renders). `stage.test.ts`\n * pins the math; the runtime (lowerToComposition CREATE_CONTENT/ON_FRAME) must\n * use these SAME constants — change them together.\n */\n\n/**\n * Camera field of view (degrees). Deliberately gentle (telephoto-ish product\n * shot) so a card tilt reads as a premium 3D lean, not a fisheye warp. Parity\n * at tilt = 0 is INDEPENDENT of this value (every layer is sized to fill the\n * frustum), so it is a pure aesthetic dial for how dramatic tilt looks.\n */\nexport const CARD_FOV = 30\n\n/**\n * Layer depths (world units in front of a camera at the origin looking down\n * −z). Absolute values are arbitrary — only the ORDER matters (painter's order\n * is set by renderOrder, not depth) and that near/far bracket them. The card\n * sits between the background (behind) and the overlay (in front).\n */\nexport const OVERLAY_Z = -2\nexport const CARD_Z = -4\nexport const BACKGROUND_Z = -6\n\n/**\n * The card layer's overscan is quantised UP to this step, so a pose that is\n * being scrubbed grows the canvas a few times, not once per tick (a resize\n * reallocates the texture and the plane).\n */\nexport const CARD_OVERSCAN_STEP = 0.05\n\n/**\n * The most the card layer will grow on an axis. The schema's 45° tilt\n * needs about 2.7 on a 16:9 frame and 3.8 on 21:9; past that the card's\n * receding edge nears the horizon and no finite texture covers it.\n */\nexport const CARD_OVERSCAN_MAX = 4\n\nexport const CAMERA_NEAR = 0.1\nexport const CAMERA_FAR = 100\n\n/**\n * The card mesh's pose, as ON_FRAME sets it: Euler XYZ rotation in DEGREES\n * (`rx` about the horizontal axis, `ry` about the vertical), a uniform\n * `scale` about the frame's centre and a rise `dy` as a fraction of the\n * frame plane's height (the card-pose track's [scale, dy] pair).\n */\nexport interface CardPose {\n rx: number\n ry: number\n scale?: number\n dy?: number\n}\n\n/**\n * How far past each frame edge the card plane is SEEN under a pose, as a\n * multiple of the frame plane's half-size on that axis (1 = the plane's own\n * edge at rest). Each frame corner's viewing ray is intersected with the\n * posed plane and read back in the plane's local units: a receding side\n * shows more plane than the frame is wide, an approaching side less, a\n * smaller card shows more all round. The visible region is the frame's\n * image under a homography, so its bounding box is its four corners'.\n * A corner whose ray no longer meets the plane in front of the camera\n * reads as `CARD_OVERSCAN_MAX`.\n *\n * This is the TypeScript twin of ON_FRAME's card-overscan math (the\n * `okx`/`oky` block in lowerToComposition) — `stage.test.ts` pins the two\n * to a three.js raycast; change them together.\n */\nexport function cardVisibleExtent(\n pose: CardPose,\n aspect: number,\n fovDeg = CARD_FOV,\n): { left: number; right: number; top: number; bottom: number } {\n const D = Math.abs(CARD_Z)\n const hh = D * Math.tan((fovDeg * Math.PI) / 180 / 2)\n const hw = hh * aspect\n const rx = (pose.rx * Math.PI) / 180\n const ry = (pose.ry * Math.PI) / 180\n const sc = Math.max(0.05, pose.scale ?? 1)\n const py = (pose.dy ?? 0) * 2 * hh\n const cx = Math.cos(rx)\n const sx = Math.sin(rx)\n const cy = Math.cos(ry)\n const sy = Math.sin(ry)\n // R = Rx(rx) · Ry(ry), three.js's Euler 'XYZ' with rz = 0, by column.\n const c0 = [cy, sx * sy, -cx * sy]\n const c1 = [0, cx, sx]\n const n = [sy, -sx * cy, cx * cy] // the plane's normal, R · ẑ\n const nDotPos = n[1] * py - n[2] * D\n let left = 0\n let right = 0\n let top = 0\n let bottom = 0\n for (const kx of [-1, 1]) {\n for (const ky of [-1, 1]) {\n const d = [kx * hw, ky * hh, -D]\n const nd = n[0] * d[0] + n[1] * d[1] + n[2] * d[2]\n const lam = nd === 0 ? -1 : nDotPos / nd\n if (!(lam > 0)) {\n left = right = top = bottom = CARD_OVERSCAN_MAX\n continue\n }\n const q = [lam * d[0], lam * d[1] - py, lam * d[2] + D]\n const lx = (c0[0] * q[0] + c0[1] * q[1] + c0[2] * q[2]) / sc / hw\n const ly = (c1[0] * q[0] + c1[1] * q[1] + c1[2] * q[2]) / sc / hh\n left = Math.max(left, -lx)\n right = Math.max(right, lx)\n top = Math.max(top, ly)\n bottom = Math.max(bottom, -ly)\n }\n }\n const cap = (v: number) => Math.min(CARD_OVERSCAN_MAX, v)\n return {\n left: cap(left),\n right: cap(right),\n top: cap(top),\n bottom: cap(bottom),\n }\n}\n\n/** Quantise an overscan factor UP to the step, never below 1. */\nexport function quantiseOverscan(k: number): number {\n if (!(k > 1)) return 1\n // In thousandths, so a value a hair under a step never rounds across it.\n const q =\n Math.ceil(\n Math.round((k + CARD_OVERSCAN_STEP / 10) * 1000) /\n (CARD_OVERSCAN_STEP * 1000),\n ) * CARD_OVERSCAN_STEP\n return Math.min(CARD_OVERSCAN_MAX, Math.round(q * 1000) / 1000)\n}\n\n/**\n * The card layer's overscan budget for a composition: the factor its canvas\n * and plane grow by on each axis, about the frame's centre, so that at\n * every pose the two tracks can reach the frame sees painted card and\n * never the texture's edge. The budget is the extent at the tracks'\n * EXTREMES (the largest lean on each axis, the smallest scale, the\n * largest rise, every sign), which bounds every interpolated frame because\n * the extent grows with each of them. `[1, 1]` for a card that never moves,\n * which is pixel-identical to a plane that fills the frame.\n *\n * Twin of the ON_FRAME block; see `cardVisibleExtent`.\n */\nexport function cardOverscanFor(\n tilt: { keyframes: { value: number[] }[] } | null | undefined,\n pose: { keyframes: { value: number[] }[] } | null | undefined,\n aspect: number,\n fovDeg = CARD_FOV,\n): [number, number] {\n let rxM = 0\n let ryM = 0\n for (const k of tilt?.keyframes ?? []) {\n rxM = Math.max(rxM, Math.abs(k.value[0] || 0))\n ryM = Math.max(ryM, Math.abs(k.value[1] || 0))\n }\n let scMin = 1\n let dyM = 0\n for (const k of pose?.keyframes ?? []) {\n scMin = Math.min(scMin, k.value[0] ?? 1)\n dyM = Math.max(dyM, Math.abs(k.value[1] || 0))\n }\n if (rxM === 0 && ryM === 0 && scMin >= 1 && dyM === 0) return [1, 1]\n let kx = 1\n let ky = 1\n for (const sr of [-1, 1]) {\n for (const sy of [-1, 1]) {\n for (const sd of [-1, 1]) {\n const e = cardVisibleExtent(\n { rx: sr * rxM, ry: sy * ryM, scale: scMin, dy: sd * dyM },\n aspect,\n fovDeg,\n )\n kx = Math.max(kx, e.left, e.right)\n ky = Math.max(ky, e.top, e.bottom)\n }\n }\n }\n return [quantiseOverscan(kx), quantiseOverscan(ky)]\n}\n\nexport interface PlaneSize {\n width: number\n height: number\n}\n\n/**\n * The world-space size of a plane that exactly fills a perspective camera's\n * frustum at `|distance|` in front of it. Height subtends the full vertical FOV;\n * width follows the viewport aspect. This is the one sizing primitive the whole\n * stack shares — every layer plane, and the host-side projection basis for\n * picking, derive from it.\n */\nexport function planeSizeAtDepth(\n distance: number,\n fovDeg: number,\n aspect: number,\n): PlaneSize {\n const height = 2 * Math.abs(distance) * Math.tan((fovDeg * Math.PI) / 180 / 2)\n return { width: height * aspect, height }\n}\n\n/**\n * Project a point on the (untilted) card plane, given in normalized card-canvas\n * coordinates (u, v ∈ [0,1], v measured from the TOP like a canvas), to\n * normalized screen coordinates (sx, sy ∈ [0,1], sy from the top). At tilt = 0\n * this is the identity — the card fills the viewport — so it is exact for the\n * common case and the basis the tilt/camera matrices extend for\n * on-canvas picking. Kept here so host and runtime never disagree on\n * where the card is.\n */\nexport function cardPointToScreen(\n u: number,\n v: number,\n): { sx: number; sy: number } {\n return { sx: u, sy: v }\n}\n","/**\n * Text-overlay presets + the host-side geometry mirror (compositor v2).\n *\n * Presets are the HOUSE text styles (Lexend for content, JetBrains Mono for\n * labels — the design-system families) and are RESOLVED AT LOWERING into plain\n * numbers/strings in ctx.data, so ON_FRAME reads no registry (the\n * MINIMAL_BAR_THEMES rule). ON_FRAME builds its canvas font string as\n * `weight + ' ' + size·scale·s + 'px ' + stack` — `overlayFontString` mirrors\n * that exactly and `overlayRect` mirrors the drawn bounding box, giving the\n * studio's on-canvas picking the same geometry the renderer paints\n * (host picks / instance renders). `overlayText.test.ts` pins the\n * mirrors to the generated code — change them together.\n *\n * Fonts load in SETUP from assets.vos.so (the self-hosted catalog — the\n * render fleet can only fetch that origin) via the FontFace API, ONLY when the doc has overlays, capped +\n * fail-open (a CDN failure degrades to the system stack, never a dead render).\n */\nimport { overlayFxSpec } from './anim'\nimport {\n findFontFamily,\n fontFaceUrl,\n fontStack,\n nearestFontWeight,\n} from '@vosjs/shared'\nimport { htmlLayerPictureBox, htmlLayerWidth } from './htmlLayer'\nimport {\n OVERLAY_LINE_HEIGHT,\n OVERLAY_MEDIA_DEFAULT_WIDTH,\n OVERLAY_TRANSITION_DUR,\n} from './types'\nimport type {\n OverlayClip,\n ProjectDoc,\n TextFxUnit,\n TextOverlayClip,\n TextOverlayPreset,\n TextOverlayStroke,\n} from './types'\n\n/** Text-box (background pill) defaults, EMs of the resolved font size. */\nexport const OVERLAY_BOX_PAD_X = 0.6\nexport const OVERLAY_BOX_PAD_Y = 0.35\nexport const OVERLAY_BOX_RADIUS = 0.25\n\n/** Baked pill geometry: design px at the clip's resolved font size. */\nexport interface ResolvedOverlayBox {\n color: string\n opacity: number\n /** Paddings/radius in design px (em multiples × resolved size). */\n padX: number\n padY: number\n radius: number\n}\n\n/**\n * Resolve a clip's background pill (null when absent). Mirrored by\n * `overlayRect`'s inflation and ON_FRAME's pill draw — change together.\n */\nexport function resolveOverlayBox(\n clip: TextOverlayClip,\n): ResolvedOverlayBox | null {\n if (!clip.box) return null\n const size = resolveOverlayStyle(clip).size\n return {\n color: clip.box.color,\n opacity: clip.box.opacity ?? 1,\n padX: (clip.box.paddingX ?? OVERLAY_BOX_PAD_X) * size,\n padY: (clip.box.paddingY ?? OVERLAY_BOX_PAD_Y) * size,\n radius: (clip.box.radius ?? OVERLAY_BOX_RADIUS) * size,\n }\n}\n\n/** A preset's base values (the 5-field house style). */\nexport interface OverlayPresetStyle {\n /** Full CSS font-family stack (primary + fallbacks). */\n stack: string\n weight: number\n /** Font size in design px (H = 1080 space), before transform.scale. */\n size: number\n color: string\n /** Legibility shadow strength 0..1 (0 = none). */\n shadow: number\n}\n\n/** Preset base + the full override surface, resolved to concrete values. */\nexport interface ResolvedOverlayStyle extends OverlayPresetStyle {\n fontStyle: 'normal' | 'italic'\n align: 'left' | 'center' | 'right'\n /** Design px at the resolved size. */\n letterSpacing: number\n /** Multiplier (default OVERLAY_LINE_HEIGHT). */\n lineHeight: number\n stroke: TextOverlayStroke | null\n}\n\n/** The catalog family each preset's stack leads with (for weight snapping). */\nconst PRESET_FAMILY: Record<TextOverlayPreset, string> = {\n title: 'Lexend',\n caption: 'Lexend',\n label: 'JetBrains Mono',\n}\n\n/** The house text styles. Sizes in design px; colors are ink-on-footage. */\nexport const TEXT_PRESETS: Record<TextOverlayPreset, OverlayPresetStyle> = {\n title: {\n stack: 'Lexend, -apple-system, system-ui, sans-serif',\n weight: 600,\n size: 64,\n color: '#fafafa',\n shadow: 0.45,\n },\n caption: {\n stack: 'Lexend, -apple-system, system-ui, sans-serif',\n weight: 400,\n size: 32,\n color: '#f4f4f5',\n shadow: 0.4,\n },\n label: {\n stack: \"'JetBrains Mono', ui-monospace, SFMono-Regular, monospace\",\n weight: 400,\n size: 22,\n color: '#e4e4e7',\n shadow: 0.35,\n },\n}\n\n/** Size override bounds (design px) — same range the inspector slider offers. */\nexport const OVERLAY_SIZE_MIN = 12\nexport const OVERLAY_SIZE_MAX = 200\n\n/**\n * woff2 faces SETUP preloads when the doc has overlays (latin subset only —\n * overlay text is product UI copy). The three base preset faces, resolved\n * through the hosted catalog so this module carries no URL of its own.\n */\nexport const OVERLAY_FONT_FACES: {\n family: string\n weight: number\n url: string\n}[] = [\n { family: 'Lexend', weight: 400, url: fontFaceUrl('lexend', 400) },\n { family: 'Lexend', weight: 600, url: fontFaceUrl('lexend', 600) },\n {\n family: 'JetBrains Mono',\n weight: 400,\n url: fontFaceUrl('jetbrains-mono', 400),\n },\n]\n\n/** Preset + per-clip overrides → the concrete style baked into ctx.data. */\nexport function resolveOverlayStyle(\n clip: TextOverlayClip,\n): ResolvedOverlayStyle {\n // Defensive lookup: agent-authored doc.json can carry an unknown preset name.\n const presetName = clip.preset in TEXT_PRESETS ? clip.preset : 'title'\n const base = TEXT_PRESETS[presetName]\n\n // Family/weight resolve against the hosted catalog. A catalog family swaps\n // the whole stack (category-true fallbacks); an unknown family fails open —\n // used verbatim ahead of the preset stack, so a locally-installed font\n // still previews while the fleet degrades to the preset. Weights snap to\n // hosted steps: canvas cannot synthesize weights.\n let stack = base.stack\n let weight = base.weight\n const familyEntry = findFontFamily(clip.family ?? PRESET_FAMILY[presetName])\n if (clip.family) {\n const quoted = clip.family.includes(' ') ? `'${clip.family}'` : clip.family\n stack = familyEntry ? fontStack(familyEntry) : `${quoted}, ${base.stack}`\n }\n if (clip.weight !== undefined || clip.family) {\n const wanted = clip.weight ?? base.weight\n weight = familyEntry ? nearestFontWeight(familyEntry, wanted) : wanted\n }\n\n return {\n ...base,\n stack,\n weight,\n ...(clip.size !== undefined\n ? {\n size: Math.min(\n OVERLAY_SIZE_MAX,\n Math.max(OVERLAY_SIZE_MIN, clip.size),\n ),\n }\n : {}),\n ...(clip.color ? { color: clip.color } : {}),\n ...(clip.shadow !== undefined\n ? { shadow: Math.min(1, Math.max(0, clip.shadow)) }\n : {}),\n fontStyle: clip.italic ? 'italic' : 'normal',\n align: clip.align ?? 'center',\n letterSpacing: clip.letterSpacing ?? 0,\n lineHeight: clip.lineHeight ?? OVERLAY_LINE_HEIGHT,\n stroke: clip.stroke ?? null,\n }\n}\n\nexport interface OverlayFontFace {\n family: string\n weight: number\n url: string\n}\n\n/**\n * The hosted face a clip's family/weight overrides resolve to, when it is\n * NOT one of the three base preset faces (null otherwise — parity: preset\n * clips carry nothing). Baked per-overlay so ON_FRAME can lazy-load it on a\n * live style edit (SET_DATA never re-runs SETUP); SETUP awaits the full list\n * from ctx.data on cold load, which is what export parity rides on.\n */\nexport function overlayFaceFor(clip: TextOverlayClip): OverlayFontFace | null {\n const entry = findFontFamily(\n clip.family ??\n PRESET_FAMILY[clip.preset in TEXT_PRESETS ? clip.preset : 'title'],\n )\n if (!entry) return null // unknown family: nothing hosted to load\n const weight = resolveOverlayStyle(clip).weight\n const inBase = OVERLAY_FONT_FACES.some(\n (f) => f.family === entry.family && f.weight === weight,\n )\n if (inBase) return null\n return {\n family: entry.family,\n weight,\n url: fontFaceUrl(entry.slug, weight),\n }\n}\n\n/**\n * Every woff2 face a doc's overlays need (SETUP await on cold load, and the\n * host document for measurement): the three base preset faces — ALWAYS, byte\n * parity for preset-only docs — plus one face per override.\n */\nexport function overlayFontFaces(\n doc: Pick<ProjectDoc, 'overlays'>,\n): OverlayFontFace[] {\n const faces = [...OVERLAY_FONT_FACES]\n const seen = new Set(faces.map((f) => `${f.family}|${f.weight}`))\n for (const o of doc.overlays ?? []) {\n if (o.kind !== 'text') continue\n const face = overlayFaceFor(o)\n if (!face) continue\n const key = `${face.family}|${face.weight}`\n if (seen.has(key)) continue\n seen.add(key)\n faces.push(face)\n }\n return faces\n}\n\nexport function overlayLines(text: string): string[] {\n const lines = text.split('\\n')\n return lines.length ? lines : ['']\n}\n\n/**\n * Word tokens with trailing whitespace preserved — the ONE tokenization\n * wrap and fx share (`overlaySegments`' word case): wrapped lines are\n * token concatenations, so char/word unit sequences are byte-identical\n * wrapped or not.\n */\nexport function overlayTokens(line: string): string[] {\n return line.match(/\\S+\\s*/g) ?? [line]\n}\n\n/**\n * Greedy token wrap at measured widths — the HOST mirror of ON_FRAME's\n * wrap (change together; overlayText.test.ts pins them). Explicit \\n lines\n * wrap independently; a token wider than the budget gets its own line.\n * Measures include each token's trailing space (the token IS the unit),\n * which over-counts the trailing gap at wrap points by design — identical\n * on both sides of the mirror, so geometry agrees.\n */\nexport function wrapOverlayLines(\n lines: string[],\n measure: (text: string) => number,\n maxPx: number,\n): string[] {\n if (!(maxPx > 0)) return lines\n const out: string[] = []\n for (const line of lines) {\n if (!line || measure(line) <= maxPx) {\n out.push(line)\n continue\n }\n let current = ''\n for (const token of overlayTokens(line)) {\n if (!current) {\n current = token\n continue\n }\n if (measure(current + token) <= maxPx) {\n current += token\n } else {\n out.push(current)\n current = token\n }\n }\n if (current) out.push(current)\n }\n return out.length ? out : ['']\n}\n\n// ---------------------------------------------------------------------------\n// Entrance animation. Segmentation happens HERE, at lowering, because\n// it is deterministic doc-derived data: ON_FRAME stays a pure interpreter\n// over baked units and seek stays f(t) (chunk cold-seeks agree by\n// construction). Units never cross line breaks.\n// ---------------------------------------------------------------------------\n\n/** Grapheme-safe char split; plain code-point split when Segmenter is absent. */\nfunction graphemesOf(line: string): string[] {\n if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {\n return [\n ...new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(\n line,\n ),\n ].map((s) => s.segment)\n }\n return [...line]\n}\n\n/**\n * Per-line unit arrays for a fx spec. `word` units keep their trailing\n * whitespace (a typewriter reveals \"Hello \" then \"world\" with stable\n * geometry); `line` units are the lines themselves. `block` has NO per-unit\n * segmentation — ON_FRAME animates the whole clip through the normal\n * per-line draw (fillText cannot render '\\n'), which is exactly the legacy\n * enter behaviour generalized.\n */\nexport function overlaySegments(text: string, unit: TextFxUnit): string[][] {\n const lines = overlayLines(text)\n if (unit === 'block') return []\n if (unit === 'line') return lines.map((l) => [l])\n if (unit === 'word') return lines.map(overlayTokens)\n return lines.map(graphemesOf)\n}\n\n/** The baked fx payload ON_FRAME interprets (short keys — it rides ctx.data). */\nexport interface BakedOverlayFx {\n /** fx kind. */\n k: 'fade' | 'rise' | 'pop' | 'blur' | 'typewriter'\n /** unit granularity ('block' behaves exactly like the legacy enter). */\n u: TextFxUnit\n /** direction: 0 forward, 1 reverse, 2 center-out. */\n d: 0 | 1 | 2\n /** effective stagger seconds (clamped so the entrance fits the clip). */\n st: number\n /** per-unit duration seconds. */\n dur: number\n /** total entrance seconds — the redraw gate's animation window. */\n tt: number\n /** per-LINE unit arrays (empty for block — the whole clip animates). */\n units: string[][]\n /** total unit count across lines. */\n n: number\n}\n\nconst FX_DIR: Record<string, 0 | 1 | 2> = { forward: 0, reverse: 1, center: 2 }\n\n/**\n * Normalize a clip's fx spec into the baked payload. Pure function of the\n * doc (determinism is the contract): defaults resolve here, stagger clamps\n * so `stagger·(n−1) + duration` never exceeds ~90% of the clip.\n */\nexport function resolveOverlayFx(\n clip: TextOverlayClip,\n clipDuration: number,\n): BakedOverlayFx | null {\n // The step on `anim`, or the legacy `fx` field for a clip read unmigrated.\n const spec = overlayFxSpec(clip) ?? clip.fx ?? null\n if (!spec) return null\n const unit = spec.unit ?? 'block'\n const units = overlaySegments(clip.text, unit)\n const n =\n unit === 'block' ? 1 : units.reduce((sum, line) => sum + line.length, 0)\n // Wrapping happens at DRAW time (it needs measurement), so with maxWidth\n // active a 'line' unit means WRAPPED lines — lowering can't know how\n // many, but it CAN bound them: wrapped lines never exceed word tokens.\n // The bound drives the stagger clamp and the redraw-gate window (tt);\n // actual per-line delays regroup in ON_FRAME.\n const upperN =\n unit === 'line' && clip.maxWidth\n ? overlayLines(clip.text).reduce(\n (sum, line) => sum + overlayTokens(line).length,\n 0,\n )\n : n\n const typewriter = spec.fx === 'typewriter'\n // Typewriter is a step reveal: the per-unit duration is irrelevant, keep\n // it tiny so the last unit lands with the stagger, not 0.35s after it.\n const dur = typewriter\n ? 0.001\n : Math.min(2, Math.max(0.05, spec.duration ?? OVERLAY_TRANSITION_DUR))\n const defaultStagger = typewriter ? 0.05 : unit === 'block' ? 0 : 0.06\n let st = Math.min(2, Math.max(0, spec.stagger ?? defaultStagger))\n if (upperN > 1) {\n const maxTotal = Math.max(dur, clipDuration * 0.9)\n st = Math.min(st, Math.max(0, (maxTotal - dur) / (upperN - 1)))\n }\n const round = (v: number) => Math.round(v * 10000) / 10000\n return {\n k: spec.fx,\n u: unit,\n d: FX_DIR[spec.direction ?? 'forward'] ?? 0,\n st: round(st),\n dur: round(dur),\n tt: round(st * (upperN - 1) + dur),\n units,\n n,\n }\n}\n\n/**\n * The canvas font string at a given comp scale — MIRRORS ON_FRAME's\n * `olW + ' ' + olPx + 'px ' + olStack` (pinned by test).\n */\nexport function overlayFontString(\n style: ResolvedOverlayStyle,\n scale: number,\n s: number,\n): string {\n const italic = style.fontStyle === 'italic' ? 'italic ' : ''\n return `${italic}${style.weight} ${style.size * scale * s}px ${style.stack}`\n}\n\nexport interface OverlayRect {\n /** Center-anchored box in DESIGN px (pre-rotation). */\n cx: number\n cy: number\n w: number\n h: number\n /** Rotation in degrees (the caller rotates points into local space to hit-test). */\n rotation: number\n}\n\n/**\n * The drawn bounding box of a text overlay in DESIGN px — the picking\n * geometry. `measure(text, font)` returns the text width in px for a font\n * string (the host passes a scratch-canvas measureText; tests stub it).\n * `frameW`/`frameH` are the design frame size (docCardLayout's W/H) — the\n * clip's transform.x/y are FRACTIONS of the frame, so the anchor is\n * x·frameW / y·frameH. Measured at s = 1 (design space), so the result maps\n * to the canvas by ·s and to CSS by the player's display scale.\n */\nexport function overlayRect(\n clip: OverlayClip,\n measure: (text: string, font: string, letterSpacingPx?: number) => number,\n frameW: number,\n frameH = 1080,\n /** Media kinds: natural aspect (w/h) once known — null/absent = assume 16:9. */\n mediaAspect?: number | null,\n): OverlayRect {\n const scale = clip.transform.scale || 1\n const base = {\n cx: clip.transform.x * frameW,\n cy: clip.transform.y * frameH,\n rotation: clip.transform.rotation || 0,\n }\n if (clip.kind === 'html') {\n // An HTML layer's aspect is its own PICTURE box (the design box plus its\n // bleed), known without probing anything, and its width defaults to the\n // design size. MIRRORS the lowering's `htmlLayerPayload`.\n const pb = htmlLayerPictureBox(clip)\n const w = htmlLayerWidth(clip) * frameW * scale\n return { ...base, w, h: w * (pb.height / pb.width) }\n }\n if (clip.kind !== 'text') {\n // MIRRORS ON_FRAME's media sizing: width fraction of the FRAME × scale,\n // height from the media's natural aspect.\n const w = (clip.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH) * frameW * scale\n return { ...base, w, h: w / (mediaAspect || 16 / 9) }\n }\n const style = resolveOverlayStyle(clip)\n const font = overlayFontString(style, scale, 1)\n const ls = style.letterSpacing * scale\n // Wrap BEFORE measuring the block — mirrors ON_FRAME's wrap (maxWidth is\n // a frame-width fraction; this rect works in design px, so the budget is\n // maxWidth × frameW directly).\n const lines = clip.maxWidth\n ? wrapOverlayLines(\n overlayLines(clip.text),\n (t) => measure(t, font, ls),\n clip.maxWidth * frameW,\n )\n : overlayLines(clip.text)\n let w = 0\n for (const line of lines) w = Math.max(w, measure(line, font, ls))\n const lineH = style.size * scale * style.lineHeight\n // The background pill extends the drawn (and thus pickable) bounds —\n // mirrors ON_FRAME's pill geometry exactly.\n const box = resolveOverlayBox(clip)\n const padX = box ? box.padX * scale : 0\n const padY = box ? box.padY * scale : 0\n return {\n ...base,\n w: Math.max(w, style.size * scale * 0.6) + padX * 2, // empty text still selectable\n h: lines.length * lineH + padY * 2,\n }\n}\n\n/** Point-in-overlay test (design px), rotation-aware (point → local space). */\nexport function overlayHit(\n rect: OverlayRect,\n px: number,\n py: number,\n padPx = 8,\n): boolean {\n let dx = px - rect.cx\n let dy = py - rect.cy\n if (rect.rotation) {\n const a = (-rect.rotation * Math.PI) / 180\n const rx = dx * Math.cos(a) - dy * Math.sin(a)\n const ry = dx * Math.sin(a) + dy * Math.cos(a)\n dx = rx\n dy = ry\n }\n return (\n Math.abs(dx) <= rect.w / 2 + padPx && Math.abs(dy) <= rect.h / 2 + padPx\n )\n}\n","/**\n * An HTML layer: a UI component authored as markup and CSS, laid out and\n * painted by the browser itself, composited over the footage like a picture.\n *\n * WHY. A product video points at a piece of UI, and the camera is the only\n * pointing device a recording has: a zoom magnifies recorded pixels, so the\n * thing the video is about becomes the blurriest thing on screen. A layer\n * rasterized FROM SOURCE at the size each frame asks for has no such ceiling\n * (measured ~4x sharper than the same component magnified at 6x).\n *\n * HOW. An SVG carrying a `<foreignObject>` lays out real HTML with real CSS,\n * and an `<img>` pointed at that SVG rasterizes it. Two rules force the shape\n * everything here serves. A foreignObject SVG is origin-clean ONLY as a\n * `data:` URI (served from a URL it taints the canvas even same-origin, and a\n * tainted frame cannot be uploaded as a WebGL texture, so every layer vanishes\n * at once). And an SVG loaded through `<img>` may fetch NOTHING: a\n * `@font-face` naming a URL is silently ignored and the text falls back. So\n * the document keeps the SOURCE (a few hundred bytes), the page fetches the\n * faces by ordinary `fetch` and inlines them, and what the compositor draws\n * is an inline, vector, origin-clean picture.\n *\n * ONE COMPOSER. `htmlLayerSvg` here is the SVG document. The page builder in\n * `lower/studioEntry.ts` is a function STRING and cannot import it, so it\n * carries a mirror (`HTML_LAYER_SVG_CODE`), pinned byte-for-byte by\n * `htmlOverlay.test.ts`. Change them together.\n *\n * Pure: the studio, the CLI's lint and a test get the same answer for the\n * same source.\n */\nimport { findFontFamily, fontFaceUrl, nearestFontWeight } from '@vosjs/shared'\nimport type { HtmlOverlayClip, HtmlOverlayFont } from './types'\n\n/** The design frame an HTML layer's `box` is measured against (1080p). */\nexport const DESIGN_FRAME_WIDTH = 1920\n\n/** Past this many bytes of markup plus CSS the lint warns, and past the max it refuses. */\nexport const HTML_LAYER_WARN_BYTES = 32_000\nexport const HTML_LAYER_MAX_BYTES = 128_000\n\n/** A face the page inlines: a URL the builder fetches, or already bytes. */\nexport interface HtmlLayerFace {\n family: string\n url: string\n weight: number\n style: 'normal' | 'italic'\n}\n\n/** What the composer takes: the source and the faces AS BYTES. */\nexport interface HtmlLayerSource {\n html: string\n css?: string\n box: { width: number; height: number }\n bleed?: number\n /**\n * A LIVE layer (§3.13): the picture is a function of clip-local time.\n * `t` is the moment being drawn, `data` the clip's own values for the\n * `{{data.<name>}}` placeholders. Absent = a still.\n */\n live?: boolean\n t?: number\n data?: Record<string, string | number | boolean>\n}\nexport interface HtmlLayerInlineFace {\n family: string\n weight: number\n style: 'normal' | 'italic'\n dataUri: string\n}\n/** An image the source names by URL, fetched and inlined by the page. */\nexport interface HtmlLayerInlineAsset {\n url: string\n dataUri: string\n}\n\n/**\n * The rate a live layer is re-rasterized at: clip-local time is quantised\n * to this grid, so a frame asks for one picture and two frames at the same\n * grid step share it. 60 Hz covers every export rate the product offers.\n * MIRRORED in `HTML_LAYER_LIVE_HZ_CODE`'s user in `lower/studioEntry.ts`.\n */\nexport const HTML_LAYER_LIVE_HZ = 60\n\n/** The host an SVG image's page can fetch an asset from (CORS and the fleet). */\nexport const HTML_LAYER_ASSET_HOST = 'https://assets.vos.so/'\n\n// ---------------------------------------------------------------- faces --\n\n/**\n * Split a CSS value on the commas that separate its LIST ITEMS, not the\n * ones inside `rgba(...)` or `url(...)`.\n */\nfunction splitTopLevel(value: string): string[] {\n const out: string[] = []\n let depth = 0\n let cur = ''\n for (const ch of value) {\n if (ch === '(') depth++\n else if (ch === ')') depth--\n if (ch === ',' && depth === 0) {\n out.push(cur)\n cur = ''\n } else cur += ch\n }\n if (cur.trim()) out.push(cur)\n return out\n}\n\nconst unquote = (s: string): string =>\n s.trim().replace(/^['\"]/, '').replace(/['\"]$/, '').trim()\n\n/** The families the CSS names, in order, deduped, generic families dropped. */\nconst GENERIC_FAMILIES = new Set([\n 'serif',\n 'sans-serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'system-ui',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n 'emoji',\n 'math',\n 'fangsong',\n 'inherit',\n 'initial',\n 'unset',\n])\n\nexport function cssFamilies(css: string | undefined): string[] {\n if (!css) return []\n const out: string[] = []\n const seen = new Set<string>()\n for (const decl of css.match(/font-family\\s*:[^;}]+/gi) ?? []) {\n for (const item of splitTopLevel(decl.replace(/^[^:]*:/, ''))) {\n const name = unquote(item.replace(/!important/i, ''))\n if (!name) continue\n const lower = name.toLowerCase()\n if (GENERIC_FAMILIES.has(lower) || lower.startsWith('-apple-')) continue\n if (seen.has(lower)) continue\n seen.add(lower)\n out.push(name)\n }\n }\n return out\n}\n\n/**\n * The weights the CSS and the markup ask for: `font-weight` numbers and\n * keywords (`bold` is 700, `normal` 400), plus 700 when the markup carries a\n * `<b>`, `<strong>` or a heading, because the browser sets those bold. 400\n * is always in the list: it is what an unweighted run sets.\n */\nexport function cssWeights(css: string | undefined, html = ''): number[] {\n const out = new Set<number>([400])\n for (const decl of css?.match(/font-weight\\s*:[^;}]+/gi) ?? []) {\n const value = decl.replace(/^[^:]*:/, '').toLowerCase()\n for (const n of value.match(/\\b[1-9]00\\b/g) ?? []) out.add(Number(n))\n if (/\\bbold(er)?\\b/.test(value)) out.add(700)\n }\n if (/<(b|strong|h[1-6])[\\s>]/i.test(html)) out.add(700)\n return [...out].sort((a, b) => a - b)\n}\n\n/**\n * The faces the page inlines for a layer: every family the CSS names that\n * the CATALOG hosts, at every weight the source asks for (snapped to the\n * steps the catalog hosts, deduped), then the clip's explicit `fonts` for a\n * face outside the catalog. Nobody types a catalog URL: the CSS names the\n * family and this finds it, the same way a text clip's `family` resolves.\n * A family the catalog does not host resolves to nothing here and falls back\n * to the wrapper's system stack in the page, which is what `vos validate`\n * warns about.\n */\nexport function htmlLayerFaces(\n clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'fonts'>,\n): HtmlLayerFace[] {\n const faces: HtmlLayerFace[] = []\n const seen = new Set<string>()\n const add = (f: HtmlLayerFace) => {\n const key = `${f.family.toLowerCase()}|${f.weight}|${f.style}`\n if (seen.has(key)) return\n seen.add(key)\n faces.push(f)\n }\n const weights = cssWeights(clip.css, clip.html)\n for (const family of cssFamilies(clip.css)) {\n const entry = findFontFamily(family)\n if (!entry) continue\n for (const w of weights)\n add({\n family: entry.family,\n url: fontFaceUrl(entry.slug, nearestFontWeight(entry, w)),\n weight: nearestFontWeight(entry, w),\n style: 'normal',\n })\n }\n for (const f of clip.fonts ?? [])\n add({\n family: f.family,\n url: f.url,\n weight: f.weight ?? 400,\n style: f.style ?? 'normal',\n })\n return faces\n}\n\n/** The families the CSS names that the catalog does NOT host and no explicit font covers. */\nexport function htmlLayerUnhostedFamilies(\n clip: Pick<HtmlOverlayClip, 'css' | 'fonts'>,\n): string[] {\n const explicit = new Set(\n (clip.fonts ?? []).map((f) => f.family.toLowerCase()),\n )\n return cssFamilies(clip.css).filter(\n (f) => !findFontFamily(f) && !explicit.has(f.toLowerCase()),\n )\n}\n\n// --------------------------------------------------------------- assets --\n\n/**\n * The images the source names by URL: `src=\"https://…\"` in the markup and\n * `url(https://…)` in the CSS. An SVG loaded through <img> fetches nothing,\n * so the page fetches these the way it fetches faces and the composer\n * swaps each URL for its bytes. Deduped, in source order. Only a host the\n * page can fetch (CORS) and the fleet can reach counts as inlinable; the\n * lint says so about any other.\n */\nexport function htmlLayerAssetUrls(\n clip: Pick<HtmlOverlayClip, 'html' | 'css'>,\n): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n const add = (u: string) => {\n if (!seen.has(u)) {\n seen.add(u)\n out.push(u)\n }\n }\n // As written in the source (the composer swaps the literal), a\n // protocol-relative `//host/…` included: it resolves against the page.\n for (const m of clip.html.matchAll(\n /\\bsrc\\s*=\\s*[\"']((?:https?:)?\\/\\/[^\"']+)[\"']/gi,\n ))\n add(m[1])\n for (const text of [clip.html, clip.css ?? ''])\n for (const m of text.matchAll(\n /url\\(\\s*[\"']?((?:https?:)?\\/\\/[^\"')\\s]+)[\"']?\\s*\\)/gi,\n ))\n add(m[1])\n return out\n}\n\n/** The named assets the page can inline, and the ones it cannot. */\nexport function htmlLayerAssets(clip: Pick<HtmlOverlayClip, 'html' | 'css'>): {\n inlinable: string[]\n foreign: string[]\n} {\n const inlinable: string[] = []\n const foreign: string[] = []\n for (const u of htmlLayerAssetUrls(clip))\n (u.replace(/^\\/\\//, 'https://').startsWith(HTML_LAYER_ASSET_HOST)\n ? inlinable\n : foreign\n ).push(u)\n return { inlinable, foreign }\n}\n\n// ---------------------------------------------------------------- bleed --\n\n/**\n * How much room the CSS's shadows need outside the component's box, in\n * design px: the furthest any shadow reaches (offset plus blur plus spread),\n * rounded up with a margin.\n *\n * The SVG viewport clips hard. A card that fills its box exactly gets its\n * shadow sliced off at the edge, and because a shadow is usually offset\n * downward it pools in the lower corners, so the layer's rounded corners\n * come out as a grey square instead of transparency (measured: corner alpha\n * 6, 6 and 32 with no bleed, 0 everywhere with it). A rough upper bound read\n * off the declarations, not a CSS parser: over-reserving costs a few\n * transparent pixels, under-reserving is the clipped-corner bug.\n */\nexport function htmlLayerBleedFor(css: string | undefined): number {\n if (!css) return 0\n let worst = 0\n const consider = (layer: string) => {\n // An inset shadow paints inside the box and needs no room outside it.\n if (/\\binset\\b/i.test(layer)) return\n // Drop colours before reading lengths: a colour carries numbers of its\n // own, and a unitless 0 is a legal length, so they cannot be told apart\n // after.\n const lengths = layer\n .replace(/[a-z-]+\\([^)]*\\)/gi, ' ')\n .replace(/#[0-9a-f]{3,8}/gi, ' ')\n .match(/-?\\d*\\.?\\d+/g)\n if (!lengths) return\n const [x = 0, y = 0, blur = 0, spread = 0] = lengths.map(Number)\n worst = Math.max(\n worst,\n Math.abs(x) + blur + spread,\n Math.abs(y) + blur + spread,\n )\n }\n for (const decl of css.match(/box-shadow\\s*:[^;}]+/gi) ?? [])\n for (const layer of splitTopLevel(decl.replace(/^[^:]*:/, '')))\n consider(layer)\n // `filter` reaches outside the box only through drop-shadow(); a blur() or\n // a brightness() stays within it.\n for (const fn of css.match(/drop-shadow\\(([^)]*)\\)/gi) ?? [])\n consider(fn.replace(/^drop-shadow\\(/i, '').replace(/\\)$/, ''))\n return worst ? Math.ceil(worst * 1.15) : 0\n}\n\n/** The bleed a layer renders with: the stated one, else what its CSS needs. */\nexport function htmlLayerBleed(\n clip: Pick<HtmlOverlayClip, 'css' | 'bleed'>,\n): number {\n return Math.max(0, Math.round(clip.bleed ?? htmlLayerBleedFor(clip.css)))\n}\n\n// ------------------------------------------------------------- geometry --\n\n/**\n * The picture's box: the component plus its bleed on every side. This is\n * what `width` sizes and what the hit rect measures, because the bleed is\n * part of the rasterized picture (transparent, but there).\n */\nexport function htmlLayerPictureBox(\n clip: Pick<HtmlOverlayClip, 'box' | 'css' | 'bleed'>,\n): { width: number; height: number; bleed: number } {\n const bleed = htmlLayerBleed(clip)\n return {\n width: clip.box.width + bleed * 2,\n height: clip.box.height + bleed * 2,\n bleed,\n }\n}\n\n/**\n * The layer's width as a fraction of the FRAME width (the media overlay\n * convention; ON_FRAME paints `w · W`). Absent, the DESIGN SIZE: the picture\n * box over 1920, so a component designed at 452 px lands at 452 px in a\n * 1080p frame and scales with the export. The bleed is in the picture, so it\n * is in the default too, or a shadowed component would land narrower than\n * it was designed.\n */\nexport function htmlLayerWidth(\n clip: Pick<HtmlOverlayClip, 'width' | 'box' | 'css' | 'bleed'>,\n): number {\n if (clip.width != null) return clip.width\n return Math.min(1, htmlLayerPictureBox(clip).width / DESIGN_FRAME_WIDTH)\n}\n\n// ---------------------------------------------------------------- label --\n\nconst ENTITIES: Record<string, string> = {\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n nbsp: ' ',\n}\n\n/** The words the markup carries, for a lane label: tags stripped, entities read. */\nexport function htmlLayerText(html: string): string {\n return html\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<[^>]*>/g, ' ')\n .replace(/&(#x[0-9a-f]+|#\\d+|[a-z]+);/gi, (m, ref: string) => {\n if (ref[0] === '#')\n return String.fromCodePoint(\n ref[1] === 'x' ? parseInt(ref.slice(2), 16) : parseInt(ref.slice(1)),\n )\n return ENTITIES[ref.toLowerCase()] ?? m\n })\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** The timeline item's label: the first words, else the kind. */\nexport function htmlLayerLabel(clip: Pick<HtmlOverlayClip, 'html'>): string {\n const words = htmlLayerText(clip.html)\n return words ? words.slice(0, 24) : 'HTML'\n}\n\n// -------------------------------------------------------------- problems --\n\nexport type HtmlLayerProblemCode =\n | 'empty'\n | 'unclosed-void'\n | 'bare-ampersand'\n | 'unquoted-attr'\n | 'too-large'\n | 'foreign-field'\n | 'shadow-clipped'\n | 'external-resource'\n | 'wall-clock'\n | 'live-delay'\n | 'placeholder-still'\n | 'unhosted-family'\n\n/** `{{t}}` or `{{data.<name>}}` anywhere in a text. */\nexport const HTML_LAYER_PLACEHOLDER = /\\{\\{\\s*(t|data\\.[A-Za-z0-9_.-]+)\\s*\\}\\}/\n\nexport interface HtmlLayerProblem {\n code: HtmlLayerProblemCode\n /** A problem stops the layer painting; a warning is a layer that paints wrong. */\n level: 'problem' | 'warning'\n message: string\n}\n\n/** XML rules HTML forgives and `foreignObject` does not. */\nconst VOID_TAGS = [\n 'br',\n 'img',\n 'hr',\n 'input',\n 'source',\n 'col',\n 'area',\n 'base',\n 'wbr',\n 'meta',\n 'link',\n]\n\n/** Media-clip fields an HTML layer refuses: its CSS is its chrome. */\nexport const HTML_LAYER_FOREIGN_FIELDS = [\n 'key',\n 'radius',\n 'shadow',\n 'border',\n 'loop',\n 'frame',\n 'fx',\n 'text',\n 'preset',\n] as const\n\n/**\n * Everything wrong with a layer that can be seen in its source, in words. A\n * malformed document does not warn in the page: the `<img>` fails to decode,\n * the layer never paints, and nothing anywhere says why. Checked here so an\n * author is told instead, the same answer in the studio, in `vos validate`\n * and in a test.\n */\nexport function htmlLayerProblems(\n clip: Pick<HtmlOverlayClip, 'html' | 'css' | 'bleed' | 'fonts' | 'live'>,\n): HtmlLayerProblem[] {\n const html = clip.html ?? ''\n const css = clip.css\n const raw = clip as unknown as Record<string, unknown>\n const problems: HtmlLayerProblem[] = []\n const push = (\n code: HtmlLayerProblemCode,\n level: HtmlLayerProblem['level'],\n message: string,\n ) => problems.push({ code, level, message })\n\n if (!html.trim()) push('empty', 'problem', 'The layer has no markup.')\n\n for (const tag of VOID_TAGS) {\n const open = new RegExp(`<${tag}(\\\\s[^>]*?)?>`, 'gi')\n let m: RegExpExecArray | null\n let said = false\n while ((m = open.exec(html))) {\n if (!m[0].trimEnd().endsWith('/>') && !said) {\n said = true\n push(\n 'unclosed-void',\n 'problem',\n `<${tag}> must close itself as <${tag} /> inside a foreignObject.`,\n )\n }\n }\n }\n const bare = html.match(/&(?!(#\\d+|#x[0-9a-fA-F]+|[a-zA-Z][a-zA-Z0-9]*);)/g)\n if (bare)\n push(\n 'bare-ampersand',\n 'problem',\n `A bare \"&\" is a parse error; write &. Found ${bare.length}.`,\n )\n if (/<[a-zA-Z][^>]*?\\s[a-zA-Z-]+=(?![\"'])[^\\s>]+/.test(html))\n push(\n 'unquoted-attr',\n 'problem',\n 'Attribute values must be quoted inside a foreignObject.',\n )\n\n const bytes = html.length + (css?.length ?? 0)\n if (bytes > HTML_LAYER_MAX_BYTES)\n push(\n 'too-large',\n 'problem',\n `The layer's source is ${Math.round(bytes / 1000)} KB; the ceiling is ${HTML_LAYER_MAX_BYTES / 1000} KB. Inline less, or split the layer.`,\n )\n else if (bytes > HTML_LAYER_WARN_BYTES)\n push(\n 'too-large',\n 'warning',\n `The layer's source is ${Math.round(bytes / 1000)} KB. A document carries it twice (the doc and the composed config); keep a layer under ${HTML_LAYER_WARN_BYTES / 1000} KB.`,\n )\n\n for (const field of HTML_LAYER_FOREIGN_FIELDS)\n if (raw[field] !== undefined)\n push(\n 'foreign-field',\n 'problem',\n `An HTML layer has no \"${field}\": its CSS is its chrome (shadow, radius, border) and its markup its content.`,\n )\n\n // A shadow with no room to land is clipped square by the SVG viewport, so\n // the layer's rounded corners come out grey. Only a STATED bleed can be\n // too small: an absent one is derived from the CSS at lowering.\n const needed = htmlLayerBleedFor(css)\n if (clip.bleed != null && needed > clip.bleed)\n push(\n 'shadow-clipped',\n 'warning',\n `The CSS paints up to ${needed}px outside the component and bleed is ${clip.bleed}, so the shadow is clipped square at the layer's edge. Set bleed to ${needed}, or leave it out to derive it.`,\n )\n\n // An SVG loaded through <img> fetches nothing, so the page fetches what\n // the source names by URL and inlines it, the way it inlines faces. It\n // can do that only from the host the page may read (CORS) and the fleet\n // can reach; anything else paints as a blank.\n for (const u of htmlLayerAssets({ html, css }).foreign)\n push(\n 'external-resource',\n 'warning',\n `The source names ${u} by URL. The page inlines only ${HTML_LAYER_ASSET_HOST}… (the host it may fetch, and the one the fleet reaches), so this will paint blank. Upload it (vos asset upload) or inline it as a data: URI.`,\n )\n if (/\\bhref\\s*=\\s*[\"']\\s*(https?:)?\\/\\//i.test(html))\n push(\n 'external-resource',\n 'warning',\n 'The source names a stylesheet or a link by URL. An SVG image fetches no href; inline the styles in the CSS.',\n )\n\n // A CSS animation inside a STILL layer runs on the WALL CLOCK from decode\n // time, never on the video's t: an export that cold-seeks catches it at an\n // arbitrary phase and never matches the preview. A LIVE layer scrubs the\n // same keyframes to t (paused, delayed by -t), which is the honest form;\n // it overrides an authored delay to do so.\n const animated = /\\b(animation|transition)(-[a-z-]+)?\\s*:/i.test(css ?? '')\n if (animated && !clip.live)\n push(\n 'wall-clock',\n 'warning',\n \"CSS animations inside a layer run on the wall clock, not the timeline: the export will not match the preview. Set live: true to scrub them to the clip's time, or animate the layer with motion and anim.\",\n )\n if (clip.live && /\\banimation-delay\\s*:/i.test(css ?? ''))\n push(\n 'live-delay',\n 'warning',\n 'A live layer scrubs its keyframes by setting animation-delay to -t, so the authored animation-delay is overridden. Fold the delay into the keyframes instead.',\n )\n if (\n !clip.live &&\n (HTML_LAYER_PLACEHOLDER.test(html) ||\n HTML_LAYER_PLACEHOLDER.test(css ?? ''))\n )\n push(\n 'placeholder-still',\n 'warning',\n 'The source carries {{t}} or {{data.…}} placeholders, which fill only on a live layer (live: true); a still layer shows them as written.',\n )\n\n for (const family of htmlLayerUnhostedFamilies(clip))\n push(\n 'unhosted-family',\n 'warning',\n `The CSS names \"${family}\", which is not a hosted family (GET https://vos.so/api/fonts); the system stack stands in.`,\n )\n\n return problems\n}\n\n/**\n * The real XML question, asked of a real parser: the studio hands in\n * `DOMParser`, a test hands in a stub. The CLI does not call this (Node\n * ships no parser) and relies on the heuristics above plus the page's own\n * decode report.\n */\nexport function wellFormedXml(\n svg: string,\n parseXml: (text: string) => {\n getElementsByTagName: (name: string) => ArrayLike<{\n textContent: string | null\n }>\n },\n): { ok: true } | { ok: false; error: string } {\n const doc = parseXml(svg)\n const errors = doc.getElementsByTagName('parsererror')\n if (!errors.length) return { ok: true }\n const text = (errors[0].textContent ?? '').replace(/\\s+/g, ' ').trim()\n return { ok: false, error: text || 'The markup is not well-formed XML.' }\n}\n\n// -------------------------------------------------------------- composer --\n\n/** `@font-face` blocks for the inlined faces, or '' when there are none. */\nfunction fontFaceCss(faces: readonly HtmlLayerInlineFace[]): string {\n let out = ''\n for (const f of faces)\n out +=\n `@font-face{font-family:'${f.family}';` +\n `src:url(${f.dataUri}) format('woff2');` +\n `font-weight:${f.weight};` +\n `font-style:${f.style};` +\n `font-display:block}`\n return out\n}\n\n/** Clip-local time as it is written into the picture: up to 3 decimals, no trailing zeros. */\nconst fmtT = (t: number): string => String(Math.round(t * 1000) / 1000)\n\n/** A placeholder's value, escaped for XML (a value is data, never markup). */\nconst escapeXml = (v: string | number | boolean): string =>\n String(v)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n\n/**\n * The SVG document for a layer: self-contained, laid out in DESIGN units\n * (the viewBox), so the browser rasterizes it at whatever size a frame asks\n * for. The wrapper fixes the box, insets the component by its bleed and\n * resets the two properties an SVG document does not inherit sensibly;\n * everything else is the author's.\n *\n * Assets the source names by URL are swapped for their bytes. A LIVE layer\n * is composed for one moment: `{{t}}` and `{{data.<name>}}` fill, and a\n * scrub block after the author's CSS pauses every animation and delays it\n * by -t, so the browser lays the frame out at exactly that offset (the\n * standard way to scrub a CSS animation, here applied per picture).\n *\n * MIRRORED by `HTML_LAYER_SVG_CODE` in `lower/studioEntry.ts`. Change both.\n */\nexport function htmlLayerSvg(\n src: HtmlLayerSource,\n faces: readonly HtmlLayerInlineFace[] = [],\n assets: readonly HtmlLayerInlineAsset[] = [],\n): string {\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 const t = src.t ?? 0\n const fill = (text: string): string => {\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(\n /\\{\\{\\s*data\\.([A-Za-z0-9_.-]+)\\s*\\}\\}/g,\n (_m, name: string) => {\n const v = src.data ? src.data[name] : undefined\n return v == null ? '' : escapeXml(v)\n },\n )\n }\n return out\n }\n const scrub = src.live\n ? `.vos-html-layer{--vos-t:${fmtT(t)}}` +\n `.vos-html-layer,.vos-html-layer *{animation-play-state:paused!important;` +\n `animation-delay:calc(var(--vos-t) * -1s)!important}`\n : ''\n const wrapper =\n `.vos-html-layer{width:${boxW}px;height:${boxH}px;` +\n `box-sizing:border-box;margin:0;padding:${bleed}px;` +\n `font-family:-apple-system,system-ui,sans-serif;` +\n `-webkit-font-smoothing:antialiased;text-rendering:geometricPrecision}`\n return (\n `<svg xmlns=\"http://www.w3.org/2000/svg\" ` +\n `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>${fontFaceCss(faces)}${wrapper}${fill(src.css ?? '')}${scrub}</style>` +\n fill(src.html) +\n `</div></foreignObject></svg>`\n )\n}\n\n// ------------------------------------------------------------------- key --\n\n/** FNV-1a over a string: cheap, stable, and enough to tell two sources apart. */\nfunction fnv1a(s: string): string {\n let h = 0x811c9dc5\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i)\n h = Math.imul(h, 0x01000193) >>> 0\n }\n return h.toString(36)\n}\n\n/**\n * The layer's CACHE KEY: content-addressed over everything the picture is\n * made of (markup, CSS, box, bleed, the resolved faces), so an edit to the\n * source lands on a new key and invalidates the picture cache, the build\n * guard and the overlay canvas's dirty signature at once. Moving, retiming,\n * scaling or animating the clip does not re-key it: none of those repaint.\n * The key is a handle, never a URL: nothing may fetch it.\n */\nexport function htmlLayerKey(\n clip: Pick<HtmlOverlayClip, 'id' | 'html' | 'css' | 'box' | 'live' | 'data'>,\n faces: readonly HtmlLayerFace[],\n bleed: number,\n assets: readonly string[] = [],\n): string {\n const src =\n clip.html +\n '\u0000' +\n (clip.css ?? '') +\n '\u0000' +\n clip.box.width +\n 'x' +\n clip.box.height +\n '\u0000' +\n bleed +\n '\u0000' +\n faces.map((f) => `${f.family}:${f.url}:${f.weight}:${f.style}`).join('|') +\n ' ' +\n assets.join('|') +\n // A live layer's picture is also a function of t: ON_FRAME appends the\n // moment to this key per frame. The data rides here, the moment there.\n (clip.live ? ' live ' + JSON.stringify(clip.data ?? null) : '')\n return `html:${clip.id}:${fnv1a(src)}`\n}\n\n/** The shape the page is handed for a layer: the source plus resolved faces. */\nexport interface HtmlLayerPayload {\n markup: string\n css: string\n box: { width: number; height: number }\n bleed: number\n faces: HtmlLayerFace[]\n /** Images the source names by URL, for the page to fetch and inline. */\n assets: string[]\n /** A live layer: the page composes the picture per frame at clip-local t. */\n live?: true\n data?: Record<string, string | number | boolean>\n}\n\n/** Everything the lowering emits for a layer, from the clip alone. */\nexport function htmlLayerPayload(clip: HtmlOverlayClip): {\n key: string\n width: number\n html: HtmlLayerPayload\n} {\n const faces = htmlLayerFaces(clip)\n const bleed = htmlLayerBleed(clip)\n const assets = htmlLayerAssets(clip).inlinable\n return {\n key: htmlLayerKey(clip, faces, bleed, assets),\n width: htmlLayerWidth(clip),\n html: {\n markup: clip.html,\n css: clip.css ?? '',\n box: { width: clip.box.width, height: clip.box.height },\n bleed,\n faces,\n assets,\n ...(clip.live\n ? { live: true as const, ...(clip.data ? { data: clip.data } : {}) }\n : {}),\n },\n }\n}\n\nexport type { HtmlOverlayFont }\n","/**\n * 3D text — lowering-side resolution, mirroring how text presets\n * resolve in overlayText.ts: the doc carries intent (typeface slug, material\n * preset name), the baked data carries plain values (URL + constructor\n * params), and ON_FRAME stays a generic interpreter with no registry.\n *\n * Material presets are FLEET-AUDITED: everything single-sided (THREE's\n * default FrontSide — DoubleSide on a transmission material hard-hangs\n * SwiftShader), no `dispersion` (blows preview-job deadlines), transmission\n * only single-sided. Keep new presets inside those constraints.\n */\nimport { DEFAULT_TYPEFACE_SLUG, findTypeface, typefaceUrl } from '@vosjs/shared'\nimport type { ObjectAsset, Text3dMaterial } from './types'\n\nexport const TEXT3D_DEPTH_DEFAULT = 0.25\nexport const TEXT3D_DEPTH_MIN = 0.02\nexport const TEXT3D_DEPTH_MAX = 1\n\nexport interface BakedText3dMaterial {\n /** THREE constructor family: MeshStandardMaterial | MeshPhysicalMaterial. */\n type: 'standard' | 'physical'\n params: Record<string, unknown>\n}\n\nexport interface BakedText3dAsset {\n kind: 'text3d'\n text: string\n /** Resolved typeface JSON URL (assets.vos.so — the fleet's one origin). */\n url: string\n /** Extrusion depth as a fraction of the glyph height. */\n depth: number\n bevel: boolean\n mat: BakedText3dMaterial\n}\n\nconst DEFAULT_INK = '#e4e4e7' // the primitive-prop default\n\nfunction materialFor(\n preset: Text3dMaterial,\n color: string,\n): BakedText3dMaterial {\n switch (preset) {\n case 'metal':\n return {\n type: 'standard',\n params: { color, metalness: 1, roughness: 0.22 },\n }\n case 'glass':\n // Deliberately NO transmission: its internal render pass composites\n // nothing in the layered compositor (measured black on SwiftShader —\n // the verify caught a fully invisible mesh), and the fleet has no env\n // map to refract anyway. Glass here is translucency + clearcoat\n // highlights; the span fade multiplies onto the base opacity.\n return {\n type: 'physical',\n params: {\n color,\n opacity: 0.55,\n metalness: 0,\n roughness: 0.06,\n clearcoat: 1,\n clearcoatRoughness: 0.15,\n },\n }\n case 'neon':\n // No bloom pass exists — the glow is emissive intensity, not post.\n return {\n type: 'standard',\n params: {\n color,\n emissive: color,\n emissiveIntensity: 1.6,\n metalness: 0,\n roughness: 0.4,\n },\n }\n default:\n return {\n type: 'standard',\n params: { color, metalness: 0.2, roughness: 0.45 },\n }\n }\n}\n\n/** Normalize a doc text3d asset into the baked payload (pure, deterministic). */\nexport function resolveText3dAsset(\n asset: Extract<ObjectAsset, { kind: 'text3d' }>,\n): BakedText3dAsset {\n const entry = asset.typeface ? findTypeface(asset.typeface) : null\n const slug = entry?.slug ?? DEFAULT_TYPEFACE_SLUG\n const depth = Math.min(\n TEXT3D_DEPTH_MAX,\n Math.max(TEXT3D_DEPTH_MIN, asset.depth ?? TEXT3D_DEPTH_DEFAULT),\n )\n return {\n kind: 'text3d',\n text: asset.text,\n url: typefaceUrl(slug),\n depth: Math.round(depth * 1000) / 1000,\n bevel: asset.bevel !== false,\n mat: materialFor(asset.material ?? 'standard', asset.color ?? DEFAULT_INK),\n }\n}\n","/**\n * Where each DOC segment lands on the output timeline: its rated pieces\n * (its own media's speed spans applied, the freezes on its footage placed)\n * and, from them, its output start and length. One home for the Video\n * row, the boundary transitions and the lint, so a clip's edges cannot be\n * three numbers. A freeze at a boundary two contiguous clips of one media\n * share belongs to the LATER clip (the frame is the same either way; the\n * band draws where the moment is), the rule `placeFreezes` keeps.\n */\nimport { splitBySpeed, totalDuration } from '@vosjs/timeline'\nimport { docFreezes, withFreezes } from './motion'\nimport { sameMedia } from '../media'\nimport { anchorSourceDuration, isRecordingDoc } from '../doc/studioDoc'\nimport type { Segment } from '@vosjs/timeline'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { FreezeSpan, SpeedSpan } from '../types'\n\n/** The doc's segments in canonical explicit form (empty = one full-source span). */\nexport function effectiveSegments(doc: StudioDoc): Segment[] {\n if (isRecordingDoc(doc) && doc.segments.length) return doc.segments\n return [{ in: 0, out: anchorSourceDuration(doc) }]\n}\n\n/** Does segment i start exactly where segment i-1 ends, on the same media (a split, not a cut)? */\nexport const contiguous = (\n segments: readonly (Segment & { media?: string })[],\n i: number,\n): boolean =>\n i > 0 &&\n sameMedia(segments[i - 1].media, segments[i].media) &&\n Math.abs(segments[i - 1].out - segments[i].in) < 1e-9\n\n/**\n * One DOC segment's rated pieces: its own media's speed spans applied and\n * the freezes on its footage placed (a freeze at its very end belongs to\n * it, one at its start to the segment before, when there is one).\n */\nexport const segmentPieces = (\n seg: Segment & { media?: string },\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[],\n leading: boolean,\n): Segment[] => {\n const own = freezes.filter(\n (f) =>\n sameMedia(f.media, seg.media) &&\n f.at <= seg.out + 1e-9 &&\n (leading ? f.at >= seg.in - 1e-9 : f.at > seg.in + 1e-9),\n )\n return withFreezes(\n splitBySpeed(\n [seg],\n speeds.filter((sp) => sameMedia(sp.media, seg.media)),\n ),\n own,\n )\n}\n\n/** Output-time length of one DOC segment (speed- and freeze-aware). */\nexport const outputLen = (\n seg: Segment & { media?: string },\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[] = [],\n leading = true,\n): number => totalDuration(segmentPieces(seg, speeds, freezes, leading))\n\n/** Output-time starts of the DOC segments (speed- and freeze-aware). */\nexport const segmentStarts = (\n segments: readonly (Segment & { media?: string })[],\n speeds: readonly SpeedSpan[],\n freezes: readonly FreezeSpan[] = [],\n): number[] => {\n const starts: number[] = []\n let acc = 0\n segments.forEach((s, i) => {\n starts.push(acc)\n acc += outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i))\n })\n return starts\n}\n\n/** One DOC segment's place on the output timeline. */\nexport interface SegmentExtent {\n start: number\n end: number\n}\n\n/**\n * Every DOC segment's output extent, in order: where each clip starts and\n * ends on the timeline, freezes included.\n */\nexport function segmentOutputExtents(doc: StudioDoc): SegmentExtent[] {\n const segments = effectiveSegments(doc) as (Segment & { media?: string })[]\n const speeds = doc.speed ?? []\n const freezes = isRecordingDoc(doc) ? docFreezes(doc) : []\n const starts = segmentStarts(segments, speeds, freezes)\n return segments.map((s, i) => ({\n start: starts[i],\n end:\n starts[i] +\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n }))\n}\n","/**\n * Transitions at the boundaries between footage clips. A clip's `anim`\n * says how it meets the clip beside it: its `exit` at the boundary after\n * it, its `enter` at the boundary before it. The lowering turns every\n * boundary that names one into a RECORD in output seconds, which ON_FRAME\n * reads with no document in hand: during the window the INCOMING clip is\n * the live one (`mapTime` lands on it as it would without a transition, so\n * the zoom remap, the audio splice and every chunk inherit the boundary\n * unchanged) and the OUTGOING card is a snapshot of its last frame on a\n * second plane, moving away. A frozen outgoing is what the references do\n * (a cut at a moment of stillness) and what makes the primitive chunk-safe:\n * a cold seek inside the window decodes one extra frame, never a second\n * time mapping. The camera rests through the window (a span that starts\n * inside it loses its head), so the move is the only motion. The card's\n * own `slide` enter at the open is the same record with no outgoing.\n */\nimport { segmentOutputExtents } from './segmentStarts'\nimport {\n enterOf,\n exitOf,\n slideSide,\n transitionSeconds,\n TRANSITION_SECONDS_MIN,\n} from '../anim'\nimport { mediaAtOutput, sameMedia } from '../media'\nimport type { Segment } from '@vosjs/timeline'\nimport type { AnimSide, AnimStep, ProjectDoc } from '../types'\n\n/** How far a slide travels, in plane widths or heights: fully off, plus a hair. */\nexport const TRANSITION_TRAVEL = 1.04\n/** How much a `scale` transition shrinks the card at its far end. */\nexport const TRANSITION_SCALE_STEP = 0.08\n/** The ghost plane sits a hair in front of the card plane, above it in the painter's order and below the stack's card planes. */\nexport const GHOST_Z_LIFT = 0.004\nexport const GHOST_RENDER_ORDER = 1.1\n\n/** The kinds a boundary moves by. */\nexport type TransitionKind = 'slide' | 'fade' | 'scale'\n\n/** One side of a transition: how a card moves, for how long. */\nexport interface TransitionMove {\n kind: TransitionKind\n /** A slide's side (where an enter comes from, where an exit goes to). */\n side: AnimSide\n /** Seconds this side takes, from the boundary. */\n d: number\n}\n\n/** The outgoing card: which media's last frame it shows, and how it leaves. */\nexport interface TransitionOut extends TransitionMove {\n /** The media (`''` = the primary). */\n media: string\n /** SOURCE seconds of the frame it shows (the clip's last frame). */\n at: number\n}\n\n/** A boundary transition in OUTPUT seconds. */\nexport interface Transition {\n /** The boundary: where the incoming clip starts. */\n t: number\n /** The window: the longer of the two sides. */\n d: number\n /** How the incoming (live) card arrives; absent = it holds still. */\n in?: TransitionMove\n /** The outgoing snapshot and how it leaves; absent = it is simply gone. */\n out?: TransitionOut\n}\n\n/** The hair of source time before a clip's `out` that holds its last frame. */\nconst LAST_FRAME_BACK = 0.002\n\nconst round3 = (v: number): number => Math.round(v * 1000) / 1000\n\nfunction move(\n step: AnimStep | null,\n which: 'enter' | 'exit',\n cap: number,\n): TransitionMove | null {\n const secs = transitionSeconds(step)\n if (!secs || !step) return null\n const kind = step.kind\n if (kind !== 'slide' && kind !== 'fade' && kind !== 'scale') return null\n // Never longer than half the shorter clip: the clip must be seen still\n // before it moves (the lint says so in words; the lowering keeps it true).\n const d = Math.min(secs, cap)\n if (d < TRANSITION_SECONDS_MIN) return null\n return { kind, side: slideSide(step, which), d: round3(d) }\n}\n\n/**\n * The document's transitions: one record per boundary that names an exit\n * or an enter, plus the card's own `slide` enter at the open. Empty for a\n * document with none, so it lowers as before.\n */\nexport function docTransitions(doc: ProjectDoc): Transition[] {\n const out: Transition[] = []\n const segments = doc.segments\n const extents = segmentOutputExtents(doc)\n const open = enterOf(doc.frame.anim)\n if (open?.kind === 'slide' && extents.length) {\n const first = extents[0]\n const m = move(open, 'enter', Math.max(0, first.end - first.start) / 2)\n if (m) out.push({ t: 0, d: m.d, in: m })\n }\n for (let i = 0; i + 1 < segments.length; i++) {\n const a = segments[i]\n const b = segments[i + 1]\n const la = extents[i].end - extents[i].start\n const lb = extents[i + 1].end - extents[i + 1].start\n const cap = Math.min(la, lb) / 2\n const exit = move(exitOf(a.anim), 'exit', cap)\n const enter = move(enterOf(b.anim), 'enter', cap)\n if (!exit && !enter) continue\n const t = round3(extents[i + 1].start)\n const rec: Transition = {\n t,\n d: Math.max(exit?.d ?? 0, enter?.d ?? 0),\n }\n if (enter) rec.in = enter\n if (exit)\n rec.out = {\n ...exit,\n media: a.media ?? '',\n at: round3(Math.max(a.in, a.out - LAST_FRAME_BACK)),\n }\n out.push(rec)\n }\n return out\n}\n\n/** How close to a boundary a span may start before the window rests it. */\nconst REST_LEAD = 0.1\n\n/**\n * The camera rests through a transition: a source-anchored span (zoom,\n * tilt) whose output start falls inside a boundary's window loses its\n * head, starting where the window ends instead, and a span too short to\n * survive the cut is dropped. Spans that start before the window keep\n * their own end. The open's record (t = 0) is the entrance's business\n * (`prependEntrance`) and is skipped here.\n */\nexport function restSpansThroughTransitions<\n T extends { in: number; out: number; media?: string },\n>(\n spans: readonly T[],\n rated: readonly (Segment & { media?: string })[],\n transitions: readonly Transition[],\n minLen: number,\n): T[] {\n const windows = transitions.filter((tr) => tr.t > 0 && tr.d > 0)\n if (!windows.length) return [...spans]\n const out: T[] = []\n for (const span of spans) {\n const start = outputStartOf(rated, span.in, span.out, span.media)\n const win =\n start === null\n ? undefined\n : windows.find((tr) => start >= tr.t - REST_LEAD && start < tr.t + tr.d)\n if (start === null || !win) {\n out.push(span)\n continue\n }\n const after = win.t + win.d\n if (mediaAtOutput(rated, after) !== (span.media ?? '')) continue\n const head = sourceAt(rated, after)\n if (head === null || head > span.out - minLen) continue\n out.push({ ...span, in: round3(head) })\n }\n return out\n}\n\n/**\n * Where a source span's kept footage STARTS on the output timeline, through\n * its media's pieces (null when the cut dropped it all).\n */\nfunction outputStartOf(\n rated: readonly (Segment & { media?: string; rate?: number })[],\n sIn: number,\n sOut: number,\n media: string | undefined,\n): number | null {\n let acc = 0\n for (const p of rated) {\n const rate = p.rate ?? 1\n const len = Math.max(0, p.out - p.in) / rate\n if (sameMedia(p.media, media)) {\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) return acc + (ovIn - p.in) / rate\n }\n acc += len\n }\n return null\n}\n\n/** The source second of the piece under an output moment. */\nfunction sourceAt(\n rated: readonly (Segment & { media?: string; rate?: number })[],\n t: number,\n): number | null {\n let acc = 0\n for (const p of rated) {\n const rate = p.rate ?? 1\n const len = Math.max(0, p.out - p.in) / rate\n if (t < acc + len) return p.in + (t - acc) * rate\n acc += len\n }\n return null\n}\n","/**\n * Gain envelope for an audio clip, in OUTPUT-timeline seconds — the single\n * source of truth shared by preview and export: the lowering bakes these\n * points into `ctx.data.audio[i].env`, the program applies them with\n * setValueAtTime/linearRampToValueAtTime, and the export applies the SAME\n * points in its OfflineAudioContext mix. Fades that together exceed the clip\n * span are scaled down proportionally so they meet instead of crossing.\n */\nimport { clipLength } from '../types'\nimport type { AudioClip } from '../types'\n\nexport interface EnvelopePoint {\n /** output-timeline seconds. */\n t: number\n /** linear gain 0..1. */\n g: number\n}\n\nexport function clipEnvelope(\n clip: Pick<\n AudioClip,\n 'start' | 'in' | 'out' | 'gain' | 'fadeIn' | 'fadeOut' | 'loop' | 'loopLen'\n >,\n): EnvelopePoint[] {\n // Fades span the PLACED length (a looped clip fades over its full run).\n const span = clipLength(clip)\n const end = clip.start + span\n let fi = Math.max(0, clip.fadeIn)\n let fo = Math.max(0, clip.fadeOut)\n if (fi + fo > span && fi + fo > 0) {\n const scale = span / (fi + fo)\n fi *= scale\n fo *= scale\n }\n const g = Math.max(0, Math.min(1, clip.gain))\n const pts: EnvelopePoint[] = []\n pts.push({ t: clip.start, g: fi > 0 ? 0 : g })\n if (fi > 0) pts.push({ t: clip.start + fi, g })\n if (fo > 0 && end - fo > clip.start + fi) pts.push({ t: end - fo, g })\n pts.push({ t: end, g: fo > 0 ? 0 : g })\n // Dedupe collapsed points (zero-span or zero-fade edge cases).\n return pts.filter((p, i) => i === 0 || p.t > pts[i - 1].t + 1e-9)\n}\n\n/** Envelope value at output time `t` (linear interpolation; 0 outside the clip). */\nexport function envelopeValueAt(env: EnvelopePoint[], t: number): number {\n if (!env.length || t < env[0].t || t > env[env.length - 1].t) return 0\n for (let i = 1; i < env.length; i++) {\n if (t <= env[i].t) {\n const a = env[i - 1]\n const b = env[i]\n const f = b.t > a.t ? (t - a.t) / (b.t - a.t) : 1\n return a.g + (b.g - a.g) * f\n }\n }\n return env[env.length - 1].g\n}\n","/**\n * Cursor-follow focus —\n * the Recordly dead-zone model, baked DETERMINISTICALLY at lowering time,\n * tuned per camera style.\n *\n * OpenScreen chases the cursor with a stateful per-frame spring; Recordly only\n * recenters when the cursor nears the edge of the visible crop — calmer, and\n * it reduces to a handful of focus keyframes we can bake into the zoom track,\n * keeping seek a pure function of t (export, backward scrub, and every verify\n * script depend on that). Cursorful adds one more trick we adopt: a LOOK-AHEAD\n * — the recenter targets where the cursor is heading (sampled from the real\n * track slightly in the future), so the camera leads the pointer instead of\n * chasing a stale position. All three knobs (safe-zone ratio, recenter glide\n * duration, look-ahead) come from the doc's zoom style.\n *\n * Semantics per span with focusMode 'auto':\n * - entry focus = the cursor position at span.in (\"land where the cursor is\")\n * - while inside the span, a recenter event fires when the cursor exits the\n * central safeRatio of the visible crop; the camera glides to the (clamped,\n * look-ahead) cursor over `recenter` seconds, then waits for the next exit\n * - the focus FREEZES for the zoom-out (the caller keeps the last focus)\n *\n * One capture subtlety: the extension's cursor recorder is event-driven with a\n * distance gate — a parked cursor emits NO move samples, so stillness appears\n * as a time GAP between samples, not as repeated samples. All the math here\n * works on positions at their timestamps, so gaps behave correctly (no events\n * → no recenters), and the look-ahead interpolates between real samples.\n */\nimport { clampFocus } from '../layout'\nimport { dragsFromTrack } from '../planner/autoZoom'\nimport { clampZoomLevel } from '../types'\nimport { ZOOM_STYLES } from '../zoomStyle'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type { CursorTrack, ZoomSpan } from '../types'\n\n/** Legacy defaults (= the default style's values); prefer FollowOptions. */\nexport const FOLLOW_SAFE_RATIO = ZOOM_STYLES.glide.followSafeRatio\nexport const FOLLOW_RECENTER = ZOOM_STYLES.glide.followRecenter\n/** seconds between path samples through a drag (10 Hz reads as a pan). */\nexport const FOLLOW_PATH_STEP = 0.1\n/** the path's smoothing window, seconds each side of a sample. */\nexport const FOLLOW_PATH_SMOOTH = 0.08\n\nexport interface FollowOptions {\n /** recenter when the cursor exits this central fraction of the crop. */\n safeRatio?: number\n /** seconds the camera takes to glide to a recentered focus. */\n recenter?: number\n /** target the cursor this many seconds ahead of the exit moment. */\n lookahead?: number\n /** the frame's camera model: the stage camera never clamps a focus. */\n camera?: CameraModel\n}\n\nexport interface FollowEvent {\n /** SOURCE seconds — the moment the recenter starts. */\n t: number\n /**\n * A PATH sample: the camera is at this focus AT `t` (a linear segment\n * from the previous keyframe), not the start of a glide toward it. Baked\n * every FOLLOW_PATH_STEP through a drag, so the camera pans with the\n * pointer for the whole press.\n */\n path?: true\n cx: number\n cy: number\n}\n\ninterface Pt {\n t: number\n nx: number\n ny: number\n}\n\nexport function followFocusEvents(\n span: ZoomSpan,\n cursor: CursorTrack,\n space: { w: number; h: number },\n layout: CardLayout,\n options: FollowOptions = {},\n): { entry: { cx: number; cy: number } | null; events: FollowEvent[] } {\n const safeRatio = options.safeRatio ?? FOLLOW_SAFE_RATIO\n const recenter = options.recenter ?? FOLLOW_RECENTER\n const lookahead = options.lookahead ?? 0\n const camera = options.camera ?? 'card'\n const level = clampZoomLevel(span.level)\n if (!cursor.length || !space.w || !space.h || level <= 1.001) {\n return { entry: null, events: [] }\n }\n // Only real cursor positions steer the follow — scroll/focus/key events\n // carry stale or synthesized points (see cursorIdle.ts for the doctrine).\n const pts: Pt[] = cursor\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({\n t: e.t / 1000,\n nx: clamp01(e.x / space.w),\n ny: clamp01(e.y / space.h),\n }))\n if (!pts.length) return { entry: null, events: [] }\n\n // Entry: the span's OWN focus. The planner derived it from the clicked\n // element's rect (or a hand wrote it), and that is where the camera must\n // land; the follow then steers from there. Seeding from the cursor's\n // last sample before span.in landed the camera where the pointer was\n // `lead` seconds before it reached the target, and the first correction\n // could not fire for another recenter period, so a click zoom opened\n // beside its target every time.\n const entry = clampFocus(span.cx, span.cy, level, layout, camera)\n\n // Exit threshold in normalized VIDEO units: the visible crop spans W/level\n // canvas px → (W/level)/dw of the video's width; half of that is the\n // center-to-edge distance, and the safe zone keeps safeRatio of it.\n const thrX = (safeRatio * layout.W) / (2 * level * layout.dw)\n const thrY = (safeRatio * layout.H) / (2 * level * layout.dh)\n\n const events: FollowEvent[] = []\n let cx = entry.cx\n let cy = entry.cy\n // Give the zoom-in arrival room to land before the first recenter.\n let nextAllowed = span.in + recenter\n\n // Drags inside the span are PATH-followed: a sample every FOLLOW_PATH_STEP\n // at the pointer's smoothed position for the whole press, so the camera\n // pans with a slider thumb or a scrubber instead of waiting for it to\n // leave the dead zone. The dead-zone follow resumes from the release.\n const drags = dragsFromTrack(cursor, space.w, space.h).filter(\n (d) => d.t1 > span.in && d.t0 < span.out,\n )\n const pathEvents: FollowEvent[] = []\n for (const d of drags) {\n const from = Math.max(d.t0, span.in)\n const to = Math.min(d.t1, span.out)\n for (let t = from; ; t += FOLLOW_PATH_STEP) {\n const at = Math.min(t, to)\n const a = sampleAt(pts, at - FOLLOW_PATH_SMOOTH)\n const b = sampleAt(pts, at)\n const c = sampleAt(pts, at + FOLLOW_PATH_SMOOTH)\n const f = clampFocus(\n (a.nx + b.nx + c.nx) / 3,\n (a.ny + b.ny + c.ny) / 3,\n level,\n layout,\n camera,\n )\n pathEvents.push({\n t: round(at),\n path: true,\n cx: round(f.cx),\n cy: round(f.cy),\n })\n if (at >= to) break\n }\n }\n let dragIdx = 0\n for (const p of pts) {\n if (p.t < span.in) continue\n if (p.t > span.out) break\n // Inside a drag the path owns the camera; past its release the\n // dead-zone follow measures from where the path left it.\n while (dragIdx < drags.length && p.t > drags[dragIdx].t1) {\n const last = pathEvents.filter((e) => e.t <= drags[dragIdx].t1).at(-1)\n if (last) {\n cx = last.cx\n cy = last.cy\n }\n nextAllowed = drags[dragIdx].t1 + recenter\n dragIdx++\n }\n if (dragIdx < drags.length && p.t >= drags[dragIdx].t0) continue\n if (p.t < nextAllowed) continue\n if (Math.abs(p.nx - cx) > thrX || Math.abs(p.ny - cy) > thrY) {\n // Look-ahead: aim at where the cursor will be, not where it was.\n const target =\n lookahead > 0 ? sampleAt(pts, Math.min(p.t + lookahead, span.out)) : p\n const f = clampFocus(target.nx, target.ny, level, layout, camera)\n // The clamp can pin distinct cursor points to the same focus — skip no-ops.\n if (Math.abs(f.cx - cx) < 1e-3 && Math.abs(f.cy - cy) < 1e-3) continue\n events.push({ t: round(p.t), cx: round(f.cx), cy: round(f.cy) })\n cx = f.cx\n cy = f.cy\n nextAllowed = p.t + recenter\n }\n }\n const merged = [...events, ...pathEvents].sort((a, b) => a.t - b.t)\n return { entry, events: merged }\n}\n\n/** Interpolate the cursor position at time t (holds the ends; pts time-sorted). */\nfunction sampleAt(pts: Pt[], t: number): Pt {\n if (t <= pts[0].t) return pts[0]\n for (let i = 1; i < pts.length; i++) {\n if (pts[i].t >= t) {\n const a = pts[i - 1]\n const b = pts[i]\n const k = b.t > a.t ? (t - a.t) / (b.t - a.t) : 1\n return { t, nx: a.nx + (b.nx - a.nx) * k, ny: a.ny + (b.ny - a.ny) * k }\n }\n }\n return pts[pts.length - 1]\n}\n\nfunction clamp01(v: number): number {\n return Math.max(0, Math.min(1, v))\n}\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Pinned layers — a layer placed beside its REFERENT and carried with it as\n * the camera moves it on screen.\n *\n * A callout is a statement bound to the thing it is about. The binding is\n * proximity (the layer sits a gap off one side of the referent), a connector\n * (an optional leader) and a mark on the subject (an optional ring or\n * underline). Nothing here paints: the placement is resolved at lowering\n * into the clip's ordinary motion track — the same clip-local\n * [x, y, scale, rotation, opacity] track a `motion` pose bakes to — so the\n * studio entry that paints layers stays camera-blind, picking in the\n * studio reads the same numbers, and every renderer (preview, export,\n * chunk, still) agrees by construction.\n *\n * The referent lives in the recording's cursor space (normalised video\n * fractions); the camera is the one function `layout.ts` pins to ON_FRAME\n * (`zoomView`), sampled from the OUTPUT-time zoom track at PIN_SAMPLE_HZ\n * through the layer's life and simplified to the samples a linear\n * interpolation cannot predict. The layer's SIZE stays screen-space: a\n * callout is the video's, not the app's, so it never shrinks with the app;\n * only its place follows.\n */\nimport { lerpArray, mapTime, sample } from '@vosjs/timeline'\nimport { htmlLayerPictureBox, htmlLayerWidth } from '../htmlLayer'\nimport { zoomView } from '../layout'\nimport { overlayRect } from '../overlayText'\nimport { dragsFromTrack } from '../planner/autoZoom'\nimport { OVERLAY_MEDIA_DEFAULT_WIDTH } from '../types'\nimport type { Keyframe, KeyframeTrack, Segment } from '@vosjs/timeline'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type {\n CursorTrack,\n OverlayClip,\n OverlayPin,\n PinSide,\n ProjectDoc,\n Rect,\n StepSpan,\n} from '../types'\n\n/** Design px between the referent and the layer's visible box. */\nexport const PIN_GAP = 24\n/** Design px the layer keeps from the frame's edge. */\nexport const PIN_MARGIN = 24\n/** Placement samples per output second through the layer's life. */\nexport const PIN_SAMPLE_HZ = 30\n/** Design px a sample may deviate from its neighbours' line and be dropped. */\nexport const PIN_SIMPLIFY_PX = 0.5\n/** The layer's own entrance/exit length (mirrors the entry's olTD). */\nconst PIN_EDGE_S = 0.35\n/** A press counts as \"nearest\" within this many source seconds. */\nconst PIN_PRESS_WINDOW_S = 0.5\n/** Rough glyph advance for a text layer's box at lowering (no canvas). */\nconst PIN_TEXT_ADVANCE = 0.55\n\nexport type PinSideResolved = Exclude<PinSide, 'auto'>\nconst SIDE_ORDER: PinSideResolved[] = ['right', 'left', 'below', 'above']\n\n/**\n * A referent the POINTER carries: a slider thumb, a scrubber, a card being\n * dragged. Its rect was measured at the press; through the drag it sits\n * wherever the pointer has taken it, and it stays at the release. SOURCE\n * seconds; `x0`/`y0` the press in normalised video fractions.\n */\nexport interface PinCarry {\n t0: number\n t1: number\n x0: number\n y0: number\n}\n\n/** The referent's rect in normalised video fractions, and how it was found. */\nexport interface PinReferent {\n rect: Rect\n by: 'step' | 'press' | 'rect'\n /** The step that named it, when `by` is 'step'. */\n step?: StepSpan\n /** SOURCE second the referent was measured at (a step's start, the press). */\n at: number | null\n /** Set when the press began a drag: the referent moves with the pointer. */\n carry?: PinCarry\n}\n\n/**\n * Find a pin's referent in the recording. A `step` reads the step's own\n * rect (the recorder measures every selector step's box), else the union\n * of the presses inside the step's window (`down` and `key` events carry\n * the element's rect), so a take recorded before the recorder kept step\n * rects still resolves. A `press` is the nearest press to that source\n * second. A `rect` is taken as written. Null when nothing names a rect.\n */\nexport function pinReferent(\n doc: ProjectDoc,\n pin: OverlayPin,\n): PinReferent | null {\n const meta = doc.source.meta\n const w = meta.width || 0\n const h = meta.height || 0\n if (pin.rect && validRect(pin.rect)) {\n return { rect: { ...pin.rect }, by: 'rect', at: null }\n }\n if (pin.step !== undefined) {\n const step = findStep(meta.steps ?? [], pin.step)\n if (!step || step.skipped) return null\n const carry = dragCarry(doc, step.tStart - 0.05, step.tEnd + 0.05)\n if (step.rect && validRect(step.rect) && w > 0 && h > 0) {\n return {\n rect: normRect(step.rect, w, h),\n by: 'step',\n step,\n at: step.tStart,\n ...(carry ? { carry } : {}),\n }\n }\n const union = unionRects(\n pressRects(doc, step.tStart - 0.05, step.tEnd + 0.05),\n )\n if (!union || w <= 0 || h <= 0) return null\n return {\n rect: normRect(union, w, h),\n by: 'step',\n step,\n at: step.tStart,\n ...(carry ? { carry } : {}),\n }\n }\n if (pin.press !== undefined && Number.isFinite(pin.press)) {\n let best: { t: number; rect: Rect } | null = null\n for (const e of doc.source.cursor) {\n if (e.type !== 'down' || !e.rect || !validRect(e.rect)) continue\n const t = e.t / 1000\n const d = Math.abs(t - pin.press)\n if (d > PIN_PRESS_WINDOW_S) continue\n if (!best || d < Math.abs(best.t - pin.press)) best = { t, rect: e.rect }\n }\n if (!best || w <= 0 || h <= 0) return null\n const carry = dragCarry(doc, best.t - 0.01, best.t + 0.01)\n return {\n rect: normRect(best.rect, w, h),\n by: 'press',\n at: best.t,\n ...(carry ? { carry } : {}),\n }\n }\n return null\n}\n\n/**\n * The drag that begins inside [from, to], as a carry: the planner's own\n * drag test (a press that travelled before its release), so a press held\n * still stays a still referent.\n */\nfunction dragCarry(doc: ProjectDoc, from: number, to: number): PinCarry | null {\n const meta = doc.source.meta\n if (!(meta.width > 0) || !(meta.height > 0)) return null\n const d = dragsFromTrack(doc.source.cursor, meta.width, meta.height).find(\n (g) => g.t0 >= from && g.t0 <= to,\n )\n if (!d) return null\n return { t0: d.t0, t1: d.t1, x0: d.nx, y0: d.ny }\n}\n\n/** The raw pointer position at SOURCE second t, normalised: interpolated\n * between the samples around t, held at the ends. */\nexport function cursorAt(\n cursor: CursorTrack,\n t: number,\n space: { w: number; h: number },\n): { nx: number; ny: number } | null {\n let prev: { t: number; nx: number; ny: number } | null = null\n for (const e of cursor) {\n if (e.type !== 'move' && e.type !== 'down' && e.type !== 'up') continue\n const cur = { t: e.t / 1000, nx: e.x / space.w, ny: e.y / space.h }\n if (cur.t >= t) {\n if (!prev) return { nx: cur.nx, ny: cur.ny }\n const k = cur.t > prev.t ? (t - prev.t) / (cur.t - prev.t) : 1\n return {\n nx: prev.nx + (cur.nx - prev.nx) * k,\n ny: prev.ny + (cur.ny - prev.ny) * k,\n }\n }\n prev = cur\n }\n return prev ? { nx: prev.nx, ny: prev.ny } : null\n}\n\n/** A step a layer could be pinned to: it has an element, and its window overlaps the layer's. */\nexport interface PinCandidate {\n step: string | number\n do: string\n selector?: string\n /** The step's OUTPUT window. */\n output: { start: number; end: number }\n /** Seconds of the layer's window the step's covers. */\n overlap: number\n}\n\n/** The step a pin names: its id first, then its record-time index. */\nexport function findStep(\n steps: readonly StepSpan[],\n ref: string | number,\n): StepSpan | undefined {\n const byId = steps.find((s) => s.id !== undefined && s.id === ref)\n if (byId) return byId\n const idx = typeof ref === 'number' ? ref : Number(ref)\n if (!Number.isInteger(idx)) return undefined\n return steps.find((s) => s.step === idx)\n}\n\nfunction pressRects(doc: ProjectDoc, from: number, to: number): Rect[] {\n const out: Rect[] = []\n for (const e of doc.source.cursor) {\n if (e.type !== 'down' && e.type !== 'key') continue\n if (!e.rect || !validRect(e.rect)) continue\n const t = e.t / 1000\n if (t < from || t > to) continue\n out.push(e.rect)\n }\n return out\n}\n\nfunction unionRects(rects: readonly Rect[]): Rect | null {\n if (!rects.length) return null\n let x0 = Infinity\n let y0 = Infinity\n let x1 = -Infinity\n let y1 = -Infinity\n for (const r of rects) {\n x0 = Math.min(x0, r.x)\n y0 = Math.min(y0, r.y)\n x1 = Math.max(x1, r.x + r.w)\n y1 = Math.max(y1, r.y + r.h)\n }\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }\n}\n\nfunction validRect(r: Rect): boolean {\n return (\n Number.isFinite(r.x) &&\n Number.isFinite(r.y) &&\n Number.isFinite(r.w) &&\n Number.isFinite(r.h) &&\n r.w > 0 &&\n r.h > 0\n )\n}\n\nfunction normRect(r: Rect, w: number, h: number): Rect {\n return { x: r.x / w, y: r.y / h, w: r.w / w, h: r.h / h }\n}\n\n/**\n * The layer's VISIBLE box in design px at scale 1 — the part a viewer reads\n * as the card. An html layer's picture carries transparent bleed around its\n * design box (the room its shadow paints in); the gap is measured from the\n * box, never the bleed, or a shadowed card reads as standing off. A text\n * layer is measured with a rough advance (there is no canvas at lowering);\n * a media layer assumes 16:9 until its bytes are known.\n */\nexport function pinBox(\n clip: OverlayClip,\n layout: CardLayout,\n): { w: number; h: number } {\n const scale = clip.transform.scale || 1\n if (clip.kind === 'html') {\n const pb = htmlLayerPictureBox(clip)\n const wPic = htmlLayerWidth(clip) * layout.W * scale\n const unit = wPic / pb.width\n return { w: clip.box.width * unit, h: clip.box.height * unit }\n }\n if (clip.kind !== 'text') {\n const w = (clip.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH) * layout.W * scale\n return { w, h: w / (16 / 9) }\n }\n const r = overlayRect(clip, estimateMeasure, layout.W, layout.H)\n return { w: r.w, h: r.h }\n}\n\nfunction estimateMeasure(\n text: string,\n font: string,\n letterSpacingPx = 0,\n): number {\n const m = /(\\d+(?:\\.\\d+)?)px/.exec(font)\n const px = m ? Number(m[1]) : 16\n return text.length * (px * PIN_TEXT_ADVANCE + letterSpacingPx)\n}\n\n/**\n * A rect in normalised video fractions as the camera shows it at OUTPUT\n * time t, in design px: the zoom track sampled at t, mapped through\n * zoomView (the one camera function ON_FRAME mirrors).\n */\nexport function pinRectOnScreen(\n rect: Rect,\n t: number,\n layout: CardLayout,\n camera: CameraModel,\n zoomTrack?: KeyframeTrack<number[]> | null,\n): Rect {\n let level = 1\n let cx = 0.5\n let cy = 0.5\n let centring: number | undefined\n if (zoomTrack && zoomTrack.keyframes.length) {\n const z = sample(zoomTrack, t, lerpArray)\n level = z[0]\n cx = z[1]\n cy = z[2]\n if (z.length > 3) centring = z[3]\n }\n const v = zoomView(level, cx, cy, layout, camera, centring)\n const map = (nx: number, ny: number) => {\n const px = layout.dx + nx * layout.dw\n const py = layout.dy + ny * layout.dh\n return {\n x: v.ox + (px - v.wcx) * v.level,\n y: v.oy + (py - v.wcy) * v.level,\n }\n }\n const a = map(rect.x, rect.y)\n const b = map(rect.x + rect.w, rect.y + rect.h)\n return { x: a.x, y: a.y, w: b.x - a.x, h: b.y - a.y }\n}\n\nexport interface PinPlacementInput {\n clip: OverlayClip\n referent: Rect\n layout: CardLayout\n camera: CameraModel\n /** The OUTPUT-time zoom track ([level, cx, cy, centring?]); absent = no camera. */\n zoomTrack?: KeyframeTrack<number[]> | null\n /** The clip's base [x, y, scale, rotation, opacity]. */\n base: readonly number[]\n /** The clip's own motion track, for scale, rotation and opacity. */\n motion?: KeyframeTrack<number[]> | null\n /**\n * A carried referent (a drag): the rect is offset by the pointer's\n * displacement at each sample's SOURCE moment, read from `cursor` in\n * `space` through `rated` (output → source).\n */\n carry?: PinCarry | null\n cursor?: CursorTrack\n space?: { w: number; h: number }\n rated?: readonly Segment[]\n}\n\n/**\n * The referent's rect at OUTPUT time t: the measured rect, moved by where\n * the pointer has carried it when the press began a drag (held at the\n * release; nothing before the press).\n */\nexport function pinReferentAt(\n referent: Rect,\n t: number,\n input: Pick<PinPlacementInput, 'carry' | 'cursor' | 'space' | 'rated'>,\n): Rect {\n const { carry, cursor, space, rated } = input\n if (!carry || !cursor || !space || !rated) return referent\n const s = mapTime(rated as Segment[], t)\n const sc = Math.max(carry.t0, Math.min(carry.t1, s))\n const p = cursorAt(cursor, sc, space)\n if (!p) return referent\n return {\n x: referent.x + (p.nx - carry.x0),\n y: referent.y + (p.ny - carry.y0),\n w: referent.w,\n h: referent.h,\n }\n}\n\nexport interface PinPlacement {\n side: PinSideResolved\n /** Clip-local [x, y, scale, rotation, opacity] in frame fractions. */\n track: KeyframeTrack<number[]>\n /** Clip-local [x, y] of the leader's tip: the layer's near-edge midpoint. */\n tip: KeyframeTrack<number[]>\n /** The most the layer had to move, in design px, to stay inside the frame. */\n clamped: number\n /** The layer's visible box in design px. */\n box: { w: number; h: number }\n}\n\n/**\n * Place a pinned layer through its life. The referent is mapped through the\n * camera at each sample; the layer's centre sits a gap off the chosen side;\n * the centre is clamped inside the frame's margin; the samples are\n * simplified. `side: 'auto'` is decided ONCE (never per frame, so the layer\n * cannot flip sides mid-life): the first side in order whose box fits at\n * the layer's start, middle and end, else the side with the most room.\n */\nexport function pinPlacement(input: PinPlacementInput): PinPlacement {\n const { clip, referent, layout, camera, zoomTrack, base, motion } = input\n const dur = Math.max(0.001, clip.duration)\n const gap = clip.pin?.gap ?? PIN_GAP\n const box = pinBox(clip, layout)\n const W = layout.W\n const H = layout.H\n\n const refAt = (local: number): Rect =>\n pinRectOnScreen(\n pinReferentAt(referent, clip.start + local, input),\n clip.start + local,\n layout,\n camera,\n zoomTrack,\n )\n\n const centreFor = (r: Rect, side: PinSideResolved) => {\n switch (side) {\n case 'right':\n return { x: r.x + r.w + gap + box.w / 2, y: r.y + r.h / 2 }\n case 'left':\n return { x: r.x - gap - box.w / 2, y: r.y + r.h / 2 }\n case 'below':\n return { x: r.x + r.w / 2, y: r.y + r.h + gap + box.h / 2 }\n case 'above':\n return { x: r.x + r.w / 2, y: r.y - gap - box.h / 2 }\n }\n }\n const fits = (c: { x: number; y: number }) =>\n c.x - box.w / 2 >= PIN_MARGIN &&\n c.x + box.w / 2 <= W - PIN_MARGIN &&\n c.y - box.h / 2 >= PIN_MARGIN &&\n c.y + box.h / 2 <= H - PIN_MARGIN\n const room = (c: { x: number; y: number }) => {\n const x0 = Math.max(PIN_MARGIN, c.x - box.w / 2)\n const x1 = Math.min(W - PIN_MARGIN, c.x + box.w / 2)\n const y0 = Math.max(PIN_MARGIN, c.y - box.h / 2)\n const y1 = Math.min(H - PIN_MARGIN, c.y + box.h / 2)\n return Math.max(0, x1 - x0) * Math.max(0, y1 - y0)\n }\n\n let side: PinSideResolved\n const asked = clip.pin?.side ?? 'auto'\n if (asked !== 'auto') side = asked\n else {\n const edge = Math.min(PIN_EDGE_S, dur / 3)\n const probes = [edge, dur / 2, dur - edge].map(refAt)\n const fitting = SIDE_ORDER.find((s) =>\n probes.every((r) => fits(centreFor(r, s))),\n )\n if (fitting) side = fitting\n else {\n let best: PinSideResolved = SIDE_ORDER[0]\n let bestRoom = -1\n for (const s of SIDE_ORDER) {\n const total = probes.reduce((acc, r) => acc + room(centreFor(r, s)), 0)\n if (total > bestRoom) {\n bestRoom = total\n best = s\n }\n }\n side = best\n }\n }\n\n const clampC = (c: { x: number; y: number }) => {\n const minX = Math.min(W / 2, PIN_MARGIN + box.w / 2)\n const maxX = Math.max(W / 2, W - PIN_MARGIN - box.w / 2)\n const minY = Math.min(H / 2, PIN_MARGIN + box.h / 2)\n const maxY = Math.max(H / 2, H - PIN_MARGIN - box.h / 2)\n return {\n x: Math.max(minX, Math.min(maxX, c.x)),\n y: Math.max(minY, Math.min(maxY, c.y)),\n }\n }\n const tipFor = (c: { x: number; y: number }) => {\n switch (side) {\n case 'right':\n return { x: c.x - box.w / 2, y: c.y }\n case 'left':\n return { x: c.x + box.w / 2, y: c.y }\n case 'below':\n return { x: c.x, y: c.y - box.h / 2 }\n case 'above':\n return { x: c.x, y: c.y + box.h / 2 }\n }\n }\n\n const n = Math.max(1, Math.ceil(dur * PIN_SAMPLE_HZ))\n const poses: Keyframe<number[]>[] = []\n const tips: Keyframe<number[]>[] = []\n let clamped = 0\n for (let i = 0; i <= n; i++) {\n const local = i === n ? dur : i / PIN_SAMPLE_HZ\n const want = centreFor(refAt(local), side)\n const c = clampC(want)\n clamped = Math.max(clamped, Math.hypot(c.x - want.x, c.y - want.y))\n const pose = motion?.keyframes.length\n ? sample(motion, local, lerpArray)\n : base\n poses.push({\n t: local,\n value: [c.x / W, c.y / H, pose[2], pose[3], pose[4]],\n ease: 'linear',\n })\n const tp = tipFor(c)\n tips.push({ t: local, value: [tp.x / W, tp.y / H], ease: 'linear' })\n }\n return {\n side,\n track: { keyframes: simplify(poses, [W, H, 1, 1, 1]) },\n tip: { keyframes: simplify(tips, [W, H]) },\n clamped: round(clamped),\n box,\n }\n}\n\n/**\n * Drop every sample its neighbours' straight line predicts within\n * PIN_SIMPLIFY_PX (per component, scaled to design px by `scalePx`): a hold\n * collapses to its two ends, a smooth ease keeps the samples its curvature\n * needs. The first and the last sample always stay.\n */\nexport function simplify(\n keys: readonly Keyframe<number[]>[],\n scalePx: readonly number[],\n): Keyframe<number[]>[] {\n if (keys.length <= 2) return keys.map(roundKey)\n const out: Keyframe<number[]>[] = [roundKey(keys[0])]\n let a = 0\n for (let i = 2; i <= keys.length - 1; i++) {\n // Would the line a → i predict every sample strictly between them?\n const ka = keys[a]\n const ki = keys[i]\n let ok = true\n for (let j = a + 1; j < i && ok; j++) {\n const kj = keys[j]\n const u = (kj.t - ka.t) / (ki.t - ka.t || 1)\n for (let k = 0; k < kj.value.length; k++) {\n const pred = ka.value[k] + (ki.value[k] - ka.value[k]) * u\n if (\n Math.abs(pred - kj.value[k]) * (scalePx[k] ?? 1) >\n PIN_SIMPLIFY_PX\n ) {\n ok = false\n break\n }\n }\n }\n if (!ok) {\n a = i - 1\n out.push(roundKey(keys[a]))\n }\n }\n const last = keys[keys.length - 1]\n if (out[out.length - 1].t !== round(last.t)) out.push(roundKey(last))\n return out\n}\n\nfunction roundKey(k: Keyframe<number[]>): Keyframe<number[]> {\n return { t: round(k.t), value: k.value.map(round4), ease: k.ease }\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\nfunction round4(v: number): number {\n return Math.round(v * 100000) / 100000\n}\n","/**\n * Idle cursor fade — the dwell detector behind `CursorStyle.hideWhenIdle`.\n *\n * A parked cursor is the most common blemish in screen footage: the dot sits in\n * frame through every scroll, every typing passage, every pause, drawing the eye\n * to nothing. This bakes a sparse opacity curve so ON_FRAME can fade it out\n * during dwells and bring it back the moment the cursor moves again.\n *\n * Pure and deterministic — seek must stay a pure function of `t`, so there are\n * no springs and no state. Output is SOURCE-anchored (like the cursor samples\n * themselves), which is what makes trims, cuts and speed spans inherit the fade\n * from one seam.\n *\n * Detection runs on the RAW track, not the smoothed path. The smoothed path is\n * resampled at a fixed cadence and linearly interpolates across gaps, so during\n * a park it creeps toward wherever the cursor goes next — ground truth for \"the\n * user isn't moving\" is the absence of raw events. The smoothing settle after\n * the last real move is bounded and well under `CURSOR_IDLE_HOLD`, so the dot\n * is always at rest before the fade begins.\n *\n * Two things deliberately do NOT count as movement:\n *\n * - **Scrolling.** `scroll` events re-emit the last known position, so a reading\n * pause looks \"active\" if you detect idleness from sample gaps. They carry no\n * real cursor motion and are ignored here, so a long scroll correctly fades\n * the cursor away — it isn't doing anything.\n * - **Focus jumps and typing.** `focus`/`key` events synthesize a position at\n * the focused element's centre, which the cursor never visited — and during\n * typing the caret is the actor, so the parked dot SHOULD fade.\n *\n * Clicks DO break a dwell: a press is not idle. The window before a click ends\n * `CURSOR_IDLE_FADE_IN` early so the dot is back at full opacity when the ring\n * blooms under it, rather than ghosting in behind its own click effect.\n */\nimport type { CursorTrack } from '../types'\n\n/** Seconds of stillness before the cursor starts fading out. */\nexport const CURSOR_IDLE_HOLD = 1\n/** Fade-out ramp, seconds. */\nexport const CURSOR_IDLE_FADE_OUT = 0.35\n/** Fade-in ramp, seconds. Snappier than the way out — motion draws the eye. */\nexport const CURSOR_IDLE_FADE_IN = 0.18\n/**\n * Movement epsilon as a fraction of the capture's short edge, floored at 2px\n * (the recorder's own distance gate). Relative so a 4K take isn't held to a\n * 1080p take's pixel budget.\n */\nexport const CURSOR_IDLE_EPS_FRAC = 0.0025\n\nexport interface CursorIdleOptions {\n /** Capture space, for the movement epsilon. */\n space: { w: number; h: number }\n /** SOURCE seconds; the trailing dwell runs to here. */\n sourceDuration: number\n}\n\n/** One point on the baked opacity curve. SOURCE seconds → alpha 0..1. */\nexport interface CursorFadeKey {\n t: number\n a: number\n}\n\ninterface Point {\n t: number\n x: number\n y: number\n}\n\nconst round = (n: number): number => Math.round(n * 1000) / 1000\n\n/**\n * Bake the opacity curve for a cursor track. Returns an empty array when\n * nothing dwells long enough to be worth hiding — callers then emit no\n * `cursorFade` key at all, so a take with a busy cursor lowers byte-identically\n * to the pre-feature lowering.\n */\nexport function cursorIdleFade(\n track: CursorTrack,\n o: CursorIdleOptions,\n): CursorFadeKey[] {\n // Only these three carry a position the cursor actually occupied.\n const moves: Point[] = track\n .filter((e) => e.type === 'move' || e.type === 'down' || e.type === 'up')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y }))\n if (moves.length === 0) return []\n\n const eps = Math.max(\n 2,\n CURSOR_IDLE_EPS_FRAC * Math.min(o.space.w || 0, o.space.h || 0),\n )\n\n // Virtual edge points so the head and tail parks are detectable: the drawn\n // dot holds the first sample's position before it and the last one's after\n // it, so those stretches are dwells even though no event lands in them.\n const pts: Point[] = []\n const first = moves[0]\n const last = moves[moves.length - 1]\n if (first.t > 0) pts.push({ t: 0, x: first.x, y: first.y })\n pts.push(...moves)\n if (o.sourceDuration > last.t) {\n pts.push({ t: o.sourceDuration, x: last.x, y: last.y })\n }\n\n // Still-windows: a window runs until the cursor leaves its anchor by `eps`.\n // Anchoring on the window START (not the previous point) is what makes slow\n // drift accumulate into a break instead of creeping unnoticed.\n const windows: { s: number; e: number }[] = []\n let anchor = pts[0]\n for (let i = 1; i < pts.length; i++) {\n const dx = pts[i].x - anchor.x\n const dy = pts[i].y - anchor.y\n if (dx * dx + dy * dy > eps * eps) {\n windows.push({ s: anchor.t, e: pts[i].t })\n anchor = pts[i]\n }\n }\n windows.push({ s: anchor.t, e: pts[pts.length - 1].t })\n\n const clicks = track\n .filter((e) => e.type === 'down' || e.type === 'up')\n .map((e) => e.t / 1000)\n .sort((a, b) => a - b)\n\n const keys: CursorFadeKey[] = []\n for (const w of windows) {\n let s = w.s\n for (;;) {\n const c = clicks.find((t) => t > s && t <= w.e)\n if (c === undefined) {\n emit(keys, s, w.e)\n break\n }\n // End early enough to be back at full opacity on the press itself.\n emit(keys, s, c - CURSOR_IDLE_FADE_IN)\n s = c\n }\n }\n return keys\n}\n\n/**\n * Append the four keys describing one hidden stretch, skipping windows too\n * short to complete the fade — a partial fade that immediately reverses reads\n * as a flicker, which is worse than leaving the cursor up.\n */\nfunction emit(keys: CursorFadeKey[], s: number, e: number): void {\n const from = s + CURSOR_IDLE_HOLD\n if (e - from < CURSOR_IDLE_FADE_OUT) return\n push(keys, from, 1)\n push(keys, from + CURSOR_IDLE_FADE_OUT, 0)\n push(keys, e, 0)\n push(keys, e + CURSOR_IDLE_FADE_IN, 1)\n}\n\n/** Keys are strictly increasing in t; a coincident key would divide by zero. */\nfunction push(keys: CursorFadeKey[], t: number, a: number): void {\n const rt = round(t)\n if (keys.length > 0 && rt <= keys[keys.length - 1].t) {\n keys[keys.length - 1].a = a\n return\n }\n keys.push({ t: rt, a })\n}\n","import { timelineRuntimeCode } from '@vosjs/timeline/bundle'\nimport { HTML_LAYER_LIVE_HZ } from '../htmlLayer'\nimport { OVERLAY_FONT_FACES } from '../overlayText'\nimport { CARD_FOV, CARD_Z } from '../stage'\nimport {\n LAYER_CARD_SHADOW,\n OVERLAY_LINE_HEIGHT,\n OVERLAY_TRANSITION_DUR,\n} from '../types'\n\n/**\n * The studio's program: the SHARED layers (text/image/video overlay clips, the\n * 3D prop pool) as ONE engine stack entry (`config.stack`, @vosjs/core ≥0.21)\n * that runs after the anchor's program on the same ctx — same scene, camera,\n * overlayScene, renderer, master clock — with its OWN `ctx.data` and its own\n * error boundary. The same entry rides every anchor: a recording's card\n * program and a user's own config alike.\n *\n * Everything here is CONSTANT text: a layer edit is `SET_DATA { target }` on\n * this entry, never a program change (the liveEdit invariant). The paint code\n * is the take editor's compositor, moved out of its main program unchanged;\n * it reads only what an entry is given — the renderer size, the output\n * clock (`ctx.time` on an entry IS the output time), the shared\n * `window.__vos__` caches and `globalThis.__vosTimeline` — never the anchor's\n * card geometry.\n *\n * The overlay layer mounts in `ctx.overlayScene` (the engine's 2D group,\n * rendered after every 3D group under the ortho `overlayCamera`), sized to that\n * camera's bounds, so it fills the frame on any anchor whatever its camera.\n * Props mount in `ctx.scene` at renderOrder 1.5 (between a recording's card and\n * its cam bubble) on the ANCHOR's camera: a perspective camera anywhere, or an\n * orthographic one (a program's `fullscreen` preset), where the prop group\n * carries the camera pose and a pixel-aspect squash. Lights: the entry adds\n * its pair when its data says `lights` (the recording anchor), and lazily,\n * once, when a program's scene turns out to have none (a shader program).\n */\n\nexport const STUDIO_ENTRY_ID = 'vosso.studio'\n\nexport interface StudioEntry {\n id: string\n data: Record<string, unknown>\n setup: string\n createContent: string\n onFrame: string\n}\n\nexport function studioEntry(data: Record<string, unknown>): StudioEntry {\n return {\n id: STUDIO_ENTRY_ID,\n data,\n setup: STUDIO_SETUP,\n createContent: STUDIO_CONTENT,\n onFrame: STUDIO_FRAME,\n }\n}\n\n/**\n * The HTML layer's SVG document, as page JS. This is the MIRROR of\n * `htmlLayerSvg` in `../htmlLayer.ts`: SETUP is a function string and cannot\n * import, so the composer is carried twice and `htmlOverlay.test.ts` pins\n * the two byte-for-byte. Change them together.\n */\nexport 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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\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}`\n\n// Fonts, overlay media and prop assets warm-load here so the first captured\n// frame is complete (preview/export parity). The timeline runtime is installed\n// when the anchor's program did not (a user's config has no reason to).\nexport const STUDIO_SETUP = `async (ctx) => {\n if (!globalThis.__vosTimeline) { ${timelineRuntimeCode} }\n const ns = (window.__vos__ = window.__vos__ || {})\n // The transport's pause state (the engine's video-renderer contract): the\n // bridge toggles it through setGlobalPaused ONLY when something installed\n // it. The card program does; a bare program has no element renderers and\n // installs nothing, so without this the audio scheduler below read\n // isPaused as undefined, never \"playing\", and a soundtrack on a program\n // was silent in the studio while the offline export mix carried it.\n if (ns.isPaused === undefined) ns.isPaused = true\n if (!ns.setGlobalPaused) ns.setGlobalPaused = (p) => { ns.isPaused = p }\n const cache = ns.videoCache || (ns.videoCache = new Map())\n const load = async (src, muted) => {\n let v = cache.get(src)\n if (v) return v\n v = document.createElement('video')\n v.src = src\n v.crossOrigin = 'anonymous'\n v.muted = muted\n v.playsInline = true\n v.preload = 'auto'\n await new Promise((res, rej) => {\n v.oncanplay = () => res()\n // The MediaError rides along: code 4 is an unreadable/unsupported source\n // (a dead blob URL, a 404), code 3 a decode failure, code 2 a network\n // stall. A bare \"failed to load\" gave the fleet log nothing to act on.\n v.onerror = () => rej(new Error('[voila] video failed to load' + (v.error ? ' (' + v.error.code + (v.error.message ? ': ' + v.error.message : '') + ')' : '')))\n v.load()\n })\n cache.set(src, v)\n return v\n }\n const loadImage = (src) => {\n const hit = cache.get(src)\n if (hit) return Promise.resolve(hit)\n return new Promise((res, rej) => {\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.onload = () => { cache.set(src, img); res(img) }\n img.onerror = () => rej(new Error('[voila] image failed to load'))\n img.src = src\n })\n }\n // Text-overlay fonts (compositor v2): the house faces from the CDN, loaded\n // ONLY when the doc has overlays. Awaiting here means the first captured frame\n // already has the faces (preview/export parity). Capped + fail-open: a CDN\n // failure degrades to the stack's system fallbacks, never a dead LOAD.\n const olv = ctx.data.overlays\n if (olv && olv.length && typeof FontFace !== 'undefined') {\n try {\n const faces = ctx.data.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}\n // Mark what SETUP registers, so ON_FRAME's live path (a text overlay\n // arriving through SET_DATA) never adds the same face twice.\n const ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\n const loads = faces.map((f) => {\n ofSet[f.family + '|' + f.weight] = 1\n const ff = new FontFace(f.family, 'url(' + f.url + ')', { weight: String(f.weight) })\n document.fonts.add(ff)\n return ff.load()\n })\n await Promise.race([\n Promise.all(loads).catch(() => {}),\n new Promise((res) => setTimeout(res, 4000)),\n ])\n } catch (e) { console.warn('[voila] overlay fonts failed to load', e) }\n }\n // An HTML layer arrives as SOURCE, and the picture is built HERE.\n //\n // Two rules force this shape. An SVG carrying a <foreignObject> is\n // origin-clean ONLY as a data: URI (served from a URL it taints the canvas\n // even same-origin, and a tainted frame cannot be uploaded as a WebGL\n // texture, so every layer vanishes at once), and an SVG loaded through\n // <img> may fetch nothing of its own, so a @font-face naming a URL is\n // silently ignored. So the faces are fetched here by ordinary fetch,\n // inlined, and what lands in the cache is an inline, vector picture under\n // the clip's content-addressed key.\n //\n // Per-clip state on the namespace: htmlWant (the key a clip wants NOW, so a\n // build a keystroke made stale is dropped before it decodes), htmlLast (the\n // last picture built for a clip, drawn while the next rasterizes so an edit\n // never blinks the layer out), htmlKeys (the key in the cache for a clip,\n // evicted when a new one lands), htmlErrors (why a clip did not decode).\n // faceData caches a face's bytes by URL across every layer on the page.\n const htmlSvg = ${HTML_LAYER_SVG_CODE}\n ns.htmlWant = ns.htmlWant || {}\n ns.htmlLast = ns.htmlLast || {}\n ns.htmlKeys = ns.htmlKeys || {}\n ns.htmlErrors = ns.htmlErrors || {}\n // One byte cache for faces AND the images a layer names by URL, keyed by\n // URL: fetched once per page however many layers name it, inlined as a\n // data: URI (a face is always woff2; an image carries its own type).\n const faceData = ns.faceData || (ns.faceData = new Map())\n const bytesOf = (url, mime, what) => {\n let p = faceData.get(url)\n if (p) return p\n p = fetch(url).then(async (r) => {\n if (!r.ok) throw new Error('HTTP ' + r.status)\n const buf = new Uint8Array(await r.arrayBuffer())\n // Chunked: String.fromCharCode.apply throws on a large spread.\n let str = ''\n for (let i = 0; i < buf.length; i += 0x8000) str += String.fromCharCode.apply(null, buf.subarray(i, i + 0x8000))\n const type = mime || ((r.headers.get('content-type') || 'application/octet-stream').split(';')[0])\n return 'data:' + type + ';base64,' + btoa(str)\n }).catch((e) => {\n // Fail open per URL, and NOT sticky: the layer paints without it now\n // and the next build asks again.\n faceData.delete(url)\n console.warn('[voila] html layer ' + what + ' failed to load', url, e)\n return ''\n })\n faceData.set(url, p)\n return p\n }\n const faceOf = (f) => bytesOf(f.url, 'font/woff2', 'face')\n const assetOf = (url) => bytesOf(url, '', 'asset')\n // The second argument names the moment a LIVE layer is composed for: its\n // own key (the clip's key plus the moment) and t. A still builds under\n // the clip's key.\n const buildHtmlLayer = async (oc, live) => {\n const key = live ? live.key : oc.key\n const want = () => ns.htmlWant[oc.id] === key\n ns.htmlWant[oc.id] = key\n const h = oc.html\n const uris = await Promise.all((h.faces || []).map(faceOf))\n const assetUris = await Promise.all((h.assets || []).map(assetOf))\n if (!want()) return\n const faces = []\n for (let i = 0; i < uris.length; i++) {\n if (uris[i]) faces.push({ family: h.faces[i].family, weight: h.faces[i].weight, style: h.faces[i].style, dataUri: uris[i] })\n }\n const assets = []\n for (let i = 0; i < assetUris.length; i++) {\n if (assetUris[i]) assets.push({ url: h.assets[i], dataUri: assetUris[i] })\n }\n const svg = htmlSvg({ html: h.markup, css: h.css, box: h.box, bleed: h.bleed, live: !!live, t: live ? live.t : 0, data: h.data }, faces, assets)\n const uri = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)))\n const img = await new Promise((res, rej) => {\n const i = new Image()\n i.onload = () => res(i)\n // foreignObject is XML: malformed markup fails to DECODE, silently, and\n // the layer would simply never appear. Say so instead.\n i.onerror = () => rej(new Error('did not decode: is the markup well-formed XML?'))\n i.src = uri\n })\n if (!want()) return\n // A browser that taints a canvas drawing a foreignObject SVG (WebKit's\n // history) would lose EVERY layer to one SecurityError at the texture\n // upload. Probe once on a scratch canvas and, failing, drop the HTML\n // layers and nothing else.\n if (ns.htmlSafe === undefined) {\n try {\n const pc = document.createElement('canvas')\n pc.width = pc.height = 1\n const px = pc.getContext('2d')\n px.drawImage(img, 0, 0, 1, 1)\n px.getImageData(0, 0, 1, 1)\n ns.htmlSafe = true\n } catch (e) {\n ns.htmlSafe = false\n console.error('[voila] html layers are off: this browser taints a canvas that draws a foreignObject SVG', e)\n }\n }\n // One picture per clip in the cache: the previous key goes when a new\n // one lands, which for a live layer is every frame.\n const prev = ns.htmlKeys[oc.id]\n if (prev && prev !== key) cache.delete(prev)\n cache.set(key, img)\n ns.htmlKeys[oc.id] = key\n ns.htmlLast[oc.id] = img\n delete ns.htmlErrors[oc.id]\n }\n // ON_FRAME is its own function string, so the builder is handed over for a\n // layer that arrives by live edit after setup has run. A failure is\n // recorded on the clip and said at the level a render page forwards.\n ns.buildHtmlLayer = (oc, live) => buildHtmlLayer(oc, live).catch((e) => {\n ns.htmlErrors[oc.id] = String((e && e.message) || e)\n console.error('[voila] html layer ' + oc.id + ' ' + ns.htmlErrors[oc.id])\n })\n // The capture contract for a LIVE layer, on ANY anchor. A recording's own\n // program installs the settle machinery (pendingDecodes, the wait a\n // capture awaits, a frame-prep hook that requests the frame's footage\n // before the paint); a program anchor installs none of it, and a live\n // layer on one would export a frame behind (the last landed picture),\n // since nothing awaited the build. So the entry installs what is absent\n // and registers its own hook: the moment every visible live layer needs\n // at t is asked for BEFORE the paint and put on pendingDecodes, so the\n // first paint draws the exact picture and a still at t is t. ON_FRAME\n // asks the same way (one source, inlined in both scopes) for the preview,\n // where the last landed picture stands in meanwhile.\n ns.pendingDecodes = ns.pendingDecodes || new Set()\n if (!ns.waitForVideosReady) ns.waitForVideosReady = async () => {\n if (ns.pendingDecodes.size) await Promise.all([...ns.pendingDecodes])\n }\n ns.framePrep = ns.framePrep || new Map()\n ns.framePrep.set('vosso.studio', (t) => {\n if (ns.isPaused === false) return\n for (const ol of (ctx.data.overlays || [])) {\n if (!ol.html || !ol.html.live) continue\n const olT = t - ol.start\n if (olT < 0 || olT > ol.dur) continue\n const q = Math.round(olT * ${HTML_LAYER_LIVE_HZ})\n const key = ol.key + '@' + q\n if (cache.get(key) || ns.htmlWant[ol.id] === key) continue\n const p = ns.buildHtmlLayer(ol, { key, t: q / ${HTML_LAYER_LIVE_HZ} })\n if (p && p.finally) {\n ns.pendingDecodes.add(p)\n p.finally(() => ns.pendingDecodes.delete(p))\n }\n }\n })\n // Media overlays (V1b): warm-load through the shared cache so the first\n // frame draws complete. Fail-open per clip (a bad key just doesn't draw).\n // A live layer warms at its first moment; ON_FRAME re-composes it per frame.\n for (const oc of (ctx.data.overlays || [])) {\n if (oc.html) { await ns.buildHtmlLayer(oc, oc.html.live ? { key: oc.key + '@0', t: 0 } : undefined); continue }\n if (oc.kind !== 'image' && oc.kind !== 'video') continue\n try {\n if (oc.kind === 'image') await loadImage(oc.key)\n else await load(oc.key, true)\n } catch (e) { console.warn('[voila] overlay media failed to load', oc.key, e) }\n }\n // GLB object assets (V3b): load via the engine's GLTFLoader addon into a\n // shared cache, bbox-NORMALIZED (norm = 1/maxDim) so transform3d.scale means\n // the same thing for every model. Fail-open per key — a bad model just\n // doesn't render (the prop pool skips unloaded keys).\n const objs = ctx.data.objects || []\n const objCache = ns.objCache || (ns.objCache = new Map())\n for (const oc of objs) {\n if (!oc.asset || oc.asset.kind !== 'gltf' || !oc.asset.key || objCache.has(oc.asset.key)) continue\n try {\n const GL = ctx.loaders && ctx.loaders.GLTFLoader\n if (!GL) { console.warn('[voila] GLTFLoader unavailable'); continue }\n const gltf = await new Promise((res, rej) => new GL().load(oc.asset.key, res, undefined, rej))\n const box = new ctx.THREE.Box3().setFromObject(gltf.scene)\n const size = new ctx.THREE.Vector3()\n box.getSize(size)\n const center = new ctx.THREE.Vector3()\n box.getCenter(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n objCache.set(oc.asset.key, { scene: gltf.scene, norm: 1 / maxDim, center: center })\n } catch (e) { console.warn('[voila] glb failed to load', oc.asset.key, e) }\n }\n // 3D-text typefaces: FontLoader JSONs into a shared cache, keyed by\n // URL. Awaited here so frame 0 is complete on cold loads (export/chunk\n // parity); live SET_DATA additions lazy-load in ON_FRAME instead. Fail-open\n // per URL — the prop pool skips unloaded typefaces.\n const fontCache = ns.fontCache || (ns.fontCache = new Map())\n for (const oc of objs) {\n if (!oc.asset || oc.asset.kind !== 'text3d' || !oc.asset.url || fontCache.has(oc.asset.url)) continue\n try {\n const FL = ctx.loaders && ctx.loaders.FontLoader\n if (!FL) { console.warn('[voila] FontLoader unavailable'); continue }\n const font = await new Promise((res, rej) => new FL().load(oc.asset.url, res, undefined, rej))\n fontCache.set(oc.asset.url, font)\n } catch (e) { console.warn('[voila] typeface failed to load', oc.asset.url, e) }\n }\n // Audio clips (music/SFX): pre-decode into a shared cache so first play is\n // instant. The AudioContext starts suspended (autoplay policy) — onFrame\n // resumes it on the first play. Failures degrade to a silent clip.\n const clips = ctx.data.audio || []\n if (clips.length && window.AudioContext) {\n const actx = ns.audioCtx || (ns.audioCtx = new window.AudioContext())\n const bufs = ns.audioBuffers || (ns.audioBuffers = new Map())\n const pend = ns.audioPending || (ns.audioPending = new Set())\n await Promise.all(clips.map(async (c) => {\n if (bufs.has(c.key) || pend.has(c.key)) return\n pend.add(c.key)\n try {\n const res = await fetch(c.key)\n bufs.set(c.key, await actx.decodeAudioData(await res.arrayBuffer()))\n } catch (e) {\n console.warn('[voila] audio decode failed', c.key, e)\n } finally {\n pend.delete(c.key)\n }\n }))\n }\n}`\n\nexport const STUDIO_CONTENT = `(ctx) => {\n const THREE = ctx.THREE\n const gl = ctx.renderer && ctx.renderer.domElement\n const res = ctx.resolution\n const W0 = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || 1280))\n const H0 = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || 720))\n // The overlay layer: one canvas on a plane that fills the engine's 2D\n // overlay camera, above every element and every 3D group.\n const canvas = document.createElement('canvas')\n canvas.width = W0\n canvas.height = H0\n const c2d = canvas.getContext('2d')\n const texture = new THREE.CanvasTexture(canvas)\n texture.colorSpace = THREE.SRGBColorSpace\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.generateMipmaps = false\n const ocam = ctx.overlayCamera\n const pw = ocam ? ocam.right - ocam.left : W0\n const ph = ocam ? ocam.top - ocam.bottom : H0\n const mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(pw, ph),\n new THREE.MeshBasicMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })\n )\n mesh.frustumCulled = false\n mesh.renderOrder = 1e6\n ;(ctx.overlayScene || ctx.scene).add(mesh)\n // Object clips: a world-space group (per-mesh renderOrder 1.5; meshes\n // depth-test among THEMSELVES). Lights only where the data asks: the\n // recording anchor's card program has none of its own.\n const objGroup = new THREE.Group()\n ctx.scene.add(objGroup)\n const objects = [mesh, objGroup]\n if (ctx.data && ctx.data.lights) {\n const amb = new THREE.AmbientLight(0xffffff, 0.75)\n const dir = new THREE.DirectionalLight(0xffffff, 1.4)\n dir.position.set(2, 3, 4)\n ctx.scene.add(amb)\n ctx.scene.add(dir)\n objects.push(amb, dir)\n }\n return {\n objects: objects,\n refs: {\n ov: { canvas: canvas, c2d: c2d, texture: texture, mesh: mesh, pw: pw, ph: ph },\n objects: { group: objGroup, pool: new Map() },\n },\n }\n}`\n\n// The studio compositor. Deterministic: a pure function of ctx.time (the\n// OUTPUT clock) + this entry's ctx.data. One `var` scope, ol*/ob* prefixed.\nexport const STUDIO_FRAME = `(ctx, content, dt) => {\n var r = content.refs\n // Stub-context tests build only the flat { c2d, canvas, texture }: fall\n // back to it like the card program does, so they drive this entry too.\n var ov = r.ov || r\n if (!ov || !ov.c2d) return\n var ovC = ov.c2d\n var res = ctx.resolution\n var gl = ctx.renderer && ctx.renderer.domElement\n var W = Math.max(2, Math.floor((gl && gl.width) || res.drawingBufferWidth || res.width || ov.canvas.width))\n var H = Math.max(2, Math.floor((gl && gl.height) || res.drawingBufferHeight || res.height || ov.canvas.height))\n // Resize: the backing canvas follows the renderer (dispose the texture so\n // THREE reallocates at the new dims), the plane follows the overlay camera.\n if (ov.canvas.width !== W || ov.canvas.height !== H) {\n ov.canvas.width = W; ov.canvas.height = H\n if (ov.texture && ov.texture.dispose) ov.texture.dispose()\n ov.sig = null\n }\n var ocam = ctx.overlayCamera\n if (ocam && ov.mesh && ctx.THREE) {\n var opw = ocam.right - ocam.left, oph = ocam.top - ocam.bottom\n if (opw !== ov.pw || oph !== ov.ph) {\n if (ov.mesh.geometry && ov.mesh.geometry.dispose) ov.mesh.geometry.dispose()\n ov.mesh.geometry = new ctx.THREE.PlaneGeometry(opw, oph)\n ov.pw = opw; ov.ph = oph\n }\n }\n var d = ctx.data || {}\n var TL = globalThis.__vosTimeline\n var t = ctx.time || 0\n var s = H / 1080 // scale design-px controls to comp px\n var ns = window.__vos__ || {}\n var playing = ns.isPaused === false\n\n // --- audio clips (music/SFX): Web Audio scheduler against the transport ---\n // Buffer sources are one-shot: (re)schedule everything on play / clip-data change\n // (SET_DATA) / drift; kill everything on pause. Seeks arrive paused (bridge forces\n // it), so scrubbing is silent like the video seek path. Export forces isPaused=true,\n // so none of this runs there — the export mixes offline from the same env points.\n var clips = d.audio || []\n var actx = ns.audioCtx\n if (!actx && clips.length && window.AudioContext) actx = ns.audioCtx = new window.AudioContext()\n if (actx) {\n var AS = ns.audioSched || (ns.audioSched = { on: false, nodes: [], sig: '', t0: 0, at0: 0, last: 0 })\n var bufs = ns.audioBuffers || (ns.audioBuffers = new Map())\n var pend = ns.audioPending || (ns.audioPending = new Set())\n // Clips added after LOAD (SET_DATA) decode lazily; completion clears the\n // signature so the next playing frame reschedules with the new buffer.\n for (var di = 0; di < clips.length; di++) {\n ;(function (cc) {\n if (bufs.has(cc.key) || pend.has(cc.key)) return\n pend.add(cc.key)\n fetch(cc.key)\n .then(function (res) { return res.arrayBuffer() })\n .then(function (ab) { return actx.decodeAudioData(ab) })\n .then(function (b) { bufs.set(cc.key, b); AS.sig = '' })\n .catch(function (e) { console.warn('[voila] audio decode failed', cc.key, e) })\n .then(function () { pend.delete(cc.key) })\n })(clips[di])\n }\n // Duck multiplier curve (output-time points, merged in by useComposition).\n var dEnv = d.duckEnv || []\n // Cheap change signature for the duck curve (full stringify would run 60×/s\n // over hundreds of points) — length + endpoints tracks every real change.\n var dSig = dEnv.length ? dEnv.length + ':' + dEnv[0].g + ',' + dEnv[dEnv.length - 1].t + ',' + dEnv[dEnv.length - 1].g : '0'\n var sig = JSON.stringify(clips) + '#' + dSig\n // Envelope value at output time tt (linear interp; def outside/empty).\n var auEnvAt = function (env, tt, def) {\n if (!env.length) return def\n if (tt <= env[0].t) return env[0].g\n if (tt >= env[env.length - 1].t) return env[env.length - 1].g\n for (var ii = 1; ii < env.length; ii++) {\n if (tt <= env[ii].t) {\n var A = env[ii - 1], B = env[ii]\n return A.g + (B.g - A.g) * ((tt - A.t) / Math.max(1e-9, B.t - A.t))\n }\n }\n return def\n }\n var kill = function () {\n for (var ki = 0; ki < AS.nodes.length; ki++) {\n try { if (AS.nodes[ki].stop) AS.nodes[ki].stop() } catch (e) {}\n try { AS.nodes[ki].disconnect() } catch (e) {}\n }\n AS.nodes = []\n }\n var drift = AS.on ? Math.abs((actx.currentTime - AS.at0) - (t - AS.t0)) : 0\n if (!playing && AS.on) { kill(); AS.on = false }\n var needSched = playing && (!AS.on || sig !== AS.sig ||\n (drift > 0.08 && actx.currentTime - AS.last > 0.25))\n if (needSched) {\n kill()\n if (actx.state === 'suspended') { try { actx.resume() } catch (e) {} }\n var now = actx.currentTime\n AS.on = true; AS.sig = sig; AS.t0 = t; AS.at0 = now; AS.last = now\n // NOTE: ON_FRAME is one var scope — locals here are prefixed (au*) so they\n // can't shadow the compositor's c/cur/etc used later in the function.\n for (var ai = 0; ai < clips.length; ai++) {\n var au = clips[ai]\n var auSpan = au.out - au.in\n var auLen = au.len || auSpan\n var auEnd = au.start + auLen\n if (auSpan <= 0 || auEnd <= t + 0.001) continue\n var auBuf = bufs.get(au.key)\n if (!auBuf) continue\n var auSrc = actx.createBufferSource()\n auSrc.buffer = auBuf\n var auGain = actx.createGain()\n auSrc.connect(auGain)\n var auTail = auGain\n // Duck under speech: a second gain stage driven by the shared curve.\n if (au.duck && dEnv.length) {\n var auDuck = actx.createGain()\n auGain.connect(auDuck); auDuck.connect(actx.destination)\n auDuck.gain.setValueAtTime(auEnvAt(dEnv, t, 1), now)\n for (var qi = 0; qi < dEnv.length; qi++) {\n if (dEnv[qi].t > t && dEnv[qi].t <= auEnd) auDuck.gain.linearRampToValueAtTime(dEnv[qi].g, now + (dEnv[qi].t - t))\n }\n auTail = auDuck\n } else {\n auGain.connect(actx.destination)\n }\n // Envelope points (output-time, baked by the lowering) → audio-clock times.\n var env = au.env || []\n auGain.gain.setValueAtTime(auEnvAt(env, t, au.gain != null ? au.gain : 1), now)\n for (var ri = 0; ri < env.length; ri++) {\n if (env[ri].t > t) auGain.gain.linearRampToValueAtTime(env[ri].g, now + (env[ri].t - t))\n }\n // Looping: native Web Audio loop over the source span fills the placed length.\n var auOff = au.in + Math.max(0, t - au.start)\n if (au.loop) {\n auSrc.loop = true\n auSrc.loopStart = au.in\n auSrc.loopEnd = au.out\n auOff = au.in + (Math.max(0, t - au.start) % auSpan)\n }\n auSrc.start(now + Math.max(0, au.start - t), auOff, auEnd - Math.max(t, au.start))\n AS.nodes.push(auSrc); AS.nodes.push(auGain); if (auTail !== auGain) AS.nodes.push(auTail)\n }\n }\n }\n\n function rr(x, y, w, h, rd, cx) {\n var cc = cx || ovC\n if (cc.roundRect) { cc.beginPath(); cc.roundRect(x, y, w, h, rd) }\n else { cc.beginPath(); cc.rect(x, y, w, h) }\n }\n\n // Text overlays: visibility signature + \"a transition is animating\".\n // During a hold the drawn pixels are static, so the signature alone drives\n // redraws (position/text/style edits change it — live SET_DATA); during\n // enter/exit windows alpha/offset change per-frame, so olAnim forces redraw.\n var ols = d.overlays || []\n var olTD = ${OVERLAY_TRANSITION_DUR}\n var olAnim = false, olVisSig = ''\n for (var oi = 0; oi < ols.length; oi++) {\n var ol0 = ols[oi]\n if (t < ol0.start || t > ol0.start + ol0.dur) continue\n // A page that taints on foreignObject draws no HTML layer (setup's probe).\n if (ol0.html && ns.htmlSafe === false) continue\n olVisSig += ol0.id + ':' + (ol0.text || ol0.key) + ':' + ol0.x + ',' + ol0.y + ',' + ol0.scale + ',' + ol0.rot + ',' + (ol0.fs || ol0.w) + ',' + (ol0.color || ol0.radius) + ',' + (ol0.opacity == null ? 1 : ol0.opacity) + (ol0.fx ? ',' + ol0.fx.k + ol0.fx.u + ol0.fx.d + ol0.fx.st : '') + (ol0.mw ? ',w' + ol0.mw : '') + ';'\n // fx widens the entrance window to the whole staggered span (tt);\n // without fx it is the legacy enter transition window.\n var olEW = ol0.fx ? ol0.fx.tt : olTD\n if (t < ol0.start + olEW || t > ol0.start + ol0.dur - olTD) olAnim = true\n // Pose keyframes: a clip with a motion track animates its transform\n // over its whole life, so it must repaint every visible frame — the\n // signature alone would freeze it between edits (the olVisSig trap).\n if (ol0.track) olAnim = true\n // A layer CARD is painted on its own plane every frame it is visible.\n if (ol0.card) olAnim = true\n // An HTML layer whose source just changed is rasterizing: keep repainting\n // until its picture lands, or the edit would not appear until something\n // else happened to dirty the canvas.\n if (ol0.html && !(ns.videoCache && ns.videoCache.get(ol0.key))) olAnim = true\n // A LIVE html layer is a new picture every frame it is on screen.\n if (ol0.html && ol0.html.live) olAnim = true\n // Video overlays advance every frame; images redraw until decoded.\n if (ol0.kind === 'video') olAnim = true\n else if (ol0.kind === 'image') {\n var olEl0 = ns.videoCache && ns.videoCache.get(ol0.key)\n if (!(olEl0 && olEl0.complete && olEl0.naturalWidth)) olAnim = true\n }\n }\n // A clip that left the document takes its picture with it: the cache\n // holds decoded images, and a session's edits would otherwise pile up.\n if (ns.htmlLast) {\n for (var hlK in ns.htmlLast) {\n var hlSeen = false\n for (var hlI = 0; hlI < ols.length; hlI++) if (ols[hlI].id === hlK) { hlSeen = true; break }\n if (hlSeen) continue\n if (ns.htmlKeys && ns.htmlKeys[hlK] && ns.videoCache) ns.videoCache.delete(ns.htmlKeys[hlK])\n delete ns.htmlLast[hlK]\n if (ns.htmlKeys) delete ns.htmlKeys[hlK]\n if (ns.htmlWant) delete ns.htmlWant[hlK]\n if (ns.htmlErrors) delete ns.htmlErrors[hlK]\n }\n }\n var ovSig = W + 'x' + H + '|' + olVisSig\n var ovDirty = ov.sig !== ovSig || ov.active || olAnim\n // The layer cards visible this frame, with their pose, for the card\n // planes below (the overlay loop collects them instead of painting).\n var lcList = []\n // Acquire a media overlay's element through the shared cache and sync it\n // to CLIP-LOCAL time (pure f(t); muted always — soundtracks belong to\n // doc.audio). Null until it has a frame to draw. One seam for the flat\n // picture and the layer card.\n function olAcquire(ol, olT) {\n var olEl = ns.videoCache ? ns.videoCache.get(ol.key) : null\n // An HTML layer's key is a CACHE HANDLE, not a URL: never hand it to an\n // <img>. A miss means setup has not built it (a live edit), so ask once\n // per key and let a later frame pick it up; a build that failed stays\n // asked, and the next source edit is a new key.\n if (ol.html) {\n if (ol.html.live) {\n // A live layer's picture is f(t): the key carries clip-local time on\n // the ${HTML_LAYER_LIVE_HZ} Hz grid, the builder keeps ONE picture per clip\n // (the previous moment's is evicted as the next lands) and the last\n // landed picture is drawn while the next composes, so the preview\n // never blinks. A build in flight for this very moment is not asked\n // again (htmlWant holds the moment). In capture the build rides\n // pendingDecodes, so the frame settles and paints again with the\n // exact picture (the harness's two-phase settle): the export is\n // deterministic where the preview is merely prompt.\n var hlQ = Math.round(olT * ${HTML_LAYER_LIVE_HZ})\n var hlKey = ol.key + '@' + hlQ\n var hlEl = ns.videoCache ? ns.videoCache.get(hlKey) : null\n if (!hlEl && ns.buildHtmlLayer && !(ns.htmlWant && ns.htmlWant[ol.id] === hlKey)) {\n var hlP = ns.buildHtmlLayer(ol, { key: hlKey, t: hlQ / ${HTML_LAYER_LIVE_HZ} })\n if (ns.pendingDecodes && hlP && hlP.finally) {\n ns.pendingDecodes.add(hlP)\n hlP.finally(function () { ns.pendingDecodes.delete(hlP) })\n }\n }\n return hlEl || (ns.htmlLast && ns.htmlLast[ol.id]) || null\n }\n if (!olEl && ns.buildHtmlLayer) {\n var hlB = ns.htmlBuilding || (ns.htmlBuilding = {})\n if (!hlB[ol.key]) { hlB[ol.key] = 1; ns.buildHtmlLayer(ol) }\n }\n // While the edited source rasterizes, keep the clip's previous picture.\n return olEl || (ns.htmlLast && ns.htmlLast[ol.id]) || null\n }\n if (!olEl && ns.videoCache) {\n if (ol.kind === 'image') {\n olEl = new Image()\n olEl.crossOrigin = 'anonymous'\n olEl.src = ol.key\n } else {\n olEl = document.createElement('video')\n olEl.crossOrigin = 'anonymous'\n olEl.muted = true\n olEl.playsInline = true\n olEl.preload = 'auto'\n olEl.src = ol.key\n olEl.load()\n }\n ns.videoCache.set(ol.key, olEl)\n }\n if (!olEl) return null\n var olIsImg = ol.kind === 'image'\n if (!olIsImg && olEl.play) {\n var olDur = olEl.duration || 0\n var olMT = olT\n if (ol.loop && olDur > 0) olMT = olT % olDur\n else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)\n try {\n if (playing) {\n if (olEl.playbackRate !== 1) olEl.playbackRate = 1\n var olDrift = Math.abs(olEl.currentTime - olMT)\n if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT\n if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }\n else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }\n } else {\n if (!olEl.paused) olEl.pause()\n var olTarget = Math.min(olMT, olEl.duration || olMT)\n // Coalesce scrub seeks (the backgroundMedia pattern): re-assigning\n // currentTime aborts the in-flight seek, so a per-frame scrub keeps\n // a remote source seeking forever. Defer until 'seeked' lands.\n if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {\n if (ns.pendingDecodes) {\n var olDp = new Promise(function (resolve) {\n var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }\n olEl.addEventListener('seeked', olDone)\n setTimeout(olDone, 250)\n })\n ns.pendingDecodes.add(olDp)\n olDp.finally(function () { ns.pendingDecodes.delete(olDp) })\n }\n olEl.currentTime = olTarget\n }\n }\n } catch (e) {}\n }\n // Video readiness is STICKY through seeks (the cam-bubble pattern):\n // readyState dips below HAVE_CURRENT_DATA while a scrub seek is in\n // flight, and this layer repaints every frame a video clip is visible —\n // gating each frame on it would blink the clip out for the whole drag.\n // After the first decoded frame, keep drawing: Chrome paints the\n // element's retained frame mid-seek.\n if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true\n var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)\n return olReady ? olEl : null\n }\n if (ovDirty) {\n ovC.clearRect(0, 0, W, H)\n // --- text overlays (compositor v2): OUTPUT-anchored clips, styles resolved\n // at lowering (fs/weight/stack/color/shadow are plain values — no registry),\n // drawn ABOVE the cam bubble in screen space. Enter/exit are pure f(t): fade\n // and rise over the transition window; center-anchored multi-line text with a\n // legibility shadow. ol.x/ol.y are FRACTIONS of the frame [0..1] (the zoom\n // cx/cy convention) so positions survive aspect-ratio changes; font size is\n // design px × s (H-relative — stable across aspects). Geometry MIRRORS\n // overlayText.ts overlayRect/overlayFontString (change together — the\n // on-canvas picking depends on it). Locals ol-prefixed (one var scope).\n for (var oj = 0; oj < ols.length; oj++) {\n var ol = ols[oj]\n var olT = t - ol.start\n if (olT < 0 || olT > ol.dur) continue\n if (ol.html && ns.htmlSafe === false) continue\n var olA = 1, olYof = 0, olScl = 1, olBlur = 0\n if (ol.fx && ol.fx.u === 'block') {\n // fx owns the entrance; block unit = the legacy presets generalized\n // (fade/rise identical math, plus pop/blur/typewriter at clip level).\n if (olT < ol.fx.dur && ol.fx.k !== 'typewriter') {\n var olU2 = Math.max(0, olT / ol.fx.dur)\n var olE2 = 1 - Math.pow(1 - olU2, 3)\n if (ol.fx.k === 'pop') {\n olA = Math.min(1, olU2 * 2)\n // easeOutBack on the RAW progress (olE2 is already eased).\n olScl = 1 + 2.70158 * Math.pow(olU2 - 1, 3) + 1.70158 * Math.pow(olU2 - 1, 2)\n } else {\n olA = olE2\n if (ol.fx.k === 'rise') olYof = (1 - olE2) * 24 * s\n if (ol.fx.k === 'blur') olBlur = (1 - olE2) * ol.fs * ol.scale * s * 0.12\n }\n }\n if (ol.fx.k === 'typewriter' && olT < ol.fx.dur) olA = 0\n } else if (!ol.fx && ol.enter !== 'none' && olT < olTD) {\n var olU = olT / olTD\n olU = 1 - Math.pow(1 - olU, 3)\n olA = olU\n if (ol.enter === 'rise') olYof = (1 - olU) * 24 * s\n }\n if (ol.exit !== 'none' && ol.dur - olT < olTD) {\n var olV = (ol.dur - olT) / olTD\n olV = 1 - Math.pow(1 - olV, 3)\n olA = Math.min(olA, olV)\n if (ol.exit === 'rise') olYof = -(1 - olV) * 24 * s\n }\n // Pose keyframes: sample the clip-local [x, y, scale, rot, opacity]\n // track at olT; absent = the static transform. Pose opacity is a\n // MULTIPLIER on the entrance/exit alpha.\n var olPose = null\n if (ol.track && ol.track.keyframes && ol.track.keyframes.length) olPose = TL.sample(ol.track, olT, TL.lerpArray)\n var olMX = olPose ? olPose[0] : ol.x\n var olMY = olPose ? olPose[1] : ol.y\n var olMS = olPose ? olPose[2] : ol.scale\n var olMR = olPose ? olPose[3] : ol.rot\n if (olPose) olA *= Math.max(0, Math.min(1, olPose[4]))\n if (olA <= 0.004) continue\n if (ol.kind === 'image' || ol.kind === 'video') {\n // A layer CARD is painted by the card painter on its own plane, below:\n // collect it with its pose and skip the flat picture.\n if (ol.card) {\n lcList.push({ ol: ol, t: olT, a: olA * (ol.opacity == null ? 1 : ol.opacity), x: olMX, y: olMY, s: olMS, r: olMR, yof: olYof })\n continue\n }\n // Media overlay: lazy-acquire through the shared cache (SET_DATA-added\n // clips load without a LOAD — the backgroundMedia pattern), sync video\n // to CLIP-LOCAL time (pure f(t)), draw a rounded media card centered on\n // the fraction anchor. Muted always — soundtracks belong to doc.audio.\n var olEl = olAcquire(ol, olT)\n if (!olEl) continue\n var olIsImg = ol.kind === 'image'\n var olNW = (olIsImg ? olEl.naturalWidth : olEl.videoWidth) || 16\n var olNH = (olIsImg ? olEl.naturalHeight : olEl.videoHeight) || 9\n var olDW = ol.w * W * olMS\n var olDH = olDW * (olNH / olNW)\n var olRad = Math.min((ol.radius || 0) * s, olDH / 2)\n ovC.save()\n ovC.globalAlpha = olA * (ol.opacity == null ? 1 : ol.opacity)\n ovC.translate(olMX * W, olMY * H + olYof)\n if (olMR) ovC.rotate(olMR * Math.PI / 180)\n // Card shadow (absent = 'soft', the baked look docs predating the field render;\n // 'strong' floats harder; 'none' is the flat cutout), then the media\n // clipped to rounded corners, then an optional border stroke drawn\n // OVER the edge — outside the clip, or half the stroke vanishes.\n var olShadow = ol.shadow || 'soft'\n if (olShadow !== 'none') {\n ovC.save()\n ovC.shadowColor = olShadow === 'strong' ? 'rgba(0,0,0,0.5)' : 'rgba(0,0,0,0.35)'\n ovC.shadowBlur = (olShadow === 'strong' ? 48 : 24) * s\n ovC.shadowOffsetY = (olShadow === 'strong' ? 16 : 8) * s\n ovC.fillStyle = '#000'\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.fill()\n ovC.restore()\n }\n ovC.save()\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.clip()\n try { ovC.drawImage(olEl, -olDW / 2, -olDH / 2, olDW, olDH) } catch (e) {}\n ovC.restore()\n if (ol.border && ol.border.width > 0) {\n ovC.strokeStyle = ol.border.color || '#ffffff'\n ovC.lineWidth = ol.border.width * s\n rr(-olDW / 2, -olDH / 2, olDW, olDH, olRad, ovC); ovC.stroke()\n }\n ovC.restore()\n continue\n }\n var olPx = ol.fs * olMS * s\n // Faces arriving LIVE: SET_DATA never re-runs SETUP, so the first text\n // overlay a session gains registers the house faces here (a title added\n // to a fresh document used to paint in the stack's system fallback until\n // the next cold load), and an override face lazy-loads the same way.\n // Keyed per window, so SETUP's cold-load faces and repeat frames never\n // re-register; fail-open, frames repaint as each face lands.\n if (typeof FontFace !== 'undefined') {\n var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\n var ofBase = d.overlayFonts || ${JSON.stringify(OVERLAY_FONT_FACES)}\n for (var ofi = 0; ofi < ofBase.length; ofi++) {\n var ofB = ofBase[ofi]\n var ofBK = ofB.family + '|' + ofB.weight\n if (ofSet[ofBK]) continue\n ofSet[ofBK] = 1\n try {\n var ofBF = new FontFace(ofB.family, 'url(' + ofB.url + ')', { weight: String(ofB.weight) })\n document.fonts.add(ofBF)\n ofBF.load().catch(function () {})\n } catch (e) {}\n }\n if (ol.face) {\n var ofKey = ol.face.f + '|' + ol.face.w\n if (!ofSet[ofKey]) {\n ofSet[ofKey] = 1\n try {\n var ofFace = new FontFace(ol.face.f, 'url(' + ol.face.u + ')', { weight: String(ol.face.w) })\n document.fonts.add(ofFace)\n ofFace.load().catch(function () {})\n } catch (e) {}\n }\n }\n }\n ovC.save()\n ovC.globalAlpha = olA\n ovC.translate(olMX * W, olMY * H + olYof)\n if (olMR) ovC.rotate(olMR * Math.PI / 180)\n // Block-unit fx entrance (pop scale / blur) — clip-level, about the anchor.\n if (olScl !== 1) ovC.scale(olScl, olScl)\n if (olBlur > 0.05) ovC.filter = 'blur(' + olBlur.toFixed(2) + 'px)'\n // Style overrides ride ctx.data (sty/ls/lh/align/stroke baked only when\n // non-default — parity — but READ unconditionally: every knob is a live\n // SET_DATA by construction).\n ovC.font = (ol.sty ? ol.sty + ' ' : '') + ol.weight + ' ' + olPx + 'px ' + ol.stack\n ovC.textAlign = 'center'\n ovC.textBaseline = 'middle'\n ovC.letterSpacing = ((ol.ls || 0) * olMS * s) + 'px'\n var olLines = ol.lines || ['']\n // maxWidth wrap (ol.mw = frame-width fraction): greedy over word tokens\n // (/\\\\S+\\\\s*/ — the SAME tokenization fx uses, trailing spaces kept, so\n // unit sequences stay byte-identical) at measured widths. MIRRORS\n // wrapOverlayLines in overlayText.ts — change together. A token wider\n // than the budget gets its own line; explicit \\\\n lines wrap independently.\n if (ol.mw) {\n var olWMax = ol.mw * W\n var olWrapped = []\n for (var olwl = 0; olwl < olLines.length; olwl++) {\n var olWLine = olLines[olwl]\n if (!olWLine || ovC.measureText(olWLine).width <= olWMax) {\n olWrapped.push(olWLine)\n continue\n }\n var olToks = olWLine.match(/\\\\S+\\\\s*/g) || [olWLine]\n var olCur = ''\n for (var olti = 0; olti < olToks.length; olti++) {\n if (!olCur) { olCur = olToks[olti]; continue }\n if (ovC.measureText(olCur + olToks[olti]).width <= olWMax) {\n olCur += olToks[olti]\n } else {\n olWrapped.push(olCur)\n olCur = olToks[olti]\n }\n }\n if (olCur) olWrapped.push(olCur)\n }\n olLines = olWrapped.length ? olWrapped : ['']\n }\n var olLH = olPx * (ol.lh || ${OVERLAY_LINE_HEIGHT})\n var olY0 = -((olLines.length - 1) * olLH) / 2\n // Per-line widths: needed by the pill, by left/right alignment (lines\n // draw centered; alignment is an x offset against the widest line), and\n // by per-unit fx (units place by prefix advance from the line's left edge).\n var olFx = ol.fx && ol.fx.units.length ? ol.fx : null\n // With wrap active, baked per-line unit arrays regroup onto the WRAPPED\n // lines. Wrapping never reorders: word/char units consume in flat order\n // by string length (wrapped lines are token concatenations); 'line'\n // units become one per wrapped line. Flat delay order is unchanged.\n if (olFx && ol.mw) {\n var olFlat = []\n for (var olfi = 0; olfi < olFx.units.length; olfi++) {\n for (var olfj = 0; olfj < olFx.units[olfi].length; olfj++) {\n olFlat.push(olFx.units[olfi][olfj])\n }\n }\n var olRe = []\n if (olFx.u === 'line') {\n for (var olri = 0; olri < olLines.length; olri++) olRe.push([olLines[olri]])\n } else {\n var olFk = 0\n for (var olri2 = 0; olri2 < olLines.length; olri2++) {\n var olNeed = olLines[olri2].length\n var olArr = []\n var olGot = 0\n while (olFk < olFlat.length && olGot < olNeed) {\n olArr.push(olFlat[olFk])\n olGot += olFlat[olFk].length\n olFk++\n }\n olRe.push(olArr)\n }\n }\n var olReN = 0\n for (var olrn = 0; olrn < olRe.length; olrn++) olReN += olRe[olrn].length\n olFx = { k: olFx.k, u: olFx.u, d: olFx.d, st: olFx.st, dur: olFx.dur, tt: olFx.tt, units: olRe, n: olReN }\n }\n var olLWs = null, olMaxW = 0\n if (ol.box || ol.align || olFx) {\n olLWs = []\n for (var olwi = 0; olwi < olLines.length; olwi++) {\n var olw = ovC.measureText(olLines[olwi]).width\n olLWs.push(olw)\n if (olw > olMaxW) olMaxW = olw\n }\n }\n // Background pill (ol.box, baked design px at fs): drawn BEFORE the text\n // and before the legibility shadow config, so the pill never inherits the\n // text shadow. Geometry mirrors overlayRect's inflation — change together.\n if (ol.box) {\n var obPX = ol.box.px * olMS * s\n var obPY = ol.box.py * olMS * s\n var obFullW = olMaxW + obPX * 2\n var obFullH = olLines.length * olLH + obPY * 2\n var obR = Math.min(ol.box.r * olMS * s, obFullH / 2)\n ovC.save()\n ovC.globalAlpha = olA * ol.box.o\n ovC.fillStyle = ol.box.c\n rr(-obFullW / 2, -obFullH / 2, obFullW, obFullH, obR, ovC)\n ovC.fill()\n ovC.restore()\n }\n if (ol.shadow > 0) {\n ovC.shadowColor = 'rgba(0,0,0,' + ol.shadow + ')'\n ovC.shadowBlur = olPx * 0.25\n ovC.shadowOffsetY = olPx * 0.04\n }\n ovC.fillStyle = ol.color\n if (!olFx) {\n for (var ok = 0; ok < olLines.length; ok++) {\n var olXof = 0\n if (ol.align && olLWs) {\n olXof = ol.align === 'left'\n ? (olLWs[ok] - olMaxW) / 2\n : (olMaxW - olLWs[ok]) / 2\n }\n var olLY = olY0 + ok * olLH\n if (ol.stroke) {\n ovC.strokeStyle = ol.stroke.c\n ovC.lineWidth = ol.stroke.w * olMS * s\n ovC.lineJoin = 'round'\n ovC.strokeText(olLines[ok], olXof, olLY)\n }\n ovC.fillText(olLines[ok], olXof, olLY)\n }\n } else {\n // Per-unit entrance: units draw LEFT-aligned at prefix advances\n // measured from the full line (exact bar cross-unit kerning), so the\n // settled frame matches the non-fx layout. Per-unit progress is pure\n // f(t): delay = order(index)·st, eased over dur; typewriter is a step\n // reveal. Stroke-under-fill per unit; pill/shadow config above apply.\n ovC.textAlign = 'left'\n var olIdx = 0\n for (var ok2 = 0; ok2 < olFx.units.length; ok2++) {\n var olUs = olFx.units[ok2]\n var olLW2 = olLWs ? olLWs[ok2] : 0\n var olLY2 = olY0 + ok2 * olLH\n var olXof2 = 0\n if (ol.align) {\n olXof2 = ol.align === 'left'\n ? (olLW2 - olMaxW) / 2\n : (olMaxW - olLW2) / 2\n }\n var olXb = olXof2 - olLW2 / 2\n var olPref = '', olPW = 0\n for (var ou = 0; ou < olUs.length; ou++, olIdx++) {\n var olOrd = olFx.d === 1\n ? (olFx.n - 1 - olIdx)\n : olFx.d === 2\n ? Math.abs(olIdx - (olFx.n - 1) / 2)\n : olIdx\n var olT2 = olT - olOrd * olFx.st\n var olNext = olPref + olUs[ou]\n var olNW = ovC.measureText(olNext).width\n var olUW = olNW - olPW\n var olUX = olXb + olPW\n var olUA = 1, olUu = 1\n if (olFx.k === 'typewriter') {\n olUA = olT2 >= 0 ? 1 : 0\n } else {\n olUu = Math.max(0, Math.min(1, olT2 / olFx.dur))\n var olUE = 1 - Math.pow(1 - olUu, 3)\n olUA = olFx.k === 'pop' ? Math.min(1, olUu * 2) : olUE\n }\n var olUnit = olUs[ou]\n olPref = olNext\n olPW = olNW\n if (olUA <= 0.004) continue\n ovC.save()\n ovC.globalAlpha = olA * olUA\n if (olUu < 1) {\n if (olFx.k === 'rise') {\n ovC.translate(0, (1 - (1 - Math.pow(1 - olUu, 3))) * 24 * s)\n } else if (olFx.k === 'pop') {\n var olPS = 1 + 2.70158 * Math.pow(olUu - 1, 3) + 1.70158 * Math.pow(olUu - 1, 2)\n ovC.translate(olUX + olUW / 2, olLY2)\n ovC.scale(olPS, olPS)\n ovC.translate(-(olUX + olUW / 2), -olLY2)\n } else if (olFx.k === 'blur') {\n ovC.filter = 'blur(' + ((1 - olUu) * olPx * 0.12).toFixed(2) + 'px)'\n }\n }\n if (ol.stroke) {\n ovC.strokeStyle = ol.stroke.c\n ovC.lineWidth = ol.stroke.w * olMS * s\n ovC.lineJoin = 'round'\n ovC.strokeText(olUnit, olUX, olLY2)\n }\n ovC.fillText(olUnit, olUX, olLY2)\n ovC.restore()\n }\n }\n }\n ovC.restore()\n }\n ov.sig = ovSig\n ov.active = olVisSig !== ''\n if (ov.texture) ov.texture.needsUpdate = true\n }\n\n // --- object clips: reconcile a mesh pool against d.objects — the\n // interpreter pattern in 3D. Add/remove/asset-change are live SET_DATA\n // (create/dispose here); transforms + span fades + animation are pure f(t).\n // Frame-fraction position maps onto the frustum plane at the object's depth\n // (stage.ts math); scale is a fraction of the frame height at the CARD depth\n // (closer objects render bigger — the perspective cue). Locals ob*.\n var obC = r.objects\n var THREE3 = ctx.THREE\n if (obC && obC.group && obC.pool && THREE3) {\n var obs = d.objects || []\n var obSeen = {}\n var obTan = Math.tan(${CARD_FOV} * Math.PI / 180 / 2)\n var obRefH = 2 * Math.abs(${CARD_Z}) * obTan // frame height at the reference depth\n var obAspect = W / H\n // The anchor's camera: a prop sits on THAT camera's frustum plane\n // at its depth, at the frame fraction it was placed at, so the host's\n // picking rect (the same fraction) holds on any anchor. The recording's\n // camera (the origin, looking down -z, CARD_FOV) reduces this to the\n // constants its card program shares; a user program's camera can be\n // anywhere, and its lights light the props. Scale stays \"a fraction of\n // the frame height at the reference depth\" because obRefH follows the\n // camera's fov.\n var obCam = ctx.camera && ctx.camera.isPerspectiveCamera && ctx.camera.quaternion ? ctx.camera : null\n // An ORTHOGRAPHIC anchor camera (a program's \\`fullscreen\\` preset is\n // OrthographicCamera(-1, 1, 1, -1, 0, 1); the generic ortho preset spans\n // width/zoom): the prop sits on the camera's own box at mid-depth, at the\n // frame fraction, scaled against the box's height. The perspective\n // constants put it metres behind a far plane of 1, which is how a prop on\n // a shader program drew its picking box and nothing else.\n var obOrtho = !obCam && ctx.camera && ctx.camera.isOrthographicCamera && ctx.camera.quaternion ? ctx.camera : null\n var obB = null\n if (obCam || obOrtho) {\n var obBCam = obCam || obOrtho\n obB = obC.basis || (obC.basis = { f: new THREE3.Vector3(), r: new THREE3.Vector3(), u: new THREE3.Vector3(), q: new THREE3.Quaternion(), e: new THREE3.Euler() })\n obB.f.set(0, 0, -1).applyQuaternion(obBCam.quaternion)\n obB.r.set(1, 0, 0).applyQuaternion(obBCam.quaternion)\n obB.u.set(0, 1, 0).applyQuaternion(obBCam.quaternion)\n }\n var obOW = 0, obOH = 0, obOCX = 0, obOCY = 0, obOD = 0\n if (obCam) {\n obTan = Math.tan(obCam.fov * Math.PI / 180 / 2)\n obRefH = 2 * Math.abs(${CARD_Z}) * obTan\n } else if (obOrtho) {\n var obOZ = obOrtho.zoom || 1\n obOW = (obOrtho.right - obOrtho.left) / obOZ\n obOH = (obOrtho.top - obOrtho.bottom) / obOZ\n obOCX = (obOrtho.right + obOrtho.left) / 2 / obOZ\n obOCY = (obOrtho.top + obOrtho.bottom) / 2 / obOZ\n obOD = obOrtho.near + (obOrtho.far - obOrtho.near) * 0.5\n obRefH = obOH\n // The box's units are not square on the canvas (the fullscreen preset\n // is -1..1 both ways over 16:9), so a sphere would draw as an ellipse.\n // The correction is a camera-space squash AFTER the prop's own\n // rotation: the group is aligned with the camera and scaled on its x\n // by the pixel aspect, and ortho props are placed in the group's\n // local space.\n var obAX = (obOW * H) / (obOH * W)\n obC.group.position.copy(obOrtho.position)\n obC.group.quaternion.copy(obOrtho.quaternion)\n obC.group.scale.set(obAX, 1, 1)\n }\n // A program's scene lights its own props, when it has lights at all: a\n // shader program has none, and an unlit MeshStandardMaterial is black.\n // Once, when the first prop appears: add the entry's pair only if no light\n // is in the scene (the recording's card program carries its own).\n if (obs.length && !obC.lit && ctx.scene && ctx.scene.traverse) {\n obC.lit = true\n var obHasLight = false\n ctx.scene.traverse(function (obN0) { if (obN0.isLight) obHasLight = true })\n if (!obHasLight) {\n var obAmb = new THREE3.AmbientLight(0xffffff, 0.75)\n var obDir = new THREE3.DirectionalLight(0xffffff, 1.4)\n obDir.position.set(2, 3, 4)\n obC.group.add(obAmb)\n obC.group.add(obDir)\n }\n }\n for (var bi = 0; bi < obs.length; bi++) {\n var ob = obs[bi]\n var obIsGltf = ob.asset.kind === 'gltf'\n var obIsT3 = ob.asset.kind === 'text3d'\n if (obIsGltf && !(ns.objCache && ns.objCache.get(ob.asset.key))) continue // not loaded (yet)\n if (obIsT3 && !(ns.fontCache && ns.fontCache.get(ob.asset.url))) {\n // Live SET_DATA additions never re-run SETUP — lazy-load the\n // typeface once (fail-open) and skip the clip until it lands.\n var obT3P = ns.fontPending || (ns.fontPending = {})\n if (!obT3P[ob.asset.url] && ctx.loaders && ctx.loaders.FontLoader) {\n obT3P[ob.asset.url] = 1\n try {\n new ctx.loaders.FontLoader().load(ob.asset.url, (function (obT3U) {\n return function (obT3F) {\n var obT3C = ns.fontCache || (ns.fontCache = new Map())\n obT3C.set(obT3U, obT3F)\n }\n })(ob.asset.url), undefined, function () {})\n } catch (e) {}\n }\n continue\n }\n obSeen[ob.id] = true\n var obSig = obIsGltf ? 'gltf|' + ob.asset.key\n : obIsT3 ? 'text3d|' + JSON.stringify(ob.asset)\n : ob.asset.shape + '|' + ob.asset.color\n var obE = obC.pool.get(ob.id)\n if (obE && obE.sig !== obSig) {\n obC.group.remove(obE.mesh)\n if (obE.mesh.traverse) obE.mesh.traverse(function (obN4) {\n if (obN4.geometry && obN4.geometry.dispose) obN4.geometry.dispose()\n if (obN4.material && obN4.material.dispose) obN4.material.dispose()\n })\n if (obE.mesh.geometry) obE.mesh.geometry.dispose()\n if (obE.mesh.material) obE.mesh.material.dispose()\n obE = null\n }\n if (!obE && obIsGltf) {\n // Clone the cached scene with CLONED materials (fade opacity must not\n // leak across instances); normalize scale via the cached bbox factor.\n var obSrc = ns.objCache.get(ob.asset.key)\n var obRoot = obSrc.scene.clone(true)\n obRoot.traverse(function (obN) {\n if (obN.isMesh) {\n obN.material = obN.material.clone()\n obN.material.transparent = true\n obN.renderOrder = 1.5\n }\n })\n obC.group.add(obRoot)\n obE = { mesh: obRoot, sig: obSig, norm: obSrc.norm, gltf: true }\n obC.pool.set(ob.id, obE)\n }\n if (!obE && obIsT3 && ctx.utils && ctx.utils.TextGeometry) {\n // Extruded text from the cached typeface. Geometry is centered and\n // bbox-normalized (norm = 1/maxDim, the GLB convention) so\n // transform3d.scale means the same thing for every asset kind.\n // Materials come pre-resolved from lowering (plain params, single-\n // sided by default — the SwiftShader-safe shape).\n var obT3Font = ns.fontCache.get(ob.asset.url)\n var obT3Geo = new ctx.utils.TextGeometry(ob.asset.text, {\n font: obT3Font,\n size: 1,\n depth: ob.asset.depth,\n curveSegments: 8,\n bevelEnabled: !!ob.asset.bevel,\n bevelThickness: 0.02,\n bevelSize: 0.015,\n bevelSegments: 2,\n })\n obT3Geo.computeBoundingBox()\n obT3Geo.center()\n var obT3Box = obT3Geo.boundingBox\n var obT3Max = Math.max(\n obT3Box.max.x - obT3Box.min.x,\n obT3Box.max.y - obT3Box.min.y,\n obT3Box.max.z - obT3Box.min.z,\n ) || 1\n var obT3Mat = ob.asset.mat.type === 'physical'\n ? new THREE3.MeshPhysicalMaterial(ob.asset.mat.params)\n : new THREE3.MeshStandardMaterial(ob.asset.mat.params)\n obT3Mat.transparent = true\n var obT3Mesh = new THREE3.Mesh(obT3Geo, obT3Mat)\n obT3Mesh.renderOrder = 1.5\n obC.group.add(obT3Mesh)\n // baseA: presets may be translucent (glass) — the span fade\n // multiplies onto it instead of stomping it to 1 during holds.\n obE = {\n mesh: obT3Mesh,\n sig: obSig,\n norm: 1 / obT3Max,\n baseA: ob.asset.mat.params.opacity == null ? 1 : ob.asset.mat.params.opacity,\n }\n obC.pool.set(ob.id, obE)\n }\n if (!obE && !obIsT3) {\n var obGeo = ob.asset.shape === 'sphere' ? new THREE3.SphereGeometry(0.55, 32, 20)\n : ob.asset.shape === 'torus' ? new THREE3.TorusGeometry(0.45, 0.18, 20, 40)\n : ob.asset.shape === 'knot' ? new THREE3.TorusKnotGeometry(0.4, 0.13, 80, 14)\n : new THREE3.BoxGeometry(0.9, 0.9, 0.9)\n var obMat = new THREE3.MeshStandardMaterial({\n color: ob.asset.color, metalness: 0.55, roughness: 0.35, transparent: true,\n })\n var obMesh = new THREE3.Mesh(obGeo, obMat)\n obMesh.renderOrder = 1.5\n obC.group.add(obMesh)\n obE = { mesh: obMesh, sig: obSig }\n obC.pool.set(ob.id, obE)\n }\n if (!obE) continue // text3d without the TextGeometry util — skip\n var obM = obE.mesh\n // Span gate with soft edge fades (OUTPUT-anchored, like overlays).\n var obA = 1\n if (ob.span) {\n var obT = t - ob.span.start\n if (obT < 0 || obT > ob.span.duration) { obM.visible = false; continue }\n var obTD = ${OVERLAY_TRANSITION_DUR}\n if (obT < obTD) obA = obT / obTD\n if (ob.span.duration - obT < obTD) obA = Math.min(obA, (ob.span.duration - obT) / obTD)\n }\n obM.visible = true\n if (obE.gltf) {\n var obA2 = obA\n obM.traverse(function (obN2) { if (obN2.isMesh) obN2.material.opacity = obA2 })\n } else {\n obM.material.opacity = obA * (obE.baseA == null ? 1 : obE.baseA)\n }\n // Pose keyframes: a clip-local [x,y,z,rx,ry,rz,scale] track over\n // the full 3D transform; spin/float presets compose ADDITIVELY on top\n // of the sampled pose (they are offsets, poses are the base).\n var obPose = null\n if (ob.track && ob.track.keyframes && ob.track.keyframes.length) obPose = TL.sample(ob.track, t - (ob.span ? ob.span.start : 0), TL.lerpArray)\n var obPX = obPose ? obPose[0] : ob.x\n var obPY = obPose ? obPose[1] : ob.y\n var obPZ = obPose ? obPose[2] : ob.z\n var obPRX = obPose ? obPose[3] : ob.rx\n var obPRY = obPose ? obPose[4] : ob.ry\n var obPRZ = obPose ? obPose[5] : ob.rz\n var obPS = obPose ? obPose[6] : ob.scale\n var obDist = Math.abs(${CARD_Z}) - obPZ // z is toward the camera\n var obPlaneH = 2 * obDist * obTan\n var obY = -(obPY - 0.5) * obPlaneH\n if (ob.anim === 'float') obY += Math.sin(t * (Math.PI * 2 / 5)) * obRefH * 0.012\n var obX = (obPX - 0.5) * obPlaneH * obAspect\n var obRy = obPRY * Math.PI / 180\n if (ob.anim === 'spin') obRy += t * 0.9\n if (obOrtho) {\n // Group-local (the group carries the camera pose and the aspect squash).\n obM.position.set((obOCX + (obPX - 0.5) * obOW) / obAX, obOCY - (obPY - 0.5) * obOH, -obOD)\n obB.e.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n obM.quaternion.setFromEuler(obB.e)\n } else if (obB) {\n obM.position.copy(obCam.position).addScaledVector(obB.f, obDist).addScaledVector(obB.r, obX).addScaledVector(obB.u, obY)\n obB.e.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n obM.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))\n } else {\n obM.position.set(obX, obY, ${CARD_Z} + obPZ)\n obM.rotation.set(obPRX * Math.PI / 180, obRy, obPRZ * Math.PI / 180)\n }\n obM.scale.setScalar(obPS * obRefH * (obE.norm || 1))\n }\n // --- layer cards (many cards): a media overlay that names a frame is\n // drawn by the card painter on its OWN plane in the prop group\n // (renderOrder 1.25: above the primary card, below the props): the\n // layered shadow (the primary's table and strengths), the media clipped\n // to its corners, the browser bar after it, the border, and for a\n // document media its click rings and cursor dot inside the card, at\n // clip-local time. Chrome scales WITH the card (a smaller window has\n // smaller chrome), the way the primary's scales with cf. The plane is\n // sized to the canvas at the card depth, placed at the clip's anchor,\n // leaned by the card's own rx/ry (the tilt convention) and turned by the\n // clip's rotation. Locals lc*.\n var lcC = obC.cards || (obC.cards = new Map())\n var lcSeen = {}\n var lcK = obRefH / H\n for (var lci = 0; lci < lcList.length; lci++) {\n var lcI = lcList[lci], lcO = lcI.ol, lcCard = lcO.card\n var lcEl = olAcquire(lcO, lcI.t)\n if (!lcEl) continue\n lcSeen[lcO.id] = true\n var lcE = lcC.get(lcO.id)\n if (!lcE) {\n var lcCv = document.createElement('canvas')\n var lcTx = new THREE3.CanvasTexture(lcCv)\n if (THREE3.SRGBColorSpace) lcTx.colorSpace = THREE3.SRGBColorSpace\n if (THREE3.LinearFilter) { lcTx.minFilter = THREE3.LinearFilter; lcTx.magFilter = THREE3.LinearFilter }\n lcTx.generateMipmaps = false\n var lcM = new THREE3.Mesh(new THREE3.PlaneGeometry(1, 1), new THREE3.MeshBasicMaterial({ map: lcTx, transparent: true, depthTest: false, depthWrite: false }))\n lcM.renderOrder = 1.25\n lcM.frustumCulled = false\n obC.group.add(lcM)\n lcE = { mesh: lcM, canvas: lcCv, c2d: lcCv.getContext('2d'), texture: lcTx }\n lcC.set(lcO.id, lcE)\n }\n var lcIsImg = lcO.kind === 'image'\n var lcCrop = lcO.crop || null\n var lcNW = lcCrop ? lcCrop.w : ((lcIsImg ? lcEl.naturalWidth : lcEl.videoWidth) || 16)\n var lcNH = lcCrop ? lcCrop.h : ((lcIsImg ? lcEl.naturalHeight : lcEl.videoHeight) || 9)\n var lcW = Math.max(2, lcO.w * W * lcI.s)\n var lcS = s * (lcW / W)\n var lcBar = lcCard.bar || {}\n var lcBarH = lcBar.kind && lcBar.kind !== 'none' ? (lcBar.height || 44) * lcS : 0\n var lcMH = lcW * (lcNH / lcNW)\n var lcH = lcMH + lcBarH\n var lcRad = Math.min((lcO.radius || 0) * lcS, lcH / 2)\n var lcMg = Math.ceil(170 * lcS)\n var lcCW = Math.ceil(lcW + 2 * lcMg), lcCH = Math.ceil(lcH + 2 * lcMg)\n var lcCv2 = lcE.canvas, lcX = lcE.c2d\n if (lcCv2.width !== lcCW || lcCv2.height !== lcCH) { lcCv2.width = lcCW; lcCv2.height = lcCH }\n lcX.clearRect(0, 0, lcCW, lcCH)\n var lcX0 = lcMg, lcY0 = lcMg\n var lcSh = lcCard.shadow == null ? ${LAYER_CARD_SHADOW} : lcCard.shadow\n var lcRgb = '0,0,0'\n var lcHex = lcCard.shadowColor\n if (typeof lcHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(lcHex)) lcRgb = parseInt(lcHex.slice(1, 3), 16) + ',' + parseInt(lcHex.slice(3, 5), 16) + ',' + parseInt(lcHex.slice(5, 7), 16)\n // the shadows are cast by a body drawn off the canvas and brought back\n // by shadowOffsetX (the primary's seam rule)\n var lcD = lcCW * 2 + lcCH * 2\n if (lcSh > 0) {\n var lcL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]\n for (var lcj = 0; lcj < lcL.length; lcj++) {\n lcX.save()\n lcX.shadowColor = 'rgba(' + lcRgb + ',' + +(lcSh * lcL[lcj][2]).toFixed(3) + ')'\n lcX.shadowBlur = lcL[lcj][0] * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = lcL[lcj][1] * lcS\n lcX.fillStyle = '#000'\n rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()\n lcX.restore()\n }\n }\n if (lcCard.shadowContact > 0) {\n lcX.save()\n lcX.shadowColor = 'rgba(' + lcRgb + ',' + lcCard.shadowContact + ')'\n lcX.shadowBlur = 10 * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = 3 * lcS\n lcX.fillStyle = '#000'\n rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()\n lcX.restore()\n }\n lcX.save()\n rr(lcX0, lcY0, lcW, lcH, lcRad, lcX); lcX.clip()\n var lcMY = lcY0 + lcBarH\n try {\n if (lcCrop) lcX.drawImage(lcEl, lcCrop.x, lcCrop.y, lcCrop.w, lcCrop.h, lcX0, lcMY, lcW, lcMH)\n else lcX.drawImage(lcEl, lcX0, lcMY, lcW, lcMH)\n } catch (e) {}\n if (lcBarH > 0) {\n var lcDark = lcBar.kind.indexOf('dark') >= 0, lcMin = lcBar.kind === 'minimal'\n var lcThm = (lcMin && lcBar.theme) || null\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.bar : '#141417') : lcDark ? '#2a2a2e' : '#e9e9eb'\n lcX.fillRect(lcX0, lcY0, lcW, lcBarH)\n lcX.fillStyle = lcDark || (lcMin && !(lcThm && lcThm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'\n lcX.fillRect(lcX0, lcY0 + lcBarH - lcS, lcW, lcS)\n var lcMid = lcY0 + lcBarH / 2\n if (lcBar.showControls !== false && !lcMin) {\n if (lcBar.kind.indexOf('mac') === 0) {\n var lcLights = ['#ff5f57', '#febc2e', '#28c840']\n for (var lcl = 0; lcl < 3; lcl++) { lcX.fillStyle = lcLights[lcl]; lcX.beginPath(); lcX.arc(lcX0 + (20 + lcl * 20) * lcS, lcMid, 6 * lcS, 0, Math.PI * 2); lcX.fill() }\n } else {\n lcX.strokeStyle = lcDark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'\n lcX.lineWidth = 1.5 * lcS\n var lcG = 4.5 * lcS, lcGx = lcX0 + lcW - 22 * lcS\n lcX.beginPath(); lcX.moveTo(lcGx - lcG, lcMid - lcG); lcX.lineTo(lcGx + lcG, lcMid + lcG); lcX.moveTo(lcGx + lcG, lcMid - lcG); lcX.lineTo(lcGx - lcG, lcMid + lcG); lcX.stroke()\n lcX.strokeRect(lcGx - 28 * lcS - lcG, lcMid - lcG, lcG * 2, lcG * 2)\n lcX.beginPath(); lcX.moveTo(lcGx - 56 * lcS - lcG, lcMid); lcX.lineTo(lcGx - 56 * lcS + lcG, lcMid); lcX.stroke()\n }\n }\n if (lcBar.showUrl !== false && lcBar.url) {\n var lcPW = Math.min(lcW * 0.5, Math.max(200 * lcS, lcW * 0.34)), lcPH = lcBarH - 16 * lcS\n var lcPx = lcX0 + (lcW - lcPW) / 2, lcPy = lcY0 + 8 * lcS\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.pill : '#26262b') : lcDark ? '#1d1d20' : '#ffffff'\n rr(lcPx, lcPy, lcPW, lcPH, lcPH / 2, lcX); lcX.fill()\n lcX.fillStyle = lcMin ? (lcThm ? lcThm.text : '#9a9aa1') : lcDark ? '#a1a1a6' : '#5f5f64'\n lcX.font = 13 * lcS + 'px -apple-system, system-ui, sans-serif'\n lcX.textAlign = 'center'; lcX.textBaseline = 'middle'\n var lcLabel = String(lcBar.url), lcMaxW = lcPW - 28 * lcS\n if (lcX.measureText(lcLabel).width > lcMaxW) {\n while (lcLabel.length > 1 && lcX.measureText(lcLabel + '\\u2026').width > lcMaxW) lcLabel = lcLabel.slice(0, -1)\n lcLabel += '\\u2026'\n }\n lcX.fillText(lcLabel, lcPx + lcPW / 2, lcPy + lcPH / 2)\n lcX.textAlign = 'start'; lcX.textBaseline = 'alphabetic'\n }\n }\n // the media's facts: click rings and the cursor dot, video-anchored,\n // at clip-local time\n var lcCu = lcO.cur\n if (lcCu && lcCu.pts && lcCu.pts.length) {\n var lcSp = lcCu.space || { w: lcNW, h: lcNH }\n var lcSize = (lcCu.size || 24) * lcS * 0.5\n var lcT = lcI.t\n var lcCks = lcCu.clicks || []\n for (var lcc = 0; lcc < lcCks.length; lcc++) {\n var lcCk = lcCks[lcc]\n var lcAge = lcT - lcCk.t\n if (lcAge < -0.08 || lcAge > 0.6) continue\n var lcU = Math.max(0, Math.min(1, (lcAge + 0.08) / 0.68))\n var lcFade = 1 - Math.pow(1 - lcU, 3)\n lcX.save()\n lcX.strokeStyle = 'rgba(255,255,255,' + (0.9 * (1 - lcU)) + ')'\n lcX.lineWidth = Math.max(1, 3 * lcS * (1 - lcU * 0.6))\n lcX.beginPath(); lcX.arc(lcX0 + (lcCk.x / (lcSp.w || lcNW)) * lcW, lcMY + (lcCk.y / (lcSp.h || lcNH)) * lcMH, lcSize * 0.8 + lcFade * 44 * lcS, 0, Math.PI * 2); lcX.stroke()\n lcX.restore()\n }\n var lcPts = lcCu.pts, lcPX = lcPts[0].x, lcPY = lcPts[0].y\n for (var lcp = 0; lcp < lcPts.length; lcp++) { if (lcPts[lcp].t <= lcT) { lcPX = lcPts[lcp].x; lcPY = lcPts[lcp].y } }\n lcX.save()\n lcX.fillStyle = 'rgba(255,255,255,0.95)'; lcX.strokeStyle = 'rgba(0,0,0,0.4)'; lcX.lineWidth = 2 * lcS\n lcX.beginPath(); lcX.arc(lcX0 + (lcPX / (lcSp.w || lcNW)) * lcW, lcMY + (lcPY / (lcSp.h || lcNH)) * lcMH, lcSize, 0, Math.PI * 2); lcX.fill(); lcX.stroke()\n lcX.restore()\n }\n lcX.restore()\n if (lcO.border && lcO.border.width > 0) {\n var lcBW = lcO.border.width * lcS\n lcX.save(); lcX.strokeStyle = lcO.border.color || '#fff'; lcX.lineWidth = lcBW\n rr(lcX0 - lcBW / 2, lcY0 - lcBW / 2, lcW + lcBW, lcH + lcBW, lcRad > 0 ? lcRad + lcBW / 2 : 0, lcX); lcX.stroke()\n lcX.restore()\n }\n lcE.texture.needsUpdate = true\n var lcM2 = lcE.mesh\n lcM2.visible = true\n if (lcM2.material) lcM2.material.opacity = lcI.a\n var lcLean = lcCard.lean || [0, 0]\n var lcRx = (lcLean[0] || 0) * Math.PI / 180, lcRy = (lcLean[1] || 0) * Math.PI / 180, lcRz = -(lcI.r || 0) * Math.PI / 180\n var lcPlW = lcCW * lcK, lcPlH = lcCH * lcK\n var lcWX = (lcI.x - 0.5) * obRefH * obAspect\n var lcWY = -(lcI.y - 0.5) * obRefH - lcI.yof * lcK\n if (obOrtho) {\n lcM2.position.set((obOCX + (lcI.x - 0.5) * obOW) / obAX, obOCY - (lcI.y - 0.5) * obOH, -obOD)\n obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.setFromEuler(obB.e)\n } else if (obB) {\n lcM2.position.copy(obCam.position).addScaledVector(obB.f, Math.abs(${CARD_Z}) - 0.01).addScaledVector(obB.r, lcWX).addScaledVector(obB.u, lcWY)\n obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))\n } else {\n lcM2.position.set(lcWX, lcWY, ${CARD_Z} + 0.01)\n lcM2.rotation.set(lcRx, lcRy, lcRz)\n }\n lcM2.scale.set(lcPlW, lcPlH, 1)\n }\n // A card off screen rests; one gone from the data is disposed.\n lcC.forEach(function (lcE2, lcId) {\n if (lcSeen[lcId]) return\n var lcStill = false\n for (var lcq = 0; lcq < ols.length; lcq++) if (ols[lcq].id === lcId && ols[lcq].card) lcStill = true\n if (lcStill) { lcE2.mesh.visible = false; return }\n obC.group.remove(lcE2.mesh)\n if (lcE2.mesh.geometry && lcE2.mesh.geometry.dispose) lcE2.mesh.geometry.dispose()\n if (lcE2.mesh.material && lcE2.mesh.material.dispose) lcE2.mesh.material.dispose()\n if (lcE2.texture && lcE2.texture.dispose) lcE2.texture.dispose()\n lcC.delete(lcId)\n })\n // Dispose props no longer in the data (live removal).\n obC.pool.forEach(function (obE2, obId) {\n if (!obSeen[obId]) {\n obC.group.remove(obE2.mesh)\n obE2.mesh.traverse\n ? obE2.mesh.traverse(function (obN3) {\n if (obN3.geometry && obN3.geometry.dispose) obN3.geometry.dispose()\n if (obN3.material && obN3.material.dispose) obN3.material.dispose()\n })\n : null\n if (obE2.mesh.geometry) obE2.mesh.geometry.dispose()\n if (obE2.mesh.material) obE2.mesh.material.dispose()\n obC.pool.delete(obId)\n }\n })\n }\n\n}`\n","/**\n * Click extraction — the lowering step that\n * turns the raw cursor track's down/up events into the compact, OUTPUT-anchored\n * records ON_FRAME draws click effects from.\n *\n * OUTPUT-anchored so effects read at constant *viewer* speed: evaluated in\n * source time an 8× speed span would compress a 450 ms ripple to ~56 ms.\n * Baking the output instants here (instead of mapping per frame) is safe\n * because every segment/speed edit re-runs the lowering and ships fresh data —\n * the baked times can never go stale. Pure & deterministic.\n */\nimport { segmentRate } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\nimport type { Segment } from '@vosjs/timeline'\nimport type { CursorTrack, Rect } from '../types'\n\n/** Anticipation lead — effects start this many output seconds before the click. */\nexport const CLICK_FX_PRE = 0.06\n/** Base effect durations in output seconds (× the intensity's `dur`). */\nexport const CLICK_RIPPLE_DUR = 0.45\nexport const CLICK_PULSE_DUR = 0.35\nexport const CLICK_HIGHLIGHT_FADE = 0.35\n/** Synthetic press length when the matching `up` is missing (nav killed it). */\nexport const CLICK_SYNTH_RELEASE = 0.12\n/** A down→up pair longer than this is treated as unmatched (lost `up`). */\nexport const CLICK_PAIR_MAX = 10\n/** Highlight uses the element rect only when it covers ≤ this viewport fraction. */\nexport const CLICK_RECT_MAX_FRAC = 0.35\n/** …and only when the click point sits inside the rect (grown by this margin). */\nconst RECT_CONTAIN_MARGIN = 8\n\nexport interface LoweredClick {\n /** OUTPUT seconds of mousedown. */\n ot: number\n /** OUTPUT seconds of release (real up, clamped into kept footage). */\n up: number\n /** SOURCE seconds of mousedown — ON_FRAME's cross-cut proximity guard. */\n st: number\n /** click point in cursorSpace px. */\n x: number\n y: number\n /** pointer button (0=left). */\n b: number\n /** element rect [x,y,w,h] in cursorSpace px — present only when the\n * highlight style wants it AND it passed the size/containment gates\n * (ON_FRAME stays branch-light: r present = draw highlight). */\n r?: [number, number, number, number]\n}\n\nexport interface ExtractClickOptions {\n /** attach gated element rects (highlight style). */\n rects?: boolean\n /** cursorSpace dims — the rect-size gate's denominator. */\n space: { w: number; h: number }\n /** The media the track belongs to; absent = the primary. Its clicks map through that media's pieces only. */\n media?: string\n}\n\n/**\n * OUTPUT time of the last kept source moment in [sIn, sOut] — the clamped\n * release for presses whose `up` fell in trimmed footage. Same accumulation\n * as lowerToComposition's spanOutputExtent (not imported: that would cycle).\n */\ntype MediaPiece = Segment & { media?: string }\n\n/** A source moment's OUTPUT time through the pieces of its media (null when cut away). */\nfunction outputAt(\n segments: readonly MediaPiece[],\n sourceT: number,\n media?: string,\n): number | null {\n if (!segments.length) return sourceT\n let acc = 0\n for (const s of segments) {\n const rate = segmentRate(s)\n if (sameMedia(s.media, media) && sourceT >= s.in && sourceT <= s.out)\n return acc + (sourceT - s.in) / rate\n acc += Math.max(0, s.out - s.in) / rate\n }\n return null\n}\n\nfunction keptOutputEnd(\n segments: readonly MediaPiece[],\n sIn: number,\n sOut: number,\n media?: string,\n): number | null {\n let acc = 0\n let end: number | null = null\n for (const p of segments) {\n const rate = segmentRate(p)\n if (sameMedia(p.media, media)) {\n const ovIn = Math.max(sIn, p.in)\n const ovOut = Math.min(sOut, p.out)\n if (ovOut > ovIn) end = acc + (ovOut - p.in) / rate\n }\n acc += Math.max(0, p.out - p.in) / rate\n }\n return end\n}\n\nfunction gatedRect(\n rect: Rect,\n x: number,\n y: number,\n space: { w: number; h: number },\n): [number, number, number, number] | null {\n const area = rect.w * rect.h\n const frame = space.w * space.h\n if (!(area > 0) || !(frame > 0) || area / frame > CLICK_RECT_MAX_FRAC)\n return null\n const m = RECT_CONTAIN_MARGIN\n const inside =\n x >= rect.x - m &&\n x <= rect.x + rect.w + m &&\n y >= rect.y - m &&\n y <= rect.y + rect.h + m\n return inside\n ? [round(rect.x), round(rect.y), round(rect.w), round(rect.h)]\n : null\n}\n\n/**\n * Extract OUTPUT-anchored clicks from a raw cursor track. Downs in trimmed-away\n * footage are dropped (they follow their footage, like every source-anchored\n * feature); a press pairs with the next `up` of the same button unless another\n * `down` of that button intervenes (a lost `up` must not chain two presses).\n */\nexport function extractClicks(\n track: CursorTrack,\n segments: readonly MediaPiece[],\n opts: ExtractClickOptions,\n): LoweredClick[] {\n const out: LoweredClick[] = []\n for (let i = 0; i < track.length; i++) {\n const e = track[i]\n if (e.type !== 'down') continue\n const button = e.button ?? 0\n const st = e.t / 1000\n\n // real release: next same-button `up`, unless a same-button `down` intervenes\n let pressLen = CLICK_SYNTH_RELEASE\n for (let j = i + 1; j < track.length; j++) {\n const n = track[j]\n if ((n.button ?? 0) !== button) continue\n if (n.type === 'down') break\n if (n.type === 'up') {\n const len = (n.t - e.t) / 1000\n if (len > 0 && len <= CLICK_PAIR_MAX) pressLen = len\n break\n }\n }\n\n const ot = outputAt(segments, st, opts.media)\n if (ot === null) continue\n const upSrc = st + Math.max(pressLen, 0.02)\n const up =\n keptOutputEnd(segments, st, upSrc, opts.media) ?? ot + CLICK_SYNTH_RELEASE\n\n const click: LoweredClick = {\n ot: round(ot),\n up: round(Math.max(up, ot + 0.02)),\n st: round(st),\n x: round(e.x),\n y: round(e.y),\n b: button,\n }\n if (opts.rects && e.rect) {\n const r = gatedRect(e.rect, e.x, e.y, opts.space)\n if (r) click.r = r\n }\n out.push(click)\n }\n return out.sort((a, b) => a.ot - b.ot)\n}\n\n/** '#rgb'/'#rrggbb' → [r,g,b] for ctx.data (ON_FRAME composes rgba() per frame). */\nexport function hexToRgbTriplet(hex: string): [number, number, number] | null {\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim())\n if (!m) return null\n let h = m[1]\n if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2]\n const n = parseInt(h, 16)\n return [n >> 16, (n >> 8) & 255, n & 255]\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Dynamic-tilt planner:\n * derive tilt spans FROM the zoom spans — the camera leans toward each zoom's\n * focus, so emphasis reads in depth as well as in scale. Zoom spans are the\n * studio's condensed \"what matters\" signal (planner clicks + dwells + human\n * curation, focus points attached), which is why this planner consumes them\n * rather than re-reading the click track: extents align by construction (the\n * two tracks ramp and chain together) and no second planner competes with\n * auto-zoom for the same clicks.\n *\n * Pure and deterministic: same spans + same intensity → same suggestions,\n * ids keyed by the source zoom span (`t-<zoomId>`). Every emitted span is\n * `source: 'auto'` — the regenerate replaces them freely and NEVER touches\n * 'manual' spans (the auto-zoom wand contract; merge lives in the store).\n *\n * Direction (verified against the real renderer — the tilt-direction\n * render scenario pins it): with the camera at the origin looking\n * down −z and the card at CARD_Z,\n * +rx swings the card's TOP edge toward the camera,\n * +ry swings the LEFT edge toward the camera (the +x edge moves away).\n * \"Lean toward the focus\" therefore means rx ∝ −(cy − 0.5), ry ∝ −(cx − 0.5).\n */\nimport { spanOutputExtent } from '../lower/lowerToComposition'\nimport { TILT_INTENSITY_MAX, clampTiltDeg } from '../types'\nimport type { Segment } from '@vosjs/timeline'\nimport type { TiltSpan, TiltStyleName, ZoomSpan } from '../types'\n\n/** Zoom spans shorter than this (OUTPUT seconds) get no tilt — a pose that\n * can't settle before the zoom leaves reads as wobble, not emphasis. */\nexport const TILT_AUTO_MIN = 1.2\n\n/** Focus offsets under this (|cx−0.5| fraction) don't tilt that axis — a\n * centered zoom keeps the pure push-in feel; tilt is for off-center focus. */\nexport const TILT_AUTO_DEAD_ZONE = 0.12\n\nexport interface PlanTiltOptions {\n /** Intensity ladder — max degrees per axis (TILT_INTENSITY_MAX). */\n intensity: Exclude<TiltStyleName, 'off'>\n}\n\n/**\n * One tilt span per qualifying zoom span, SAME source extents (the tracks\n * ramp/chain together), pose aimed at the zoom's focus. Spans whose footage\n * is cut away, whose output run is too short, or whose focus is centered\n * (both axes inside the dead zone) emit nothing.\n */\nexport function planAutoTilt(\n zoom: readonly ZoomSpan[],\n segments: Segment[],\n options: PlanTiltOptions,\n): TiltSpan[] {\n const max = TILT_INTENSITY_MAX[options.intensity]\n const spans: TiltSpan[] = []\n for (const z of [...zoom].sort((a, b) => a.in - b.in)) {\n const ext = spanOutputExtent(segments, z.in, z.out)\n if (!ext || ext.end - ext.start < TILT_AUTO_MIN) continue\n // Focus offset from center; auto-focus (cursor-follow) spans use their\n // stored entry focus — the tilt pose HOLDS while the zoom's internal\n // focus glides (one move per beat, never a re-aiming wobble).\n const rx = axisLean(-(z.cy - 0.5), max)\n const ry = axisLean(-(z.cx - 0.5), max)\n if (rx === 0 && ry === 0) continue\n spans.push({\n id: `t-${z.id}`,\n in: z.in,\n out: z.out,\n rx,\n ry,\n source: 'auto',\n })\n }\n return spans\n}\n\n/** Linear lean over the dead zone: offset ±0.5 → ±max degrees, quantized. */\nfunction axisLean(offset: number, max: number): number {\n if (Math.abs(offset) < TILT_AUTO_DEAD_ZONE) return 0\n return clampTiltDeg(Math.max(-1, Math.min(1, offset / 0.5)) * max)\n}\n","/**\n * Auto-speed planner: propose speed-up spans for the three\n * stretches everyone compresses in a screen recording — typing passages,\n * long scrolls, and idle gaps — read deterministically from the cursor track.\n * The wand contract is auto-zoom's: spans arrive `source:'auto'`, any gesture\n * promotes to 'manual', and a re-plan replaces only the auto ones.\n *\n * Signals, in priority order (higher wins an overlap):\n * 1. TYPING — `key` activity pings grouped into sessions (the typing-zoom\n * grouping rule: a ping joins while the silence stays small). The caret is the\n * actor and nothing else moves, so 3× still reads.\n * 2. SCROLL — runs of `scroll` events with small gaps: skimming.\n * 3. IDLE — a gap between ANY two consecutive events (plus the head before\n * the first and the tail after the last): nothing happened at all. Padded\n * so the moment of stopping and resuming plays at 1×.\n *\n * Deterministic, pure, and empty-track-safe (a browser-recorder take with no\n * cursor track plans nothing).\n */\nimport { clampSpeedRate } from '../types'\nimport type { CursorEvent, SpeedSpan } from '../types'\n\nexport interface SpeedParams {\n /** Seconds of no input at all before a stretch counts as idle. */\n idleMin: number\n /** Rate applied to idle stretches. */\n idleRate: number\n /** Seconds a typing session must last to earn a span. */\n typingMin: number\n /** Rate applied to typing passages. */\n typingRate: number\n /** Seconds a scroll run must last to earn a span. */\n scrollMin: number\n /** Rate applied to scroll runs. */\n scrollRate: number\n}\n\n/** Conservative defaults: only stretches nobody wants to watch in real time. */\nexport const DEFAULT_SPEED_PARAMS: SpeedParams = {\n idleMin: 5,\n idleRate: 4,\n typingMin: 3,\n typingRate: 3,\n scrollMin: 2.5,\n scrollRate: 2,\n}\n\n/** Max silence inside a typing session (mirrors the typing-zoom grouping scale). */\nconst TYPING_GAP = 1.5\n/** Max gap inside a scroll run. */\nconst SCROLL_GAP = 0.8\n/** Idle spans start/end this far inside the gap so stop/resume play at 1×. */\nconst IDLE_PAD = 0.6\n/** Shortest span worth proposing (source seconds). */\nconst MIN_SPAN = 1\n/**\n * An idle gap whose measured frame activity (the digest's per-second\n * changed-pixel fraction) averages above this is the video PLAYING — the\n * recording's own playback, a render in progress — not idle. Speeding it up\n * compresses the payoff. Five real takes (2026-08-25) each had one; the\n * cursor track alone cannot tell, so this needs the activity witness, and\n * without one (the studio's ingest) the gap still plans as idle.\n */\nexport const PLAYBACK_ACTIVITY = 0.1\n\ninterface Candidate {\n in: number\n out: number\n rate: number\n}\n\nexport function planAutoSpeed(\n track: readonly CursorEvent[],\n opts: {\n durationMs: number\n params?: Partial<SpeedParams>\n /** Per-SOURCE-second motion bins (0..1) when a digest measured them. */\n activity?: readonly number[] | null\n },\n): SpeedSpan[] {\n const p = { ...DEFAULT_SPEED_PARAMS, ...opts.params }\n const durS = opts.durationMs / 1000\n if (!track.length || !(durS > 0)) return []\n const evs = [...track].sort((a, b) => a.t - b.t)\n\n const cands: Candidate[] = []\n\n // 1. typing sessions\n collectRuns(\n evs.filter((e) => e.type === 'key'),\n TYPING_GAP,\n p.typingMin,\n (start, last) => cands.push({ in: start, out: last, rate: p.typingRate }),\n )\n\n // 2. scroll runs\n collectRuns(\n evs.filter((e) => e.type === 'scroll'),\n SCROLL_GAP,\n p.scrollMin,\n (start, last) => cands.push({ in: start, out: last, rate: p.scrollRate }),\n )\n\n // 3. idle gaps — between ANY events, plus the head and the tail\n for (const [a, b] of idleGaps(evs, durS, p.idleMin)) {\n if (isPlayback(opts.activity, a, b)) continue\n const start = a + IDLE_PAD\n const end = b - IDLE_PAD\n if (end - start >= MIN_SPAN)\n cands.push({ in: start, out: end, rate: p.idleRate })\n }\n\n // Resolve overlaps by priority (candidates arrive typing → scroll → idle):\n // a later candidate is clipped to the space the accepted ones left, and a\n // clipped crumb below MIN_SPAN is dropped.\n const accepted: Candidate[] = []\n for (const c of cands) {\n let pieces: Candidate[] = [\n { ...c, in: Math.max(0, c.in), out: Math.min(durS, c.out) },\n ]\n for (const a of accepted) {\n pieces = pieces.flatMap((pc) => {\n if (pc.out <= a.in || pc.in >= a.out) return [pc]\n const kept: Candidate[] = []\n if (a.in - pc.in >= MIN_SPAN) kept.push({ ...pc, out: a.in })\n if (pc.out - a.out >= MIN_SPAN) kept.push({ ...pc, in: a.out })\n return kept\n })\n }\n accepted.push(...pieces.filter((pc) => pc.out - pc.in >= MIN_SPAN))\n }\n\n accepted.sort((a, b) => a.in - b.in)\n return accepted.map((c, i) => ({\n id: `s${i}`,\n in: round(c.in),\n out: round(c.out),\n rate: clampSpeedRate(c.rate),\n source: 'auto' as const,\n }))\n}\n\n/**\n * Scroll runs as [start, last] seconds — the same grouping the speed planner\n * proposes 2× over, exported for the take digest.\n */\nexport function scrollRuns(\n track: readonly CursorEvent[],\n minLen = 1,\n): [number, number][] {\n const out: [number, number][] = []\n collectRuns(\n [...track].filter((e) => e.type === 'scroll').sort((a, b) => a.t - b.t),\n SCROLL_GAP,\n minLen,\n (a, b) => out.push([a, b]),\n )\n return out\n}\n\n/**\n * Idle gaps as [start, end] seconds: no event of ANY kind for ≥ idleMin,\n * head and tail included — the digest's `idle` moments and the speed\n * planner's 4× candidates come from this one derivation.\n */\nexport function idleGaps(\n track: readonly CursorEvent[],\n durationS: number,\n idleMin = DEFAULT_SPEED_PARAMS.idleMin,\n): [number, number][] {\n const gaps: [number, number][] = []\n let prev = 0\n for (const e of [...track].sort((a, b) => a.t - b.t)) {\n const t = e.t / 1000\n if (t - prev >= idleMin) gaps.push([prev, t])\n if (t > prev) prev = t\n }\n if (durationS - prev >= idleMin) gaps.push([prev, durationS])\n return gaps\n}\n\n/** Mean activity over [a, b) source seconds exceeds PLAYBACK_ACTIVITY. */\nexport function isPlayback(\n activity: readonly number[] | null | undefined,\n a: number,\n b: number,\n): boolean {\n if (!activity?.length) return false\n const lo = Math.max(0, Math.floor(a))\n const hi = Math.min(activity.length, Math.ceil(b))\n if (hi <= lo) return false\n let sum = 0\n for (let i = lo; i < hi; i++) sum += activity[i]\n return sum / (hi - lo) > PLAYBACK_ACTIVITY\n}\n\n/** Group events into runs: one joins while the silence stays ≤ gap. */\nfunction collectRuns(\n evs: readonly CursorEvent[],\n gap: number,\n minLen: number,\n emit: (startS: number, lastS: number) => void,\n) {\n let start = -1\n let last = -1\n const flush = () => {\n if (start >= 0 && last - start >= minLen) emit(start, last)\n }\n for (const e of evs) {\n const t = e.t / 1000\n if (start >= 0 && t - last <= gap) {\n last = t\n } else {\n flush()\n start = t\n last = t\n }\n }\n flush()\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * The take digest's MOMENTS: the instants the cursor telemetry\n * says matter, each in the doc's own units so an agent's decision is a copy,\n * never a conversion. One grouping, shared with the planners (groupTrack,\n * dwellSpans, scrollRuns, idleGaps) — the digest lists exactly what the\n * planners zoom, speed and tilt over, plus the head, the tail, and the visual\n * scene changes a frame-diff pass finds. Pure and deterministic: same doc →\n * same moments, same ids.\n *\n * Time: `source` extents are footage seconds (SOURCE-anchored like the spans\n * they sit on); `output` is the same window mapped through the rate map, or\n * null when a trim cut it away. `at` is the source instant the digest frames.\n */\nimport {\n clusterFocus,\n dwellSpans,\n groupTrack,\n planAutoZoom,\n} from '../planner/autoZoom'\nimport {\n DEFAULT_SPEED_PARAMS,\n idleGaps,\n planAutoSpeed,\n scrollRuns,\n} from '../planner/autoSpeed'\nimport { planAutoTilt } from '../planner/autoTilt'\nimport { resolveZoomStyle } from '../zoomStyle'\nimport { ratedSegments, spanOutputExtent } from '../lower/lowerToComposition'\nimport type { Click } from '../planner/autoZoom'\nimport type { ProjectDoc, SpeedSpan, TiltSpan, ZoomSpan } from '../types'\n\nexport type MomentKind =\n | 'head'\n | 'tail'\n | 'click'\n | 'typing'\n | 'scroll'\n | 'dwell'\n | 'idle'\n | 'scene'\n\n/** A rect in normalized [0..1] frame fractions (the zoom cx/cy convention). */\nexport interface NormRect {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport interface Moment {\n id: string\n kind: MomentKind\n /** Footage seconds. Instants (scene/head/tail) have in === out. */\n source: { in: number; out: number }\n /** The same window in OUTPUT seconds, null when trimmed away. */\n output: { in: number; out: number } | null\n /** The source instant the digest frames (null = no frame, e.g. idle). */\n at: number | null\n /** `at` mapped to output time (null when cut or frameless). */\n outputAt: number | null\n /** Normalized focus — copy into a ZoomSpan's cx/cy. */\n focus: { cx: number; cy: number } | null\n /** Normalized target bounds (element rect union), when the events had one. */\n rect: NormRect | null\n clicks?: number\n /**\n * A click cluster on a frame-sized element (a canvas, a panel, a drag):\n * the planner frames nothing on it (DRAG_FIT_LEVEL), so a zoom here is\n * the agent's call, never a proposal.\n */\n surface?: boolean\n pings?: number\n /** Motion in the window, 0..1 (fraction of changed pixels), null without frames. */\n activity: number | null\n /** Planner spans (from `plan`) that cover this moment, by id. */\n proposed: { zoom?: string; speed?: string; tilt?: string }\n /** Transcript text over the window, when a transcript was merged. */\n said: string | null\n}\n\nexport interface DigestPlan {\n zoom: ZoomSpan[]\n speed: SpeedSpan[]\n tilt: TiltSpan[]\n}\n\nexport interface TranscriptSegment {\n /** SOURCE seconds (the recording's own clock). */\n start: number\n end: number\n text: string\n}\n\nexport interface MomentsOptions {\n /** Per-SOURCE-second motion bins (0..1) from the frame-diff pass. */\n bins?: readonly number[] | null\n /** Source seconds of visual scene changes (see scenes.ts). */\n scenes?: readonly number[]\n transcript?: readonly TranscriptSegment[] | null\n /** Idle threshold (seconds); defaults to the speed planner's. */\n idleMin?: number\n}\n\n/**\n * The three planners, run fresh under the doc's own style — the proposals.\n * With activity bins (a digest's decode pass), the speed planner can tell\n * playback from idle.\n */\nexport function planForDigest(\n doc: ProjectDoc,\n activity?: readonly number[] | null,\n): DigestPlan {\n const { cursor, meta } = doc.source\n const zoom = planAutoZoom(cursor, {\n width: meta.width,\n height: meta.height,\n style: doc.zoomStyle,\n params: doc.zoomParams,\n })\n const speed = planAutoSpeed(cursor, {\n durationMs: meta.durationMs,\n params: doc.speedParams,\n activity,\n })\n const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const intensity = doc.tiltStyle ?? style.tilt.intensity\n const tilt =\n intensity === 'off'\n ? []\n : planAutoTilt(zoom, ratedSegments(doc), { intensity })\n return { zoom, speed, tilt }\n}\n\ninterface Draft {\n kind: MomentKind\n in: number\n out: number\n at: number | null\n focus: { cx: number; cy: number } | null\n rect: NormRect | null\n clicks?: number\n surface?: boolean\n pings?: number\n}\n\nexport function momentsFromDoc(\n doc: ProjectDoc,\n plan: DigestPlan,\n opts: MomentsOptions = {},\n): Moment[] {\n const { cursor: track, meta } = doc.source\n const width = meta.width\n const height = meta.height\n const dur = meta.durationMs / 1000\n if (!(dur > 0)) return []\n const style = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const drafts: Draft[] = []\n\n const edge = Math.min(0.1, dur / 4)\n drafts.push({\n kind: 'head',\n in: 0,\n out: 0,\n at: edge,\n focus: null,\n rect: null,\n })\n drafts.push({\n kind: 'tail',\n in: dur,\n out: dur,\n at: Math.max(0, dur - edge),\n focus: null,\n rect: null,\n })\n\n if (track.length) {\n const { sessions, clusters, surfaces } = groupTrack(track, {\n width,\n height,\n clusterGap: style.clusterGap,\n typingGap: style.typingGap,\n typingZoom: style.typingZoom,\n targetFill: style.targetFill,\n minLevel: style.minLevel,\n })\n const pressed = (c: Click[], surface: boolean) => {\n const f = clusterFocus(c, width, height)\n drafts.push({\n kind: 'click',\n in: c[0].t,\n out: c[c.length - 1].t,\n // The frame AT the press shows what was clicked; the consequence is\n // the next moment's (or a scene) frame.\n at: c[0].t,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n clicks: c.length,\n ...(surface ? { surface: true } : {}),\n })\n }\n for (const c of clusters) pressed(c, false)\n for (const c of surfaces) pressed(c, true)\n for (const s of sessions) {\n const f = clusterFocus(s.events, width, height)\n drafts.push({\n kind: 'typing',\n in: s.start,\n out: s.last,\n // The filled field: the last ping, after the caret stopped.\n at: s.last,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n pings: s.events.filter((e) => e.t >= s.first).length,\n })\n }\n const scrollEvents: Click[] = track\n .filter((e) => e.type === 'scroll')\n .map((e) => ({ t: e.t / 1000, x: e.x, y: e.y, rect: e.rect }))\n for (const [a, b] of scrollRuns(track, 1)) {\n const evs = scrollEvents.filter((e) => e.t >= a && e.t <= b)\n const f = clusterFocus(evs, width, height)\n drafts.push({\n kind: 'scroll',\n in: a,\n out: b,\n at: (a + b) / 2,\n focus: { cx: f.cx, cy: f.cy },\n rect: f.rect,\n })\n }\n // Dwells only where no click/typing moment already is (the planner's rule).\n const reserved: ZoomSpan[] = drafts\n .filter((d) => d.kind === 'click' || d.kind === 'typing')\n .map((d, i) => ({\n id: `r${i}`,\n in: d.in,\n out: d.out,\n level: 1,\n cx: 0.5,\n cy: 0.5,\n }))\n for (const d of dwellSpans(\n track,\n width,\n height,\n style.maxLevel,\n reserved,\n )) {\n drafts.push({\n kind: 'dwell',\n in: d.in,\n out: d.out,\n at: (d.in + d.out) / 2,\n focus: { cx: d.cx, cy: d.cy },\n rect: null,\n })\n }\n for (const [a, b] of idleGaps(\n track,\n dur,\n opts.idleMin ?? DEFAULT_SPEED_PARAMS.idleMin,\n )) {\n drafts.push({\n kind: 'idle',\n in: a,\n out: b,\n at: null,\n focus: null,\n rect: null,\n })\n }\n }\n\n for (const t of opts.scenes ?? []) {\n if (t <= edge || t >= dur - edge) continue\n drafts.push({\n kind: 'scene',\n in: t,\n out: t,\n // A hair past the change so a cold seek lands on the new frame.\n at: Math.min(dur, t + 0.04),\n focus: null,\n rect: null,\n })\n }\n\n const order: Record<MomentKind, number> = {\n head: 0,\n click: 1,\n typing: 2,\n scroll: 3,\n dwell: 4,\n scene: 5,\n idle: 6,\n tail: 7,\n }\n drafts.sort((a, b) => a.in - b.in || order[a.kind] - order[b.kind])\n\n const rated = ratedSegments(doc)\n // An instant is a hair-wide window; at the very end it leans back inside.\n const outputOf = (a: number, b: number) => {\n const lo = a >= dur ? Math.max(0, dur - 0.001) : a\n return spanOutputExtent(rated, lo, Math.max(b, lo + 0.001))\n }\n const covers = (s: { in: number; out: number }, d: Draft) =>\n d.in === d.out ? s.in <= d.in && d.in < s.out : s.in < d.out && s.out > d.in\n\n return drafts.map((d, i) => {\n const output = outputOf(d.in, d.out)\n const outAt = d.at === null ? null : outputOf(d.at, d.at)\n const proposed: Moment['proposed'] = {}\n const z = plan.zoom.find((s) => covers(s, d))\n if (z) proposed.zoom = z.id\n const sp = plan.speed.find((s) => covers(s, d))\n if (sp) proposed.speed = sp.id\n const tl = plan.tilt.find((s) => covers(s, d))\n if (tl) proposed.tilt = tl.id\n return {\n id: `m${String(i + 1).padStart(2, '0')}`,\n kind: d.kind,\n source: { in: round(d.in), out: round(d.out) },\n output: output\n ? { in: round(output.start), out: round(output.end) }\n : null,\n at: d.at === null ? null : round(d.at),\n outputAt: outAt ? round(outAt.start) : null,\n focus: d.focus ? { cx: round(d.focus.cx), cy: round(d.focus.cy) } : null,\n rect: d.rect\n ? {\n x: round(d.rect.x),\n y: round(d.rect.y),\n w: round(d.rect.w),\n h: round(d.rect.h),\n }\n : null,\n ...(d.clicks !== undefined ? { clicks: d.clicks } : {}),\n ...(d.surface ? { surface: true } : {}),\n ...(d.pings !== undefined ? { pings: d.pings } : {}),\n activity: activityOf(opts.bins, d),\n proposed,\n said: saidOver(opts.transcript, d),\n }\n })\n}\n\n/** Mean motion of the bins a window touches (or the bin at an instant). */\nfunction activityOf(\n bins: readonly number[] | null | undefined,\n d: Draft,\n): number | null {\n if (!bins || !bins.length) return null\n const a = Math.max(0, Math.floor(d.in))\n const b = Math.min(bins.length - 1, Math.max(a, Math.ceil(d.out) - 1))\n let sum = 0\n let n = 0\n for (let i = a; i <= b; i++) {\n sum += bins[i]\n n++\n }\n return n ? round(sum / n) : null\n}\n\nfunction saidOver(\n transcript: readonly TranscriptSegment[] | null | undefined,\n d: Draft,\n): string | null {\n if (!transcript?.length) return null\n const lo = d.in\n const hi = d.in === d.out ? d.in + 0.5 : d.out\n const text = transcript\n .filter((s) => s.start < hi && s.end > lo)\n .map((s) => s.text.trim())\n .filter(Boolean)\n .join(' ')\n return text || null\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Scene changes from the digest's motion bins: a SOURCE second whose\n * changed-pixel fraction jumps past `motion` after a second at or below\n * `quiet` is a scene — a navigation, a dialog, a page swap. A luma diff, not\n * a scene detector: a video playing inside the page is a permanent change,\n * and the moment kind says `scene`, never \"navigation\" — the agent looks at\n * the frame to say what it was.\n */\nexport interface SceneOptions {\n /** Changed-pixel fraction that reads as a change. */\n motion?: number\n /** The bin before must be at or below this. */\n quiet?: number\n}\n\n/**\n * Calibrated on a dark-theme CLI take (launch-d2, 2026-08-25): a page swap\n * moved 0.33–0.36 of a 64×36 luma thumb, a click's own ripple ≤0.03 — so a\n * quarter of the pixels is a scene and a tenth is still quiet.\n */\nexport const SCENE_MOTION = 0.25\nexport const SCENE_QUIET = 0.1\n\nexport function sceneChanges(\n bins: readonly number[],\n opts: SceneOptions = {},\n): number[] {\n const motion = opts.motion ?? SCENE_MOTION\n const quiet = opts.quiet ?? SCENE_QUIET\n const out: number[] = []\n for (let i = 1; i < bins.length; i++) {\n if (bins[i] >= motion && bins[i - 1] <= quiet) out.push(i)\n }\n return out\n}\n","/**\n * The framing check: does a zoom span's visible window contain the\n * thing it points at? A host-side mirror of ON_FRAME's transform, the same\n * derivation focusBounds uses (layout.ts) — content point p maps to\n * f + (p − f)·L around the anchor f = dx + cx·dw, so the canvas [0, W] shows\n * content [f − f/L, f + (W − f)/L]. `layout.test.ts`'s rule binds: change the\n * two together. Lowering clamps the focus first (clampFocus), so the window\n * is computed from the clamped focus, exactly what renders.\n */\nimport { clampFocus, zoomViewport } from '../layout'\nimport type { CameraModel, CardLayout } from '../layout'\nimport type { NormRect } from './moments'\n\nexport interface ZoomWindow {\n x0: number\n x1: number\n y0: number\n y1: number\n}\n\n/**\n * The visible window in normalized video coords at `level` around the\n * focus, under the frame's camera model (absent = the magnifier, whose\n * focus is clamped first exactly as the lowering clamps it).\n */\nexport function zoomWindow(\n span: { level: number; cx: number; cy: number },\n layout: CardLayout,\n camera: CameraModel = 'card',\n): ZoomWindow {\n const L = Math.max(1, span.level)\n const { cx, cy } = clampFocus(span.cx, span.cy, L, layout, camera)\n const v = zoomViewport(L, cx, cy, layout, camera)\n const px0 = v.x * layout.W\n const px1 = (v.x + v.w) * layout.W\n const py0 = v.y * layout.H\n const py1 = (v.y + v.h) * layout.H\n return {\n x0: (px0 - layout.dx) / layout.dw,\n x1: (px1 - layout.dx) / layout.dw,\n y0: (py0 - layout.dy) / layout.dh,\n y1: (py1 - layout.dy) / layout.dh,\n }\n}\n\n/**\n * True when the rect (normalized) sits inside the zoom's visible window, with\n * `tol` of slack per edge (frame fractions). A level ≤ 1 zoom shows the whole\n * frame and covers everything.\n */\nexport function zoomCoversRect(\n span: { level: number; cx: number; cy: number },\n rect: NormRect,\n layout: CardLayout,\n tol = 0.02,\n camera: CameraModel = 'card',\n): boolean {\n if (span.level <= 1.001) return true\n const w = zoomWindow(span, layout, camera)\n return (\n rect.x >= w.x0 - tol &&\n rect.x + rect.w <= w.x1 + tol &&\n rect.y >= w.y0 - tol &&\n rect.y + rect.h <= w.y1 + tol\n )\n}\n","/**\n * Style as DATA: the fields of a signed-off take's doc that ARE its\n * style — camera, speed, tilt personality, frame, cursor, cam bubble, export.\n * `copyStyle` carries them onto another take deterministically, so a series\n * shares them by construction and the recipe (CUT.md) only has to say what a\n * number cannot. Never the spans, overlays or audio: those are the cut.\n *\n * A LAYOUT is the same idea for a poster: an exemplar document holds the\n * card's placement and presentation, the words' clips, the rest lean and\n * the trailing hold, and `copyLayout` carries exactly those onto another\n * take. A layout is never a name a document learns; it is a document on a\n * shelf, applied by copy.\n */\nimport { totalDuration } from '@vosjs/timeline'\nimport { sameMedia } from '../media'\nimport { anchorSourceDuration } from '../doc/studioDoc'\nimport { docOutputDuration } from '../audioBeds'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport { docFreezes } from '../lower/motion'\nimport type {\n AudioClip,\n FrameStyle,\n FreezeSpan,\n ObjectClip,\n OverlayClip,\n ProjectDoc,\n} from '../types'\n\nexport const STYLE_FIELDS = [\n 'zoomStyle',\n 'zoomParams',\n 'speedParams',\n 'tiltStyle',\n 'frame',\n 'cursor',\n 'cam',\n 'export',\n] as const satisfies readonly (keyof ProjectDoc)[]\n\nexport type StyleField = (typeof STYLE_FIELDS)[number]\n\n/**\n * The `export` keys a style CARRIES. `resolution` is deliberately absent:\n * it names how big the output is, which is a fact of the FOOTAGE, not a\n * taste the seed gets to hold. Copying it whole meant a 1280x720 seed\n * stamped 720p onto a 2560x1440 take, so a series looked consistent by\n * throwing away half of every later take's pixels.\n */\nexport const STYLE_EXPORT_KEYS = [\n 'fps',\n 'format',\n] as const satisfies readonly (keyof ProjectDoc['export'])[]\n\n/** The style fields present on a doc, deep-cloned. */\nexport function pickStyle(\n doc: ProjectDoc,\n): Partial<Pick<ProjectDoc, StyleField>> {\n const out: Record<string, unknown> = {}\n for (const k of STYLE_FIELDS) {\n const v: unknown = doc[k]\n if (v !== undefined) out[k] = structuredClone(v)\n }\n return out as Partial<Pick<ProjectDoc, StyleField>>\n}\n\nexport interface CopyStyleOptions {\n /**\n * Carry the seed's LAYOUT too (`copyLayout`): its stage clips by id, its\n * rest lean and its trailing hold. Off by default, so a series cut keeps\n * copying style alone.\n */\n layout?: boolean\n /** Passed through to `copyLayout`. */\n keys?: Record<string, string>\n}\n\n/**\n * A new doc: `to` with `from`'s style fields. A field absent on `from` is\n * removed from the result (the seed's absence is a choice — the default).\n */\nexport function copyStyle(\n from: ProjectDoc,\n to: ProjectDoc,\n opts: CopyStyleOptions = {},\n): ProjectDoc {\n const next = structuredClone(to) as unknown as Record<string, unknown>\n const style = pickStyle(from) as Record<string, unknown>\n for (const k of STYLE_FIELDS) {\n if (k === 'export') continue // merged per key below\n if (k in style) next[k] = style[k]\n else delete next[k]\n }\n // `export` is the one style field that is PART taste and PART fact, so it\n // merges per key instead of replacing: the seed's frame rate and format\n // land, the target keeps the resolution its own footage earned. A seed\n // without an export block leaves the target's alone — the field is\n // required, and dropping it would hand back a doc that cannot render.\n const fromExport = style.export as ProjectDoc['export'] | undefined\n if (fromExport) {\n const merged = { ...((next.export ?? {}) as ProjectDoc['export']) }\n for (const k of STYLE_EXPORT_KEYS) {\n if (fromExport[k] !== undefined) merged[k] = fromExport[k] as never\n }\n next.export = merged\n }\n const styled = next as unknown as ProjectDoc\n return opts.layout\n ? copyLayout(from, styled, { keys: opts.keys }).doc\n : styled\n}\n\n// ── layout ──────────────────────────────────────────────────────────────\n\n/** The frame fields a layout owns. Never `aspectRatio` or `browserBar`. */\nexport const LAYOUT_FRAME_FIELDS = [\n 'background',\n 'padding',\n 'radius',\n 'shadow',\n 'shadowContact',\n 'shadowColor',\n 'inset',\n 'border',\n 'borderWidth',\n 'borderColor',\n 'fit',\n 'focus',\n 'focusFollow',\n 'parallax',\n 'anim',\n] as const satisfies readonly (keyof FrameStyle)[]\n\n/**\n * The id prefix an exemplar reserves for the clips that ARE its layout:\n * the words and the mark a poster places (`stage-title`, `stage-kicker`,\n * `stage-brand`, `stage-mark`). Every other clip is the take's own.\n */\nexport const STAGE_CLIP_PREFIX = 'stage-'\n\n/** The id of the whole-take lean a layout or a canvas gesture writes. */\nexport const REST_TILT_ID = 'rest'\n\nexport const isStageClip = (clip: Pick<OverlayClip, 'id'>): boolean =>\n clip.id.startsWith(STAGE_CLIP_PREFIX)\n\n/** The exemplar's rest lean: its `rest`/`stage` span, or a span covering (nearly) the whole source. */\nexport function restLeanOf(doc: ProjectDoc): { rx: number; ry: number } | null {\n const spans = doc.tilt ?? []\n const named = spans.find((z) => z.id === REST_TILT_ID || z.id === 'stage')\n if (named) return { rx: named.rx, ry: named.ry }\n const total = anchorSourceDuration(doc)\n const whole = spans.find((z) => z.out - z.in >= total * 0.9)\n return whole ? { rx: whole.rx, ry: whole.ry } : null\n}\n\n/** What a document carries of a layout, for a report. */\nexport interface LayoutParts {\n /** The layout-owned frame fields present on the document. */\n frame: string[]\n /** The stage clips' ids. */\n clips: string[]\n lean: boolean\n /** A trailing freeze (the poster's rest). */\n freeze: boolean\n}\n\nexport function layoutOf(doc: ProjectDoc): LayoutParts {\n const frame = doc.frame as unknown as Record<string, unknown>\n return {\n frame: LAYOUT_FRAME_FIELDS.filter((k) => frame[k] !== undefined),\n clips: (doc.overlays ?? []).filter(isStageClip).map((o) => o.id),\n lean: restLeanOf(doc) !== null,\n freeze: trailingFreeze(doc) !== null,\n }\n}\n\nexport interface CopyLayoutOptions {\n /**\n * Media keys this document should show in the exemplar's image clips,\n * by clip id (`{ 'stage-mark': 'brand/mark.svg' }`): an exemplar's mark\n * is its brand's, so without a key of its own (here, or already on the\n * document) an image stage clip is left out and said in `notes`.\n */\n keys?: Record<string, string>\n}\n\nexport interface CopiedLayout {\n doc: ProjectDoc\n /** What the copy left alone, in plain words (empty when nothing was). */\n notes: string[]\n}\n\n/**\n * `to` with `from`'s layout. Pure: returns a new document; never mutates\n * either input.\n *\n * Copied: the layout-owned frame fields (present on the exemplar ⇒ copied,\n * absent ⇒ removed; the animated ground rides along only when the exemplar\n * has one), the stage clips by id (a same-id text clip keeps the words this\n * document already has; an image clip keeps this document's key, or takes\n * one from `keys`), the rest lean as one whole-take span where this\n * document has no tilt spans of its own, and the trailing hold. Kept: the\n * aspect, the browser chrome, every clip that is not a stage clip, the cut.\n */\nexport function copyLayout(\n from: ProjectDoc,\n to: ProjectDoc,\n opts: CopyLayoutOptions = {},\n): CopiedLayout {\n const doc = structuredClone(to)\n const notes: string[] = []\n\n const frame = doc.frame as unknown as Record<string, unknown>\n const src = from.frame as unknown as Record<string, unknown>\n for (const k of LAYOUT_FRAME_FIELDS) {\n if (src[k] !== undefined) frame[k] = structuredClone(src[k])\n else delete frame[k]\n }\n if (from.frame.backgroundMedia)\n doc.frame.backgroundMedia = structuredClone(from.frame.backgroundMedia)\n\n const outDur = docOutputDuration(doc)\n const own = new Map((doc.overlays ?? []).map((o) => [o.id, o]))\n const merged: OverlayClip[] = (doc.overlays ?? []).filter(\n (o) => !isStageClip(o),\n )\n const skippedMarks: string[] = []\n for (const clip of (from.overlays ?? []).filter(isStageClip)) {\n const mine = own.get(clip.id)\n const next = structuredClone(clip)\n next.start = 0\n next.duration = Math.max(0.3, Math.round(outDur * 1000) / 1000)\n if (next.kind === 'text') {\n if (mine?.kind === 'text') next.text = mine.text\n } else if (next.kind === 'html') {\n // An HTML layer has no key: its content IS its source. There is nothing\n // to swap in, and nothing missing to skip the clip for.\n } else if (opts.keys?.[clip.id]) {\n next.key = opts.keys[clip.id]\n } else if (mine && mine.kind !== 'text' && mine.kind !== 'html') {\n next.key = mine.key\n } else {\n skippedMarks.push(clip.id)\n continue\n }\n merged.push(next)\n }\n for (const o of doc.overlays ?? []) {\n if (isStageClip(o) && !merged.some((m) => m.id === o.id)) merged.push(o)\n }\n doc.overlays = merged.length ? merged : undefined\n if (skippedMarks.length)\n notes.push(\n `${skippedMarks.join(', ')} left out: the layout's image is its own brand's; add yours`,\n )\n\n const lean = restLeanOf(from)\n if (lean) {\n if (!doc.tilt?.length) {\n doc.tilt = [\n {\n id: REST_TILT_ID,\n in: 0,\n out: Math.round(anchorSourceDuration(doc) * 1000) / 1000,\n rx: lean.rx,\n ry: lean.ry,\n source: 'manual',\n },\n ]\n } else {\n notes.push('lean kept: this take has tilt spans of its own')\n }\n }\n\n // The trailing freeze: the exemplar's rest becomes this take's, at the\n // end of ITS last segment, with the same seconds.\n const freeze = trailingFreeze(from)\n const last = doc.segments.at(-1)\n if (freeze && last) {\n // The last clip may play another media (concat): the freeze names\n // it, or its seconds would land on the primary's footage instead.\n const atEnd = (f: FreezeSpan) =>\n Math.abs(f.at - last.out) <= 1e-9 && sameMedia(f.media, last.media)\n const own = (doc.freeze ?? []).filter((f) => !atEnd(f))\n // A freeze the take OWNS at its end survives (it is the take's rest,\n // the frame that stands for it); it grows to the layout's seconds.\n const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from)\n const taken = new Set(own.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n doc.freeze = [\n ...own,\n kept\n ? { ...kept, seconds: Math.max(kept.seconds, freeze.seconds) }\n : {\n id: `f${n}`,\n at: last.out,\n seconds: freeze.seconds,\n ...(last.media ? { media: last.media } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n doc.segments = doc.segments.map((s) => {\n if (s.hold === undefined) return s\n const next = { ...s }\n delete next.hold\n return next\n })\n }\n\n return { doc, notes }\n}\n\n/** The freeze at the end of the document's last segment, if any. */\nfunction trailingFreeze(doc: ProjectDoc): FreezeSpan | null {\n // A template may be a PROGRAM document (one-layer members, a component on\n // the shelf): no footage, no segments, no freeze to carry.\n const last = doc.segments?.at(-1)\n if (!last) return null\n return docFreezes(doc).find((f) => Math.abs(f.at - last.out) < 1e-9) ?? null\n}\n\n/** Where a template's clips land on the take. */\nexport type TemplateAnchor = 'start' | 'end' | number\n\nexport interface ApplyTemplateOptions {\n /**\n * The anchor: `start` keeps the template's times; `end` lays them\n * relative to the take's footage end as they sat relative to the\n * template's own (an end card); a number is an output second the\n * template's clock starts at (a caption at a step's settle).\n */\n at: TemplateAnchor\n /** Words for the template's text clips, by clip id. */\n words?: Record<string, string>\n /** Media keys for the template's image and video clips, by clip id. */\n keys?: Record<string, string>\n /**\n * What the clips say they came from (the template's vos id). Absent =\n * `'template'`. A re-apply with the same `from` replaces its earlier\n * clips and leaves everything else.\n */\n from?: string\n /**\n * Carry the template's look too (the layout-owned frame fields, its\n * backdrop, the rest lean, the trailing hold): a poster. Absent = the\n * clips and the card's animation only.\n */\n look?: boolean\n}\n\nexport interface AppliedTemplate {\n doc: ProjectDoc\n notes: string[]\n}\n\nconst shiftBy = (at: TemplateAnchor, templateEnd: number, takeEnd: number) =>\n at === 'start' ? 0 : at === 'end' ? takeEnd - templateEnd : at\n\n/**\n * The clips a template placed on a document (by `from`), in document order.\n */\nexport function clipsFrom(\n doc: Pick<ProjectDoc, 'overlays' | 'objects' | 'audio'>,\n from: string,\n): { overlays: OverlayClip[]; objects: ObjectClip[]; audio: AudioClip[] } {\n return {\n overlays: (doc.overlays ?? []).filter((o) => o.from === from),\n objects: (doc.objects ?? []).filter((o) => o.from === from),\n audio: (doc.audio ?? []).filter((a) => a.from === from),\n }\n}\n\n/**\n * The document without the clips a template placed. The card's `anim` is\n * the card's own once set, so it stays; a caller that wants the card still\n * clears it. Returns the same object when nothing came from `from`.\n */\nexport function dropTemplate(doc: ProjectDoc, from: string): ProjectDoc {\n const has = clipsFrom(doc, from)\n const freezes = (doc.freeze ?? []).some((f) => f.from === from)\n if (\n !has.overlays.length &&\n !has.objects.length &&\n !has.audio.length &&\n !freezes\n )\n return doc\n const out: ProjectDoc = { ...doc }\n if (doc.overlays) out.overlays = doc.overlays.filter((o) => o.from !== from)\n if (doc.objects) out.objects = doc.objects.filter((o) => o.from !== from)\n out.audio = doc.audio.filter((a) => a.from !== from)\n if (doc.freeze) out.freeze = doc.freeze.filter((f) => f.from !== from)\n return out\n}\n\n/**\n * A TEMPLATE is a vos: a plain take document whose clips carry stable ids.\n * Applying it lays those clips onto another take at an ANCHOR, stamped\n * `from`, with the release's words and keys patched in by id; the take's\n * own same-id clip keeps its words unless `words` names them (the layout\n * rule); the template's card `anim` merges over the take's (an end card\n * brings the exit, a poster the entrance); with `look`, the layout-owned\n * frame fields, the backdrop, the rest lean and the hold come too, which\n * is `copyLayout`. The one assembly verb; a recipe says which template and\n * where, never the document.\n */\nexport function applyTemplate(\n template: ProjectDoc,\n take: ProjectDoc,\n opts: ApplyTemplateOptions,\n): AppliedTemplate {\n const from = opts.from ?? 'template'\n const notes: string[] = []\n const base = dropTemplate(take, from)\n const doc: ProjectDoc = structuredClone(base)\n // A template that ends on a freeze keeps its card under its clips; laid\n // at the end of a take, the same freeze goes on the take's last frame\n // (stamped `from`), and the clips place against the end that includes it.\n const tail = trailingFreeze(template)\n const lastSeg = doc.segments.at(-1)\n if (tail && opts.at === 'end' && lastSeg) {\n // The last clip may play another media (concat): the freeze names\n // it, or its seconds would land on the primary's footage instead.\n const atEnd = (f: FreezeSpan) =>\n Math.abs(f.at - lastSeg.out) <= 1e-9 && sameMedia(f.media, lastSeg.media)\n // A freeze the take OWNS at its end survives the template (it is the\n // take's rest, the frame that stands for it) and grows to the\n // template's seconds; only then is the template's own freeze laid,\n // stamped `from`, so a still never reads it as the rest.\n const kept = (doc.freeze ?? []).find((f) => atEnd(f) && !f.from)\n const own = (doc.freeze ?? []).filter((f) => !atEnd(f))\n const taken = new Set(own.map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n doc.freeze = [\n ...own,\n kept\n ? { ...kept, seconds: Math.max(kept.seconds, tail.seconds) }\n : {\n id: `f${n}`,\n at: lastSeg.out,\n seconds: tail.seconds,\n from,\n ...(lastSeg.media ? { media: lastSeg.media } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n }\n const templateEnd = totalDuration(ratedSegments(template))\n const takeEnd = totalDuration(ratedSegments(doc))\n const shift = shiftBy(opts.at, templateEnd, takeEnd)\n const own = new Map((take.overlays ?? []).map((o) => [o.id, o]))\n\n const overlays: OverlayClip[] = (doc.overlays ?? []).filter(\n (o) => !(template.overlays ?? []).some((t) => t.id === o.id),\n )\n const skipped: string[] = []\n for (const clip of template.overlays ?? []) {\n const next = structuredClone(clip) as OverlayClip\n next.start = Math.max(0, round3(clip.start + shift))\n next.from = from\n const mine = own.get(clip.id)\n if (next.kind === 'text') {\n const word = opts.words?.[clip.id]\n if (word !== undefined) next.text = word\n else if (mine?.kind === 'text') next.text = mine.text\n } else if (next.kind === 'html') {\n // No key to swap: an HTML layer carries its own source.\n } else if (opts.keys?.[clip.id]) {\n next.key = opts.keys[clip.id]\n } else if (mine && mine.kind !== 'text' && mine.kind !== 'html') {\n next.key = mine.key\n } else if (!/^(https?:|\\/\\/)/.test(next.key)) {\n skipped.push(clip.id)\n continue\n }\n overlays.push(next)\n }\n if (skipped.length)\n notes.push(\n `${skipped.join(', ')}: the template's media key is its own; pass keys[id] or the take's clip`,\n )\n if (overlays.length || doc.overlays) doc.overlays = overlays\n\n const objects: ObjectClip[] = (doc.objects ?? []).filter(\n (o) => !(template.objects ?? []).some((t) => t.id === o.id),\n )\n for (const clip of template.objects ?? []) {\n const next = structuredClone(clip) as ObjectClip\n if (next.span)\n next.span.start = Math.max(0, round3(next.span.start + shift))\n next.from = from\n objects.push(next)\n }\n if (objects.length || doc.objects) doc.objects = objects\n\n const audio: AudioClip[] = doc.audio.filter(\n (a) => !(template.audio ?? []).some((t) => t.id === a.id),\n )\n for (const clip of template.audio ?? []) {\n const next = structuredClone(clip) as AudioClip\n next.start = Math.max(0, round3(clip.start + shift))\n next.from = from\n audio.push(next)\n }\n doc.audio = audio\n\n if (template.frame?.anim) {\n doc.frame = {\n ...doc.frame,\n anim: { ...(doc.frame.anim ?? {}), ...template.frame.anim },\n }\n }\n\n if (opts.look) {\n const laid = copyLayout(template, doc, { keys: opts.keys })\n // copyLayout owns the stage clips; everything above stays as placed.\n laid.doc.overlays = (laid.doc.overlays ?? []).map((o) =>\n isStageClip(o) ? { ...o, from } : o,\n )\n notes.push(...laid.notes)\n return { doc: laid.doc, notes }\n }\n return { doc, notes }\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n","/**\n * Music beds — the doc semantics of \"background music under the cut\".\n *\n * A bed is an ordinary AudioClip with music-true defaults: it starts at 0,\n * covers the whole output (looping to fill when the track is shorter than\n * the cut, trimming its tail when it is longer), ducks under the mic when\n * there is a voice to duck under, and fades out instead of stopping dead.\n * Every default is a plain clip field the user can change afterwards —\n * defaults, not policy.\n *\n * `refillAudioBeds` is the trim-follow rule: when an edit changes the output\n * duration, any clip whose END tracked the previous output end is re-fit to\n * the new one, inside the SAME edit (one undo step, never a stored rule in\n * the doc — lowering stays pure).\n */\nimport { totalDuration } from '@vosjs/timeline'\nimport { ratedSegments } from './lower/lowerToComposition'\nimport { migrateMotion, outputEnd } from './lower/motion'\nimport { isRecordingDoc } from './doc/studioDoc'\nimport type { StudioDoc } from './doc/studioDoc'\nimport type { AudioClip, ProjectDoc } from './types'\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000\n\n/** Rate-aware OUTPUT duration of a doc (what the viewer experiences). */\n/**\n * The OUTPUT length of either document: a recording's kept footage\n * through its speed spans, a program's own length (its `program.duration`,\n * else the config's).\n */\nexport function docOutputDuration(doc: StudioDoc): number {\n const footage = totalDuration(ratedSegments(doc))\n // A recording's output lasts until its last visual clip ends (a legacy\n // end card's clips are placed after the footage on read); a program's\n // length is its own.\n return isRecordingDoc(doc) ? outputEnd(migrateMotion(doc), footage) : footage\n}\n\nexport interface MusicBedInput {\n id: string\n /** Durable URL of the track (assets.vos.so catalog or an owned asset). */\n key: string\n name: string\n /** Full source-file length, seconds. */\n trackDuration: number\n /** Current output duration of the doc, seconds. */\n outputDuration: number\n /** Whether the recording carries a mic track (ducking default). */\n hasMic: boolean\n}\n\n/** A catalog track placed as the doc's background music bed. */\nexport function musicBedClip(input: MusicBedInput): AudioClip {\n const track = round3(input.trackDuration)\n const output = round3(input.outputDuration)\n const clip: AudioClip = {\n id: input.id,\n key: input.key,\n name: input.name,\n start: 0,\n in: 0,\n out: track,\n duration: track,\n // Under speech, not over it; catalog tracks are loudness-normalized so\n // one default means the same thing across the library.\n gain: 0.5,\n fadeIn: 0,\n // A bed that just stops reads as a glitch; clamp so a very short cut\n // still spends most of its time at full level.\n fadeOut: output > 0 ? Math.min(1.5, round3(output / 4)) : 1.5,\n duck: input.hasMic || undefined,\n }\n if (output <= 0) return clip\n if (track >= output) {\n // Track outruns the cut: trim the tail to the output end.\n clip.out = output\n } else {\n // Cut outruns the track: loop the whole track to fill it.\n clip.loop = true\n clip.loopLen = output\n }\n return clip\n}\n\n/**\n * Is this clip a music BED — background music covering the cut? Beds are\n * what \"add a track\" replaces (one bed at a time; trying another vibe must\n * not stack). A clip the user moved off 0 or shortened mid-cut stopped\n * being a bed on purpose, so it is theirs to manage and never auto-replaced.\n */\nexport function isMusicBed(clip: AudioClip, outputDuration: number): boolean {\n if (clip.start > 0.05) return false\n if (clip.loop) return true\n const placed = clip.out - clip.in\n return Math.abs(placed - outputDuration) <= 1\n}\n\n/**\n * Re-fit clips that tracked the output end after the duration changed from\n * `prevDuration` to `nextDuration` (both OUTPUT seconds). Mutates `doc`\n * (an immer draft in practice); returns whether anything changed.\n */\nexport function refillAudioBeds(\n doc: ProjectDoc,\n prevDuration: number,\n nextDuration: number,\n): boolean {\n const EPS = 0.05\n if (Math.abs(nextDuration - prevDuration) <= EPS || nextDuration <= 0) {\n return false\n }\n let changed = false\n for (const clip of doc.audio) {\n if (clip.start >= nextDuration) continue\n const placed = clip.loop\n ? Math.max(clip.out - clip.in, clip.loopLen ?? clip.out - clip.in)\n : clip.out - clip.in\n // Only clips whose end sat AT the previous output end follow it — a clip\n // deliberately placed mid-timeline is the user's to manage.\n if (Math.abs(clip.start + placed - prevDuration) > EPS) continue\n const span = clip.out - clip.in\n const nextLen = round3(nextDuration - clip.start)\n if (clip.loop) {\n if (nextLen >= span) {\n clip.loopLen = nextLen\n } else {\n // Shorter than one pass: a loop cannot shrink below its span\n // (clipLength floors at the span), so it becomes a plain trim.\n clip.loop = undefined\n clip.loopLen = undefined\n clip.out = round3(clip.in + nextLen)\n }\n } else {\n if (clip.in + nextLen <= clip.duration) {\n clip.out = round3(clip.in + nextLen)\n } else {\n // The cut outgrew the source file: loop the full remainder to fill.\n clip.out = clip.duration\n clip.loop = true\n clip.loopLen = nextLen\n }\n }\n changed = true\n }\n return changed\n}\n\n/**\n * The take's VOICE source key, or null when it has none: the mic sidecar when\n * the take was recorded split (AT), else the legacy mixed track (pre-split\n * takes carried mic+system in the recording's own file). Ducking, the duck-RMS\n * decode and every \"has a voice?\" UI gate share this one derivation.\n */\nexport function voiceKey(\n doc: Pick<ProjectDoc, 'source'> | StudioDoc,\n): string | null {\n // A program has no voice: the duck controls simply do not show.\n if (!('source' in doc)) return null\n const src = doc.source\n if (src.micKey) return src.micKey\n if (src.meta.hasAudio && !src.meta.hasMic) return src.videoKey\n return null\n}\n","/**\n * The digest's crop geometry, pure and shared by the CLI's page and\n * the fleet's page: cursor/meta coords are CSS px of the viewport (or the\n * crop space when `source.crop` is set); the frame is capture px. A window\n * take's crop applies to the FRAME, never to the already-cropped cursor\n * coords. Both hosts compute boxes here so the two never drift by a dpr.\n */\nimport type { ProjectDoc } from '../types'\nimport type { Moment } from './moments'\n\nexport interface PxRect {\n x: number\n y: number\n w: number\n h: number\n}\n\n/** A crop box is at least this fraction of the frame width (floor 320px). */\nexport const CROP_MIN_FRAC = 0.25\nexport const CROP_MIN_PX = 320\nexport const CROP_MAX_PX = 1024\nexport const CROP_PAD = 0.25\n/** Default long edges (px) of the emitted images — the agent's token budget. */\nexport const DIGEST_FULL_MAX = 960\nexport const DIGEST_CROP_MAX = 640\n/** Changed-pixel threshold (luma, 0..255) for the motion bins. */\nexport const MOTION_DELTA = 24\n\nexport interface FrameGeometry {\n /** The region of the frame the doc renders (the viewport crop, or all). */\n region: PxRect\n /** Cursor px → frame px. */\n scale: number\n}\n\n/** The frame's pixel size when no decode has told us: capture px, else CSS×dpr. */\nexport function expectedFrameSize(doc: ProjectDoc): {\n width: number\n height: number\n} {\n const meta = doc.source.meta\n const dpr = meta.dpr > 0 ? meta.dpr : 1\n return {\n width: meta.captureWidth ?? Math.round(meta.width * dpr),\n height: meta.captureHeight ?? Math.round(meta.height * dpr),\n }\n}\n\nexport function frameGeometry(\n doc: ProjectDoc,\n frameW: number,\n frameH: number,\n): FrameGeometry {\n const meta = doc.source.meta\n const crop = doc.source.crop\n const region: PxRect = crop\n ? {\n x: Math.max(0, Math.round(crop.x)),\n y: Math.max(0, Math.round(crop.y)),\n w: Math.min(frameW, Math.round(crop.w)),\n h: Math.min(frameH, Math.round(crop.h)),\n }\n : { x: 0, y: 0, w: frameW, h: frameH }\n return { region, scale: region.w / Math.max(1, meta.width) }\n}\n\n/** The crop box (frame px) around a moment's rect or focus point, or null. */\nexport function cropBox(\n m: Pick<Moment, 'rect' | 'focus'>,\n geo: FrameGeometry,\n meta: { width: number; height: number },\n): PxRect | null {\n if (!m.rect && !m.focus) return null\n const { region, scale } = geo\n const toPx = (nx: number, ny: number) => ({\n x: region.x + nx * meta.width * scale,\n y: region.y + ny * meta.height * scale,\n })\n let x0: number\n let y0: number\n let x1: number\n let y1: number\n if (m.rect) {\n const a = toPx(m.rect.x, m.rect.y)\n const b = toPx(m.rect.x + m.rect.w, m.rect.y + m.rect.h)\n const pad = CROP_PAD * Math.max(b.x - a.x, b.y - a.y)\n x0 = a.x - pad\n y0 = a.y - pad\n x1 = b.x + pad\n y1 = b.y + pad\n } else {\n const p = toPx(m.focus!.cx, m.focus!.cy)\n x0 = x1 = p.x\n y0 = y1 = p.y\n }\n const min = Math.max(CROP_MIN_PX, CROP_MIN_FRAC * region.w)\n const cx = (x0 + x1) / 2\n const cy = (y0 + y1) / 2\n let w = Math.max(min, x1 - x0)\n let h = Math.max(min, y1 - y0)\n const cap = Math.min(CROP_MAX_PX, region.w, region.h)\n if (Math.max(w, h) > cap) {\n const s = cap / Math.max(w, h)\n w *= s\n h *= s\n }\n let x = cx - w / 2\n let y = cy - h / 2\n x = Math.max(region.x, Math.min(x, region.x + region.w - w))\n y = Math.max(region.y, Math.min(y, region.y + region.h - h))\n return {\n x: Math.round(x),\n y: Math.round(y),\n w: Math.round(Math.min(w, region.w)),\n h: Math.round(Math.min(h, region.h)),\n }\n}\n","/**\n * The digest document (digest.json), assembled from a take's doc, the\n * planners' proposals, the moments, and whatever a decode pass measured —\n * one pure builder for the CLI and the fleet, so a hosted digest\n * is byte-comparable with a local one. `images` maps moment ids to the\n * files a page wrote and their sizes; a builder with none (no frames) emits\n * a frameless digest.\n */\nimport { docOutputDuration } from '../audioBeds'\nimport { pickStyle } from './style'\nimport type { ProjectDoc, RecordingMeta } from '../types'\nimport type { DigestPlan, Moment, TranscriptSegment } from './moments'\nimport type { PxRect } from './geometry'\n\nexport const DIGEST_VERSION = 1\n\nexport interface DigestImageRef {\n full: string | null\n crop: string | null\n /** The crop's source box in FRAME px (what `crop` shows). */\n box: PxRect | null\n fullSize?: { width: number; height: number } | null\n cropSize?: { width: number; height: number } | null\n}\n\nexport interface DigestTakeFacts {\n sourceDuration: number\n outputDuration: number\n width: number\n height: number\n captureWidth: number | null\n captureHeight: number | null\n frameWidth: number | null\n frameHeight: number | null\n surface: string\n producer: string\n pageUrl: string | null\n pageTitle: string | null\n hasMic: boolean\n hasSystemAudio: boolean\n hasCursor: boolean\n windowFocusedFrac: number | null\n /**\n * The recorder did not land where it was asked (a sign-in, a redirect) and\n * the take was recorded anyway: the footage may be the wrong page. null\n * when it landed where it was asked, or the recorder does not check.\n */\n wall: RecordingMeta['wall'] | null\n /** Sensitive-looking text the recorder saw in the frame (kind + place, never the string). */\n exposures: NonNullable<RecordingMeta['exposures']>\n /** Masks the script asked for, with how many elements each reached. */\n masks: NonNullable<RecordingMeta['masks']>\n}\n\nexport interface Digest {\n digestVersion: number\n take: DigestTakeFacts\n units: {\n source: 'seconds of footage'\n output: 'seconds of the rendered video (trims and speed applied)'\n focus: 'fractions of the video frame [0..1], the zoom cx/cy convention'\n activity: 'fraction of pixels that changed, per SOURCE second'\n }\n moments: (Moment & {\n full: string | null\n crop: string | null\n box: PxRect | null\n })[]\n activity: number[] | null\n plan: DigestPlan\n doc: {\n manual: { zoom: number; speed: number; tilt: number; overlays: number }\n zoomStyle: string | null\n tiltStyle: string | null\n }\n style: { from: string; fields: Record<string, unknown> } | null\n transcript: TranscriptSegment[] | null\n images: {\n full: number\n crop: number\n sheet: string | null\n /** Sum of (w×h)/750 over the full + crop images: a reading-budget hint, not a bill. */\n tokensEstimate: number\n /** @deprecated The same number under its old name; gone next minor. */\n tokensEstimateClaude: number\n }\n}\n\nexport interface BuildDigestInput {\n doc: ProjectDoc\n plan: DigestPlan\n moments: Moment[]\n outputDuration: number\n bins: number[] | null\n frame: { width: number; height: number } | null\n images: Map<string, DigestImageRef>\n sheet: string | null\n style?: { from: string; doc: ProjectDoc } | null\n transcript?: readonly TranscriptSegment[] | null\n}\n\nexport function buildDigest(input: BuildDigestInput): Digest {\n const { doc, meta } = { doc: input.doc, meta: input.doc.source.meta }\n let tokens = 0\n const withFiles = input.moments.map((m) => {\n const ref = input.images.get(m.id)\n for (const s of [ref?.fullSize, ref?.cropSize]) {\n if (s) tokens += (s.width * s.height) / 750\n }\n return {\n ...m,\n full: ref?.full ?? null,\n crop: ref?.crop ?? null,\n box: ref?.crop ? (ref.box ?? null) : null,\n }\n })\n return {\n digestVersion: DIGEST_VERSION,\n take: {\n sourceDuration: round(meta.durationMs / 1000),\n outputDuration: round(input.outputDuration),\n width: meta.width,\n height: meta.height,\n captureWidth: meta.captureWidth ?? null,\n captureHeight: meta.captureHeight ?? null,\n frameWidth: input.frame?.width ?? null,\n frameHeight: input.frame?.height ?? null,\n surface: meta.captureSurface ?? 'tab',\n producer: meta.producer ?? 'extension',\n pageUrl: meta.pageUrl ?? null,\n pageTitle: meta.pageTitle ?? null,\n hasMic: Boolean(doc.source.micKey) || meta.hasMic === true,\n hasSystemAudio: meta.hasAudio === true,\n hasCursor: doc.source.cursor.length > 0,\n windowFocusedFrac: meta.windowFocusedFrac ?? null,\n wall: meta.wall ?? null,\n exposures: meta.exposures ?? [],\n masks: meta.masks ?? [],\n },\n units: {\n source: 'seconds of footage',\n output: 'seconds of the rendered video (trims and speed applied)',\n focus: 'fractions of the video frame [0..1], the zoom cx/cy convention',\n activity: 'fraction of pixels that changed, per SOURCE second',\n },\n moments: withFiles,\n activity: input.bins,\n plan: input.plan,\n doc: {\n manual: {\n zoom: doc.zoom.filter((z) => z.source === 'manual').length,\n speed: (doc.speed ?? []).filter((s) => s.source !== 'auto').length,\n tilt: (doc.tilt ?? []).filter((t) => t.source === 'manual').length,\n overlays: doc.overlays?.length ?? 0,\n },\n zoomStyle: doc.zoomStyle ?? null,\n tiltStyle: doc.tiltStyle ?? null,\n },\n style: input.style\n ? { from: input.style.from, fields: pickStyle(input.style.doc) }\n : null,\n transcript: input.transcript ? [...input.transcript] : null,\n images: {\n full: withFiles.filter((m) => m.full).length,\n crop: withFiles.filter((m) => m.crop).length,\n sheet: input.sheet,\n tokensEstimate: Math.round(tokens),\n tokensEstimateClaude: Math.round(tokens),\n },\n }\n}\n\n/** OUTPUT seconds of a doc: its kept footage through the rate map. */\nexport function outputDurationOf(doc: ProjectDoc): number {\n return docOutputDuration(doc)\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Range-first chip actions: pure recipes over the doc's span\n * arrays for a SOURCE-time range the user selected on the Video row. The\n * chips are the primary create path — creation and placement collapse into\n * one act, so the trim handles become a correction tool, not the way things\n * are made.\n */\nimport { mapTime } from '@vosjs/timeline'\nimport { DEFAULT_ZOOM_LEVEL, ZOOM_SPAN_MIN, clampSpeedRate } from '../types'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport type { Segment } from '@vosjs/timeline'\nimport type { StudioDoc } from '../doc/studioDoc'\nimport type { SpeedSpan, ZoomSpan } from '../types'\n\n/** Map an OUTPUT-time range to SOURCE seconds through the doc's rate map. */\nexport function outputRangeToSource(\n doc: StudioDoc,\n t0: number,\n t1: number,\n): { srcIn: number; srcOut: number } {\n const rated = ratedSegments(doc)\n return {\n srcIn: mapTime(rated, Math.max(0, t0)),\n srcOut: mapTime(rated, Math.max(0, t1)),\n }\n}\n\nconst EPS = 1e-6\n/** A trimmed remainder below this (source seconds) is a crumb — drop it. */\nconst MIN_REMAINDER = 0.25\n/** Kept-segment floor after a cut (matches the video lane's split guard). */\nconst MIN_SEGMENT = 0.05\n\n/**\n * Re-rate a SOURCE range: spans overlapping it are trimmed (a span split in\n * two mints a fresh id for the second half), then a manual span at `rate`\n * covers the range. `rate: null` just clears — the 1× chip.\n */\nexport function setSpeedInRange(\n spans: readonly SpeedSpan[],\n srcIn: number,\n srcOut: number,\n rate: number | null,\n): SpeedSpan[] {\n if (srcOut - srcIn < EPS) return [...spans]\n const kept: SpeedSpan[] = []\n const used = new Set(spans.map((s) => s.id))\n for (const s of spans) {\n if (s.out <= srcIn + EPS || s.in >= srcOut - EPS) {\n kept.push(s)\n continue\n }\n if (srcIn - s.in >= MIN_REMAINDER)\n kept.push({ ...s, out: round(srcIn), source: 'manual' })\n if (s.out - srcOut >= MIN_REMAINDER)\n kept.push({\n ...s,\n id: mintId(used),\n in: round(srcOut),\n source: 'manual',\n })\n }\n if (rate != null)\n kept.push({\n id: mintId(used),\n in: round(srcIn),\n out: round(srcOut),\n rate: clampSpeedRate(rate),\n source: 'manual',\n })\n return kept.sort((a, b) => a.in - b.in)\n}\n\n/** The Remove chip: drop every span the range touches, whole. */\nexport function removeSpeedInRange(\n spans: readonly SpeedSpan[],\n srcIn: number,\n srcOut: number,\n): SpeedSpan[] {\n return spans.filter((s) => s.out <= srcIn + EPS || s.in >= srcOut - EPS)\n}\n\n/**\n * The Cut chip: subtract a SOURCE range from the kept segments. Segment\n * order (and any reorder) is preserved; a remainder below MIN_SEGMENT is\n * dropped with its parent. Never empties the take: cutting everything\n * returns the original list unchanged.\n */\nexport function removeSourceRange(\n segments: readonly Segment[],\n srcIn: number,\n srcOut: number,\n): Segment[] {\n const next: Segment[] = []\n for (const seg of segments) {\n if (seg.out <= srcIn + EPS || seg.in >= srcOut - EPS) {\n next.push(seg)\n continue\n }\n if (srcIn - seg.in >= MIN_SEGMENT) next.push({ ...seg, out: round(srcIn) })\n if (seg.out - srcOut >= MIN_SEGMENT)\n next.push({ ...seg, in: round(srcOut) })\n }\n return next.length ? next : [...segments]\n}\n\n/**\n * The Zoom chip: a manual zoom span covering as much of the range as the\n * lane's non-overlap rule allows — clipped against existing spans, starting\n * at the first free moment inside the range. Null when the free room is\n * below the zoom floor (the chip greys out).\n */\nexport function zoomSpanForRange(\n zoom: readonly ZoomSpan[],\n srcIn: number,\n srcOut: number,\n): ZoomSpan | null {\n let start = srcIn\n const covering = zoom.find((z) => z.in <= start + EPS && z.out > start + EPS)\n if (covering) start = covering.out\n let end = srcOut\n for (const z of zoom) {\n if (z.in >= start - EPS && z.in < end) end = z.in\n }\n if (end - start < ZOOM_SPAN_MIN) return null\n const used = new Set(zoom.map((z) => z.id))\n let n = 0\n while (used.has(`u${n}`)) n++\n return {\n id: `u${n}`,\n in: round(start),\n out: round(end),\n level: DEFAULT_ZOOM_LEVEL,\n cx: 0.5,\n cy: 0.5,\n source: 'manual',\n }\n}\n\n/** Smallest unused `sp{n}` id, reserving it in `used` for the next mint. */\nfunction mintId(used: Set<string>): string {\n let n = 0\n while (used.has(`sp${n}`)) n++\n const id = `sp${n}`\n used.add(id)\n return id\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","import { applyTimelineEdits } from '@vosjs/shared/timelineEdits'\nimport { totalDuration } from '@vosjs/timeline'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport {\n lowerToComposition,\n ratedSegments,\n studioLayerData,\n} from './lowerToComposition'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\nimport type { ProgramAnchorDoc, StudioDoc } from '../doc/studioDoc'\nimport type { LoweredComposition } from './lowerToComposition'\n\n/**\n * ONE lowering for the document family: the anchor's program\n * plus the studio stack entry, on every anchor.\n *\n * - A recording lowers to its card program (`lowerToComposition`), which\n * already carries the entry.\n * - A program lowers to the user's config, COMPLETE and untouched (the\n * execution IR, D1) with its tween-timing overlay baked into\n * `createTimeline`, plus the same entry carrying the shared layers. Params\n * and Looks ride the config as authored. `retime` arrives with speed spans\n * absent, the engine's identity is the identity.\n *\n * The composed config is what the platform stores for a layered program and\n * what the fleet compiles; the player runs it MINUS every data object (the\n * structural hash), with `data` and `stack` delivered live.\n */\nexport function lowerStudioDoc(\n doc: StudioDoc,\n opts: LowerProgramOptions = {},\n): LoweredComposition {\n return isRecordingDoc(doc)\n ? lowerToComposition(doc)\n : lowerProgramDoc(doc, opts)\n}\n\nexport interface LowerProgramOptions {\n /**\n * Bake the tween overlay into `createTimeline` (the STORED composed\n * config: the fleet, the watch page and `vos render` have no bridge to\n * hand an overlay to). Off by default: the player runs the user's\n * timeline and retimes it live, so the program string is constant\n * across every timing edit.\n */\n bake?: boolean\n}\n\nexport { programDuration } from '../doc/studioDoc'\n\n/**\n * `config.retime` for a program with speed spans: output time →\n * program time through the RATED segments on `data.retime` (the same map\n * `mapTime` performs; inlined so the config stays self-contained, tested\n * against it). Reads data live, so a rate edit is SET_DATA, never a LOAD.\n */\nexport const PROGRAM_RETIME = `(t, data) => {\n var s = data && data.retime\n if (!s || !s.length) return t\n var acc = 0\n for (var i = 0; i < s.length; i++) {\n var r = s[i].rate && s[i].rate > 0 ? s[i].rate : 1\n var d = (s[i].out - s[i].in) / r\n if (t < acc + d) return s[i].in + (t - acc) * r\n acc += d\n }\n return s[s.length - 1].out\n}`\n\n/**\n * With speed spans the OUTPUT length is not the program's own, but the\n * engine hands ONE \\`duration\\` to both the clock and \\`createTimeline\\`. The\n * composed config's \\`duration\\` is the output length (the clock, the fleet's\n * render length); this wrapper hands the user's function the program's own\n * length from \\`data.programDuration\\` — data, so a rate edit stays live.\n */\nexport function wrapProgramLength(source: string): string {\n return `(ctx, content, duration) => {\n const __base = (${source});\n const __own = ctx && ctx.data && typeof ctx.data.programDuration === 'number' ? ctx.data.programDuration : duration;\n return __base(ctx, content, __own);\n}`\n}\n\nexport function lowerProgramDoc(\n doc: ProgramAnchorDoc,\n opts: LowerProgramOptions = {},\n): LoweredComposition {\n const edits = Object.values(doc.program.tweenEdits ?? {})\n const anchor = (\n opts.bake\n ? applyTimelineEdits(\n doc.program.config as { createTimeline?: unknown },\n edits,\n )\n : doc.program.config\n ) as Record<string, unknown>\n // Speed spans retime the program on the engine. The program's own\n // length is the source clock; the rated segments give the output length.\n const own = programDuration(doc)\n const rated = ratedSegments(doc)\n const duration = own > 0 ? totalDuration(rated) : 0\n const entryData: Record<string, unknown> = studioLayerData(doc, duration)\n const baseData =\n anchor.data && typeof anchor.data === 'object'\n ? (anchor.data as Record<string, unknown>)\n : {}\n const retimed = !!doc.speed?.length && own > 0\n const data: Record<string, unknown> = retimed\n ? { ...baseData, retime: rated, programDuration: own }\n : baseData\n const config: Record<string, unknown> = {\n ...anchor,\n ...(retimed\n ? {\n duration,\n data,\n retime: PROGRAM_RETIME,\n createTimeline: wrapProgramLength(\n String(anchor.createTimeline ?? ''),\n ),\n }\n : {}),\n stack: [studioEntry(entryData)],\n }\n return {\n config,\n data,\n stack: { [STUDIO_ENTRY_ID]: entryData },\n ...(opts.bake ? {} : { tweenEdits: edits }),\n duration,\n }\n}\n","// GENERATED by packages/studio-core/scripts/build-destinations.mjs — do not edit.\n// Source: packages/cli/schema/channel-specs.json (verified 2026-08-04).\n// Re-run the script and commit whenever the specs change; destinations.test.ts\n// gates staleness on CHANNEL_SPECS_HASH.\n\n/**\n * A destination is where a release's media goes — the output twin of the\n * doors registry's \"a door is what you bring\". One row per channel asset,\n * derived from the verified channel specs; `vos deliver` loops these and\n * the kit manifest records them.\n */\nexport interface Destination {\n /** `${channel}-${asset}` — the id `vos deliver --to` and kit.json use. */\n id: string\n channel: string\n asset: string\n label: string\n kind: 'video' | 'still' | 'still-set'\n /** Reduced aspect ratio, the exportSizeFor convention. */\n ratio: string\n px: { w: number; h: number }\n /** still-set only: how many the channel takes. */\n count?: { min: number; max: number }\n /**\n * Image genre: 'screenshot' = real UI from the take (store policy demands\n * real UX); 'card' = a COMPOSED cover — rendered from the maker's poster\n * program when `vos deliver --poster` has one.\n */\n genre?: 'screenshot' | 'card'\n minSeconds?: number\n maxSeconds?: number\n maxBytes?: number\n /** The format the kit renders. */\n format: 'mp4' | 'png'\n /** What the channel accepts, in the spec's own words. */\n accepts: string\n /** How footage meets an off-ratio frame (a still fills, a video letterboxes). */\n fit: 'contain' | 'cover'\n /**\n * The destination's word policy: 'none' = the picture carries it alone (a\n * tile, a store screenshot), 'allowed', 'expected' = a card that should\n * say something. The picture checks read it.\n */\n text: 'none' | 'allowed' | 'expected'\n /**\n * The rect, as fractions of the frame, every text box must sit inside:\n * the platform's own chrome or crop covers the rest.\n */\n safe: { x: number; y: number; w: number; h: number }\n /**\n * The poster template a card destination renders from by default (the\n * family that ships with the CLI); absent on screenshots and videos.\n */\n template?: string\n notes: string\n}\n\nexport const CHANNEL_SPECS_VERIFIED = '2026-08-04'\n\nexport const CHANNEL_SPECS_HASH =\n 'e48926c22737b5dfaabf5cb33916a50a5eccd4b93498c2059a1468bd963a0a86'\n\nexport const DESTINATIONS: Destination[] = [\n {\n id: 'youtube-main-demo',\n channel: 'youtube',\n asset: 'main-demo',\n label: 'YouTube demo',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 60,\n maxSeconds: 120,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC)',\n fit: 'contain',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n 'Captions burned in. The same public upload serves the Chrome Web Store promo video and Product Hunt video (both take YouTube URLs only).',\n },\n {\n id: 'x-feed-cut',\n channel: 'x',\n asset: 'feed-cut',\n label: 'X feed cut',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 30,\n maxSeconds: 140,\n maxBytes: 536870912,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC ONLY — HEVC/VP9/AV1 rejected)',\n fit: 'contain',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n '30–60s plays best; 140s/512MB is the free-tier ceiling. 1:1 also allowed. Native upload, never a link post.',\n },\n {\n id: 'shorts-linkedin-vertical-cut',\n channel: 'shorts-linkedin',\n asset: 'vertical-cut',\n label: 'Shorts / LinkedIn vertical cut',\n kind: 'video',\n ratio: '9:16',\n px: {\n w: 1080,\n h: 1920,\n },\n minSeconds: 30,\n maxSeconds: 90,\n format: 'mp4',\n accepts: 'mp4 (H.264+AAC)',\n fit: 'contain',\n text: 'expected',\n safe: {\n x: 0.0833,\n y: 0.1979,\n w: 0.8333,\n h: 0.6042,\n },\n notes:\n 'Critical text inside a centered ~900×1160 safe zone — platform chrome covers the rest. Serves YouTube Shorts and LinkedIn native vertical.',\n },\n {\n id: 'github-readme-loop',\n channel: 'github',\n asset: 'readme-loop',\n label: 'GitHub README loop',\n kind: 'video',\n ratio: '16:9',\n px: {\n w: 1920,\n h: 1080,\n },\n minSeconds: 10,\n maxSeconds: 20,\n maxBytes: 10485760,\n format: 'mp4',\n accepts: 'mp4 (H.264)',\n fit: 'contain',\n text: 'none',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n notes:\n '≤10MB is the free-plan attachment ceiling; two-pass target bitrate from duration.',\n },\n {\n id: 'youtube-thumbnail',\n channel: 'youtube',\n asset: 'thumbnail',\n label: 'YouTube thumbnail',\n kind: 'still',\n ratio: '16:9',\n px: {\n w: 1280,\n h: 720,\n },\n genre: 'card',\n maxBytes: 2097152,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '',\n },\n {\n id: 'cws-screenshot',\n channel: 'cws',\n asset: 'screenshot',\n label: 'Chrome Web Store screenshot',\n kind: 'still-set',\n ratio: '8:5',\n px: {\n w: 1280,\n h: 800,\n },\n count: {\n min: 1,\n max: 5,\n },\n genre: 'screenshot',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes:\n 'Real UX only — misleading listing images are a removal-grade violation. Full bleed, square corners.',\n },\n {\n id: 'cws-small-promo-tile',\n channel: 'cws',\n asset: 'small-promo-tile',\n label: 'Chrome Web Store small promo tile',\n kind: 'still',\n ratio: '11:7',\n px: {\n w: 440,\n h: 280,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n template: 'card-on-gradient',\n notes:\n 'Listings without one rank lower. No text; fill the region; subject centered.',\n },\n {\n id: 'cws-marquee',\n channel: 'cws',\n asset: 'marquee',\n label: 'Chrome Web Store marquee',\n kind: 'still',\n ratio: '5:2',\n px: {\n w: 1400,\n h: 560,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: 'Carousel eligibility.',\n },\n {\n id: 'cws-icon',\n channel: 'cws',\n asset: 'icon',\n label: 'Chrome Web Store icon',\n kind: 'still',\n ratio: '1:1',\n px: {\n w: 128,\n h: 128,\n },\n format: 'png',\n accepts: 'png',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes: '96×96 art + 16px transparent padding.',\n },\n {\n id: 'producthunt-thumbnail',\n channel: 'producthunt',\n asset: 'thumbnail',\n label: 'Product Hunt thumbnail',\n kind: 'still',\n ratio: '1:1',\n px: {\n w: 240,\n h: 240,\n },\n genre: 'card',\n maxBytes: 3145728,\n format: 'png',\n accepts: 'gif|png',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0.06,\n y: 0.06,\n w: 0.88,\n h: 0.88,\n },\n template: 'card-on-gradient',\n notes: 'GIF animates on hover only — the first frame must stand alone.',\n },\n {\n id: 'producthunt-gallery',\n channel: 'producthunt',\n asset: 'gallery',\n label: 'Product Hunt gallery',\n kind: 'still-set',\n ratio: '127:76',\n px: {\n w: 1270,\n h: 760,\n },\n count: {\n min: 4,\n max: 8,\n },\n genre: 'screenshot',\n format: 'png',\n accepts: 'png|jpg|gif',\n fit: 'cover',\n text: 'none',\n safe: {\n x: 0,\n y: 0,\n w: 1,\n h: 1,\n },\n notes: 'First image is the hero.',\n },\n {\n id: 'x-feed-image',\n channel: 'x',\n asset: 'feed-image',\n label: 'X feed image',\n kind: 'still',\n ratio: '16:9',\n px: {\n w: 1200,\n h: 675,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'allowed',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '',\n },\n {\n id: 'linkedin-feed-image',\n channel: 'linkedin',\n asset: 'feed-image',\n label: 'LinkedIn feed image',\n kind: 'still',\n ratio: '400:209',\n px: {\n w: 1200,\n h: 627,\n },\n genre: 'card',\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.08,\n w: 0.9,\n h: 0.84,\n },\n template: 'split-cover',\n notes: '1080×1350 vertical also performs in the mobile feed.',\n },\n {\n id: 'og-card',\n channel: 'og',\n asset: 'card',\n label: 'OG card',\n kind: 'still',\n ratio: '40:21',\n px: {\n w: 1200,\n h: 630,\n },\n genre: 'card',\n maxBytes: 1048576,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.05,\n y: 0.0238,\n w: 0.9,\n h: 0.9524,\n },\n template: 'split-cover',\n notes:\n 'Text in the center ~1080×600. Set twitter:card=summary_large_image explicitly — og:image alone gets the small card.',\n },\n {\n id: 'github-social-preview',\n channel: 'github',\n asset: 'social-preview',\n label: 'GitHub social preview',\n kind: 'still',\n ratio: '2:1',\n px: {\n w: 1280,\n h: 640,\n },\n genre: 'card',\n maxBytes: 1048576,\n format: 'png',\n accepts: 'png|jpg',\n fit: 'cover',\n text: 'expected',\n safe: {\n x: 0.0391,\n y: 0.0781,\n w: 0.9219,\n h: 0.8438,\n },\n template: 'card-on-gradient',\n notes: 'Key text ≥50px from every edge.',\n },\n]\n\nexport function destinationById(id: string): Destination | undefined {\n return DESTINATIONS.find((d) => d.id === id)\n}\n\nexport function destinationsForChannel(channel: string): Destination[] {\n return DESTINATIONS.filter((d) => d.channel === channel)\n}\n","/**\n * The house starters for an HTML layer: what one press of the studio's HTML\n * verb puts on the canvas, and what its panel's Starter select swaps in.\n * Product defaults the way the three text presets are (code, not shelf\n * data), five and not fifty: a LIBRARY of components is an official project\n * on the shelf that an agent applies as a template.\n *\n * Each is real, readable markup and CSS a person can retune, in the house\n * look (a dark card, Inter for words, JetBrains Mono for code, both hosted so\n * the faces resolve from the CSS alone), with a `box-shadow` so the derived\n * bleed is exercised. `htmlStarters.test.ts` proves every one passes the\n * gate with no problem and no warning.\n */\nimport { HOUSE_REGISTER, calloutClip } from './callout'\nimport type { HtmlOverlayClip } from './types'\n\nexport interface HtmlStarter {\n id: 'note' | 'tag' | 'code' | 'terminal' | 'keys' | 'badge'\n name: string\n html: string\n css: string\n box: { width: number; height: number }\n}\n\n/** The grammar's shape as a starter: its source in the house register. */\nfunction grammarStarter(\n id: 'note' | 'tag' | 'code',\n name: string,\n words: Parameters<typeof calloutClip>[2],\n): HtmlStarter {\n const clip = calloutClip(id, HOUSE_REGISTER, words, {\n id,\n start: 0,\n duration: 1,\n scale: 1.4,\n })\n return { id, name, html: clip.html, css: clip.css ?? '', box: clip.box }\n}\n\nconst WORDS =\n \"font-family:'Inter',-apple-system,system-ui,sans-serif;color:#fafafa;-webkit-font-smoothing:antialiased;\"\nconst MONO = \"font-family:'JetBrains Mono',ui-monospace,monospace;\"\nconst CARD =\n 'background:#0b0b0d;border:1px solid rgba(255,255,255,.08);border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45);'\n\nexport const HTML_STARTERS: readonly HtmlStarter[] = [\n // The callout grammar (three shapes a viewer learns once), in the house\n // register: a dark card in the house hue over a light app.\n grammarStarter('note', 'Note', {\n kicker: 'Presets',\n title: 'One of 43, picked here.',\n body: 'One line that says what this is about.',\n }),\n grammarStarter('tag', 'Tag', { kicker: 'New in 2.0' }),\n grammarStarter('code', 'Code', {\n kicker: 'index.css',\n code: ':root {\\n --primary: oklch(0.63 0.06 4.6);\\n --radius: 2.25rem;\\n}',\n }),\n {\n id: 'terminal',\n name: 'Terminal',\n box: { width: 460, height: 152 },\n html:\n '<div class=\"term\"><span class=\"prompt\">$</span> <span class=\"cmd\">npx vos record https://your.app</span>\\n' +\n '<span class=\"mut\">recording 1920x1080 at 30 fps</span>\\n' +\n '<span class=\"mut\">saved take/</span></div>',\n css:\n `.term{${MONO}${CARD}background:#08080a;color:#e8e8ec;box-sizing:border-box;width:460px;height:152px;padding:22px 24px;font-size:14px;line-height:1.8;white-space:pre}` +\n '.prompt{color:#4ade80}.mut{color:#5b5b66}',\n },\n {\n id: 'keys',\n name: 'Keys',\n box: { width: 220, height: 64 },\n html: '<div class=\"keys\"><kbd>⌘</kbd><span class=\"plus\">+</span><kbd>K</kbd></div>',\n css:\n `.keys{${WORDS}display:flex;align-items:center;justify-content:center;gap:10px;width:220px;height:64px}` +\n 'kbd{display:inline-flex;align-items:center;justify-content:center;min-width:48px;height:48px;padding:0 14px;border-radius:10px;background:#1a1a1f;border:1px solid rgba(255,255,255,.14);border-bottom-width:3px;box-shadow:0 10px 24px rgba(0,0,0,.4);font-size:22px;font-weight:600}' +\n '.plus{color:#8b8b94;font-size:18px}',\n },\n {\n id: 'badge',\n name: 'Badge',\n box: { width: 200, height: 52 },\n html: '<div class=\"badge\"><span class=\"dot\"></span>New in 2.0</div>',\n css:\n `.badge{${WORDS}display:inline-flex;align-items:center;gap:10px;height:44px;padding:0 20px;border-radius:99px;background:#1a1a1f;border:1px solid rgba(255,255,255,.12);box-shadow:0 12px 30px rgba(0,0,0,.4);font-size:16px;font-weight:600;letter-spacing:-0.01em}` +\n '.dot{width:8px;height:8px;border-radius:99px;background:#3b82f6;box-shadow:0 0 10px rgba(59,130,246,.9)}',\n },\n]\n\n/**\n * A starter as a clip at a moment: the studio's verb and the panel's Starter\n * select both call this, so what a press creates and what a swap replaces\n * are one shape. `bleed` is left absent (derived), `width` absent (the\n * design size), the transform centred.\n */\nexport function htmlStarterClip(\n starter: HtmlStarter,\n id: string,\n start: number,\n duration: number,\n): HtmlOverlayClip {\n return {\n id,\n kind: 'html',\n html: starter.html,\n css: starter.css,\n box: { ...starter.box },\n start,\n duration,\n transform: { x: 0.5, y: 0.5, scale: 1, rotation: 0 },\n }\n}\n","import type { EnvelopePoint } from './audioEnvelope'\n\n/**\n * The studio's audio clips as an ENGINE audio plan:\n * the shape `@vosjs/core/audio`'s `mixAudio` renders. One builder for every\n * export path (the device exporter, the fleet's audio page) from the same\n * lowered data the preview scheduler plays, so what you hear is what exports.\n *\n * A clip is OUTPUT-anchored: it plays from `start` for `len` seconds, reading\n * the source from `in` (looping over `[in, out]` when `loop`), at its gain\n * envelope (`env`, absolute output seconds, fades included) times the duck\n * curve when it ducks. The plan samples that at `step` (240/s, the engine's\n * default); the mixer interpolates between points and treats the loop's\n * wrap as a seek.\n *\n * Structurally typed: the lowered clip, not the doc — this runs on the fleet\n * from stored config data as well as in the studio.\n */\nexport interface LoweredAudioClip {\n key: string\n start: number\n in: number\n out: number\n gain: number\n loop: boolean\n len: number\n duck: boolean\n env: EnvelopePoint[]\n}\n\nexport interface AudioPlanPoint {\n t: number\n on: boolean\n pos: number\n gain: number\n}\n\nexport interface AudioPlanTrack {\n id: string\n src: string\n loop: boolean\n points: AudioPlanPoint[]\n}\n\nexport interface StudioAudioPlan {\n duration: number\n step: number\n tracks: AudioPlanTrack[]\n}\n\nexport const AUDIO_PLAN_STEP = 1 / 240\n\n/** Linear interpolation over absolute-time envelope points; `def` outside an empty one. */\nexport function envelopeAt(\n env: readonly EnvelopePoint[],\n t: number,\n def: number,\n): number {\n const n = env.length\n if (!n) return def\n if (t <= env[0].t) return env[0].g\n if (t >= env[n - 1].t) return env[n - 1].g\n let lo = 0\n let hi = n - 1\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1\n if (env[mid].t <= t) lo = mid\n else hi = mid\n }\n const a = env[lo]\n const b = env[hi]\n if (b.t <= a.t) return b.g\n return a.g + ((b.g - a.g) * (t - a.t)) / (b.t - a.t)\n}\n\nexport function studioAudioPlan(\n clips: readonly LoweredAudioClip[],\n duckEnv: readonly EnvelopePoint[],\n duration: number,\n step = AUDIO_PLAN_STEP,\n): StudioAudioPlan {\n const count = Math.max(0, Math.ceil(duration / step)) + 1\n const tracks: AudioPlanTrack[] = []\n clips.forEach((clip, i) => {\n const span = clip.out - clip.in\n const len = clip.len > 0 ? clip.len : span\n if (!(span > 0) || !(len > 0) || clip.start >= duration) return\n const end = clip.start + len\n const points: AudioPlanPoint[] = new Array(count)\n for (let k = 0; k < count; k++) {\n const t = k * step\n const on = t >= clip.start && t < end\n const local = Math.max(0, t - clip.start)\n const pos = clip.loop\n ? clip.in + (local % span)\n : clip.in + Math.min(local, span)\n const gain = on\n ? Math.max(\n 0,\n envelopeAt(clip.env, t, clip.gain) *\n (clip.duck ? envelopeAt(duckEnv, t, 1) : 1),\n )\n : 0\n points[k] = { t, on, pos, gain }\n }\n // The mixer loops over the WHOLE source; a clip loops over `[in, out]`,\n // which the positions above express themselves (the wrap is a seek).\n tracks.push({ id: `clip${i}`, src: clip.key, loop: false, points })\n })\n return { duration, step, tracks }\n}\n","/**\n * A LOOK is how the card is presented: the ground it sits on, how much of\n * the frame it takes, the shadow that makes it an object, the stroke that\n * separates a light card from a light ground. Three house looks cover the\n * feature-clip and poster grammar the premium tools default to (a cream\n * plate with a soft wide shadow; a warm gradient; a near-black plate with a\n * light streak and a glow), and a maker's brand kit resolves to one of them\n * with its own colours. Pure: a look resolves to `FrameStyle` fields, so\n * every renderer and every still reads the same placement.\n */\nimport type { FrameStyle } from './types'\n\nexport type LookKind = 'plate' | 'gradient' | 'dark'\n\n/**\n * Where the card goes. `hero` is the feature-clip grammar: the card at\n * ~84% of the width with headroom above and its bottom edge running off\n * the frame (\"this continues\"). `card` is the poster grammar: the card\n * centred on the ground with even room around it; on a frame too wide to\n * hold the whole card it falls back to `hero`.\n */\nexport type LookPlacement = 'hero' | 'card'\n\nexport interface Look {\n kind: LookKind\n /** CSS ground painted under the card (a colour or a gradient). */\n ground: string\n /** The card's width as a fraction of the frame. */\n cardWidth: number\n /** Room above the card in the hero placement, a fraction of the height. */\n headroom: number\n /** Corner radius in design px. */\n radius: number\n /** Ambient shadow 0..1. */\n shadow: number\n /** Contact shadow 0..1. */\n shadowContact: number\n /** `#rrggbb`; absent = black. */\n shadowColor?: string\n /** Hairline stroke alpha 0..1 (0 = none). */\n border: number\n borderColor?: string\n}\n\n/** The frontmatter roles a brand kit carries that a look reads. */\nexport interface LookBrand {\n look?: string | null\n ground?: string | null\n bgA?: string | null\n bgB?: string | null\n bgC?: string | null\n accent?: string | null\n ink?: string | null\n}\n\n/** The house coral, the ground every take opened on before looks existed. */\nexport const HOUSE_GRADIENT = 'linear-gradient(135deg, #ff5148, #ffb03a)'\n\n/** The Chrome feature-clip plate, measured on three clips. */\nexport const PLATE_GROUND = '#f0f2f4'\n\n/** Near-black with a warm light streak from the top right. */\nexport const DARK_GROUND =\n 'radial-gradient(ellipse at 82% 0%, #2a1d16, #07080b)'\n\nconst LOOKS: Record<LookKind, Look> = {\n plate: {\n kind: 'plate',\n ground: PLATE_GROUND,\n cardWidth: 0.84,\n headroom: 0.16,\n radius: 12,\n shadow: 0.42,\n shadowContact: 0,\n border: 0.08,\n borderColor: '#000000',\n },\n gradient: {\n kind: 'gradient',\n ground: HOUSE_GRADIENT,\n cardWidth: 0.84,\n headroom: 0.16,\n radius: 12,\n shadow: 0.5,\n shadowContact: 0,\n border: 0,\n },\n dark: {\n kind: 'dark',\n ground: DARK_GROUND,\n cardWidth: 0.86,\n headroom: 0.14,\n radius: 12,\n shadow: 0.7,\n shadowContact: 0,\n shadowColor: '#000000',\n border: 0.12,\n borderColor: '#ffffff',\n },\n}\n\nexport const LOOK_KINDS: LookKind[] = ['plate', 'gradient', 'dark']\n\nexport function isLookKind(v: unknown): v is LookKind {\n return typeof v === 'string' && (LOOK_KINDS as string[]).includes(v)\n}\n\n/** One of the three house looks, copied. */\nexport function houseLook(kind: LookKind): Look {\n return { ...LOOKS[kind] }\n}\n\nconst HEX = /^#([0-9a-f]{6})$/i\n\nfunction luma(hex: string): number | null {\n const m = HEX.exec(hex.trim())\n if (!m) return null\n const n = parseInt(m[1], 16)\n const r = (n >> 16) & 255\n const g = (n >> 8) & 255\n const b = n & 255\n return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255\n}\n\n/**\n * Which house look a site's own ground asks for: a paper site is a plate, a\n * dark site is dark, anything else takes the gradient. What `vos brand`\n * writes as the `look` role, so an outside maker never gets the house coral\n * by accident.\n */\nexport function lookKindForGround(bgA: string | null | undefined): LookKind {\n const l = bgA ? luma(bgA) : null\n if (l === null) return 'gradient'\n if (l >= 0.85) return 'plate'\n if (l <= 0.2) return 'dark'\n return 'gradient'\n}\n\n/**\n * Resolve a brand kit to a look: the `look` role picks the kind (else the\n * site's ground decides), `ground` overrides the ground outright, and the\n * kit's colours colour the house look (plate = bgB, gradient = accent →\n * bgC, dark = bgA with a lifted shadow). A kit with none of them is the\n * house look unchanged.\n */\nexport function lookFromBrand(brand: LookBrand | null | undefined): Look {\n const b = brand ?? {}\n const kind = isLookKind(b.look) ? b.look : lookKindForGround(b.bgA)\n const look = houseLook(kind)\n if (kind === 'plate' && b.bgB && HEX.test(b.bgB)) look.ground = b.bgB\n if (kind === 'gradient' && b.accent && b.bgC && HEX.test(b.accent)) {\n look.ground = `linear-gradient(135deg, ${b.accent}, ${b.bgC})`\n }\n if (kind === 'dark' && b.bgA && HEX.test(b.bgA)) {\n look.ground = `radial-gradient(ellipse at 82% 0%, ${mix(b.bgA, b.accent ?? '#ffffff', 0.18)}, ${b.bgA})`\n }\n if (b.ground && b.ground.trim()) look.ground = b.ground.trim()\n return look\n}\n\nfunction mix(a: string, b: string, t: number): string {\n const pa = HEX.exec(a)\n const pb = HEX.exec(b)\n if (!pa || !pb) return a\n const na = parseInt(pa[1], 16)\n const nb = parseInt(pb[1], 16)\n const ch = (sh: number) => {\n const x = (na >> sh) & 255\n const y = (nb >> sh) & 255\n return Math.round(x + (y - x) * t)\n }\n return `#${((ch(16) << 16) | (ch(8) << 8) | ch(0)).toString(16).padStart(6, '0')}`\n}\n\n/**\n * The card's per-side inset for a frame of `frameW:frameH` holding footage\n * of `videoW:videoH` under contain fit, as fractions of the frame. Card\n * height follows from the width (contain keeps the footage aspect): a card\n * that fits is centred (`card`) or given headroom (`hero`); one taller than\n * the frame bleeds off the bottom by at least 2%, the feature-clip cue.\n */\nexport function cardInset(\n look: Pick<Look, 'cardWidth' | 'headroom'>,\n frame: { w: number; h: number },\n video: { w: number; h: number },\n placement: LookPlacement,\n): NonNullable<FrameStyle['inset']> {\n const R = frame.w / Math.max(1, frame.h)\n const V = video.w / Math.max(1, video.h)\n const cw = look.cardWidth\n const ch = (cw * R) / V\n const side = (1 - cw) / 2\n if (placement === 'card' && ch <= 1 - look.headroom) {\n const v = (1 - ch) / 2\n return { left: side, right: side, top: v, bottom: v }\n }\n const top = look.headroom\n const bottom = Math.min(-0.02, 1 - top - ch)\n return { left: side, right: side, top, bottom }\n}\n\n/**\n * Apply a look to a frame for one output size and placement: the ground,\n * the inset, the radius, both shadow layers and the hairline. The bar and\n * the media layer are the document's own (a look presents the card, it\n * does not decide what chrome the card wears). `fit` is forced to contain,\n * because the card IS the footage's aspect under a look.\n */\nexport function applyLook(\n frame: FrameStyle,\n look: Look,\n size: { w: number; h: number },\n video: { w: number; h: number },\n placement: LookPlacement,\n): FrameStyle {\n const out: FrameStyle = {\n ...frame,\n background: look.ground,\n fit: 'contain',\n inset: cardInset(look, size, video, placement),\n radius: look.radius,\n shadow: look.shadow,\n shadowContact: look.shadowContact,\n border: look.border,\n }\n delete out.focus\n if (look.shadowColor) out.shadowColor = look.shadowColor\n else delete out.shadowColor\n if (look.border > 0) {\n out.borderWidth = 1\n out.borderColor = look.borderColor ?? '#000000'\n }\n return out\n}\n","/**\n * The studio's lane adapters — the app's opinion of its timeline: a video lane\n * (segments as clips; trim/split/remove), a speed lane (rate spans; retime/\n * re-rate/remove), and a zoom lane (zoom regions as clips; move/resize/add/\n * remove). Zoom spans and speed spans are SOURCE-anchored in the doc, so lanes\n * map them through the RATED segment list both ways (display: sourceToTimeline/\n * spanOutputExtent; gestures: mapTime) — output positions contract/stretch\n * with speed changes.\n */\nimport {\n mapTime,\n removeSegment,\n segmentRate,\n sourceToTimeline,\n splitBySpeed,\n totalDuration,\n trimSegment,\n} from '@vosjs/timeline'\nimport { ratedSegments, spanOutputExtent } from '../lower/lowerToComposition'\nimport { docFreezes, freezeOutputExtents, withFreezes } from '../lower/motion'\nimport {\n contiguous,\n effectiveSegments,\n outputLen,\n segmentPieces,\n segmentStarts,\n} from '../lower/segmentStarts'\nimport { mediaAtOutput, mediaDuration, mediaSource } from '../media'\nimport { docOutputDuration, voiceKey } from '../audioBeds'\nimport { htmlLayerLabel } from '../htmlLayer'\nimport { anchorSourceDuration } from '../doc/studioDoc'\nimport {\n CAM_SPAN_MIN,\n DEFAULT_CAM_POSE,\n FREEZE_DEFAULT_SECONDS,\n FREEZE_SECONDS_MIN,\n clampFreezeSeconds,\n DEFAULT_TILT_POSE,\n DEFAULT_ZOOM_LEVEL,\n OVERLAY_MIN_DURATION,\n SPEED_SPAN_MIN,\n TILT_SPAN_MIN,\n ZOOM_SPAN_MIN,\n clipLength,\n} from '../types'\nimport type { Segment } from '@vosjs/timeline'\nimport type { FreezeSpan, ProjectDoc } from '../types'\nimport type { LaneAdapter, LaneItem } from '@vosjs/editor'\n\nexport { effectiveSegments }\n\nexport const videoLane: LaneAdapter<ProjectDoc> = {\n id: 'video',\n label: 'Video',\n\n items(doc): LaneItem[] {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n // A clip is its footage plus the freezes on it (a freeze is output\n // time, so it lengthens the clip and pushes what follows).\n return segments.map((s, i) => ({\n id: `seg-${i}`,\n kind: 'clip',\n t: round(starts[i]),\n duration: round(\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n ),\n }))\n },\n\n gesture(doc, g) {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n switch (g.type) {\n case 'move': {\n // Dragging a segment REORDERS the cut sequence: pull it out, then\n // insert where the dragged start lands among the remaining segments\n // (midpoint rule). Single-segment docs have nothing to reorder.\n // NOTE ids are index-based (`seg-N`) — mid-drag the live doc reorders\n // under a frozen id, which is fine for the math (the anchoring\n // contract evaluates against the pointer-down doc) but means the drag\n // highlight can momentarily sit on a neighbor. Cosmetic only.\n if (segments.length < 2) return null\n const index = segIndex(g.id)\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n const others = segments.filter((_, i) => i !== index)\n let acc = 0\n let insert = others.length\n for (let i = 0; i < others.length; i++) {\n const dur = outputLen(others[i], speeds, freezes)\n if (g.t < acc + dur / 2) {\n insert = i\n break\n }\n acc += dur\n }\n if (insert === index) return null\n const next = [...others]\n next.splice(insert, 0, seg)\n return (d) => {\n d.segments = next\n }\n }\n case 'resize': {\n const index = segIndex(g.id)\n if (index < 0 || index >= segments.length) return null\n const seg = segments[index]\n // Translate the dragged output delta into a SOURCE edge position by\n // walking the full-source rate map (speed spans apply everywhere, so\n // an edge dragged across a 2× span consumes source 2× as fast — and\n // trimmed footage can still be dragged back out past the segment).\n const starts = segmentStarts(segments, speeds, freezes)\n const fullMap = splitBySpeed([{ in: 0, out: sourceDuration }], speeds)\n const edgeSrc = g.edge === 'start' ? seg.in : seg.out\n // The edge's output position as drawn, freezes included: the delta\n // to the pointer is then pure footage (speed-mapped), and a freeze\n // at the dragged end rides along with its frame.\n const edgeOutNow =\n g.edge === 'start'\n ? starts[index]\n : starts[index] +\n outputLen(\n seg,\n speeds,\n freezes.filter((f) => f.at < seg.out - 1e-9),\n index === 0 || !contiguous(segments, index),\n )\n const anchorOut =\n sourceToTimeline(fullMap, Math.min(edgeSrc, sourceDuration)) ??\n edgeSrc\n const sourceT = mapTime(fullMap, anchorOut + (g.t - edgeOutNow))\n const next = trimSegment(\n segments,\n index,\n g.edge === 'start' ? 'in' : 'out',\n sourceT,\n sourceDuration,\n )\n return (d) => {\n d.segments = next\n }\n }\n case 'create': {\n // Split under the playhead: locate the DOC segment whose output span\n // contains g.t (speed-aware starts), map the local output offset to a\n // source moment through that segment's own rated pieces, split there.\n // Doc segments never carry rates — those stay in doc.speed. No-op at\n // boundaries (either half would be degenerate), like splitSegments.\n const starts = segmentStarts(segments, speeds, freezes)\n const index = segments.findIndex(\n (s, i) =>\n g.t >= starts[i] &&\n g.t <\n starts[i] +\n outputLen(\n s,\n speeds,\n freezes,\n i === 0 || !contiguous(segments, i),\n ),\n )\n if (index < 0) return null\n const s = segments[index]\n const leading = index === 0 || !contiguous(segments, index)\n // Inside a freeze there is no footage to split: the press does nothing.\n const local = g.t - starts[index]\n for (const m of freezeOutputExtents(\n splitBySpeed([s], speeds),\n freezes.filter(\n (f) =>\n f.at <= s.out + 1e-9 &&\n (leading ? f.at >= s.in - 1e-9 : f.at > s.in + 1e-9),\n ),\n ).values()) {\n if (local >= m.t - 1e-9 && local <= m.t + m.duration + 1e-9)\n return null\n }\n const sourceT = round(\n mapTime(segmentPieces(s, speeds, freezes, leading), local),\n )\n if (sourceT - s.in < 0.05 || s.out - sourceT < 0.05) return null\n const next = [\n ...segments.slice(0, index),\n { ...s, out: sourceT },\n { ...s, in: sourceT },\n ...segments.slice(index + 1),\n ]\n return (d) => {\n d.segments = next\n }\n }\n case 'remove': {\n const next = removeSegment(segments, segIndex(g.id))\n if (next.length === segments.length) return null\n return (d) => {\n d.segments = next\n }\n }\n default:\n return null\n }\n },\n\n magnets(doc): number[] {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n const n = segments.length\n return [\n ...starts,\n ...(n\n ? [\n starts[n - 1] +\n outputLen(\n segments[n - 1],\n speeds,\n freezes,\n n === 1 || !contiguous(segments, n - 1),\n ),\n ]\n : []),\n ].map(round)\n },\n}\n\n/** The next free freeze id (`f{n}`). */\nfunction nextFreezeId(doc: ProjectDoc): string {\n const taken = new Set((doc.freeze ?? []).map((f) => f.id))\n let n = 0\n while (taken.has(`f${n}`)) n++\n return `f${n}`\n}\n\n/** Two freezes may not sit closer than this in source seconds. */\nconst FREEZE_GAP = 0.05\n\n/**\n * Freeze lane — freezes as hatched bands ON the footage clip, beside the\n * speed bands (the retime lane). A freeze is a SOURCE moment with output\n * seconds; the lane draws it where the rated pieces put it (a freeze\n * whose frame is cut away draws nothing) and the band's width is its\n * seconds. Create at the playhead (a press inside a freeze or off kept\n * footage no-ops); move re-anchors the moment pointer-true through the\n * rate map without this freeze; resize on the END edge sets the seconds,\n * on the START edge re-anchors the moment and keeps the end where it is.\n */\nexport const freezeLane: LaneAdapter<ProjectDoc> = {\n id: 'freeze',\n label: 'Freeze',\n\n items(doc): LaneItem[] {\n const freezes = docFreezes(doc)\n if (!freezes.length) return []\n const segs = effectiveSegments(doc)\n const marks = freezeOutputExtents(\n splitBySpeed(segs, doc.speed ?? []),\n freezes,\n )\n return freezes.flatMap((f) => {\n const m = marks.get(f.id)\n return m\n ? [\n {\n id: f.id,\n kind: 'clip' as const,\n t: round(m.t),\n duration: round(m.duration),\n label: `${trimZeros(f.seconds)}s`,\n },\n ]\n : []\n })\n },\n\n gesture(doc, g) {\n const freezes = docFreezes(doc)\n const segs = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const sourceDuration = anchorSourceDuration(doc)\n const kept = (t: number): boolean =>\n segs.some((s) => t >= s.in - 1e-9 && t <= s.out + 1e-9)\n const clear = (t: number, except?: string): boolean =>\n !freezes.some((f) => f.id !== except && Math.abs(f.at - t) < FREEZE_GAP)\n\n if (g.type === 'create') {\n const rated = ratedSegments(doc)\n // A press inside a freeze band is on that freeze, not on footage.\n for (const m of freezeOutputExtents(\n splitBySpeed(segs, speeds),\n freezes,\n ).values()) {\n if (g.t >= m.t - 1e-9 && g.t <= m.t + m.duration + 1e-9) return null\n }\n // Past the footage's end there is no frame to freeze.\n if (g.t > totalDuration(rated) + 1e-9) return null\n const srcT = round(mapTime(rated, Math.max(0, g.t)))\n if (!kept(srcT) || !clear(srcT)) return null\n const id = nextFreezeId(doc)\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n return (d) => {\n d.freeze = [\n ...(d.freeze ?? []),\n {\n id,\n at: srcT,\n seconds: FREEZE_DEFAULT_SECONDS,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.at - b.at)\n }\n }\n\n const f = freezes.find((x) => x.id === g.id)\n if (!f) return null\n // A legacy segment hold is read as a freeze but lives on the segment:\n // spell it as one before editing it, so the edit has a home.\n const own = (d: ProjectDoc): FreezeSpan | undefined => {\n const list = d.freeze ?? []\n let mine = list.find((x) => x.id === f.id)\n if (!mine) {\n const id = nextFreezeId(d)\n mine = { id, at: f.at, seconds: f.seconds }\n d.freeze = [...list, mine]\n d.segments = d.segments.map((sg) => {\n if (sg.hold === undefined || Math.abs(sg.out - f.at) > 1e-9) return sg\n const next = { ...sg }\n delete next.hold\n return next\n })\n }\n return mine\n }\n // POINTER-TRUE: map through the rate map WITHOUT this freeze.\n const base = withFreezes(\n splitBySpeed(segs, speeds),\n freezes.filter((x) => x.id !== f.id),\n )\n\n if (g.type === 'move' || (g.type === 'resize' && g.edge === 'start')) {\n const marks = freezeOutputExtents(splitBySpeed(segs, speeds), freezes)\n const mine = marks.get(f.id)\n const endOut = mine ? mine.t + mine.duration : null\n const srcT = round(\n Math.min(sourceDuration, mapTime(base, Math.max(0, g.t))),\n )\n if (!kept(srcT) || !clear(srcT, f.id)) return null\n return (d) => {\n const x = own(d)\n if (!x) return\n x.at = srcT\n if (g.type === 'resize' && endOut !== null) {\n // The end stays put: the seconds absorb the moved start.\n const after = freezeOutputExtents(\n splitBySpeed(effectiveSegments(d), d.speed ?? []),\n docFreezes({\n ...d,\n freeze: (d.freeze ?? []).map((y) =>\n y.id === x.id ? { ...y, seconds: FREEZE_SECONDS_MIN } : y,\n ),\n }),\n ).get(x.id)\n if (after) x.seconds = clampFreezeSeconds(endOut - after.t)\n }\n d.freeze!.sort((a, b) => a.at - b.at)\n }\n }\n\n if (g.type === 'resize') {\n const marks = freezeOutputExtents(splitBySpeed(segs, speeds), freezes)\n const mine = marks.get(f.id)\n if (!mine) return null\n const seconds = clampFreezeSeconds(g.t - mine.t)\n if (Math.abs(seconds - f.seconds) < 1e-6) return null\n return (d) => {\n const x = own(d)\n if (x) x.seconds = seconds\n }\n }\n\n if (g.type === 'remove') {\n return (d) => {\n const x = own(d)\n d.freeze = (d.freeze ?? []).filter((y) => y !== x)\n }\n }\n return null\n },\n\n magnets(doc): number[] {\n return freezeLane\n .items(doc)\n .flatMap((it) => [it.t, it.t + (it.duration ?? 0)])\n },\n}\n\n/** `2` for 2, `2.5` for 2.5: a band label without trailing zeros. */\nfunction trimZeros(n: number): string {\n return String(Math.round(n * 100) / 100)\n}\n\n/**\n * Zoom lane — zoom regions as clips (\"1.80×\"). Spans are SOURCE-anchored\n * (footage-anchored like speed spans and the cam window); the lane displays\n * the output extent of each span's KEPT footage (spanOutputExtent — partial\n * cuts snap the clip's edges, full cuts hide it until the trim is undone).\n * Move/resize are pointer-true through the FULL rated map — zoom never alters\n * rates, so no exclusion trick is needed (unlike speedLane). Spans never\n * overlap: create no-ops inside an existing span, move pushes out of\n * collisions (or no-ops), resize clamps against neighbors. Level/focus are\n * edited in the toolbar/inspector, not by gesture. Any gesture promotes the\n * span to source:'manual' — it survives an auto-zoom regenerate.\n */\nexport const zoomLane: LaneAdapter<ProjectDoc> = {\n id: 'zoom',\n label: 'Zoom',\n\n items(doc): LaneItem[] {\n const segments = ratedSegments(doc)\n return doc.zoom.flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: `${z.level.toFixed(2)}×`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.zoom\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n // The media under the playhead: the span is its, clears its spans\n // only, and runs to its own end.\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n const here = spans.filter((z) => (z.media ?? '') === mediaHere)\n const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source)\n if (here.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = here\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(mediaLen, next ? next.in : mediaLen)\n const len = Math.min(spanDefaultLen(mediaLen), limit - srcT)\n if (len < ZOOM_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextZoomId(doc)\n // Seed the focus from the cursor at the playhead (element-aware capture\n // means the cursor usually sits on the thing worth framing); center for\n // uploads with no track.\n const { cx, cy } = cursorFocusAt(doc, srcT)\n return (d) => {\n d.zoom = [\n ...d.zoom,\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n level: DEFAULT_ZOOM_LEVEL,\n cx,\n cy,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = d.zoom.find((x) => x.id === g.id)\n if (!z) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.zoom.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n // The floor is OUTPUT seconds: convert through the rate in force so a\n // span under a 5× speed-up cannot shrink to a sliver of screen time.\n // Never larger than the span's CURRENT length — a floor that exceeded\n // the room to a neighbour would shove the edge PAST the neighbour, and\n // a span already below floor must stay resizable, not grow by force.\n const minSrc = Math.min(\n ZOOM_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = d.zoom.find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.zoom = d.zoom.filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return zoomLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Focus seed for a new zoom: the cursor position nearest the playhead's source\n * moment, normalized to the cursor coordinate space (meta.width/height —\n * CursorEvent.x/y after normalizeCaptureSpace). Center when there's no track.\n */\nfunction cursorFocusAt(\n doc: ProjectDoc,\n srcT: number,\n): { cx: number; cy: number } {\n const track = doc.source.cursor\n const { width, height } = doc.source.meta\n if (!track.length || !width || !height) return { cx: 0.5, cy: 0.5 }\n const ms = srcT * 1000\n let best = track[0]\n for (const e of track)\n if (Math.abs(e.t - ms) < Math.abs(best.t - ms)) best = e\n const clamp01 = (v: number) => Math.max(0, Math.min(1, v))\n return {\n cx: round(clamp01(best.x / width)),\n cy: round(clamp01(best.y / height)),\n }\n}\n\n/**\n * Tilt lane — card-pose regions as clips (label = \"rx°/ry°\"). SOURCE-anchored\n * like zoom spans (footage-anchored through trims and speed changes; the full\n * rated map applies — tilt doesn't alter rates); non-overlapping. The pose\n * itself is edited in the span editor (like zoom level), not by gesture. Any\n * gesture promotes the span to source:'manual' — it survives a Dynamic-tilt\n * regenerate (the auto-zoom wand contract).\n */\nexport const tiltLane: LaneAdapter<ProjectDoc> = {\n id: 'tilt',\n label: 'Tilt',\n\n items(doc): LaneItem[] {\n const segments = ratedSegments(doc)\n return (doc.tilt ?? []).flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: `${formatDeg(z.rx)}°/${formatDeg(z.ry)}°`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.tilt ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n const here = spans.filter((z) => (z.media ?? '') === mediaHere)\n const mediaLen = mediaDuration(mediaSource(doc, mediaHere) ?? doc.source)\n if (here.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = here\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(mediaLen, next ? next.in : mediaLen)\n const len = Math.min(spanDefaultLen(mediaLen), limit - srcT)\n if (len < TILT_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextTiltId(doc)\n return (d) => {\n d.tilt = [\n ...(d.tilt ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n rx: DEFAULT_TILT_POSE.rx,\n ry: DEFAULT_TILT_POSE.ry,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = (d.tilt ?? []).find((x) => x.id === g.id)\n if (!z || !d.tilt) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.tilt.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n const minSrc = Math.min(\n TILT_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = (d.tilt ?? []).find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.tilt = (d.tilt ?? []).filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return tiltLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/** Degrees for clip labels: whole numbers stay whole (\"6\", not \"6.0\"). */\nfunction formatDeg(v: number): string {\n return Number.isInteger(v) ? String(v) : v.toFixed(1)\n}\n\n/**\n * Cam-move lane — animated cam layout regions as clips (label = the\n * pose size as a percent when set). SOURCE-anchored like tilt spans (the\n * full rated map applies); non-overlapping. The pose itself is edited on the\n * canvas or in the span editor, never by lane gesture. Structurally the tilt\n * lane with a different payload; kept separate so neither lane's clamps can\n * drift the other's.\n */\nexport const camMoveLane: LaneAdapter<ProjectDoc> = {\n id: 'camMove',\n label: 'Cam move',\n\n items(doc): LaneItem[] {\n if (!doc.source.camKey) return []\n const segments = ratedSegments(doc)\n return (doc.camMotion ?? []).flatMap((z) => {\n const ext = spanOutputExtent(segments, z.in, z.out, z.media)\n return ext === null\n ? []\n : [\n {\n id: z.id,\n kind: 'clip' as const,\n t: round(ext.start),\n duration: round(ext.end - ext.start),\n label: z.size != null ? `${Math.round(z.size * 100)}%` : 'Move',\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.camMotion ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n if (!doc.source.camKey) return null\n const srcT = mapTime(rated, Math.max(0, g.t))\n if (spans.some((z) => srcT >= z.in && srcT < z.out)) return null\n const next = spans\n .filter((z) => z.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(sourceDuration, next ? next.in : sourceDuration)\n const len = Math.min(spanDefaultLen(sourceDuration), limit - srcT)\n if (len < CAM_SPAN_MIN * rateAt(rated, srcT)) return null\n const id = nextCamMoveId(doc)\n return (d) => {\n d.camMotion = [\n ...(d.camMotion ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n ...DEFAULT_CAM_POSE,\n source: 'manual' as const,\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((z) => z.id === g.id)\n if (!sp) return null\n const others = spans.filter((o) => o.id !== g.id)\n\n if (g.type === 'move') {\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(rated, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const z = (d.camMotion ?? []).find((x) => x.id === g.id)\n if (!z || !d.camMotion) return\n z.in = round(newIn)\n z.out = round(newIn + len)\n z.source = 'manual'\n d.camMotion.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const lo = Math.max(\n 0,\n ...others.filter((o) => o.out <= sp.in).map((o) => o.out),\n )\n const hi = Math.min(\n sourceDuration,\n ...others.filter((o) => o.in >= sp.out).map((o) => o.in),\n )\n const sourceT = mapTime(rated, Math.max(0, g.t))\n const minSrc = Math.min(\n CAM_SPAN_MIN * rateAt(rated, sp.in),\n sp.out - sp.in,\n )\n const next =\n g.edge === 'start'\n ? {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n : {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n return (d) => {\n const z = (d.camMotion ?? []).find((x) => x.id === g.id)\n if (!z) return\n z.in = round(next.in)\n z.out = round(next.out)\n z.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.camMotion = (d.camMotion ?? []).filter((z) => z.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return camMoveLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Webcam lane — the Cam member row of the take group. Its clips are the\n * visibility window INTERSECTED with each kept segment, at the video lane's\n * exact output positions, so a split on the Video row visibly splits this row\n * too. Gestures still edit only the WINDOW (`cam.window`, SOURCE time,\n * footage-anchored): move slides it (dragging any of its clips moves the one\n * window), resize lives on the window's REAL edges — the first clip's start\n * and the last clip's end; the cut boundaries between them belong to the\n * Video row. There is no remove — hide the bubble via its panel.\n */\nexport const camLane: LaneAdapter<ProjectDoc> = {\n id: 'cam',\n label: 'Cam',\n\n items(doc): LaneItem[] {\n if (!doc.source.camKey || !doc.cam.visible) return []\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n const first = segments[0]\n const last = segments[segments.length - 1]\n const win = doc.cam.window ?? { in: first.in, out: last.out }\n const items: LaneItem[] = []\n segments.forEach((s, i) => {\n const a = Math.max(s.in, win.in)\n const b = Math.min(s.out, win.out)\n if (b - a <= 1e-6) return\n const leading = i === 0 || !contiguous(segments, i)\n items.push({\n id: `cam-${i}`,\n kind: 'clip',\n t: round(\n starts[i] + outputLen({ in: s.in, out: a }, speeds, freezes, leading),\n ),\n duration: round(\n outputLen({ in: a, out: b }, speeds, freezes, a === s.in && leading),\n ),\n })\n })\n return items\n },\n\n gesture(doc, g) {\n if (g.type !== 'move' && g.type !== 'resize') return null\n if (!g.id.startsWith('cam-')) return null\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n const current = doc.cam.window ?? { in: 0, out: sourceDuration }\n const eff = effectiveSegments(doc)\n const first = eff[0]\n const last = eff[eff.length - 1]\n if (g.type === 'move') {\n // Slide the whole window by the dragged clip's SOURCE delta — all the\n // row's clips are ONE window, so dragging any of them moves it.\n const index = Number(g.id.slice(4))\n const seg = eff.at(index)\n if (!seg) return null\n const clipSrcStart = Math.max(seg.in, Math.max(current.in, first.in))\n const span = Math.max(0.05, current.out - current.in)\n const delta = mapTime(rated, Math.max(0, g.t)) - clipSrcStart\n const base = Math.max(first.in, current.in)\n const newIn = Math.min(\n Math.max(first.in, base + delta),\n Math.max(first.in, last.out - span),\n )\n return (d) => {\n d.cam.window = { in: round(newIn), out: round(newIn + span) }\n }\n }\n // Only the window's REAL edges resize; interior edges are cut boundaries.\n // Derived from the window itself (not items(), which gates on camKey):\n // the first/last kept segment the window overlaps carry its edges.\n const overlapping = eff\n .map((s, i) => ({\n i,\n len: Math.min(s.out, current.out) - Math.max(s.in, current.in),\n }))\n .filter((x) => x.len > 1e-6)\n const edgeIdx =\n g.edge === 'start' ? overlapping.at(0)?.i : overlapping.at(-1)?.i\n if (edgeIdx === undefined || g.id !== `cam-${edgeIdx}`) return null\n const sourceT = Math.min(Math.max(mapTime(rated, g.t), 0), sourceDuration)\n const next =\n g.edge === 'start'\n ? { in: Math.min(sourceT, current.out - 0.05), out: current.out }\n : { in: current.in, out: Math.max(sourceT, current.in + 0.05) }\n return (d) => {\n d.cam.window = {\n in: round(Math.max(0, next.in)),\n out: round(Math.min(sourceDuration, next.out)),\n }\n }\n },\n\n magnets(doc): number[] {\n return camLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Mic sub-row of the take group: mirrors the video lane's cut boundaries\n * EXACTLY — one clip per kept segment at the same output positions — so the\n * voice visibly cuts and splits with the footage. VIEW-ONLY by design: cutting\n * happens on the Video row, because the take has ONE shared `segments` list\n * (that is what makes sub-track desync structurally impossible; per-row\n * segments would only buy bugs). Selecting a clip opens the Voice panel\n * (level/mute); the waveform is the row's content, so items carry no label.\n */\nexport const micLane: LaneAdapter<ProjectDoc> = {\n id: 'mic',\n label: 'Mic',\n\n items(doc): LaneItem[] {\n if (!voiceKey(doc)) return []\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\n const freezes = docFreezes(doc)\n const starts = segmentStarts(segments, speeds, freezes)\n return segments.map((s, i) => ({\n id: `mic-${i}`,\n kind: 'clip',\n t: round(starts[i]),\n duration: round(\n outputLen(s, speeds, freezes, i === 0 || !contiguous(segments, i)),\n ),\n }))\n },\n\n gesture() {\n return null\n },\n\n magnets() {\n return []\n },\n}\n\n/** Default new-span length in SOURCE seconds (openscreen-style: ≥1s, ~5% of the take). */\nconst spanDefaultLen = (sourceDuration: number): number =>\n Math.max(1, sourceDuration * 0.05)\n\n/** Rate in force at a SOURCE moment (1 outside every rated piece). */\nfunction rateAt(rated: Segment[], srcT: number): number {\n for (const p of rated)\n if (srcT >= p.in - 1e-9 && srcT < p.out + 1e-9) return segmentRate(p)\n return 1\n}\n\n/**\n * Keep a moved span's START on KEPT footage (as corrected\n * 2026-08-24). The only thing this guards is the span vanishing: a\n * start inside removed footage renders nothing and looks deleted. It is NOT\n * a wall at the cut — a span is source-anchored and legitimately straddles a\n * cut, so a drag pushes it ACROSS the line continuously (the first cut of\n * this clamp held a span flush behind the cut until its whole length had\n * passed, which read as \"nothing can be dragged into the next clip\"). The\n * tail may extend into cut footage; the lane draws the kept part.\n */\nfunction clampToKept(kept: Segment[], newIn: number, len: number): number {\n void len\n let home: Segment | null = null\n let bestD = Infinity\n for (const s of kept) {\n const d = newIn < s.in ? s.in - newIn : newIn >= s.out ? newIn - s.out : 0\n if (d < bestD) {\n bestD = d\n home = s\n }\n }\n if (!home) return newIn\n const hi = Math.max(home.in, home.out - MIN_VISIBLE)\n return Math.min(Math.max(home.in, newIn), hi)\n}\n\n/** A span start must keep at least this much kept footage under it (s). */\nconst MIN_VISIBLE = 0.05\n\n/** New spans speed UP by default — the archetypal screen-recording edit. */\nconst DEFAULT_SPEED_RATE = 2\n\n/**\n * Speed lane — rate spans as clips (\"2×\"). Spans are SOURCE-anchored (footage\n * follows them through trims); the lane displays them at the output positions\n * of the rated pieces they produce, so a span visually contracts as its rate\n * grows. Spans never overlap: create no-ops inside an existing span, move\n * pushes out of collisions (or no-ops), resize clamps against neighbors.\n * The rate itself is edited in the toolbar (like zoom level), not by gesture.\n * Move/resize are POINTER-TRUE: the dragged edge's resulting output position\n * is exactly the pointer's (mapped through the rate map without this span),\n * so edges never lag the pointer at 1/rate speed.\n */\nexport const speedLane: LaneAdapter<ProjectDoc> = {\n id: 'speed',\n label: 'Speed',\n\n items(doc): LaneItem[] {\n const rated = ratedSegments(doc)\n return (doc.speed ?? []).flatMap((sp) => {\n // Output extent: accumulate the rated pieces this span produced\n // (containment match — spans don't overlap and pieces never cross span\n // boundaries). A span whose footage is fully cut away has no pieces and\n // renders nothing — it follows its footage, like zoom keyframes.\n let acc = 0\n let start: number | null = null\n let end = 0\n for (const p of rated) {\n const len = Math.max(0, p.out - p.in) / segmentRate(p)\n if (\n p.rate === sp.rate &&\n p.in >= sp.in - 1e-9 &&\n p.out <= sp.out + 1e-9\n ) {\n if (start === null) start = acc\n end = acc + len\n }\n acc += len\n }\n return start === null\n ? []\n : [\n {\n id: sp.id,\n kind: 'clip' as const,\n t: round(start),\n duration: round(end - start),\n label: `${sp.rate}×`,\n },\n ]\n })\n },\n\n gesture(doc, g) {\n const spans = doc.speed ?? []\n const rated = ratedSegments(doc)\n const sourceDuration = anchorSourceDuration(doc)\n\n if (g.type === 'create') {\n const srcT = mapTime(rated, Math.max(0, g.t))\n if (spans.some((s) => srcT >= s.in && srcT < s.out)) return null\n const next = spans\n .filter((s) => s.in > srcT)\n .sort((a, b) => a.in - b.in)\n .at(0)\n const limit = Math.min(sourceDuration, next ? next.in : sourceDuration)\n const len = Math.min(spanDefaultLen(sourceDuration), limit - srcT)\n if (len < SPEED_SPAN_MIN * DEFAULT_SPEED_RATE) return null\n const id = nextSpeedId(doc)\n const mediaHere = mediaAtOutput(rated, Math.max(0, g.t))\n return (d) => {\n d.speed = [\n ...(d.speed ?? []),\n {\n id,\n in: round(srcT),\n out: round(srcT + len),\n rate: DEFAULT_SPEED_RATE,\n source: 'manual' as const,\n ...(mediaHere ? { media: mediaHere } : {}),\n },\n ].sort((a, b) => a.in - b.in)\n }\n }\n\n const sp = spans.find((s) => s.id === g.id)\n if (!sp) return null\n\n // POINTER-TRUE mapping: move/resize evaluate output positions through the\n // rate map WITHOUT the edited span. Mapping through the full map would\n // re-rate the footage being dragged across mid-gesture, making the edge\n // chase the pointer at 1/rate speed (and the clip land short of the drop).\n // With the span excluded, the dragged edge's resulting output position is\n // exactly g.t — the edge stays under the pointer.\n const others = spans.filter((o) => o.id !== g.id)\n const base = splitBySpeed(effectiveSegments(doc), others)\n\n if (g.type === 'move') {\n // Keep the SOURCE span length; retarget its start to the dragged output\n // position. Push out of any collision toward the nearer side; if it\n // still collides (dense lane), no-op rather than overlap.\n const len = sp.out - sp.in\n let newIn = clampToKept(\n effectiveSegments(doc),\n mapTime(base, Math.max(0, g.t)),\n len,\n )\n for (const o of others) {\n if (newIn < o.out && newIn + len > o.in) {\n const centerDelta = newIn + len / 2 - (o.in + o.out) / 2\n newIn = centerDelta < 0 ? o.in - len : o.out\n }\n }\n newIn = clampToKept(effectiveSegments(doc), newIn, len)\n if (newIn < 0 || newIn + len > sourceDuration) return null\n if (others.some((o) => newIn < o.out && newIn + len > o.in)) return null\n return (d) => {\n const s = d.speed?.find((x) => x.id === g.id)\n if (!s) return\n s.in = round(newIn)\n s.out = round(newIn + len)\n s.source = 'manual'\n d.speed!.sort((a, b) => a.in - b.in)\n }\n }\n\n if (g.type === 'resize') {\n const prev = others.filter((o) => o.out <= sp.in).map((o) => o.out)\n const nextIn = others.filter((o) => o.in >= sp.out).map((o) => o.in)\n const lo = Math.max(0, ...prev)\n const hi = Math.min(sourceDuration, ...nextIn)\n // The floor is OUTPUT seconds through the span's OWN rate: a 2× span\n // may not shrink below 0.5s of source (= 0.25s of screen). The old bare\n // `0.1` was a SOURCE floor — at 5× that was 20ms of screen, the sliver.\n const minSrc = Math.min(SPEED_SPAN_MIN * sp.rate, sp.out - sp.in)\n let next: { in: number; out: number }\n if (g.edge === 'start') {\n // Footage before the new in-point is unaffected by this span, so its\n // output position IS mapTime(base, g.t) — pointer-true directly.\n const sourceT = mapTime(base, Math.max(0, g.t))\n next = {\n in: Math.min(Math.max(lo, sourceT), sp.out - minSrc),\n out: sp.out,\n }\n } else {\n // Place the new out-point so the span's output END lands at g.t:\n // the span occupies (out - in) / rate output seconds after its start.\n const startOut = sourceToTimeline(base, sp.in) ?? sp.in\n const sourceT = sp.in + sp.rate * Math.max(0, g.t - startOut)\n next = {\n in: sp.in,\n out: Math.max(Math.min(hi, sourceT), sp.in + minSrc),\n }\n }\n return (d) => {\n const s = d.speed?.find((x) => x.id === g.id)\n if (!s) return\n s.in = round(next.in)\n s.out = round(next.out)\n s.source = 'manual'\n }\n }\n\n // Only 'remove' remains in the gesture union.\n return (d) => {\n d.speed = (d.speed ?? []).filter((s) => s.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return speedLane.items(doc).flatMap((i) => [i.t, i.t + (i.duration ?? 0)])\n },\n}\n\n/**\n * Music/SFX lane — clips are OUTPUT-anchored (`clip.start` is final-cut\n * seconds; they do NOT follow footage through trims — see AudioClip). Move\n * retimes `start`; resizing trims into the source file: the start edge shifts\n * `in` and `start` together (content stays put under the untouched edge), the\n * end edge adjusts `out`. Clips are created from the audio inspector, not by\n * double-click (there is no meaningful \"blank\" audio clip).\n */\nexport const audioLane: LaneAdapter<ProjectDoc> = {\n id: 'audio',\n label: 'Audio',\n\n items(doc): LaneItem[] {\n return doc.audio.map((c) => ({\n id: c.id,\n kind: 'clip',\n t: round(c.start),\n duration: round(clipLength(c)),\n label: c.name,\n }))\n },\n\n gesture(doc, g) {\n if (g.type === 'move') {\n const clip = doc.audio.find((c) => c.id === g.id)\n if (!clip) return null\n const start = Math.max(0, g.t)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.start = round(start)\n }\n }\n if (g.type === 'resize') {\n const clip = doc.audio.find((c) => c.id === g.id)\n if (!clip) return null\n if (g.edge === 'start') {\n if (clip.loop) {\n // Looping head-trim: keep the END fixed, shrink/grow the placed length\n // (the loop phase, not the source in-point, is what the edge drags).\n const end = clip.start + clipLength(clip)\n const newStart = Math.min(Math.max(0, g.t), end - 0.1)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (!c) return\n c.start = round(newStart)\n c.loopLen = round(end - newStart)\n }\n }\n // Trim the head: consume/restore source material while the tail stays put.\n const delta = g.t - clip.start\n const newIn = Math.min(Math.max(0, clip.in + delta), clip.out - 0.05)\n const newStart = Math.max(0, clip.start + (newIn - clip.in))\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (!c) return\n c.in = round(newIn)\n c.start = round(newStart)\n }\n }\n if (clip.loop) {\n // Looping end-trim: the placed length is unbounded — the span repeats.\n const newLen = Math.max(0.1, g.t - clip.start)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.loopLen = round(newLen)\n }\n }\n const span = Math.max(0.05, g.t - clip.start)\n const newOut = Math.min(clip.in + span, clip.duration)\n return (d) => {\n const c = d.audio.find((x) => x.id === g.id)\n if (c) c.out = round(Math.max(c.in + 0.05, newOut))\n }\n }\n if (g.type === 'remove') {\n if (!doc.audio.some((c) => c.id === g.id)) return null\n return (d) => {\n d.audio = d.audio.filter((c) => c.id !== g.id)\n }\n }\n return null\n },\n\n magnets(doc): number[] {\n return doc.audio.flatMap((c) => [\n round(c.start),\n round(c.start + clipLength(c)),\n ])\n },\n}\n\n/** Smallest unused overlay id. */\nfunction nextOverlayId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.overlays ?? []).some((o) => o.id === `t${n}`)) n++\n return `t${n}`\n}\n\n/**\n * Pose-diamond item ids: `{clipId}::k{index}` — a keyframe LaneItem\n * riding its clip's lane. Index-addressed into the clip's UNSORTED `motion`\n * array (writes never reorder it; the lowering sorts), so the id stays stable\n * through a drag that crosses a sibling pose.\n */\nconst POSE_ID = /^(.+)::k(\\d+)$/\nexport function parsePoseId(\n id: string,\n): { clipId: string; index: number } | null {\n const m = POSE_ID.exec(id)\n return m ? { clipId: m[1], index: Number(m[2]) } : null\n}\n\n/** Diamond items for a clip's poses (clip-local `at` → absolute lane time). */\nfunction poseItems(\n clipId: string,\n start: number,\n duration: number,\n motion: readonly { at: number }[] | undefined,\n): LaneItem[] {\n return (motion ?? []).map((p, i) => ({\n id: `${clipId}::k${i}`,\n kind: 'keyframe' as const,\n t: round(start + Math.min(Math.max(0, p.at), duration)),\n }))\n}\n\n/**\n * Text-overlay lane (compositor v2) — clips are OUTPUT-anchored like audio\n * (`start` is final-cut seconds; a title never retimes with trims/speed).\n * Overlaps are allowed (two titles can coexist — z-order is array order).\n * Create adds a house 'title' clip at the playhead, centered, lower-third.\n */\nexport const overlaysLane: LaneAdapter<ProjectDoc> = {\n id: 'overlays',\n label: 'Text',\n\n items(doc): LaneItem[] {\n return (doc.overlays ?? []).flatMap((o) => [\n {\n id: o.id,\n kind: 'clip' as const,\n t: round(o.start),\n duration: round(o.duration),\n label:\n o.kind === 'text'\n ? o.text.split('\\n')[0].slice(0, 24) || 'Text'\n : o.kind === 'html'\n ? htmlLayerLabel(o)\n : o.kind === 'image'\n ? 'Image'\n : 'Video',\n },\n // Pose diamonds render after the clips, so they sit on top.\n ...poseItems(o.id, o.start, o.duration, o.motion),\n ])\n },\n\n gesture(doc, g) {\n const overlays = doc.overlays ?? []\n\n // Pose diamonds: retime within the clip, or remove. The diamond's\n // absolute lane time maps back to clip-local `at`.\n if (g.type !== 'create') {\n const kf = parsePoseId(g.id)\n if (kf) {\n const clip = overlays.find((o) => o.id === kf.clipId)\n const pose = clip?.motion?.[kf.index]\n if (!clip || !pose) return null\n if (g.type === 'move') {\n const at = Math.min(Math.max(0, g.t - clip.start), clip.duration)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === kf.clipId)\n const p = o?.motion?.[kf.index]\n if (p) p.at = round(at)\n }\n }\n if (g.type === 'remove') {\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === kf.clipId)\n if (!o?.motion) return\n const next = o.motion.filter((_, i) => i !== kf.index)\n if (next.length) o.motion = next\n else delete o.motion\n }\n }\n return null\n }\n }\n\n if (g.type === 'create') {\n // Either document's output length: overlays ride the program\n // anchor too, whose length is not a footage sum.\n const outDur = docOutputDuration(doc)\n const start = Math.min(\n Math.max(0, g.t),\n Math.max(0, outDur - OVERLAY_MIN_DURATION),\n )\n const len = Math.max(OVERLAY_MIN_DURATION, Math.min(3, outDur - start))\n const id = nextOverlayId(doc)\n return (d) => {\n d.overlays = [\n ...(d.overlays ?? []),\n {\n id,\n kind: 'text' as const,\n start: round(start),\n duration: round(len),\n text: 'Title',\n preset: 'title' as const,\n // Frame fractions: centered, lower-third — at any aspect ratio.\n transform: { x: 0.5, y: 0.82, scale: 1, rotation: 0 },\n },\n ]\n }\n }\n\n const clip = overlays.find((o) => o.id === g.id)\n if (!clip) return null\n\n if (g.type === 'move') {\n const start = Math.max(0, g.t)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (o) o.start = round(start)\n }\n }\n if (g.type === 'resize') {\n if (g.edge === 'start') {\n // Keep the END fixed; the head drags start + duration together.\n const end = clip.start + clip.duration\n const newStart = Math.min(Math.max(0, g.t), end - OVERLAY_MIN_DURATION)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (!o) return\n o.start = round(newStart)\n o.duration = round(end - newStart)\n }\n }\n const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.start)\n return (d) => {\n const o = (d.overlays ?? []).find((x) => x.id === g.id)\n if (o) o.duration = round(newLen)\n }\n }\n // Only 'remove' remains (the gesture union is exhausted above).\n return (d) => {\n d.overlays = (d.overlays ?? []).filter((o) => o.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return (doc.overlays ?? []).flatMap((o) => [\n round(o.start),\n round(o.start + o.duration),\n ])\n },\n}\n\n/**\n * Object lane — world-space props. Clips show the span (objects with no\n * span render as a full-length block and don't move — the span IS the lane's\n * noun). Created from the toolbar; asset/transform edited in the inspector.\n */\nexport const objectsLane: LaneAdapter<ProjectDoc> = {\n id: 'objects',\n label: '3D',\n\n items(doc): LaneItem[] {\n const outDur = docOutputDuration(doc)\n return (doc.objects ?? []).flatMap((o) => [\n {\n id: o.id,\n kind: 'clip' as const,\n t: round(o.span?.start ?? 0),\n duration: round(o.span?.duration ?? outDur),\n label:\n o.asset.kind === 'primitive'\n ? o.asset.shape\n : o.asset.kind === 'text3d'\n ? o.asset.text\n : 'model',\n },\n // Pose diamonds: clip-local `at` from the span start (0 span-less).\n ...poseItems(\n o.id,\n o.span?.start ?? 0,\n o.span?.duration ?? outDur,\n o.motion,\n ),\n ])\n },\n\n gesture(doc, g) {\n const objects = doc.objects ?? []\n if (g.type === 'create') return null // toolbar-created (needs asset choice)\n\n // Pose diamonds: retime within the clip, or remove.\n const kf = parsePoseId(g.id)\n if (kf) {\n const outDur = docOutputDuration(doc)\n const clip = objects.find((o) => o.id === kf.clipId)\n const pose = clip?.motion?.[kf.index]\n if (!clip || !pose) return null\n const start = clip.span?.start ?? 0\n const dur = clip.span?.duration ?? outDur\n if (g.type === 'move') {\n const at = Math.min(Math.max(0, g.t - start), dur)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === kf.clipId)\n const p = o?.motion?.[kf.index]\n if (p) p.at = round(at)\n }\n }\n if (g.type === 'remove') {\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === kf.clipId)\n if (!o?.motion) return\n const next = o.motion.filter((_, i) => i !== kf.index)\n if (next.length) o.motion = next\n else delete o.motion\n }\n }\n return null\n }\n\n const clip = objects.find((o) => o.id === g.id)\n if (!clip) return null\n if (g.type === 'move') {\n if (!clip.span) return null\n const start = Math.max(0, g.t)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (o?.span) o.span.start = round(start)\n }\n }\n if (g.type === 'resize') {\n if (!clip.span) return null\n if (g.edge === 'start') {\n const end = clip.span.start + clip.span.duration\n const newStart = Math.min(Math.max(0, g.t), end - OVERLAY_MIN_DURATION)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (!o?.span) return\n o.span.start = round(newStart)\n o.span.duration = round(end - newStart)\n }\n }\n const newLen = Math.max(OVERLAY_MIN_DURATION, g.t - clip.span.start)\n return (d) => {\n const o = (d.objects ?? []).find((x) => x.id === g.id)\n if (o?.span) o.span.duration = round(newLen)\n }\n }\n // remove\n return (d) => {\n d.objects = (d.objects ?? []).filter((o) => o.id !== g.id)\n }\n },\n\n magnets(doc): number[] {\n return (doc.objects ?? []).flatMap((o) =>\n o.span\n ? [round(o.span.start), round(o.span.start + o.span.duration)]\n : [],\n )\n },\n}\n\n/** Smallest unused user-span id (planner spans are `z{n}`). */\nfunction nextZoomId(doc: ProjectDoc): string {\n let n = 0\n while (doc.zoom.some((z) => z.id === `u${n}`)) n++\n return `u${n}`\n}\n\n/** Smallest unused user tilt-span id (Dynamic-tilt wand spans are `t{n}`). */\nfunction nextTiltId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.tilt ?? []).some((z) => z.id === `u${n}`)) n++\n return `u${n}`\n}\n\n/** Smallest unused cam-move span id (`m{n}` — reserved `auto` never collides). */\nfunction nextCamMoveId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.camMotion ?? []).some((z) => z.id === `m${n}`)) n++\n return `m${n}`\n}\n\nfunction nextSpeedId(doc: ProjectDoc): string {\n let n = 0\n while ((doc.speed ?? []).some((s) => s.id === `sp${n}`)) n++\n return `sp${n}`\n}\n\nfunction segIndex(id: string): number {\n return Number(id.replace('seg-', ''))\n}\n\nfunction round(v: number): number {\n return Math.round(v * 1000) / 1000\n}\n","/**\n * Waveform peaks — pure downsampling for timeline clip rendering. The host\n * decodes the file (Web Audio) and hands channel data here; the result is one\n * max-|sample| value per bucket in [0..1], drawn as symmetric bars.\n */\nexport function computePeaks(\n channels: Float32Array[],\n buckets: number,\n): Float32Array {\n const peaks = new Float32Array(Math.max(1, buckets))\n if (!channels.length || !channels[0].length) return peaks\n const length = channels[0].length\n const perBucket = length / peaks.length\n for (let b = 0; b < peaks.length; b++) {\n const from = Math.floor(b * perBucket)\n const to = Math.min(\n length,\n Math.max(from + 1, Math.floor((b + 1) * perBucket)),\n )\n let peak = 0\n for (const ch of channels) {\n for (let i = from; i < to; i++) {\n const v = Math.abs(ch[i])\n if (v > peak) peak = v\n }\n }\n peaks[b] = Math.min(1, peak)\n }\n return peaks\n}\n","/**\n * Auto-ducking — lower music under speech. Pure math over a precomputed mic\n * loudness envelope, so both consumers apply identical values:\n *\n * host (async): decode mic → `micRms` (SOURCE-time loudness grid, cached)\n * host (sync): `duckCurve(rms, segments, duration)` → OUTPUT-time gain\n * points, merged into ctx.data as `duckEnv`\n * preview: per ducked clip, a second GainNode applies the points\n * export: same points in the OfflineAudioContext mix\n */\nimport { mapTime } from '@vosjs/timeline'\nimport type { Segment } from '@vosjs/timeline'\nimport type { EnvelopePoint } from './audioEnvelope'\n\n/** SOURCE-time loudness grid (RMS per window). */\nexport interface MicRms {\n /** RMS value per window, linear 0..1. */\n values: Float32Array\n /** windows per second. */\n rate: number\n}\n\nexport interface DuckOptions {\n /** RMS above this counts as speech. */\n threshold: number\n /** gain while ducked (≈ -12 dB). */\n duckTo: number\n /** seconds to reach the ducked level once speech starts. */\n attack: number\n /** seconds to recover after speech stops. */\n release: number\n /** output grid resolution, points per second. */\n gridHz: number\n}\n\nexport const DEFAULT_DUCK: DuckOptions = {\n threshold: 0.02,\n duckTo: 0.25,\n attack: 0.2,\n release: 0.5,\n gridHz: 20,\n}\n\n/** RMS windows from raw PCM — the host runs this once per recording (cached). */\nexport function computeMicRms(\n channels: Float32Array[],\n sampleRate: number,\n windowSec = 0.05,\n): MicRms {\n const rate = 1 / windowSec\n if (!channels.length || !channels[0].length)\n return { values: new Float32Array(0), rate }\n const length = channels[0].length\n const perWindow = Math.max(1, Math.round(sampleRate * windowSec))\n const windows = Math.ceil(length / perWindow)\n const values = new Float32Array(windows)\n for (let w = 0; w < windows; w++) {\n const from = w * perWindow\n const to = Math.min(length, from + perWindow)\n let sum = 0\n for (const ch of channels) {\n for (let i = from; i < to; i++) sum += ch[i] * ch[i]\n }\n values[w] = Math.sqrt(sum / Math.max(1, (to - from) * channels.length))\n }\n return { values, rate }\n}\n\n/**\n * The OUTPUT-time duck multiplier curve: walk an output grid, look up the mic\n * loudness at the mapped SOURCE moment, and smooth engage/recover with\n * attack/release one-poles. Points are thinned (emitted on ≥1% change).\n */\nexport function duckCurve(\n rms: MicRms,\n segments: Segment[],\n durationSec: number,\n opts: DuckOptions = DEFAULT_DUCK,\n): EnvelopePoint[] {\n if (!rms.values.length || durationSec <= 0) return []\n const dt = 1 / opts.gridHz\n const points: EnvelopePoint[] = []\n let g = 1\n let lastEmitted = Number.NaN\n const steps = Math.ceil(durationSec * opts.gridHz)\n for (let i = 0; i <= steps; i++) {\n const t = Math.min(durationSec, i * dt)\n const srcT = mapTime(segments, t)\n const w = Math.min(\n rms.values.length - 1,\n Math.max(0, Math.floor(srcT * rms.rate)),\n )\n const speech = rms.values[w] > opts.threshold\n const target = speech ? opts.duckTo : 1\n const tau = target < g ? opts.attack : opts.release\n g += (target - g) * Math.min(1, dt / Math.max(1e-3, tau))\n if (\n Number.isNaN(lastEmitted) ||\n Math.abs(g - lastEmitted) >= 0.01 ||\n i === steps\n ) {\n points.push({\n t: Math.round(t * 1000) / 1000,\n g: Math.round(g * 1000) / 1000,\n })\n lastEmitted = g\n }\n }\n return points\n}\n","/**\n * A backdrop is the loop (or still) a frame opens on, together with its\n * GROUND: the loop's average colour, written into `frame.background` so\n * the frame before the first decoded frame, the reduced-motion still and\n * the offline fail-open all land on the loop's own colour instead of\n * whatever the frame carried before.\n *\n * WHICH backdrop a new take opens on is the host's decision, never this\n * package's: the studio hands its pick to `projectFromArtifact` as `frame`,\n * the CLI reads the set the platform publishes. The document model carries\n * the mechanism only, so the default here is a frame with no backdrop.\n * A doc that already carries a frame is never touched either way.\n */\nimport type { BackgroundMedia, FrameStyle } from './types'\n\nexport interface Backdrop {\n /** The loop's URL, or a take-dir path (`/bg.webm`). */\n key: string\n /** `video` unless said otherwise. */\n kind?: BackgroundMedia['kind']\n /** The loop's period in seconds, a fact of the asset. Video only. */\n duration?: number\n /** A still to show before the first decoded frame and under reduced motion. */\n poster?: string\n /**\n * The loop's average colour. Written as `frame.background` when given; a\n * backdrop without one keeps the frame's own fill under it.\n */\n ground?: string\n}\n\n/** A backdrop as a doc's `frame.backgroundMedia`. */\nexport function backdropMedia(backdrop: Backdrop): BackgroundMedia {\n return {\n kind: backdrop.kind ?? 'video',\n key: backdrop.key,\n ...(backdrop.duration !== undefined ? { duration: backdrop.duration } : {}),\n ...(backdrop.poster ? { poster: backdrop.poster } : {}),\n dim: 0,\n }\n}\n\n/**\n * A frame opening on a backdrop: the media, and its ground when it has one,\n * in one write. Everything else on the frame is kept.\n */\nexport function withBackdrop(\n frame: FrameStyle,\n backdrop: Backdrop,\n): FrameStyle {\n return {\n ...frame,\n ...(backdrop.ground ? { background: backdrop.ground } : {}),\n backgroundMedia: backdropMedia(backdrop),\n }\n}\n","/**\n * Rejected proposals: the document's way to say \"not this one\".\n *\n * A planner proposal (an `auto` zoom, tilt or speed span) that the human or\n * the agent deleted is not data by itself: the doc records what was written,\n * not what was removed, so the next re-plan (a style pick, `vos plan`, a\n * re-record carried by `plan --reuse`) proposed it again and the deletion\n * had to be repeated by hand. `doc.rejected` keeps the deletion as a span\n * of its own — the lane and the source extent, plus the step anchor when\n * the span had one — and every auto-merge drops a fresh proposal that lands\n * on it. Manual spans never need this: a re-plan keeps them by contract.\n *\n * Matching is by extent, never by id: planner ids are positional and a\n * re-record moves every beat by tens of milliseconds, so a proposal is\n * rejected when it overlaps a rejected extent on the same lane by at least\n * REJECT_OVERLAP of the shorter of the two.\n */\nimport type { RejectedLane, RejectedSpan, StepAnchor } from './types'\n\n/** Fraction of the shorter extent two spans must share to be \"the same beat\". */\nexport const REJECT_OVERLAP = 0.5\n\ninterface Extent {\n in: number\n out: number\n}\n\n/** Shared length over the shorter length; 0 when apart or degenerate. */\nexport function overlapFraction(a: Extent, b: Extent): number {\n const shared = Math.min(a.out, b.out) - Math.max(a.in, b.in)\n if (shared <= 0) return 0\n const shorter = Math.min(a.out - a.in, b.out - b.in)\n return shorter > 0 ? shared / shorter : 0\n}\n\n/** Whether a proposal on this lane lands on a rejected extent. */\nexport function isRejected(\n lane: RejectedLane,\n span: Extent,\n rejected: RejectedSpan[] | undefined,\n): boolean {\n if (!rejected?.length) return false\n return rejected.some(\n (r) => r.lane === lane && overlapFraction(span, r) >= REJECT_OVERLAP,\n )\n}\n\n/** The proposals that survive the rejected list, in their given order. */\nexport function withoutRejected<T extends Extent>(\n lane: RejectedLane,\n spans: T[],\n rejected: RejectedSpan[] | undefined,\n): T[] {\n if (!rejected?.length) return spans\n return spans.filter((s) => !isRejected(lane, s, rejected))\n}\n\n/**\n * The entry a deleted auto span leaves behind. Ids are `r{n}` over the\n * existing list so a differ and a history can name the rejection; the\n * anchor rides along when the span had one, so a re-record re-times the\n * rejection exactly the way it re-times the span it replaced.\n */\nexport function rejectSpan(\n rejected: RejectedSpan[] | undefined,\n lane: RejectedLane,\n span: Extent & { anchor?: StepAnchor },\n note?: string,\n): RejectedSpan {\n const taken = new Set((rejected ?? []).map((r) => r.id))\n let n = 0\n while (taken.has(`r${n}`)) n++\n const entry: RejectedSpan = {\n id: `r${n}`,\n lane,\n in: +span.in.toFixed(3),\n out: +span.out.toFixed(3),\n }\n if (span.anchor) entry.anchor = { ...span.anchor }\n if (note) entry.note = note\n return entry\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAqHJ,kBAAkB;AAAA;AAAA;AAAA,sDAGC,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4EjE,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoDvB,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAyJb,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBA+DpB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAQE,kBAAkB;AAAA;AAAA;AAAA;AAAA,mEAIY,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCA+L9C,KAAK,UAAU,kBAAkB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAoEvC,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BA4K1B,QAAQ;AAAA,gCACH,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BA6BR,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAwJf,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAuBb,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAiBC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2CAuDA,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wDAsVN,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAsD3D,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yDA+EwB,0BAA0B;AAAA;AAAA;AAAA;AAAA,0CAIzC,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAoDlD,YAAY;AAAA,iBACb,gBAAgB;AAAA,iBAChB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yDAUyB,oBAAoB,6BAA6B,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uCA0FvF,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["kicker","push","outputEnd","clamp","clamp01","clamp","lerpArray","sample","segmentRate","sortKeyframes","splitBySpeed","totalDuration","timelineRuntimeCode","sc","dw","dh","clamp01","dx","dy","x","y","findFontFamily","fontFaceUrl","nearestFontWeight","push","fontFaceUrl","findFontFamily","nearestFontWeight","round","w","splitBySpeed","totalDuration","splitBySpeed","totalDuration","round3","clamp01","sampleAt","round","side","round","round4","round","round","splitBySpeed","totalDuration","segmentRate","push","round","next","sortKeyframes","sample","lerpArray","timelineRuntimeCode","round4","round","emit","round","totalDuration","totalDuration","round3","totalDuration","EPS","own","totalDuration","round3","round","mapTime","mapTime","round","totalDuration","totalDuration","mapTime","segmentRate","sourceToTimeline","splitBySpeed","totalDuration","round","splitBySpeed","sourceToTimeline","mapTime","s","f","totalDuration","clamp01","segmentRate","newStart","clip","mapTime","durationSec"]}
|