@vosjs/studio-core 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +67 -1
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/backdrop.ts","../src/types.ts","../src/capture.ts","../src/ingest.ts","../src/docVersion.ts","../src/doc/studioDoc.ts","../src/planner/smoothing.ts","../src/zoomStyle.ts","../src/planner/autoZoom.ts","../src/lower/lowerToComposition.ts","../src/layout.ts","../src/stage.ts","../src/overlayText.ts","../src/text3d.ts","../src/lower/audioEnvelope.ts","../src/lower/cursorFollow.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/digest/geometry.ts","../src/digest/build.ts","../src/timeline/rangeActions.ts","../src/lower/lowerStudioDoc.ts","../src/destinations.ts","../src/lower/audioPlan.ts","../src/timeline/lanes.ts","../src/audioBeds.ts","../src/waveform.ts","../src/lower/duckCurve.ts"],"sourcesContent":["/**\n * The DEFAULT backdrop: the house loop every NEW take opens on\n * once the flag below is on. A committed constant, not a live vos, the same\n * way Home's door tiles are a deliberate asset push (decided 2026-08-17):\n * changing it is a code change with a review, never something an autosave\n * can re-skin.\n *\n * `ground` is 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. The\n * keys are the pre-registry bucket objects, which stay in the bucket\n * forever; the registry's `backdrops/{slug}/…` keys replace them when the\n * house loop is featured through the verb.\n *\n * BACKDROP_DEFAULT_ON is the flip. It stays OFF until the set has a signed-off\n * seed and the fleet measurement has run: a switch, not a surprise.\n * Docs already carrying a frame are never touched either way.\n */\nimport type { BackgroundMedia, FrameStyle } from './types'\n\nexport const DEFAULT_BACKDROP = {\n slug: 'soft-beams',\n title: 'Soft Beams',\n key: 'https://assets.vos.so/backgrounds/soft-beams-1080p.webm',\n key2k: 'https://assets.vos.so/backgrounds/soft-beams-2k.webm',\n poster: 'https://assets.vos.so/backgrounds/soft-beams-poster.jpg',\n duration: 10,\n ground: '#a7b2d1',\n} as const\n\nexport const BACKDROP_DEFAULT_ON = false as boolean\n\n/** The default backdrop as a doc's `frame.backgroundMedia`. */\nexport function defaultBackdropMedia(): BackgroundMedia {\n return {\n kind: 'video',\n key: DEFAULT_BACKDROP.key,\n duration: DEFAULT_BACKDROP.duration,\n poster: DEFAULT_BACKDROP.poster,\n dim: 0,\n }\n}\n\n/** A frame style opening on the default backdrop (media + its ground). */\nexport function withDefaultBackdrop(frame: FrameStyle): FrameStyle {\n return {\n ...frame,\n background: DEFAULT_BACKDROP.ground,\n backgroundMedia: defaultBackdropMedia(),\n }\n}\n","/**\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 { BACKDROP_DEFAULT_ON, withDefaultBackdrop } from './backdrop'\nimport type { Segment } from '@vosjs/timeline'\nimport type { ZoomStyleName, ZoomStyleParams } from './zoomStyle'\nimport type { SpeedParams } from './planner/autoSpeed'\n\nexport type { Segment }\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\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/** 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\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 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 * 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 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 /** 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 style: 'default' | 'dot' | 'ring'\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 /** 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}\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 /** shadow strength 0..1. */\n shadow: 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 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\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'\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/** 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\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 /** Absent = 'rise' for enter, 'fade' for exit (the house default motion). */\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 /** Text outline, drawn under the fill. */\n stroke?: TextOverlayStroke\n /** Background pill behind the text block (absent = none). */\n box?: TextOverlayBox\n /** Entrance animation. Absent = the legacy `enter` transition. */\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 key: string\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\nexport type OverlayClip = TextOverlayClip | MediaOverlayClip\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 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: Segment[]\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 /** 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 /** 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 * 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 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\nconst 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 // '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. Once `BACKDROP_DEFAULT_ON`\n * flips (backdrop.ts), the default is the house loop on its own ground;\n * until then the brand gradient. Every ingest path spreads this, so the\n * flip reaches the extension handoff, the in-page recorder, a dropped file\n * and `vos record` at once; a doc that already carries a frame keeps it.\n */\nexport const DEFAULT_FRAME_STYLE: FrameStyle = BACKDROP_DEFAULT_ON\n ? withDefaultBackdrop(BASE_FRAME_STYLE)\n : 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 * 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 * 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 { ProjectDoc, RecordingArtifact } from './types'\n\n/** Build a ProjectDoc from a RecordingArtifact handed off by a recorder. */\nexport function projectFromArtifact(\n artifact: RecordingArtifact,\n videoUrl: string,\n): { doc: ProjectDoc; videoUrl: string } {\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 cursor: { ...DEFAULT_CURSOR_STYLE },\n cam: { ...DEFAULT_CAM_STYLE },\n frame: {\n ...DEFAULT_FRAME_STYLE,\n browserBar: {\n ...DEFAULT_FRAME_STYLE.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 export: { resolution: '1080p', fps: 30, format: 'mp4' },\n }\n return { doc, videoUrl }\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 */\n\n/**\n * 2 = the document FAMILY era: a doc is a recording document (`source`)\n * or a program document (`program.config`). A v1 doc IS a recording document,\n * field for field, so 1 → 2 is a stamp; 0 → 1 was a stamp too.\n */\nexport const DOC_SCHEMA_VERSION = 2\n\n/**\n * Upgrade a hosted doc.json payload to the current schema version.\n * Unstamped docs are v0 — the pre-stamp era. Every step so far is a stamp\n * (a post-v1 doc field is optional by doctrine, and v2 only widened the\n * family), so migration is structural identity. A real shape change chains\n * 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 // v0 → v1 → v2: stamp only.\n return { ...raw, docSchemaVersion: DOC_SCHEMA_VERSION }\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 * 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). One name → one coherent feel.\n *\n * The presets are grounded in a measured comparison of the four shipping\n * strategies (frame-by-frame optical-flow tracking of real exports + source\n * audits of Recordly/OpenScreen + Cursorful bundle/behavior research):\n *\n * - Cursorful (\"glide\"): ONE modest zoom (~1.5×) per activity session —\n * 2+ clicks within a rolling window keep the zoom alive — and the camera\n * TRAVELS by panning between focus points while zoomed. Measured zoom ramp\n * fits css-bezier(0.26, 0, 0.16, 1): near-zero initial velocity, soft\n * landing, zero overshoot. Pan durations scale with distance.\n * - Screen Studio \"Focused\" / Recordly (\"focus\"): a zoom block per click\n * cluster at ~1.8×, spring-settled ramps (~1.5 s in / ~1 s out), direct\n * pans across gaps ≤ ~1.35 s, dead-zone cursor follow. The measured\n * OpenScreen/Recordly ramp (their bezier filtered through the spring) fits\n * css-bezier(0.28, 0.03, 0.09, 1) — that curve IS the family feel.\n * - Screen Studio \"Smooth\" (\"cinema\"): the same block model, slower and\n * more fluid — for content that is watched, not read.\n * - \"snappy\": the studio's original fast cycles, with the defect fixed — the raw\n * css-bezier(0.16, 1, 0.3, 1) arrival had an initial velocity 6.25× the\n * ramp average (the \"jump cut\" complaint); competitors always filter that\n * curve through a spring. This preset keeps the pace but caps the onset.\n * - \"cut\": Screen Studio's \"instant zoom\" option — hard cut-in, no glide.\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 | 'keynote'\n | 'drift'\n\n/**\n * The tilt half of a camera style: the Dynamic-tilt\n * intensity the style ships with, plus optional overrides on the tilt track's\n * motion constants. A style pick stamps `doc.tiltStyle` with `intensity` and\n * re-plans auto tilt spans alongside the auto zooms — one name, one coherent\n * 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 /** minimum clicks for a cluster to earn a zoom (Cursorful's ≥2 rule). */\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 /** 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 */\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 /** output-time gap ≤ this → pan straight to the next span (no zoom-out). */\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\nexport const ZOOM_STYLES: Record<ZoomStyleName, ZoomStyleParams> = {\n // Cursorful strategy — one steady zoom per activity session, travel by pans.\n glide: {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.6,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 1.1,\n rampInOverlap: 0.35,\n rampOut: 1.0,\n chainGap: 2.5,\n pan: 1.0,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0.4,\n tilt: { intensity: 'off' },\n },\n // Screen Studio \"Focused\" / Recordly — block zooms, spring-settled, readable.\n focus: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.5,\n maxLevel: 2.2,\n lead: 0.35,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.0,\n typingHold: 1.2,\n typingMinLevel: 1.6,\n rampIn: 1.4,\n rampInOverlap: 0.5,\n rampOut: 1.0,\n chainGap: 1.35,\n pan: 1.0,\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.6,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // Screen Studio \"Smooth\" — slower, more fluid; creative content over reading.\n cinema: {\n autoZoom: true,\n clusterGap: 1.8,\n minClusterClicks: 1,\n targetFill: 0.45,\n minLevel: 1.35,\n maxLevel: 2.0,\n lead: 0.6,\n hold: 1.4,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.6,\n typingMinLevel: 1.45,\n rampIn: 1.8,\n rampInOverlap: 0.55,\n rampOut: 1.5,\n chainGap: 2.2,\n pan: 1.4,\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: 1.0,\n followLookahead: 0.5,\n tilt: { intensity: 'off' },\n },\n // The studio's original pace with the instant-velocity onset defect fixed.\n snappy: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.5,\n lead: 0.25,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.6,\n typingHold: 0.8,\n typingMinLevel: 1.5,\n rampIn: 0.7,\n rampInOverlap: 0.3,\n rampOut: 0.75,\n chainGap: 1.5,\n pan: 0.8,\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.5,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // Screen Studio's \"instant zoom\" — a cut-in (2–4 frames), tutorial tempo.\n cut: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.5,\n maxLevel: 2.2,\n lead: 0.2,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.2,\n typingHold: 0.6,\n typingMinLevel: 1.6,\n rampIn: 0.14,\n rampInOverlap: 0.07,\n rampOut: 0.14,\n chainGap: 1.2,\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: { intensity: 'off' },\n },\n // Auto-zoom off (every competitor ships this switch). Camera params still\n // apply to MANUAL spans — they get the default (glide) motion.\n none: {\n autoZoom: false,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.6,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 1.1,\n rampInOverlap: 0.35,\n rampOut: 1.0,\n chainGap: 2.5,\n pan: 1.0,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // ── tilt-forward styles — the camera moves in DEPTH too.\n // Research note (2026-08-03): no competitor ships a document-wide personality\n // driving BOTH auto-zoom dynamics and focus-following tilt from one name —\n // FocuSee's Subtle/Default/Strong 3D Motion is a separate layered effect,\n // TiltIt/ScreenDrift sell per-clip templates. These two own that space.\n //\n // \"keynote\": the launch-film sentence (the Apple register: rehearsed,\n // restrained, one move per beat) — glide's session-merged, modest zooms plus\n // a MEDIUM lean toward each zoom's focus, tilt ramps MATCHED to the zoom\n // ramps so lean and zoom read as one camera move; chained so back-to-back\n // beats swing pose-to-pose. Never oscillation, ±5..18° band.\n keynote: {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.7,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.2,\n typingMinLevel: 1.4,\n rampIn: 1.2,\n rampInOverlap: 0.35,\n rampOut: 1.1,\n chainGap: 2.5,\n pan: 1.1,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0.4,\n // Lean lands WITH the zoom (matched ramps read as one camera move).\n tilt: {\n intensity: 'medium',\n rampIn: 1.2,\n rampOut: 1.1,\n chainGap: 2.5,\n pan: 1.1,\n },\n },\n // \"drift\": calm ambient depth — cinema's slow, fluid blocks with a SUBTLE\n // lean that eases in over ~1.6s and lingers (long chain gap keeps the card\n // from flattening between nearby beats). For watched-not-read content:\n // launch films, portfolio clips, hero loops.\n drift: {\n autoZoom: true,\n clusterGap: 1.8,\n minClusterClicks: 1,\n targetFill: 0.45,\n minLevel: 1.35,\n maxLevel: 2.0,\n lead: 0.6,\n hold: 1.4,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.6,\n typingMinLevel: 1.45,\n rampIn: 1.8,\n rampInOverlap: 0.55,\n rampOut: 1.5,\n chainGap: 2.2,\n pan: 1.4,\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: 1.0,\n followLookahead: 0.5,\n tilt: {\n intensity: 'subtle',\n rampIn: 1.6,\n rampOut: 1.4,\n chainGap: 3.0,\n pan: 1.4,\n },\n },\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. Unknown names from hand-edited doc.json fall\n * back to the default style.\n */\nexport function resolveZoomStyle(\n name?: ZoomStyleName,\n overrides?: Partial<ZoomStyleParams>,\n): ZoomStyleParams {\n const params: ZoomStyleParams | undefined = name\n ? ZOOM_STYLES[name]\n : undefined\n return { ...(params ?? ZOOM_STYLES[DEFAULT_ZOOM_STYLE]), ...overrides }\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: 'keynote',\n label: 'Keynote',\n hint: 'Gliding zooms that lean toward each focus',\n },\n {\n name: 'drift',\n label: 'Drift',\n hint: 'Slow, fluid moves with a subtle ambient lean',\n },\n {\n name: 'focus',\n label: 'Focus',\n hint: 'A zoom per click cluster, settles fast',\n },\n { name: 'cinema', label: 'Cinema', hint: 'Slow, fluid camera moves' },\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 * 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 whose element FITS the frame at less than this level is\n * not a target: it is a drag (aiming, scrubbing, moving a thing across the\n * canvas) or a frame-sized surface, and a zoom on it says nothing. Five real\n * takes (2026-08-25) each carried 1-4 such clusters, planned at the floor\n * level for 10-25s; every one was dropped by hand. Now they plan nothing.\n */\nexport const DRAG_FIT_LEVEL = 1.15\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 /** 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 },\n): { sessions: TypingSession[]; clusters: Click[][] } {\n const { width, height, clusterGap, typingGap, typingZoom } = opts\n const clicks: Click[] = track\n .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 // Merge clusters of clicks that are close in time into one sustained zoom.\n const clusters: Click[][] = []\n for (const c of clicks) {\n if (absorbed.has(c)) continue\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 { sessions, clusters }\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 followByDefault = style.followByDefault,\n typingZoom = style.typingZoom,\n typingGap = style.typingGap,\n typingHold = style.typingHold,\n typingMinLevel = style.typingMinLevel,\n } = options\n\n const { sessions, clusters } = groupTrack(track, {\n width,\n height,\n clusterGap,\n typingGap,\n typingZoom,\n })\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 // 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 // Drag clusters plan no zoom but still RESERVE their window: the cursor\n // was working there, and the pauses between drags are not dwells.\n const dragReserved: ZoomSpan[] = []\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 // A drag or a frame-sized surface (DRAG_FIT_LEVEL): no zoom at all.\n if (f.fit !== null && f.fit < DRAG_FIT_LEVEL) {\n dragReserved.push({\n id: `drag${dragReserved.length}`,\n in: Math.max(0, first.t - lead),\n out: last.t + hold,\n level: 1,\n cx: f.cx,\n cy: f.cy,\n })\n continue\n }\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].sort((a, b) => a.in - b.in)\n\n // Dwell augmentation: sustained cursor rests no click/typing span 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 if (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; fit: number | null } {\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 element fills ~targetFill of the frame (element-aware).\n // No rect → use the max level (point zoom).\n let level = maxLevel\n let fit: number | null = null\n if (maxW > 0 && maxH > 0) {\n const fitX = (width * targetFill) / maxW\n const fitY = (height * targetFill) / maxH\n level = Math.min(fitX, fitY)\n fit = level\n }\n return { cx, cy, level: clamp(level, minLevel, maxLevel), fit }\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 { camBubbleRect, clampFocus, docCardLayout } from '../layout'\nimport { smoothCursor } from '../planner/smoothing'\nimport {\n BACKGROUND_Z,\n CAMERA_FAR,\n CAMERA_NEAR,\n CARD_FOV,\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 CLICK_FX_INTENSITY,\n FRAME_BORDER_COLOR_DEFAULT,\n FRAME_BORDER_WIDTH_DEFAULT,\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 transitionMult,\n} from '../types'\nimport { DEFAULT_ZOOM_STYLE, ZOOM_STYLES, resolveZoomStyle } from '../zoomStyle'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport { clipEnvelope } from './audioEnvelope'\nimport { followFocusEvents } from './cursorFollow'\nimport { cursorIdleFade } from './cursorIdle'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\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 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 } from '../layout'\nimport type { FollowEvent } from './cursorFollow'\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). Fixed constants, deliberately NOT the\n * zoom style's (switching\n * \"Camera style\" must not silently change tilt feel; a tiltParams override\n * layer can arrive later if real use demands it). The eases are the same\n * measured css-bezier family the default zoom style uses. Unlike zoom, the\n * pose is 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 return splitBySpeed(segs, doc.speed ?? [])\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: Segment[],\n sIn: number,\n sOut: number,\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 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 * 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)\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 const entry = [clampZoomLevel(z.level), z.cx, z.cy]\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 start = push(\n tIn - (style.rampIn - style.rampInOverlap) * m,\n [1, z.cx, z.cy],\n 'none',\n )\n push(start + style.rampIn * m, entry, spanEase(z.ease, style.ease))\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 for (const e of z.followEvents ?? []) {\n const eOut = sourceToTimeline(segments, e.t)\n if (eOut === null || eOut <= tIn || eOut >= tOut) continue\n const next = [cur[0], e.cx, e.cy]\n push(eOut, cur, 'none')\n push(Math.min(eOut + style.followRecenter, tOut), next, panEase)\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]\n push(\n Math.min(\n tOut + style.pan * mNext,\n next.tIn + style.rampInOverlap * mNext,\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]],\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)\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 start = push(tIn - rampInDur * m, rest, 'none')\n push(start + rampInDur * m, pose, spanEase(z.ease, TILT_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 <= 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.min(tOut + panDur * transitionMult(next.z.transition), next.tIn),\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)\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// 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 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.27.3?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 // 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 return { video: video, cam: cam, mic: mic }\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 },\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 if (cv.width !== W || cv.height !== H) {\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 var lyr = [[bg, ${BACKGROUND_Z}], [card, ${CARD_Z}], [ov, ${OVERLAY_Z}]]\n for (var Li = 0; Li < lyr.length; Li++) {\n var Ly = lyr[Li][0], Lz = lyr[Li][1]\n if (Ly.canvas) { Ly.canvas.width = W; Ly.canvas.height = H }\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, Lh)\n }\n }\n }\n var d = ctx.data || {}\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 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 // 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 function syncVid(vid) {\n try {\n if (playing) {\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 if (!vid.paused) vid.pause()\n // A WebCodecs provider (capture pages) services the frame by\n // PTS at decode speed — no element seek, no 250ms settle cap. The\n // element stays 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 } catch (e) {}\n }\n if (video.play) syncVid(video) // stills (HTMLImageElement) have nothing to sync\n if (r.cam) syncVid(r.cam)\n if (r.mic) syncVid(r.mic)\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 if (!bgEl.paused) bgEl.pause()\n var bgTarget = Math.min(bgT, bgEl.duration || bgT)\n // COALESCE seeks: a scrub moves t every frame, and re-assigning\n // currentTime ABORTS the in-flight seek — on a remote (assets.vos.so)\n // source that keeps the element mid-seek for the whole drag, so no\n // frame ever decodes and the background pops in seconds late. Issue\n // a seek only when none is in flight; the frame after 'seeked' fires\n // corrects toward the latest target, so seeks run serially and\n // converge on the release point.\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 } 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 layer canvas starts transparent each frame — the padding around\n // the contain-fit card shows the background layer through the plane's alpha.\n c.clearRect(0, 0, W, H)\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 var bar = frame.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 = 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 frame.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 = frame.fit === 'cover'\n var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))\n var s2 = s * cf\n var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0\n var availW = Math.max(1, W - pad * 2), availH = Math.max(1, H - pad * 2 - 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 = pad; cardY = pad; cardW = availW; cardH = availH + barH\n var fcv = frame.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 var vTop = pad + 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 dx = (W - dw) / 2; dy = (H - dh + barH) / 2\n cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH\n }\n var radius = (frame.radius || 0) * s2\n var shadow = frame.shadow || 0\n\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 var lvl = 1, zx = 0.5, zy = 0.5\n var zt = d.zoomTrack\n if (zt && zt.keyframes && zt.keyframes.length) {\n var z = TL.sample(zt, t, TL.lerpArray)\n lvl = z[0]; zx = z[1]; zy = z[2]\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 if (lvl > 1.001 && !d.zoomSuppressed) {\n var fx = dx + zx * dw, fy = dy + zy * dh\n c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)\n }\n // soft shadow behind the card (bar strip + video)\n if (shadow > 0) {\n c.save()\n c.shadowColor = 'rgba(0,0,0,' + shadow + ')'\n c.shadowBlur = 60 * s2; c.shadowOffsetY = 24 * s2\n c.fillStyle = '#000'\n rr(cardX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\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, dx, dy, dw, dh))) {\n if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, dx, dy, dw, dh)\n else c.drawImage(video, dx, dy, dw, dh)\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, cardY, cardW, barH)\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. frame.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 (frame.border) {\n var bdW = (frame.borderWidth > 0 ? frame.borderWidth : ${FRAME_BORDER_WIDTH_DEFAULT}) * s2\n var bdH = bdW / 2\n c.save()\n c.globalAlpha = Math.min(1, frame.border)\n c.strokeStyle = frame.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 = d.cursorSpace || { w: vw, h: vh }\n var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5\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) {\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 ckAx = dx + (ck.x / (space.w || vw)) * dw\n var ckAy = dy + (ck.y / (space.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 // 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 = 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 = 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 if (cuA > 0.01) {\n c.save()\n c.fillStyle = 'rgba(255,255,255,' + (0.95 * cuA) + ')'\n c.strokeStyle = 'rgba(0,0,0,' + (0.4 * cuA) + ')'\n c.lineWidth = 2 * s2\n c.beginPath(); c.arc(ax, ay, curSize * ckPress, 0, Math.PI * 2); c.fill(); c.stroke()\n c.restore()\n }\n }\n if (fitCover) c.restore()\n c.restore()\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 = 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 var ovSig = W + 'x' + H\n var ovDirty = ov.sig !== ovSig || camActiveNow || ov.active\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 ov.sig = ovSig\n ov.active = camActiveNow\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 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 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): Record<string, unknown> {\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: o.animation ?? null,\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: o.enter ?? 'rise',\n exit: o.exit ?? 'fade',\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 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 !== '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 return {\n ...base,\n key: o.key,\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(doc: ProjectDoc): LoweredComposition {\n const rated = ratedSegments(doc)\n const duration = durationSec(doc, rated)\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 meta = doc.source.meta\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const zoomSpans: LoweredZoomSpan[] = doc.zoom.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 },\n )\n if (f.entry)\n return { ...z, cx: f.entry.cx, cy: f.entry.cy, followEvents: f.events }\n }\n return { ...z, ...clampFocus(z.cx, z.cy, clampZoomLevel(z.level), layout) }\n })\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 })),\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 ...(cursorFade.length ? { cursorFade } : {}),\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: zoomTrackFromDoc(zoomSpans, rated, zoomStyle),\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 — byte parity.\n ...(doc.tilt && doc.tilt.length\n ? {\n tiltTrack: tiltTrackFromDoc(doc.tilt, rated, zoomStyle.tilt),\n }\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),\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 * 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 ? extractClicks(doc.source.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: meta.width, h: meta.height },\n })\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'\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): 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 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 - pad * 2)\n const availH = Math.max(1, H - pad * 2 - 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 fcx = clamp01(frame.focus?.cx ?? 0.5)\n const fcy = clamp01(frame.focus?.cy ?? 0.5)\n const vTop = pad + barH\n const dx = Math.min(\n pad,\n Math.max(pad + availW - dw, pad + 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: pad,\n cardY: pad,\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 const dx = (W - dw) / 2\n const dy = (H - dh + barH) / 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'>,\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 return computeCardLayout(\n doc.frame,\n {\n width: meta.captureWidth ?? meta.width,\n height: meta.captureHeight ?? meta.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(level: number, layout: CardLayout): 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 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/** 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): { cx: number; cy: number } {\n const b = focusBounds(level, layout)\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 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\nexport const CAMERA_NEAR = 0.1\nexport const CAMERA_FAR = 100\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 {\n findFontFamily,\n fontFaceUrl,\n fontStack,\n nearestFontWeight,\n} from '@vosjs/shared'\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). URLs are the self-hosted catalog on\n * assets.vos.so; studio-core stays dependency-free, so the three base faces\n * are literals — keep them within the catalog `@vosjs/shared` hosts.\n */\nexport const OVERLAY_FONT_FACES: {\n family: string\n weight: number\n url: string\n}[] = [\n {\n family: 'Lexend',\n weight: 400,\n url: 'https://assets.vos.so/fonts/lexend/400.woff2',\n },\n {\n family: 'Lexend',\n weight: 600,\n url: 'https://assets.vos.so/fonts/lexend/600.woff2',\n },\n {\n family: 'JetBrains Mono',\n weight: 400,\n url: 'https://assets.vos.so/fonts/jetbrains-mono/400.woff2',\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 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 const spec = clip.fx\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 !== '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 * 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 * 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 { clampZoomLevel } from '../types'\nimport { ZOOM_STYLES } from '../zoomStyle'\nimport type { 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\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}\n\nexport interface FollowEvent {\n /** SOURCE seconds — the moment the recenter starts. */\n t: number\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 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 last sample at/before span.in (the first sample if none precede).\n let entryPt = pts[0]\n for (const p of pts) {\n if (p.t > span.in) break\n entryPt = p\n }\n const entry = clampFocus(entryPt.nx, entryPt.ny, level, layout)\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 for (const p of pts) {\n if (p.t < span.in) continue\n if (p.t > span.out) break\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)\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 return { entry, events }\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 * 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 { OVERLAY_FONT_FACES } from '../overlayText'\nimport { CARD_FOV, CARD_Z } from '../stage'\nimport { OVERLAY_LINE_HEIGHT, OVERLAY_TRANSITION_DUR } 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// 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 const loads = faces.map((f) => {\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 // 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 for (const oc of (ctx.data.overlays || [])) {\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 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 // 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 var ovSig = W + 'x' + H + '|' + olVisSig\n var ovDirty = ov.sig !== ovSig || ov.active || olAnim\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 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 // 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 = ns.videoCache ? ns.videoCache.get(ol.key) : null\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) continue\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 if (!olReady) continue\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 // Live style edits: SET_DATA never re-runs SETUP, so an override face\n // arriving mid-session lazy-loads here (fail-open; frames repaint as it\n // lands). Cold loads (export) awaited the full list in SETUP already.\n if (ol.face && typeof FontFace !== 'undefined') {\n var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\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 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 // 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, sourceToTimeline } from '@vosjs/timeline'\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}\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 */\nfunction keptOutputEnd(\n segments: Segment[],\n sIn: number,\n sOut: number,\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 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 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: Segment[],\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 = sourceToTimeline(segments, st)\n if (ot === null) continue\n const upSrc = st + Math.max(pressLen, 0.02)\n const up = keptOutputEnd(segments, st, upSrc) ?? 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 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 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 } = groupTrack(track, {\n width,\n height,\n clusterGap: style.clusterGap,\n typingGap: style.typingGap,\n typingZoom: style.typingZoom,\n })\n for (const c of clusters) {\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 })\n }\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.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 } from '../layout'\nimport type { 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/** The visible window in normalized video coords at `level` around the focus. */\nexport function zoomWindow(\n span: { level: number; cx: number; cy: number },\n layout: CardLayout,\n): ZoomWindow {\n const L = Math.max(1, span.level)\n const { cx, cy } = clampFocus(span.cx, span.cy, L, layout)\n const fx = layout.dx + cx * layout.dw\n const fy = layout.dy + cy * layout.dh\n const px0 = fx - fx / L\n const px1 = fx + (layout.W - fx) / L\n const py0 = fy - fy / L\n const py1 = fy + (layout.H - fy) / L\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): boolean {\n if (span.level <= 1.001) return true\n const w = zoomWindow(span, layout)\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 */\nimport type { ProjectDoc } 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/** 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\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(from: ProjectDoc, to: ProjectDoc): 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 in style) next[k] = style[k]\n else delete next[k]\n }\n return next as unknown as ProjectDoc\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 { totalDuration } from '@vosjs/timeline'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport { pickStyle } from './style'\nimport type { ProjectDoc } 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\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 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 },\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 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 totalDuration(ratedSegments(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 notes: string\n}\n\nexport const CHANNEL_SPECS_VERIFIED = '2026-08-04'\n\nexport const CHANNEL_SPECS_HASH =\n '7a3ad4301f96507ec471a35fa37f367dcaf4bd405b008f551d0ac4cd395d61f7'\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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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","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 * 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 { docOutputDuration, voiceKey } from '../audioBeds'\nimport { anchorSourceDuration, isRecordingDoc } from '../doc/studioDoc'\nimport {\n CAM_SPAN_MIN,\n DEFAULT_CAM_POSE,\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 { StudioDoc } from '../doc/studioDoc'\nimport type { Segment } from '@vosjs/timeline'\nimport type { ProjectDoc, SpeedSpan } from '../types'\nimport type { LaneAdapter, LaneItem } from '@vosjs/editor'\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/** Output-time length of one DOC segment with the doc's speed spans applied. */\nconst outputLen = (seg: Segment, speeds: readonly SpeedSpan[]): number =>\n totalDuration(splitBySpeed([seg], speeds))\n\n/** Output-time starts of the DOC segments (speed-aware). */\nconst segmentStarts = (\n segments: Segment[],\n speeds: readonly SpeedSpan[],\n): number[] => {\n const starts: number[] = []\n let acc = 0\n for (const s of segments) {\n starts.push(acc)\n acc += outputLen(s, speeds)\n }\n return starts\n}\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 starts = segmentStarts(segments, speeds)\n return segments.map((s, i) => ({\n id: `seg-${i}`,\n kind: 'clip',\n t: starts[i],\n duration: outputLen(s, speeds),\n }))\n },\n\n gesture(doc, g) {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\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)\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)\n const fullMap = splitBySpeed([{ in: 0, out: sourceDuration }], speeds)\n const edgeSrc = g.edge === 'start' ? seg.in : seg.out\n const edgeOutNow =\n g.edge === 'start'\n ? starts[index]\n : starts[index] + outputLen(seg, speeds)\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)\n const index = segments.findIndex(\n (s, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(s, speeds),\n )\n if (index < 0) return null\n const s = segments[index]\n const sourceT = mapTime(splitBySpeed([s], speeds), g.t - starts[index])\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 starts = segmentStarts(segments, speeds)\n return [\n ...starts,\n ...(starts.length\n ? [\n starts[starts.length - 1] +\n outputLen(segments[segments.length - 1], speeds),\n ]\n : []),\n ]\n },\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)\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 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 < 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 },\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)\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 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 < 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 },\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)\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 starts = segmentStarts(segments, speeds)\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 items.push({\n id: `cam-${i}`,\n kind: 'clip',\n t: round(starts[i] + outputLen({ in: s.in, out: a }, speeds)),\n duration: round(outputLen({ in: a, out: b }, speeds)),\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 starts = segmentStarts(segments, speeds)\n return segments.map((s, i) => ({\n id: `mic-${i}`,\n kind: 'clip',\n t: starts[i],\n duration: outputLen(s, speeds),\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 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 },\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 === '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 * 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 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 return totalDuration(ratedSegments(doc))\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 * 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"],"mappings":"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A;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAgPH,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;AAA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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;AA8NxB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qDAa8B,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,yBAyBpC,YAAY;AAAA,2BACV,MAAM;AAAA,yBACR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBlC,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAQxB,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAuBK,YAAY,aAAa,MAAM,WAAW,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAO1B,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["clamp01","clamp","segmentRate","sourceToTimeline","timelineRuntimeCode","sc","dw","dh","clamp01","dx","dy","round","w","clamp01","sampleAt","round","round","round","segmentRate","push","round","sourceToTimeline","next","timelineRuntimeCode","round","emit","round","totalDuration","round","totalDuration","round","totalDuration","totalDuration","mapTime","segmentRate","sourceToTimeline","splitBySpeed","totalDuration","totalDuration","round3","totalDuration","EPS","totalDuration","splitBySpeed","sourceToTimeline","mapTime","s","round","clamp01","segmentRate","newStart","clip","mapTime","durationSec"]}
|
|
1
|
+
{"version":3,"sources":["../src/backdrop.ts","../src/types.ts","../src/capture.ts","../src/ingest.ts","../src/docVersion.ts","../src/doc/studioDoc.ts","../src/planner/smoothing.ts","../src/zoomStyle.ts","../src/planner/autoZoom.ts","../src/lower/lowerToComposition.ts","../src/layout.ts","../src/stage.ts","../src/overlayText.ts","../src/text3d.ts","../src/lower/audioEnvelope.ts","../src/lower/cursorFollow.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/digest/geometry.ts","../src/digest/build.ts","../src/timeline/rangeActions.ts","../src/lower/lowerStudioDoc.ts","../src/destinations.ts","../src/lower/audioPlan.ts","../src/timeline/lanes.ts","../src/audioBeds.ts","../src/waveform.ts","../src/lower/duckCurve.ts","../src/rejected.ts"],"sourcesContent":["/**\n * The DEFAULT backdrop: the house loop every NEW take opens on\n * once the flag below is on. A committed constant, not a live vos, the same\n * way Home's door tiles are a deliberate asset push (decided 2026-08-17):\n * changing it is a code change with a review, never something an autosave\n * can re-skin.\n *\n * `ground` is 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. The\n * keys are the pre-registry bucket objects, which stay in the bucket\n * forever; the registry's `backdrops/{slug}/…` keys replace them when the\n * house loop is featured through the verb.\n *\n * BACKDROP_DEFAULT_ON is the flip. It stays OFF until the set has a signed-off\n * seed and the fleet measurement has run: a switch, not a surprise.\n * Docs already carrying a frame are never touched either way.\n */\nimport type { BackgroundMedia, FrameStyle } from './types'\n\nexport const DEFAULT_BACKDROP = {\n slug: 'soft-beams',\n title: 'Soft Beams',\n key: 'https://assets.vos.so/backgrounds/soft-beams-1080p.webm',\n key2k: 'https://assets.vos.so/backgrounds/soft-beams-2k.webm',\n poster: 'https://assets.vos.so/backgrounds/soft-beams-poster.jpg',\n duration: 10,\n ground: '#a7b2d1',\n} as const\n\nexport const BACKDROP_DEFAULT_ON = false as boolean\n\n/** The default backdrop as a doc's `frame.backgroundMedia`. */\nexport function defaultBackdropMedia(): BackgroundMedia {\n return {\n kind: 'video',\n key: DEFAULT_BACKDROP.key,\n duration: DEFAULT_BACKDROP.duration,\n poster: DEFAULT_BACKDROP.poster,\n dim: 0,\n }\n}\n\n/** A frame style opening on the default backdrop (media + its ground). */\nexport function withDefaultBackdrop(frame: FrameStyle): FrameStyle {\n return {\n ...frame,\n background: DEFAULT_BACKDROP.ground,\n backgroundMedia: defaultBackdropMedia(),\n }\n}\n","/**\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 { BACKDROP_DEFAULT_ON, withDefaultBackdrop } from './backdrop'\nimport type { Segment } from '@vosjs/timeline'\nimport type { ZoomStyleName, ZoomStyleParams } from './zoomStyle'\nimport type { SpeedParams } from './planner/autoSpeed'\n\nexport type { Segment }\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\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 /** 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\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 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 * 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 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 /** 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 style: 'default' | 'dot' | 'ring'\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 /** 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}\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 /** shadow strength 0..1. */\n shadow: 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 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\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'\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/** 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\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 /** Absent = 'rise' for enter, 'fade' for exit (the house default motion). */\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 /** Text outline, drawn under the fill. */\n stroke?: TextOverlayStroke\n /** Background pill behind the text block (absent = none). */\n box?: TextOverlayBox\n /** Entrance animation. Absent = the legacy `enter` transition. */\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 key: string\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\nexport type OverlayClip = TextOverlayClip | MediaOverlayClip\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 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: Segment[]\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 /** 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 * 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 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\nconst 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 // '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. Once `BACKDROP_DEFAULT_ON`\n * flips (backdrop.ts), the default is the house loop on its own ground;\n * until then the brand gradient. Every ingest path spreads this, so the\n * flip reaches the extension handoff, the in-page recorder, a dropped file\n * and `vos record` at once; a doc that already carries a frame keeps it.\n */\nexport const DEFAULT_FRAME_STYLE: FrameStyle = BACKDROP_DEFAULT_ON\n ? withDefaultBackdrop(BASE_FRAME_STYLE)\n : 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 * 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 * 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 { ProjectDoc, RecordingArtifact } from './types'\n\n/** Build a ProjectDoc from a RecordingArtifact handed off by a recorder. */\nexport function projectFromArtifact(\n artifact: RecordingArtifact,\n videoUrl: string,\n): { doc: ProjectDoc; videoUrl: string } {\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 cursor: { ...DEFAULT_CURSOR_STYLE },\n cam: { ...DEFAULT_CAM_STYLE },\n frame: {\n ...DEFAULT_FRAME_STYLE,\n browserBar: {\n ...DEFAULT_FRAME_STYLE.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 export: { resolution: '1080p', fps: 30, format: 'mp4' },\n }\n return { doc, videoUrl }\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 */\n\n/**\n * 2 = the document FAMILY era: a doc is a recording document (`source`)\n * or a program document (`program.config`). A v1 doc IS a recording document,\n * field for field, so 1 → 2 is a stamp; 0 → 1 was a stamp too.\n */\nexport const DOC_SCHEMA_VERSION = 2\n\n/**\n * Upgrade a hosted doc.json payload to the current schema version.\n * Unstamped docs are v0 — the pre-stamp era. Every step so far is a stamp\n * (a post-v1 doc field is optional by doctrine, and v2 only widened the\n * family), so migration is structural identity. A real shape change chains\n * 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 // v0 → v1 → v2: stamp only.\n return { ...raw, docSchemaVersion: DOC_SCHEMA_VERSION }\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 * 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). One name → one coherent feel.\n *\n * The presets are grounded in a measured comparison of the four shipping\n * strategies (frame-by-frame optical-flow tracking of real exports + source\n * audits of Recordly/OpenScreen + Cursorful bundle/behavior research):\n *\n * - Cursorful (\"glide\"): ONE modest zoom (~1.5×) per activity session —\n * 2+ clicks within a rolling window keep the zoom alive — and the camera\n * TRAVELS by panning between focus points while zoomed. Measured zoom ramp\n * fits css-bezier(0.26, 0, 0.16, 1): near-zero initial velocity, soft\n * landing, zero overshoot. Pan durations scale with distance.\n * - Screen Studio \"Focused\" / Recordly (\"focus\"): a zoom block per click\n * cluster at ~1.8×, spring-settled ramps (~1.5 s in / ~1 s out), direct\n * pans across gaps ≤ ~1.35 s, dead-zone cursor follow. The measured\n * OpenScreen/Recordly ramp (their bezier filtered through the spring) fits\n * css-bezier(0.28, 0.03, 0.09, 1) — that curve IS the family feel.\n * - Screen Studio \"Smooth\" (\"cinema\"): the same block model, slower and\n * more fluid — for content that is watched, not read.\n * - \"snappy\": the studio's original fast cycles, with the defect fixed — the raw\n * css-bezier(0.16, 1, 0.3, 1) arrival had an initial velocity 6.25× the\n * ramp average (the \"jump cut\" complaint); competitors always filter that\n * curve through a spring. This preset keeps the pace but caps the onset.\n * - \"cut\": Screen Studio's \"instant zoom\" option — hard cut-in, no glide.\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 | 'keynote'\n | 'drift'\n\n/**\n * The tilt half of a camera style: the Dynamic-tilt\n * intensity the style ships with, plus optional overrides on the tilt track's\n * motion constants. A style pick stamps `doc.tiltStyle` with `intensity` and\n * re-plans auto tilt spans alongside the auto zooms — one name, one coherent\n * 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 /** minimum clicks for a cluster to earn a zoom (Cursorful's ≥2 rule). */\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 /** 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 */\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 /** output-time gap ≤ this → pan straight to the next span (no zoom-out). */\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\nexport const ZOOM_STYLES: Record<ZoomStyleName, ZoomStyleParams> = {\n // Cursorful strategy — one steady zoom per activity session, travel by pans.\n glide: {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.6,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 1.1,\n rampInOverlap: 0.35,\n rampOut: 1.0,\n chainGap: 2.5,\n pan: 1.0,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0.4,\n tilt: { intensity: 'off' },\n },\n // Screen Studio \"Focused\" / Recordly — block zooms, spring-settled, readable.\n focus: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.5,\n maxLevel: 2.2,\n lead: 0.35,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.0,\n typingHold: 1.2,\n typingMinLevel: 1.6,\n rampIn: 1.4,\n rampInOverlap: 0.5,\n rampOut: 1.0,\n chainGap: 1.35,\n pan: 1.0,\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.6,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // Screen Studio \"Smooth\" — slower, more fluid; creative content over reading.\n cinema: {\n autoZoom: true,\n clusterGap: 1.8,\n minClusterClicks: 1,\n targetFill: 0.45,\n minLevel: 1.35,\n maxLevel: 2.0,\n lead: 0.6,\n hold: 1.4,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.6,\n typingMinLevel: 1.45,\n rampIn: 1.8,\n rampInOverlap: 0.55,\n rampOut: 1.5,\n chainGap: 2.2,\n pan: 1.4,\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: 1.0,\n followLookahead: 0.5,\n tilt: { intensity: 'off' },\n },\n // The studio's original pace with the instant-velocity onset defect fixed.\n snappy: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.4,\n maxLevel: 2.5,\n lead: 0.25,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.6,\n typingHold: 0.8,\n typingMinLevel: 1.5,\n rampIn: 0.7,\n rampInOverlap: 0.3,\n rampOut: 0.75,\n chainGap: 1.5,\n pan: 0.8,\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.5,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // Screen Studio's \"instant zoom\" — a cut-in (2–4 frames), tutorial tempo.\n cut: {\n autoZoom: true,\n clusterGap: 1.2,\n minClusterClicks: 1,\n targetFill: 0.5,\n minLevel: 1.5,\n maxLevel: 2.2,\n lead: 0.2,\n hold: 1.0,\n followByDefault: false,\n typingZoom: true,\n typingGap: 1.2,\n typingHold: 0.6,\n typingMinLevel: 1.6,\n rampIn: 0.14,\n rampInOverlap: 0.07,\n rampOut: 0.14,\n chainGap: 1.2,\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: { intensity: 'off' },\n },\n // Auto-zoom off (every competitor ships this switch). Camera params still\n // apply to MANUAL spans — they get the default (glide) motion.\n none: {\n autoZoom: false,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.6,\n followByDefault: false,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.1,\n typingMinLevel: 1.4,\n rampIn: 1.1,\n rampInOverlap: 0.35,\n rampOut: 1.0,\n chainGap: 2.5,\n pan: 1.0,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0,\n tilt: { intensity: 'off' },\n },\n // ── tilt-forward styles — the camera moves in DEPTH too.\n // Research note (2026-08-03): no competitor ships a document-wide personality\n // driving BOTH auto-zoom dynamics and focus-following tilt from one name —\n // FocuSee's Subtle/Default/Strong 3D Motion is a separate layered effect,\n // TiltIt/ScreenDrift sell per-clip templates. These two own that space.\n //\n // \"keynote\": the launch-film sentence (the Apple register: rehearsed,\n // restrained, one move per beat) — glide's session-merged, modest zooms plus\n // a MEDIUM lean toward each zoom's focus, tilt ramps MATCHED to the zoom\n // ramps so lean and zoom read as one camera move; chained so back-to-back\n // beats swing pose-to-pose. Never oscillation, ±5..18° band.\n keynote: {\n autoZoom: true,\n clusterGap: 3.0,\n minClusterClicks: 2,\n targetFill: 0.42,\n minLevel: 1.3,\n maxLevel: 1.8,\n lead: 0.5,\n hold: 0.7,\n followByDefault: true,\n typingZoom: true,\n typingGap: 2.5,\n typingHold: 1.2,\n typingMinLevel: 1.4,\n rampIn: 1.2,\n rampInOverlap: 0.35,\n rampOut: 1.1,\n chainGap: 2.5,\n pan: 1.1,\n ease: 'css-bezier(0.26, 0, 0.16, 1)',\n panEase: 'css-bezier(0.3, 0, 0.2, 1)',\n followSafeRatio: 0.45,\n followRecenter: 0.8,\n followLookahead: 0.4,\n // Lean lands WITH the zoom (matched ramps read as one camera move).\n tilt: {\n intensity: 'medium',\n rampIn: 1.2,\n rampOut: 1.1,\n chainGap: 2.5,\n pan: 1.1,\n },\n },\n // \"drift\": calm ambient depth — cinema's slow, fluid blocks with a SUBTLE\n // lean that eases in over ~1.6s and lingers (long chain gap keeps the card\n // from flattening between nearby beats). For watched-not-read content:\n // launch films, portfolio clips, hero loops.\n drift: {\n autoZoom: true,\n clusterGap: 1.8,\n minClusterClicks: 1,\n targetFill: 0.45,\n minLevel: 1.35,\n maxLevel: 2.0,\n lead: 0.6,\n hold: 1.4,\n followByDefault: true,\n typingZoom: true,\n typingGap: 3.0,\n typingHold: 1.6,\n typingMinLevel: 1.45,\n rampIn: 1.8,\n rampInOverlap: 0.55,\n rampOut: 1.5,\n chainGap: 2.2,\n pan: 1.4,\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: 1.0,\n followLookahead: 0.5,\n tilt: {\n intensity: 'subtle',\n rampIn: 1.6,\n rampOut: 1.4,\n chainGap: 3.0,\n pan: 1.4,\n },\n },\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. Unknown names from hand-edited doc.json fall\n * back to the default style.\n */\nexport function resolveZoomStyle(\n name?: ZoomStyleName,\n overrides?: Partial<ZoomStyleParams>,\n): ZoomStyleParams {\n const params: ZoomStyleParams | undefined = name\n ? ZOOM_STYLES[name]\n : undefined\n return { ...(params ?? ZOOM_STYLES[DEFAULT_ZOOM_STYLE]), ...overrides }\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: 'keynote',\n label: 'Keynote',\n hint: 'Gliding zooms that lean toward each focus',\n },\n {\n name: 'drift',\n label: 'Drift',\n hint: 'Slow, fluid moves with a subtle ambient lean',\n },\n {\n name: 'focus',\n label: 'Focus',\n hint: 'A zoom per click cluster, settles fast',\n },\n { name: 'cinema', label: 'Cinema', hint: 'Slow, fluid camera moves' },\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 * 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 whose element FITS the frame at less than this level is\n * not a target: it is a drag (aiming, scrubbing, moving a thing across the\n * canvas) or a frame-sized surface, and a zoom on it says nothing. Five real\n * takes (2026-08-25) each carried 1-4 such clusters, planned at the floor\n * level for 10-25s; every one was dropped by hand. Now they plan nothing.\n */\nexport const DRAG_FIT_LEVEL = 1.15\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 /** 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 },\n): { sessions: TypingSession[]; clusters: Click[][] } {\n const { width, height, clusterGap, typingGap, typingZoom } = opts\n const clicks: Click[] = track\n .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 // Merge clusters of clicks that are close in time into one sustained zoom.\n const clusters: Click[][] = []\n for (const c of clicks) {\n if (absorbed.has(c)) continue\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 { sessions, clusters }\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 followByDefault = style.followByDefault,\n typingZoom = style.typingZoom,\n typingGap = style.typingGap,\n typingHold = style.typingHold,\n typingMinLevel = style.typingMinLevel,\n } = options\n\n const { sessions, clusters } = groupTrack(track, {\n width,\n height,\n clusterGap,\n typingGap,\n typingZoom,\n })\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 // 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 // Drag clusters plan no zoom but still RESERVE their window: the cursor\n // was working there, and the pauses between drags are not dwells.\n const dragReserved: ZoomSpan[] = []\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 // A drag or a frame-sized surface (DRAG_FIT_LEVEL): no zoom at all.\n if (f.fit !== null && f.fit < DRAG_FIT_LEVEL) {\n dragReserved.push({\n id: `drag${dragReserved.length}`,\n in: Math.max(0, first.t - lead),\n out: last.t + hold,\n level: 1,\n cx: f.cx,\n cy: f.cy,\n })\n continue\n }\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].sort((a, b) => a.in - b.in)\n\n // Dwell augmentation: sustained cursor rests no click/typing span 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 if (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; fit: number | null } {\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 element fills ~targetFill of the frame (element-aware).\n // No rect → use the max level (point zoom).\n let level = maxLevel\n let fit: number | null = null\n if (maxW > 0 && maxH > 0) {\n const fitX = (width * targetFill) / maxW\n const fitY = (height * targetFill) / maxH\n level = Math.min(fitX, fitY)\n fit = level\n }\n return { cx, cy, level: clamp(level, minLevel, maxLevel), fit }\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 { camBubbleRect, clampFocus, docCardLayout } from '../layout'\nimport { smoothCursor } from '../planner/smoothing'\nimport {\n BACKGROUND_Z,\n CAMERA_FAR,\n CAMERA_NEAR,\n CARD_FOV,\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 CLICK_FX_INTENSITY,\n FRAME_BORDER_COLOR_DEFAULT,\n FRAME_BORDER_WIDTH_DEFAULT,\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 transitionMult,\n} from '../types'\nimport { DEFAULT_ZOOM_STYLE, ZOOM_STYLES, resolveZoomStyle } from '../zoomStyle'\nimport { isRecordingDoc, programDuration } from '../doc/studioDoc'\nimport { clipEnvelope } from './audioEnvelope'\nimport { followFocusEvents } from './cursorFollow'\nimport { cursorIdleFade } from './cursorIdle'\nimport { STUDIO_ENTRY_ID, studioEntry } from './studioEntry'\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 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 } from '../layout'\nimport type { FollowEvent } from './cursorFollow'\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). Fixed constants, deliberately NOT the\n * zoom style's (switching\n * \"Camera style\" must not silently change tilt feel; a tiltParams override\n * layer can arrive later if real use demands it). The eases are the same\n * measured css-bezier family the default zoom style uses. Unlike zoom, the\n * pose is 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 return splitBySpeed(segs, doc.speed ?? [])\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: Segment[],\n sIn: number,\n sOut: number,\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 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 * 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)\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 const entry = [clampZoomLevel(z.level), z.cx, z.cy]\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 start = push(\n tIn - (style.rampIn - style.rampInOverlap) * m,\n [1, z.cx, z.cy],\n 'none',\n )\n push(start + style.rampIn * m, entry, spanEase(z.ease, style.ease))\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 for (const e of z.followEvents ?? []) {\n const eOut = sourceToTimeline(segments, e.t)\n if (eOut === null || eOut <= tIn || eOut >= tOut) continue\n const next = [cur[0], e.cx, e.cy]\n push(eOut, cur, 'none')\n push(Math.min(eOut + style.followRecenter, tOut), next, panEase)\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]\n push(\n Math.min(\n tOut + style.pan * mNext,\n next.tIn + style.rampInOverlap * mNext,\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]],\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)\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 start = push(tIn - rampInDur * m, rest, 'none')\n push(start + rampInDur * m, pose, spanEase(z.ease, TILT_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 <= 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.min(tOut + panDur * transitionMult(next.z.transition), next.tIn),\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)\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// 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 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.27.3?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 // 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 return { video: video, cam: cam, mic: mic }\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 },\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 if (cv.width !== W || cv.height !== H) {\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 var lyr = [[bg, ${BACKGROUND_Z}], [card, ${CARD_Z}], [ov, ${OVERLAY_Z}]]\n for (var Li = 0; Li < lyr.length; Li++) {\n var Ly = lyr[Li][0], Lz = lyr[Li][1]\n if (Ly.canvas) { Ly.canvas.width = W; Ly.canvas.height = H }\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, Lh)\n }\n }\n }\n var d = ctx.data || {}\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 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 // 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 function syncVid(vid) {\n try {\n if (playing) {\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 if (!vid.paused) vid.pause()\n // A WebCodecs provider (capture pages) services the frame by\n // PTS at decode speed — no element seek, no 250ms settle cap. The\n // element stays 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 } catch (e) {}\n }\n if (video.play) syncVid(video) // stills (HTMLImageElement) have nothing to sync\n if (r.cam) syncVid(r.cam)\n if (r.mic) syncVid(r.mic)\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 if (!bgEl.paused) bgEl.pause()\n var bgTarget = Math.min(bgT, bgEl.duration || bgT)\n // COALESCE seeks: a scrub moves t every frame, and re-assigning\n // currentTime ABORTS the in-flight seek — on a remote (assets.vos.so)\n // source that keeps the element mid-seek for the whole drag, so no\n // frame ever decodes and the background pops in seconds late. Issue\n // a seek only when none is in flight; the frame after 'seeked' fires\n // corrects toward the latest target, so seeks run serially and\n // converge on the release point.\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 } 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 layer canvas starts transparent each frame — the padding around\n // the contain-fit card shows the background layer through the plane's alpha.\n c.clearRect(0, 0, W, H)\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 var bar = frame.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 = 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 frame.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 = frame.fit === 'cover'\n var cf = fitCover ? 1 : Math.min(1, (W / H) / (vw / vh))\n var s2 = s * cf\n var barH = bar.kind && bar.kind !== 'none' ? (bar.height || 44) * s2 : 0\n var availW = Math.max(1, W - pad * 2), availH = Math.max(1, H - pad * 2 - 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 = pad; cardY = pad; cardW = availW; cardH = availH + barH\n var fcv = frame.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 var vTop = pad + 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 dx = (W - dw) / 2; dy = (H - dh + barH) / 2\n cardX = dx; cardY = dy - barH; cardW = dw; cardH = dh + barH\n }\n var radius = (frame.radius || 0) * s2\n var shadow = frame.shadow || 0\n\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 var lvl = 1, zx = 0.5, zy = 0.5\n var zt = d.zoomTrack\n if (zt && zt.keyframes && zt.keyframes.length) {\n var z = TL.sample(zt, t, TL.lerpArray)\n lvl = z[0]; zx = z[1]; zy = z[2]\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 if (lvl > 1.001 && !d.zoomSuppressed) {\n var fx = dx + zx * dw, fy = dy + zy * dh\n c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)\n }\n // soft shadow behind the card (bar strip + video)\n if (shadow > 0) {\n c.save()\n c.shadowColor = 'rgba(0,0,0,' + shadow + ')'\n c.shadowBlur = 60 * s2; c.shadowOffsetY = 24 * s2\n c.fillStyle = '#000'\n rr(cardX, cardY, cardW, cardH, radius); c.fill()\n c.restore()\n }\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, dx, dy, dw, dh))) {\n if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, dx, dy, dw, dh)\n else c.drawImage(video, dx, dy, dw, dh)\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, cardY, cardW, barH)\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. frame.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 (frame.border) {\n var bdW = (frame.borderWidth > 0 ? frame.borderWidth : ${FRAME_BORDER_WIDTH_DEFAULT}) * s2\n var bdH = bdW / 2\n c.save()\n c.globalAlpha = Math.min(1, frame.border)\n c.strokeStyle = frame.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 = d.cursorSpace || { w: vw, h: vh }\n var curSize = ((d.cursorStyle && d.cursorStyle.size) || 24) * s2 * 0.5\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) {\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 ckAx = dx + (ck.x / (space.w || vw)) * dw\n var ckAy = dy + (ck.y / (space.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 // 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 = 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 = 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 if (cuA > 0.01) {\n c.save()\n c.fillStyle = 'rgba(255,255,255,' + (0.95 * cuA) + ')'\n c.strokeStyle = 'rgba(0,0,0,' + (0.4 * cuA) + ')'\n c.lineWidth = 2 * s2\n c.beginPath(); c.arc(ax, ay, curSize * ckPress, 0, Math.PI * 2); c.fill(); c.stroke()\n c.restore()\n }\n }\n if (fitCover) c.restore()\n c.restore()\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 = 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 var ovSig = W + 'x' + H\n var ovDirty = ov.sig !== ovSig || camActiveNow || ov.active\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 ov.sig = ovSig\n ov.active = camActiveNow\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 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 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): Record<string, unknown> {\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: o.animation ?? null,\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: o.enter ?? 'rise',\n exit: o.exit ?? 'fade',\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 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 !== '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 return {\n ...base,\n key: o.key,\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(doc: ProjectDoc): LoweredComposition {\n const rated = ratedSegments(doc)\n const duration = durationSec(doc, rated)\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 meta = doc.source.meta\n const zoomStyle = resolveZoomStyle(doc.zoomStyle, doc.zoomParams)\n const zoomSpans: LoweredZoomSpan[] = doc.zoom.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 },\n )\n if (f.entry)\n return { ...z, cx: f.entry.cx, cy: f.entry.cy, followEvents: f.events }\n }\n return { ...z, ...clampFocus(z.cx, z.cy, clampZoomLevel(z.level), layout) }\n })\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 })),\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 ...(cursorFade.length ? { cursorFade } : {}),\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: zoomTrackFromDoc(zoomSpans, rated, zoomStyle),\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 — byte parity.\n ...(doc.tilt && doc.tilt.length\n ? {\n tiltTrack: tiltTrackFromDoc(doc.tilt, rated, zoomStyle.tilt),\n }\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),\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 * 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 ? extractClicks(doc.source.cursor, rated, {\n rects: fx.style === 'highlight',\n space: { w: meta.width, h: meta.height },\n })\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'\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): 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 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 - pad * 2)\n const availH = Math.max(1, H - pad * 2 - 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 fcx = clamp01(frame.focus?.cx ?? 0.5)\n const fcy = clamp01(frame.focus?.cy ?? 0.5)\n const vTop = pad + barH\n const dx = Math.min(\n pad,\n Math.max(pad + availW - dw, pad + 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: pad,\n cardY: pad,\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 const dx = (W - dw) / 2\n const dy = (H - dh + barH) / 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'>,\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 return computeCardLayout(\n doc.frame,\n {\n width: meta.captureWidth ?? meta.width,\n height: meta.captureHeight ?? meta.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(level: number, layout: CardLayout): 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 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/** 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): { cx: number; cy: number } {\n const b = focusBounds(level, layout)\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 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\nexport const CAMERA_NEAR = 0.1\nexport const CAMERA_FAR = 100\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 {\n findFontFamily,\n fontFaceUrl,\n fontStack,\n nearestFontWeight,\n} from '@vosjs/shared'\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). URLs are the self-hosted catalog on\n * assets.vos.so; studio-core stays dependency-free, so the three base faces\n * are literals — keep them within the catalog `@vosjs/shared` hosts.\n */\nexport const OVERLAY_FONT_FACES: {\n family: string\n weight: number\n url: string\n}[] = [\n {\n family: 'Lexend',\n weight: 400,\n url: 'https://assets.vos.so/fonts/lexend/400.woff2',\n },\n {\n family: 'Lexend',\n weight: 600,\n url: 'https://assets.vos.so/fonts/lexend/600.woff2',\n },\n {\n family: 'JetBrains Mono',\n weight: 400,\n url: 'https://assets.vos.so/fonts/jetbrains-mono/400.woff2',\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 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 const spec = clip.fx\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 !== '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 * 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 * 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 { clampZoomLevel } from '../types'\nimport { ZOOM_STYLES } from '../zoomStyle'\nimport type { 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\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}\n\nexport interface FollowEvent {\n /** SOURCE seconds — the moment the recenter starts. */\n t: number\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 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 last sample at/before span.in (the first sample if none precede).\n let entryPt = pts[0]\n for (const p of pts) {\n if (p.t > span.in) break\n entryPt = p\n }\n const entry = clampFocus(entryPt.nx, entryPt.ny, level, layout)\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 for (const p of pts) {\n if (p.t < span.in) continue\n if (p.t > span.out) break\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)\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 return { entry, events }\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 * 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 { OVERLAY_FONT_FACES } from '../overlayText'\nimport { CARD_FOV, CARD_Z } from '../stage'\nimport { OVERLAY_LINE_HEIGHT, OVERLAY_TRANSITION_DUR } 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// 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 const loads = faces.map((f) => {\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 // 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 for (const oc of (ctx.data.overlays || [])) {\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 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 // 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 var ovSig = W + 'x' + H + '|' + olVisSig\n var ovDirty = ov.sig !== ovSig || ov.active || olAnim\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 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 // 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 = ns.videoCache ? ns.videoCache.get(ol.key) : null\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) continue\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 if (!olReady) continue\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 // Live style edits: SET_DATA never re-runs SETUP, so an override face\n // arriving mid-session lazy-loads here (fail-open; frames repaint as it\n // lands). Cold loads (export) awaited the full list in SETUP already.\n if (ol.face && typeof FontFace !== 'undefined') {\n var ofSet = window.__voilaFontSet || (window.__voilaFontSet = {})\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 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 // 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, sourceToTimeline } from '@vosjs/timeline'\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}\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 */\nfunction keptOutputEnd(\n segments: Segment[],\n sIn: number,\n sOut: number,\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 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 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: Segment[],\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 = sourceToTimeline(segments, st)\n if (ot === null) continue\n const upSrc = st + Math.max(pressLen, 0.02)\n const up = keptOutputEnd(segments, st, upSrc) ?? 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 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 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 } = groupTrack(track, {\n width,\n height,\n clusterGap: style.clusterGap,\n typingGap: style.typingGap,\n typingZoom: style.typingZoom,\n })\n for (const c of clusters) {\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 })\n }\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.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 } from '../layout'\nimport type { 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/** The visible window in normalized video coords at `level` around the focus. */\nexport function zoomWindow(\n span: { level: number; cx: number; cy: number },\n layout: CardLayout,\n): ZoomWindow {\n const L = Math.max(1, span.level)\n const { cx, cy } = clampFocus(span.cx, span.cy, L, layout)\n const fx = layout.dx + cx * layout.dw\n const fy = layout.dy + cy * layout.dh\n const px0 = fx - fx / L\n const px1 = fx + (layout.W - fx) / L\n const py0 = fy - fy / L\n const py1 = fy + (layout.H - fy) / L\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): boolean {\n if (span.level <= 1.001) return true\n const w = zoomWindow(span, layout)\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 */\nimport type { ProjectDoc } 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/** 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\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(from: ProjectDoc, to: ProjectDoc): 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 in style) next[k] = style[k]\n else delete next[k]\n }\n return next as unknown as ProjectDoc\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 { totalDuration } from '@vosjs/timeline'\nimport { ratedSegments } from '../lower/lowerToComposition'\nimport { pickStyle } from './style'\nimport type { ProjectDoc } 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\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 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 },\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 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 totalDuration(ratedSegments(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 notes: string\n}\n\nexport const CHANNEL_SPECS_VERIFIED = '2026-08-04'\n\nexport const CHANNEL_SPECS_HASH =\n '7a3ad4301f96507ec471a35fa37f367dcaf4bd405b008f551d0ac4cd395d61f7'\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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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","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 * 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 { docOutputDuration, voiceKey } from '../audioBeds'\nimport { anchorSourceDuration, isRecordingDoc } from '../doc/studioDoc'\nimport {\n CAM_SPAN_MIN,\n DEFAULT_CAM_POSE,\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 { StudioDoc } from '../doc/studioDoc'\nimport type { Segment } from '@vosjs/timeline'\nimport type { ProjectDoc, SpeedSpan } from '../types'\nimport type { LaneAdapter, LaneItem } from '@vosjs/editor'\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/** Output-time length of one DOC segment with the doc's speed spans applied. */\nconst outputLen = (seg: Segment, speeds: readonly SpeedSpan[]): number =>\n totalDuration(splitBySpeed([seg], speeds))\n\n/** Output-time starts of the DOC segments (speed-aware). */\nconst segmentStarts = (\n segments: Segment[],\n speeds: readonly SpeedSpan[],\n): number[] => {\n const starts: number[] = []\n let acc = 0\n for (const s of segments) {\n starts.push(acc)\n acc += outputLen(s, speeds)\n }\n return starts\n}\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 starts = segmentStarts(segments, speeds)\n return segments.map((s, i) => ({\n id: `seg-${i}`,\n kind: 'clip',\n t: starts[i],\n duration: outputLen(s, speeds),\n }))\n },\n\n gesture(doc, g) {\n const segments = effectiveSegments(doc)\n const speeds = doc.speed ?? []\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)\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)\n const fullMap = splitBySpeed([{ in: 0, out: sourceDuration }], speeds)\n const edgeSrc = g.edge === 'start' ? seg.in : seg.out\n const edgeOutNow =\n g.edge === 'start'\n ? starts[index]\n : starts[index] + outputLen(seg, speeds)\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)\n const index = segments.findIndex(\n (s, i) => g.t >= starts[i] && g.t < starts[i] + outputLen(s, speeds),\n )\n if (index < 0) return null\n const s = segments[index]\n const sourceT = mapTime(splitBySpeed([s], speeds), g.t - starts[index])\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 starts = segmentStarts(segments, speeds)\n return [\n ...starts,\n ...(starts.length\n ? [\n starts[starts.length - 1] +\n outputLen(segments[segments.length - 1], speeds),\n ]\n : []),\n ]\n },\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)\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 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 < 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 },\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)\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 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 < 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 },\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)\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 starts = segmentStarts(segments, speeds)\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 items.push({\n id: `cam-${i}`,\n kind: 'clip',\n t: round(starts[i] + outputLen({ in: s.in, out: a }, speeds)),\n duration: round(outputLen({ in: a, out: b }, speeds)),\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 starts = segmentStarts(segments, speeds)\n return segments.map((s, i) => ({\n id: `mic-${i}`,\n kind: 'clip',\n t: starts[i],\n duration: outputLen(s, speeds),\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 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 },\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 === '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 * 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 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 return totalDuration(ratedSegments(doc))\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 * 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 * 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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A;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kCAgPH,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;AAA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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;AA8NxB,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qDAa8B,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,yBAyBpC,YAAY;AAAA,2BACV,MAAM;AAAA,yBACR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBlC,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAQxB,IAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAuBK,YAAY,aAAa,MAAM,WAAW,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAO1B,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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["clamp01","clamp","segmentRate","sourceToTimeline","timelineRuntimeCode","sc","dw","dh","clamp01","dx","dy","round","w","clamp01","sampleAt","round","round","round","segmentRate","push","round","sourceToTimeline","next","timelineRuntimeCode","round","emit","round","totalDuration","round","totalDuration","round","totalDuration","totalDuration","mapTime","segmentRate","sourceToTimeline","splitBySpeed","totalDuration","totalDuration","round3","totalDuration","EPS","totalDuration","splitBySpeed","sourceToTimeline","mapTime","s","round","clamp01","segmentRate","newStart","clip","mapTime","durationSec"]}
|