@vosjs/studio-core 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1221,7 +1221,22 @@ interface TextOverlayBox {
1221
1221
  */
1222
1222
  interface MediaOverlayClip extends OverlayClipBase {
1223
1223
  kind: 'image' | 'video';
1224
+ /**
1225
+ * The picture: an asset URL, a take-dir file, or a DOCUMENT MEDIA by
1226
+ * reference, `media:<id>` (`media:` alone is the primary), which brings
1227
+ * the media's facts with it (its cursor track, its recorded page) and
1228
+ * follows the media's kind. The lowering resolves the reference to the
1229
+ * media's own key; the document keeps the reference.
1230
+ */
1224
1231
  key: string;
1232
+ /**
1233
+ * The layer as a CARD: drawn by the card painter on its own plane above
1234
+ * the primary card (the layered shadow, the media clipped to its corners,
1235
+ * a browser bar, the border, a lean, and for a document media its cursor
1236
+ * dot and click rings). Absent, the layer is the flat picture it always
1237
+ * was, byte-identically.
1238
+ */
1239
+ frame?: LayerCard;
1225
1240
  /**
1226
1241
  * The card shadow. Absent = 'soft' — the baked look every
1227
1242
  * doc predating the field renders, so absence lowers byte-identically. 'strong' is the
@@ -1247,6 +1262,27 @@ interface MediaOverlayClip extends OverlayClipBase {
1247
1262
  loop?: boolean;
1248
1263
  }
1249
1264
  type OverlayClip = TextOverlayClip | MediaOverlayClip;
1265
+ /**
1266
+ * A layer's own card. The bar is ALLOWED on any card and opens off; its
1267
+ * address defaults to the media's recorded page when the layer shows a
1268
+ * document media. The lean is the tilt convention, in degrees.
1269
+ */
1270
+ interface LayerCard {
1271
+ browserBar?: Partial<BrowserBarStyle>;
1272
+ lean?: {
1273
+ rx: number;
1274
+ ry: number;
1275
+ };
1276
+ /** The layered shadow's strength; absent = LAYER_CARD_SHADOW. */
1277
+ shadow?: number;
1278
+ shadowContact?: number;
1279
+ shadowColor?: string;
1280
+ /** Draw the media's cursor and clicks inside the card; absent = yes when it has a track. */
1281
+ cursor?: boolean;
1282
+ }
1283
+ declare const LAYER_CARD_SHADOW = 0.32;
1284
+ /** The prefix of a media overlay key that names a document media. */
1285
+ declare const MEDIA_KEY_PREFIX = "media:";
1250
1286
  /**
1251
1287
  * Ceiling on `transform.scale` for text overlays, shared by the canvas box
1252
1288
  * and the panel so the two can never disagree about where growth stops (a
@@ -3280,6 +3316,12 @@ declare function mediaAtOutput(rated: readonly MediaSegment[], t: number): strin
3280
3316
  declare function mediaFrame(doc: Pick<ProjectDoc, 'source' | 'media' | 'frame'>, id: string | undefined | null): FrameStyle;
3281
3317
  /** The card-owned fields of a frame, alone (what a media's card is made of). */
3282
3318
  declare function cardFields(frame: FrameStyle): Pick<FrameStyle, MediaFrameKey>;
3319
+ /** The overlay key that shows a document media (`''` = the primary). */
3320
+ declare const mediaRef: (id: string) => string;
3321
+ /** The media id an overlay key names, `''` for the primary, null for a plain key. */
3322
+ declare function mediaRefId(key: string | undefined | null): string | null;
3323
+ /** The document media a layer's key shows, or null for a plain key or a stranger. */
3324
+ declare function layerMedia(doc: Pick<ProjectDoc, 'source' | 'media'>, key: string | undefined | null): ProjectDoc['source'] | null;
3283
3325
  /** The next free media id (`m1`, `m2`, …). */
3284
3326
  declare function nextMediaId(doc: Pick<ProjectDoc, 'media'>): string;
3285
3327
 
@@ -3863,4 +3905,4 @@ declare function rejectSpan(rejected: RejectedSpan[] | undefined, lane: Rejected
3863
3905
  anchor?: StepAnchor;
3864
3906
  }, note?: string): RejectedSpan;
3865
3907
 
3866
- export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimStep, type AppliedTemplate, type ApplyTemplateOptions, type AspectRatioOption, type AudioClip, type AudioPlanPoint, type AudioPlanTrack, BACKGROUND_Z, BASE_FRAME_STYLE, type Backdrop, type BackgroundMedia, type BakedText3dAsset, type BakedText3dMaterial, type BrowserBarStyle, type BuildDigestInput, CAMERA_FAR, CAMERA_NEAR, CAM_CHAIN_GAP, CAM_EASE, CAM_PAN, CAM_PAN_EASE, CAM_RAMP_IN, CAM_RAMP_OUT, CAM_SIZE_MAX, CAM_SIZE_MIN, CAM_SPAN_MIN, CAPTURE_COVERAGE_MIN, CARD_EDGE_OVERDRAW, CARD_ENTER_KINDS, CARD_EXIT_KINDS, CARD_FADE_SECONDS, CARD_FOV, CARD_Z, CHANNEL_SPECS_HASH, CHANNEL_SPECS_VERIFIED, CLICK_FX_INTENSITY, CLICK_FX_PRE, CLICK_HIGHLIGHT_FADE, CLICK_PAIR_MAX, CLICK_PULSE_DUR, CLICK_RECT_MAX_FRAC, CLICK_RIPPLE_DUR, CLICK_SYNTH_RELEASE, CROP_MAX_PX, CROP_MIN_FRAC, CROP_MIN_PX, CROP_PAD, CURSOR_IDLE_EPS_FRAC, CURSOR_IDLE_FADE_IN, CURSOR_IDLE_FADE_OUT, CURSOR_IDLE_HOLD, type CamBubbleRect, type CamPoseSpan, type CamStyle, type CaptureNormalization, type CardLayout, type ClickFxStyle, type CopiedLayout, type CopyLayoutOptions, type CopyStyleOptions, type CursorEvent, type CursorFadeKey, type CursorIdleOptions, type CursorStyle, type CursorTrack, DARK_GROUND, DEFAULT_BROWSER_BAR, DEFAULT_CAM_POSE, DEFAULT_CAM_STYLE, DEFAULT_CLICK_FX, DEFAULT_CURSOR_STYLE, DEFAULT_DUCK, DEFAULT_FRAME_STYLE, DEFAULT_SPEED_PARAMS, DEFAULT_TILT_POSE, DEFAULT_ZOOM_LEVEL, DEFAULT_ZOOM_STYLE, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type DuckOptions, END_CARD_FROM, END_CARD_RECEDE, END_CARD_SECONDS, ENTRANCE_SECONDS, EXPORT_RESOLUTION_OPTIONS, EXPORT_SHORT_EDGE, type EndCard, type EnvelopePoint, type ExportResolution, type ExtractClickOptions, FOLLOW_RECENTER, FOLLOW_SAFE_RATIO, FRAME_BORDER_COLOR_DEFAULT, FRAME_BORDER_DEFAULT, FRAME_BORDER_WIDTH_DEFAULT, FREEZE_DEFAULT_SECONDS, FREEZE_SECONDS_MAX, FREEZE_SECONDS_MIN, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, type FreezeExtent, type FreezeSpan, HOUSE_GRADIENT, IDLE_KINDS, type IdleKind, type IngestOptions, LAYOUT_FRAME_FIELDS, LOOK_KINDS, type LayoutParts, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, MEDIA_ENTER_KINDS, MEDIA_EXIT_KINDS, MEDIA_FRAME_KEYS, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type Media, type MediaFrame, type MediaFrameKey, type MediaOverlayClip, type MediaSegment, type MicRms, type MinimalBarTheme, type Moment, type MomentKind, type MomentsOptions, type MotionKey, type MotionPose, type MotionPose3D, type MusicBedInput, type NormRect, OBJECT_DEFAULT_SCALE, OVERLAY_FONT_FACES, OVERLAY_LINE_HEIGHT, OVERLAY_MEDIA_DEFAULT_RADIUS, OVERLAY_MEDIA_DEFAULT_WIDTH, OVERLAY_MIN_DURATION, OVERLAY_SCALE_MAX, OVERLAY_SIZE_MAX, OVERLAY_SIZE_MIN, OVERLAY_TRANSITION_DUR, OVERLAY_Z, type ObjectAnimation, type ObjectAsset, type ObjectClip, type ObjectPrimitiveShape, type OverlayClip, type OverlayFontFace, type OverlayKind, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, REST_TILT_ID, type RecordingArtifact, type RecordingMeta, type Rect, type RejectedLane, type RejectedSpan, type ResolvedOverlayBox, type ResolvedOverlayStyle, SCENE_MOTION, SCENE_QUIET, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SmoothOptions, type SmoothPoint, type SpeedParams, type SpeedSpan, type StepAnchor, type StepSpan, type StudioAudioPlan, type StudioDoc, type StudioEntry, type StyleField, TEXT3D_DEPTH_DEFAULT, TEXT3D_DEPTH_MAX, TEXT3D_DEPTH_MIN, TEXT_ENTER_KINDS, TEXT_EXIT_KINDS, TEXT_PRESETS, TILT_AUTO_DEAD_ZONE, TILT_AUTO_MIN, TILT_CHAIN_GAP, TILT_DEG_MAX, TILT_EASE, TILT_INTENSITY_MAX, TILT_PAN, TILT_PAN_EASE, TILT_RAMP_IN, TILT_RAMP_OUT, TILT_SPAN_MIN, TILT_UI_DEG_MAX, TRANSITION_SPEED_MULT, type TemplateAnchor, type Text3dMaterial, type TextFxDirection, type TextFxKind, type TextFxSpec, type TextFxUnit, type TextOverlayBox, type TextOverlayClip, type TextOverlayPreset, type TextOverlayStroke, type TiltPersonality, type TiltSpan, type TiltStyleName, type TranscriptSegment, type TransitionSpeed, WINDOW_FOCUS_MIN, ZOOM_CHAIN_GAP, ZOOM_EASE, ZOOM_LEVELS, ZOOM_LEVEL_MAX, ZOOM_LEVEL_MIN, ZOOM_PAN, ZOOM_PAN_EASE, ZOOM_RAMP_IN, ZOOM_RAMP_IN_OVERLAP, ZOOM_RAMP_OUT, ZOOM_SPAN_MIN, ZOOM_STYLES, ZOOM_STYLE_OPTIONS, type ZoomSpan, type ZoomStyleName, type ZoomStyleParams, type ZoomWindow, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, buildDigest, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cursorIdleFade, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, focusBounds, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgbTriplet, houseLook, idleGaps, idleKey, isLookKind, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, isStageClip, isUnitStep, lastLayerEnd, layoutOf, levelForFocusFraction, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, mediaAtOutput, mediaDuration, mediaFrame, mediaKey, mediaSource, micLane, migrateHostedDoc, migrateMotion, momentsFromDoc, motionTrack, musicBedClip, nextMediaId, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputEnd, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayFxSpec, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parsePoseId, pickStyle, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolveText3dAsset, resolveZoomStyle, restLeanOf, sameMedia, sceneChanges, scrollRuns, setSpeedInRange, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, videoLane, voiceKey, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomWindow };
3908
+ export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimStep, type AppliedTemplate, type ApplyTemplateOptions, type AspectRatioOption, type AudioClip, type AudioPlanPoint, type AudioPlanTrack, BACKGROUND_Z, BASE_FRAME_STYLE, type Backdrop, type BackgroundMedia, type BakedText3dAsset, type BakedText3dMaterial, type BrowserBarStyle, type BuildDigestInput, CAMERA_FAR, CAMERA_NEAR, CAM_CHAIN_GAP, CAM_EASE, CAM_PAN, CAM_PAN_EASE, CAM_RAMP_IN, CAM_RAMP_OUT, CAM_SIZE_MAX, CAM_SIZE_MIN, CAM_SPAN_MIN, CAPTURE_COVERAGE_MIN, CARD_EDGE_OVERDRAW, CARD_ENTER_KINDS, CARD_EXIT_KINDS, CARD_FADE_SECONDS, CARD_FOV, CARD_Z, CHANNEL_SPECS_HASH, CHANNEL_SPECS_VERIFIED, CLICK_FX_INTENSITY, CLICK_FX_PRE, CLICK_HIGHLIGHT_FADE, CLICK_PAIR_MAX, CLICK_PULSE_DUR, CLICK_RECT_MAX_FRAC, CLICK_RIPPLE_DUR, CLICK_SYNTH_RELEASE, CROP_MAX_PX, CROP_MIN_FRAC, CROP_MIN_PX, CROP_PAD, CURSOR_IDLE_EPS_FRAC, CURSOR_IDLE_FADE_IN, CURSOR_IDLE_FADE_OUT, CURSOR_IDLE_HOLD, type CamBubbleRect, type CamPoseSpan, type CamStyle, type CaptureNormalization, type CardLayout, type ClickFxStyle, type CopiedLayout, type CopyLayoutOptions, type CopyStyleOptions, type CursorEvent, type CursorFadeKey, type CursorIdleOptions, type CursorStyle, type CursorTrack, DARK_GROUND, DEFAULT_BROWSER_BAR, DEFAULT_CAM_POSE, DEFAULT_CAM_STYLE, DEFAULT_CLICK_FX, DEFAULT_CURSOR_STYLE, DEFAULT_DUCK, DEFAULT_FRAME_STYLE, DEFAULT_SPEED_PARAMS, DEFAULT_TILT_POSE, DEFAULT_ZOOM_LEVEL, DEFAULT_ZOOM_STYLE, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type DuckOptions, END_CARD_FROM, END_CARD_RECEDE, END_CARD_SECONDS, ENTRANCE_SECONDS, EXPORT_RESOLUTION_OPTIONS, EXPORT_SHORT_EDGE, type EndCard, type EnvelopePoint, type ExportResolution, type ExtractClickOptions, FOLLOW_RECENTER, FOLLOW_SAFE_RATIO, FRAME_BORDER_COLOR_DEFAULT, FRAME_BORDER_DEFAULT, FRAME_BORDER_WIDTH_DEFAULT, FREEZE_DEFAULT_SECONDS, FREEZE_SECONDS_MAX, FREEZE_SECONDS_MIN, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, type FreezeExtent, type FreezeSpan, HOUSE_GRADIENT, IDLE_KINDS, type IdleKind, type IngestOptions, LAYER_CARD_SHADOW, LAYOUT_FRAME_FIELDS, LOOK_KINDS, type LayerCard, type LayoutParts, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, MEDIA_ENTER_KINDS, MEDIA_EXIT_KINDS, MEDIA_FRAME_KEYS, MEDIA_KEY_PREFIX, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type Media, type MediaFrame, type MediaFrameKey, type MediaOverlayClip, type MediaSegment, type MicRms, type MinimalBarTheme, type Moment, type MomentKind, type MomentsOptions, type MotionKey, type MotionPose, type MotionPose3D, type MusicBedInput, type NormRect, OBJECT_DEFAULT_SCALE, OVERLAY_FONT_FACES, OVERLAY_LINE_HEIGHT, OVERLAY_MEDIA_DEFAULT_RADIUS, OVERLAY_MEDIA_DEFAULT_WIDTH, OVERLAY_MIN_DURATION, OVERLAY_SCALE_MAX, OVERLAY_SIZE_MAX, OVERLAY_SIZE_MIN, OVERLAY_TRANSITION_DUR, OVERLAY_Z, type ObjectAnimation, type ObjectAsset, type ObjectClip, type ObjectPrimitiveShape, type OverlayClip, type OverlayFontFace, type OverlayKind, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, REST_TILT_ID, type RecordingArtifact, type RecordingMeta, type Rect, type RejectedLane, type RejectedSpan, type ResolvedOverlayBox, type ResolvedOverlayStyle, SCENE_MOTION, SCENE_QUIET, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SmoothOptions, type SmoothPoint, type SpeedParams, type SpeedSpan, type StepAnchor, type StepSpan, type StudioAudioPlan, type StudioDoc, type StudioEntry, type StyleField, TEXT3D_DEPTH_DEFAULT, TEXT3D_DEPTH_MAX, TEXT3D_DEPTH_MIN, TEXT_ENTER_KINDS, TEXT_EXIT_KINDS, TEXT_PRESETS, TILT_AUTO_DEAD_ZONE, TILT_AUTO_MIN, TILT_CHAIN_GAP, TILT_DEG_MAX, TILT_EASE, TILT_INTENSITY_MAX, TILT_PAN, TILT_PAN_EASE, TILT_RAMP_IN, TILT_RAMP_OUT, TILT_SPAN_MIN, TILT_UI_DEG_MAX, TRANSITION_SPEED_MULT, type TemplateAnchor, type Text3dMaterial, type TextFxDirection, type TextFxKind, type TextFxSpec, type TextFxUnit, type TextOverlayBox, type TextOverlayClip, type TextOverlayPreset, type TextOverlayStroke, type TiltPersonality, type TiltSpan, type TiltStyleName, type TranscriptSegment, type TransitionSpeed, WINDOW_FOCUS_MIN, ZOOM_CHAIN_GAP, ZOOM_EASE, ZOOM_LEVELS, ZOOM_LEVEL_MAX, ZOOM_LEVEL_MIN, ZOOM_PAN, ZOOM_PAN_EASE, ZOOM_RAMP_IN, ZOOM_RAMP_IN_OVERLAP, ZOOM_RAMP_OUT, ZOOM_SPAN_MIN, ZOOM_STYLES, ZOOM_STYLE_OPTIONS, type ZoomSpan, type ZoomStyleName, type ZoomStyleParams, type ZoomWindow, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, buildDigest, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cursorIdleFade, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, focusBounds, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgbTriplet, houseLook, idleGaps, idleKey, isLookKind, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, isStageClip, isUnitStep, lastLayerEnd, layerMedia, layoutOf, levelForFocusFraction, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, mediaAtOutput, mediaDuration, mediaFrame, mediaKey, mediaRef, mediaRefId, mediaSource, micLane, migrateHostedDoc, migrateMotion, momentsFromDoc, motionTrack, musicBedClip, nextMediaId, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputEnd, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayFxSpec, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parsePoseId, pickStyle, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolveText3dAsset, resolveZoomStyle, restLeanOf, sameMedia, sceneChanges, scrollRuns, setSpeedInRange, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, videoLane, voiceKey, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomWindow };
package/dist/index.js CHANGED
@@ -98,6 +98,8 @@ function clipLength(clip) {
98
98
  return clip.loop ? Math.max(span, clip.loopLen ?? span) : span;
99
99
  }
100
100
  var MOTION_EASE = "power2.inOut";
101
+ var LAYER_CARD_SHADOW = 0.32;
102
+ var MEDIA_KEY_PREFIX = "media:";
101
103
  var OVERLAY_SCALE_MAX = 8;
102
104
  var OVERLAY_MEDIA_DEFAULT_WIDTH = 0.35;
103
105
  var OVERLAY_MEDIA_DEFAULT_RADIUS = 12;
@@ -582,6 +584,15 @@ function cardFields(frame) {
582
584
  }
583
585
  return out;
584
586
  }
587
+ var mediaRef = (id) => MEDIA_KEY_PREFIX + id;
588
+ function mediaRefId(key) {
589
+ if (typeof key !== "string" || !key.startsWith(MEDIA_KEY_PREFIX)) return null;
590
+ return key.slice(MEDIA_KEY_PREFIX.length);
591
+ }
592
+ function layerMedia(doc, key) {
593
+ const id = mediaRefId(key);
594
+ return id === null ? null : mediaSource(doc, id);
595
+ }
585
596
  function nextMediaId(doc) {
586
597
  const taken = new Set((doc.media ?? []).map((m) => m.id));
587
598
  let n = 1;
@@ -2698,6 +2709,8 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
2698
2709
  // over its whole life, so it must repaint every visible frame \u2014 the
2699
2710
  // signature alone would freeze it between edits (the olVisSig trap).
2700
2711
  if (ol0.track) olAnim = true
2712
+ // A layer CARD is painted on its own plane every frame it is visible.
2713
+ if (ol0.card) olAnim = true
2701
2714
  // Video overlays advance every frame; images redraw until decoded.
2702
2715
  if (ol0.kind === 'video') olAnim = true
2703
2716
  else if (ol0.kind === 'image') {
@@ -2707,6 +2720,76 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
2707
2720
  }
2708
2721
  var ovSig = W + 'x' + H + '|' + olVisSig
2709
2722
  var ovDirty = ov.sig !== ovSig || ov.active || olAnim
2723
+ // The layer cards visible this frame, with their pose, for the card
2724
+ // planes below (the overlay loop collects them instead of painting).
2725
+ var lcList = []
2726
+ // Acquire a media overlay's element through the shared cache and sync it
2727
+ // to CLIP-LOCAL time (pure f(t); muted always \u2014 soundtracks belong to
2728
+ // doc.audio). Null until it has a frame to draw. One seam for the flat
2729
+ // picture and the layer card.
2730
+ function olAcquire(ol, olT) {
2731
+ var olEl = ns.videoCache ? ns.videoCache.get(ol.key) : null
2732
+ if (!olEl && ns.videoCache) {
2733
+ if (ol.kind === 'image') {
2734
+ olEl = new Image()
2735
+ olEl.crossOrigin = 'anonymous'
2736
+ olEl.src = ol.key
2737
+ } else {
2738
+ olEl = document.createElement('video')
2739
+ olEl.crossOrigin = 'anonymous'
2740
+ olEl.muted = true
2741
+ olEl.playsInline = true
2742
+ olEl.preload = 'auto'
2743
+ olEl.src = ol.key
2744
+ olEl.load()
2745
+ }
2746
+ ns.videoCache.set(ol.key, olEl)
2747
+ }
2748
+ if (!olEl) return null
2749
+ var olIsImg = ol.kind === 'image'
2750
+ if (!olIsImg && olEl.play) {
2751
+ var olDur = olEl.duration || 0
2752
+ var olMT = olT
2753
+ if (ol.loop && olDur > 0) olMT = olT % olDur
2754
+ else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)
2755
+ try {
2756
+ if (playing) {
2757
+ if (olEl.playbackRate !== 1) olEl.playbackRate = 1
2758
+ var olDrift = Math.abs(olEl.currentTime - olMT)
2759
+ if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT
2760
+ if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }
2761
+ else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }
2762
+ } else {
2763
+ if (!olEl.paused) olEl.pause()
2764
+ var olTarget = Math.min(olMT, olEl.duration || olMT)
2765
+ // Coalesce scrub seeks (the backgroundMedia pattern): re-assigning
2766
+ // currentTime aborts the in-flight seek, so a per-frame scrub keeps
2767
+ // a remote source seeking forever. Defer until 'seeked' lands.
2768
+ if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {
2769
+ if (ns.pendingDecodes) {
2770
+ var olDp = new Promise(function (resolve) {
2771
+ var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }
2772
+ olEl.addEventListener('seeked', olDone)
2773
+ setTimeout(olDone, 250)
2774
+ })
2775
+ ns.pendingDecodes.add(olDp)
2776
+ olDp.finally(function () { ns.pendingDecodes.delete(olDp) })
2777
+ }
2778
+ olEl.currentTime = olTarget
2779
+ }
2780
+ }
2781
+ } catch (e) {}
2782
+ }
2783
+ // Video readiness is STICKY through seeks (the cam-bubble pattern):
2784
+ // readyState dips below HAVE_CURRENT_DATA while a scrub seek is in
2785
+ // flight, and this layer repaints every frame a video clip is visible \u2014
2786
+ // gating each frame on it would blink the clip out for the whole drag.
2787
+ // After the first decoded frame, keep drawing: Chrome paints the
2788
+ // element's retained frame mid-seek.
2789
+ if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true
2790
+ var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)
2791
+ return olReady ? olEl : null
2792
+ }
2710
2793
  if (ovDirty) {
2711
2794
  ovC.clearRect(0, 0, W, H)
2712
2795
  // --- text overlays (compositor v2): OUTPUT-anchored clips, styles resolved
@@ -2764,71 +2847,19 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
2764
2847
  if (olPose) olA *= Math.max(0, Math.min(1, olPose[4]))
2765
2848
  if (olA <= 0.004) continue
2766
2849
  if (ol.kind === 'image' || ol.kind === 'video') {
2850
+ // A layer CARD is painted by the card painter on its own plane, below:
2851
+ // collect it with its pose and skip the flat picture.
2852
+ if (ol.card) {
2853
+ lcList.push({ ol: ol, t: olT, a: olA * (ol.opacity == null ? 1 : ol.opacity), x: olMX, y: olMY, s: olMS, r: olMR, yof: olYof })
2854
+ continue
2855
+ }
2767
2856
  // Media overlay: lazy-acquire through the shared cache (SET_DATA-added
2768
2857
  // clips load without a LOAD \u2014 the backgroundMedia pattern), sync video
2769
2858
  // to CLIP-LOCAL time (pure f(t)), draw a rounded media card centered on
2770
2859
  // the fraction anchor. Muted always \u2014 soundtracks belong to doc.audio.
2771
- var olEl = ns.videoCache ? ns.videoCache.get(ol.key) : null
2772
- if (!olEl && ns.videoCache) {
2773
- if (ol.kind === 'image') {
2774
- olEl = new Image()
2775
- olEl.crossOrigin = 'anonymous'
2776
- olEl.src = ol.key
2777
- } else {
2778
- olEl = document.createElement('video')
2779
- olEl.crossOrigin = 'anonymous'
2780
- olEl.muted = true
2781
- olEl.playsInline = true
2782
- olEl.preload = 'auto'
2783
- olEl.src = ol.key
2784
- olEl.load()
2785
- }
2786
- ns.videoCache.set(ol.key, olEl)
2787
- }
2860
+ var olEl = olAcquire(ol, olT)
2788
2861
  if (!olEl) continue
2789
2862
  var olIsImg = ol.kind === 'image'
2790
- if (!olIsImg && olEl.play) {
2791
- var olDur = olEl.duration || 0
2792
- var olMT = olT
2793
- if (ol.loop && olDur > 0) olMT = olT % olDur
2794
- else if (olDur > 0) olMT = Math.min(olT, olDur - 0.001)
2795
- try {
2796
- if (playing) {
2797
- if (olEl.playbackRate !== 1) olEl.playbackRate = 1
2798
- var olDrift = Math.abs(olEl.currentTime - olMT)
2799
- if (olDur > 0 && !olEl.seeking && olDrift > 0.3 && (!ol.loop || olDur - olDrift > 0.3)) olEl.currentTime = olMT
2800
- if (!ol.loop && olDur > 0 && olT >= olDur) { if (!olEl.paused) olEl.pause() }
2801
- else if (olEl.paused) { var olP = olEl.play(); if (olP && olP.catch) olP.catch(function () {}) }
2802
- } else {
2803
- if (!olEl.paused) olEl.pause()
2804
- var olTarget = Math.min(olMT, olEl.duration || olMT)
2805
- // Coalesce scrub seeks (the backgroundMedia pattern): re-assigning
2806
- // currentTime aborts the in-flight seek, so a per-frame scrub keeps
2807
- // a remote source seeking forever. Defer until 'seeked' lands.
2808
- if (olEl.readyState >= 1 && !olEl.seeking && Math.abs(olEl.currentTime - olTarget) > 0.02) {
2809
- if (ns.pendingDecodes) {
2810
- var olDp = new Promise(function (resolve) {
2811
- var olDone = function () { olEl.removeEventListener('seeked', olDone); resolve() }
2812
- olEl.addEventListener('seeked', olDone)
2813
- setTimeout(olDone, 250)
2814
- })
2815
- ns.pendingDecodes.add(olDp)
2816
- olDp.finally(function () { ns.pendingDecodes.delete(olDp) })
2817
- }
2818
- olEl.currentTime = olTarget
2819
- }
2820
- }
2821
- } catch (e) {}
2822
- }
2823
- // Video readiness is STICKY through seeks (the cam-bubble pattern):
2824
- // readyState dips below HAVE_CURRENT_DATA while a scrub seek is in
2825
- // flight, and this layer repaints every frame a video clip is visible \u2014
2826
- // gating each frame on it would blink the clip out for the whole drag.
2827
- // After the first decoded frame, keep drawing: Chrome paints the
2828
- // element's retained frame mid-seek.
2829
- if (!olIsImg && olEl.readyState >= 2) olEl.__vosHasFrame = true
2830
- var olReady = olIsImg ? !!(olEl.complete && olEl.naturalWidth) : !!(olEl.readyState >= 2 || olEl.__vosHasFrame)
2831
- if (!olReady) continue
2832
2863
  var olNW = (olIsImg ? olEl.naturalWidth : olEl.videoWidth) || 16
2833
2864
  var olNH = (olIsImg ? olEl.naturalHeight : olEl.videoHeight) || 9
2834
2865
  var olDW = ol.w * W * olMS
@@ -3340,6 +3371,194 @@ var STUDIO_FRAME = `(ctx, content, dt) => {
3340
3371
  }
3341
3372
  obM.scale.setScalar(obPS * obRefH * (obE.norm || 1))
3342
3373
  }
3374
+ // --- layer cards (many cards): a media overlay that names a frame is
3375
+ // drawn by the card painter on its OWN plane in the prop group
3376
+ // (renderOrder 1.25: above the primary card, below the props): the
3377
+ // layered shadow (the primary's table and strengths), the media clipped
3378
+ // to its corners, the browser bar after it, the border, and for a
3379
+ // document media its click rings and cursor dot inside the card, at
3380
+ // clip-local time. Chrome scales WITH the card (a smaller window has
3381
+ // smaller chrome), the way the primary's scales with cf. The plane is
3382
+ // sized to the canvas at the card depth, placed at the clip's anchor,
3383
+ // leaned by the card's own rx/ry (the tilt convention) and turned by the
3384
+ // clip's rotation. Locals lc*.
3385
+ var lcC = obC.cards || (obC.cards = new Map())
3386
+ var lcSeen = {}
3387
+ var lcK = obRefH / H
3388
+ for (var lci = 0; lci < lcList.length; lci++) {
3389
+ var lcI = lcList[lci], lcO = lcI.ol, lcCard = lcO.card
3390
+ var lcEl = olAcquire(lcO, lcI.t)
3391
+ if (!lcEl) continue
3392
+ lcSeen[lcO.id] = true
3393
+ var lcE = lcC.get(lcO.id)
3394
+ if (!lcE) {
3395
+ var lcCv = document.createElement('canvas')
3396
+ var lcTx = new THREE3.CanvasTexture(lcCv)
3397
+ if (THREE3.SRGBColorSpace) lcTx.colorSpace = THREE3.SRGBColorSpace
3398
+ if (THREE3.LinearFilter) { lcTx.minFilter = THREE3.LinearFilter; lcTx.magFilter = THREE3.LinearFilter }
3399
+ lcTx.generateMipmaps = false
3400
+ var lcM = new THREE3.Mesh(new THREE3.PlaneGeometry(1, 1), new THREE3.MeshBasicMaterial({ map: lcTx, transparent: true, depthTest: false, depthWrite: false }))
3401
+ lcM.renderOrder = 1.25
3402
+ lcM.frustumCulled = false
3403
+ obC.group.add(lcM)
3404
+ lcE = { mesh: lcM, canvas: lcCv, c2d: lcCv.getContext('2d'), texture: lcTx }
3405
+ lcC.set(lcO.id, lcE)
3406
+ }
3407
+ var lcIsImg = lcO.kind === 'image'
3408
+ var lcCrop = lcO.crop || null
3409
+ var lcNW = lcCrop ? lcCrop.w : ((lcIsImg ? lcEl.naturalWidth : lcEl.videoWidth) || 16)
3410
+ var lcNH = lcCrop ? lcCrop.h : ((lcIsImg ? lcEl.naturalHeight : lcEl.videoHeight) || 9)
3411
+ var lcW = Math.max(2, lcO.w * W * lcI.s)
3412
+ var lcS = s * (lcW / W)
3413
+ var lcBar = lcCard.bar || {}
3414
+ var lcBarH = lcBar.kind && lcBar.kind !== 'none' ? (lcBar.height || 44) * lcS : 0
3415
+ var lcMH = lcW * (lcNH / lcNW)
3416
+ var lcH = lcMH + lcBarH
3417
+ var lcRad = Math.min((lcO.radius || 0) * lcS, lcH / 2)
3418
+ var lcMg = Math.ceil(170 * lcS)
3419
+ var lcCW = Math.ceil(lcW + 2 * lcMg), lcCH = Math.ceil(lcH + 2 * lcMg)
3420
+ var lcCv2 = lcE.canvas, lcX = lcE.c2d
3421
+ if (lcCv2.width !== lcCW || lcCv2.height !== lcCH) { lcCv2.width = lcCW; lcCv2.height = lcCH }
3422
+ lcX.clearRect(0, 0, lcCW, lcCH)
3423
+ var lcX0 = lcMg, lcY0 = lcMg
3424
+ var lcSh = lcCard.shadow == null ? ${LAYER_CARD_SHADOW} : lcCard.shadow
3425
+ var lcRgb = '0,0,0'
3426
+ var lcHex = lcCard.shadowColor
3427
+ if (typeof lcHex === 'string' && /^#[0-9a-fA-F]{6}$/.test(lcHex)) lcRgb = parseInt(lcHex.slice(1, 3), 16) + ',' + parseInt(lcHex.slice(3, 5), 16) + ',' + parseInt(lcHex.slice(5, 7), 16)
3428
+ // the shadows are cast by a body drawn off the canvas and brought back
3429
+ // by shadowOffsetX (the primary's seam rule)
3430
+ var lcD = lcCW * 2 + lcCH * 2
3431
+ if (lcSh > 0) {
3432
+ var lcL = [[2, 1, 0.1], [12, 4, 0.1], [40, 16, 0.14], [120, 44, 0.3]]
3433
+ for (var lcj = 0; lcj < lcL.length; lcj++) {
3434
+ lcX.save()
3435
+ lcX.shadowColor = 'rgba(' + lcRgb + ',' + +(lcSh * lcL[lcj][2]).toFixed(3) + ')'
3436
+ lcX.shadowBlur = lcL[lcj][0] * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = lcL[lcj][1] * lcS
3437
+ lcX.fillStyle = '#000'
3438
+ rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()
3439
+ lcX.restore()
3440
+ }
3441
+ }
3442
+ if (lcCard.shadowContact > 0) {
3443
+ lcX.save()
3444
+ lcX.shadowColor = 'rgba(' + lcRgb + ',' + lcCard.shadowContact + ')'
3445
+ lcX.shadowBlur = 10 * lcS; lcX.shadowOffsetX = lcD; lcX.shadowOffsetY = 3 * lcS
3446
+ lcX.fillStyle = '#000'
3447
+ rr(lcX0 - lcD, lcY0, lcW, lcH, lcRad, lcX); lcX.fill()
3448
+ lcX.restore()
3449
+ }
3450
+ lcX.save()
3451
+ rr(lcX0, lcY0, lcW, lcH, lcRad, lcX); lcX.clip()
3452
+ var lcMY = lcY0 + lcBarH
3453
+ try {
3454
+ if (lcCrop) lcX.drawImage(lcEl, lcCrop.x, lcCrop.y, lcCrop.w, lcCrop.h, lcX0, lcMY, lcW, lcMH)
3455
+ else lcX.drawImage(lcEl, lcX0, lcMY, lcW, lcMH)
3456
+ } catch (e) {}
3457
+ if (lcBarH > 0) {
3458
+ var lcDark = lcBar.kind.indexOf('dark') >= 0, lcMin = lcBar.kind === 'minimal'
3459
+ var lcThm = (lcMin && lcBar.theme) || null
3460
+ lcX.fillStyle = lcMin ? (lcThm ? lcThm.bar : '#141417') : lcDark ? '#2a2a2e' : '#e9e9eb'
3461
+ lcX.fillRect(lcX0, lcY0, lcW, lcBarH)
3462
+ lcX.fillStyle = lcDark || (lcMin && !(lcThm && lcThm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'
3463
+ lcX.fillRect(lcX0, lcY0 + lcBarH - lcS, lcW, lcS)
3464
+ var lcMid = lcY0 + lcBarH / 2
3465
+ if (lcBar.showControls !== false && !lcMin) {
3466
+ if (lcBar.kind.indexOf('mac') === 0) {
3467
+ var lcLights = ['#ff5f57', '#febc2e', '#28c840']
3468
+ for (var lcl = 0; lcl < 3; lcl++) { lcX.fillStyle = lcLights[lcl]; lcX.beginPath(); lcX.arc(lcX0 + (20 + lcl * 20) * lcS, lcMid, 6 * lcS, 0, Math.PI * 2); lcX.fill() }
3469
+ } else {
3470
+ lcX.strokeStyle = lcDark ? 'rgba(255,255,255,0.75)' : 'rgba(0,0,0,0.6)'
3471
+ lcX.lineWidth = 1.5 * lcS
3472
+ var lcG = 4.5 * lcS, lcGx = lcX0 + lcW - 22 * lcS
3473
+ lcX.beginPath(); lcX.moveTo(lcGx - lcG, lcMid - lcG); lcX.lineTo(lcGx + lcG, lcMid + lcG); lcX.moveTo(lcGx + lcG, lcMid - lcG); lcX.lineTo(lcGx - lcG, lcMid + lcG); lcX.stroke()
3474
+ lcX.strokeRect(lcGx - 28 * lcS - lcG, lcMid - lcG, lcG * 2, lcG * 2)
3475
+ lcX.beginPath(); lcX.moveTo(lcGx - 56 * lcS - lcG, lcMid); lcX.lineTo(lcGx - 56 * lcS + lcG, lcMid); lcX.stroke()
3476
+ }
3477
+ }
3478
+ if (lcBar.showUrl !== false && lcBar.url) {
3479
+ var lcPW = Math.min(lcW * 0.5, Math.max(200 * lcS, lcW * 0.34)), lcPH = lcBarH - 16 * lcS
3480
+ var lcPx = lcX0 + (lcW - lcPW) / 2, lcPy = lcY0 + 8 * lcS
3481
+ lcX.fillStyle = lcMin ? (lcThm ? lcThm.pill : '#26262b') : lcDark ? '#1d1d20' : '#ffffff'
3482
+ rr(lcPx, lcPy, lcPW, lcPH, lcPH / 2, lcX); lcX.fill()
3483
+ lcX.fillStyle = lcMin ? (lcThm ? lcThm.text : '#9a9aa1') : lcDark ? '#a1a1a6' : '#5f5f64'
3484
+ lcX.font = 13 * lcS + 'px -apple-system, system-ui, sans-serif'
3485
+ lcX.textAlign = 'center'; lcX.textBaseline = 'middle'
3486
+ var lcLabel = String(lcBar.url), lcMaxW = lcPW - 28 * lcS
3487
+ if (lcX.measureText(lcLabel).width > lcMaxW) {
3488
+ while (lcLabel.length > 1 && lcX.measureText(lcLabel + '\u2026').width > lcMaxW) lcLabel = lcLabel.slice(0, -1)
3489
+ lcLabel += '\u2026'
3490
+ }
3491
+ lcX.fillText(lcLabel, lcPx + lcPW / 2, lcPy + lcPH / 2)
3492
+ lcX.textAlign = 'start'; lcX.textBaseline = 'alphabetic'
3493
+ }
3494
+ }
3495
+ // the media's facts: click rings and the cursor dot, video-anchored,
3496
+ // at clip-local time
3497
+ var lcCu = lcO.cur
3498
+ if (lcCu && lcCu.pts && lcCu.pts.length) {
3499
+ var lcSp = lcCu.space || { w: lcNW, h: lcNH }
3500
+ var lcSize = (lcCu.size || 24) * lcS * 0.5
3501
+ var lcT = lcI.t
3502
+ var lcCks = lcCu.clicks || []
3503
+ for (var lcc = 0; lcc < lcCks.length; lcc++) {
3504
+ var lcCk = lcCks[lcc]
3505
+ var lcAge = lcT - lcCk.t
3506
+ if (lcAge < -0.08 || lcAge > 0.6) continue
3507
+ var lcU = Math.max(0, Math.min(1, (lcAge + 0.08) / 0.68))
3508
+ var lcFade = 1 - Math.pow(1 - lcU, 3)
3509
+ lcX.save()
3510
+ lcX.strokeStyle = 'rgba(255,255,255,' + (0.9 * (1 - lcU)) + ')'
3511
+ lcX.lineWidth = Math.max(1, 3 * lcS * (1 - lcU * 0.6))
3512
+ lcX.beginPath(); lcX.arc(lcX0 + (lcCk.x / (lcSp.w || lcNW)) * lcW, lcMY + (lcCk.y / (lcSp.h || lcNH)) * lcMH, lcSize * 0.8 + lcFade * 44 * lcS, 0, Math.PI * 2); lcX.stroke()
3513
+ lcX.restore()
3514
+ }
3515
+ var lcPts = lcCu.pts, lcPX = lcPts[0].x, lcPY = lcPts[0].y
3516
+ for (var lcp = 0; lcp < lcPts.length; lcp++) { if (lcPts[lcp].t <= lcT) { lcPX = lcPts[lcp].x; lcPY = lcPts[lcp].y } }
3517
+ lcX.save()
3518
+ lcX.fillStyle = 'rgba(255,255,255,0.95)'; lcX.strokeStyle = 'rgba(0,0,0,0.4)'; lcX.lineWidth = 2 * lcS
3519
+ lcX.beginPath(); lcX.arc(lcX0 + (lcPX / (lcSp.w || lcNW)) * lcW, lcMY + (lcPY / (lcSp.h || lcNH)) * lcMH, lcSize, 0, Math.PI * 2); lcX.fill(); lcX.stroke()
3520
+ lcX.restore()
3521
+ }
3522
+ lcX.restore()
3523
+ if (lcO.border && lcO.border.width > 0) {
3524
+ var lcBW = lcO.border.width * lcS
3525
+ lcX.save(); lcX.strokeStyle = lcO.border.color || '#fff'; lcX.lineWidth = lcBW
3526
+ rr(lcX0 - lcBW / 2, lcY0 - lcBW / 2, lcW + lcBW, lcH + lcBW, lcRad > 0 ? lcRad + lcBW / 2 : 0, lcX); lcX.stroke()
3527
+ lcX.restore()
3528
+ }
3529
+ lcE.texture.needsUpdate = true
3530
+ var lcM2 = lcE.mesh
3531
+ lcM2.visible = true
3532
+ if (lcM2.material) lcM2.material.opacity = lcI.a
3533
+ var lcLean = lcCard.lean || [0, 0]
3534
+ var lcRx = (lcLean[0] || 0) * Math.PI / 180, lcRy = (lcLean[1] || 0) * Math.PI / 180, lcRz = -(lcI.r || 0) * Math.PI / 180
3535
+ var lcPlW = lcCW * lcK, lcPlH = lcCH * lcK
3536
+ var lcWX = (lcI.x - 0.5) * obRefH * obAspect
3537
+ var lcWY = -(lcI.y - 0.5) * obRefH - lcI.yof * lcK
3538
+ if (obOrtho) {
3539
+ lcM2.position.set((obOCX + (lcI.x - 0.5) * obOW) / obAX, obOCY - (lcI.y - 0.5) * obOH, -obOD)
3540
+ obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.setFromEuler(obB.e)
3541
+ } else if (obB) {
3542
+ lcM2.position.copy(obCam.position).addScaledVector(obB.f, Math.abs(${CARD_Z}) - 0.01).addScaledVector(obB.r, lcWX).addScaledVector(obB.u, lcWY)
3543
+ obB.e.set(lcRx, lcRy, lcRz); lcM2.quaternion.copy(obCam.quaternion).multiply(obB.q.setFromEuler(obB.e))
3544
+ } else {
3545
+ lcM2.position.set(lcWX, lcWY, ${CARD_Z} + 0.01)
3546
+ lcM2.rotation.set(lcRx, lcRy, lcRz)
3547
+ }
3548
+ lcM2.scale.set(lcPlW, lcPlH, 1)
3549
+ }
3550
+ // A card off screen rests; one gone from the data is disposed.
3551
+ lcC.forEach(function (lcE2, lcId) {
3552
+ if (lcSeen[lcId]) return
3553
+ var lcStill = false
3554
+ for (var lcq = 0; lcq < ols.length; lcq++) if (ols[lcq].id === lcId && ols[lcq].card) lcStill = true
3555
+ if (lcStill) { lcE2.mesh.visible = false; return }
3556
+ obC.group.remove(lcE2.mesh)
3557
+ if (lcE2.mesh.geometry && lcE2.mesh.geometry.dispose) lcE2.mesh.geometry.dispose()
3558
+ if (lcE2.mesh.material && lcE2.mesh.material.dispose) lcE2.mesh.material.dispose()
3559
+ if (lcE2.texture && lcE2.texture.dispose) lcE2.texture.dispose()
3560
+ lcC.delete(lcId)
3561
+ })
3343
3562
  // Dispose props no longer in the data (live removal).
3344
3563
  obC.pool.forEach(function (obE2, obId) {
3345
3564
  if (!obSeen[obId]) {
@@ -5118,6 +5337,7 @@ var ON_FRAME = `(ctx, content, dt) => {
5118
5337
  }
5119
5338
  }`;
5120
5339
  function studioLayerData(layers, duration) {
5340
+ const rec = "source" in layers ? layers : null;
5121
5341
  return {
5122
5342
  // Music/SFX clips with their gain envelopes baked (shared truth for the
5123
5343
  // preview scheduler and the export's offline mix). `duckEnv` (the mic-derived
@@ -5205,9 +5425,60 @@ function studioLayerData(layers, duration) {
5205
5425
  })()
5206
5426
  };
5207
5427
  if (o.kind !== "text") {
5428
+ const lm = rec ? layerMedia(rec, o.key) : null;
5429
+ const lmImage = lm ? lm.sourceKind === "image" : o.kind === "image";
5430
+ const lmCard = o.frame ? {
5431
+ bar: {
5432
+ kind: o.frame.browserBar?.kind ?? "none",
5433
+ url: o.frame.browserBar?.url ?? (lm ? pageDisplayUrl(lm.meta.pageUrl) : ""),
5434
+ showUrl: o.frame.browserBar?.showUrl ?? true,
5435
+ showControls: o.frame.browserBar?.showControls ?? true,
5436
+ height: o.frame.browserBar?.height ?? 44,
5437
+ ...o.frame.browserBar?.theme ? { theme: o.frame.browserBar.theme } : {}
5438
+ },
5439
+ lean: [
5440
+ round6(o.frame.lean?.rx ?? 0),
5441
+ round6(o.frame.lean?.ry ?? 0)
5442
+ ],
5443
+ shadow: o.frame.shadow ?? LAYER_CARD_SHADOW,
5444
+ shadowContact: o.frame.shadowContact ?? 0,
5445
+ ...o.frame.shadowColor ? { shadowColor: o.frame.shadowColor } : {}
5446
+ } : null;
5447
+ const lmFacts = lm && o.frame && o.frame.cursor !== false && lm.cursor.length ? (() => {
5448
+ const lfx = rec.cursor.clickFx;
5449
+ const sm = smoothCursor(lm.cursor, {
5450
+ factor: rec.cursor.smoothing,
5451
+ clickSnap: lfx.style !== "none" || lfx.press
5452
+ });
5453
+ const clicks = [];
5454
+ for (const ev of lm.cursor) {
5455
+ if (ev.type === "down")
5456
+ clicks.push({
5457
+ t: round6(ev.t / 1e3),
5458
+ x: round6(ev.x),
5459
+ y: round6(ev.y)
5460
+ });
5461
+ }
5462
+ return {
5463
+ cur: {
5464
+ pts: sm.map((p) => ({
5465
+ t: round6(p.t),
5466
+ x: round6(p.x),
5467
+ y: round6(p.y)
5468
+ })),
5469
+ space: { w: lm.meta.width, h: lm.meta.height },
5470
+ size: rec.cursor.size,
5471
+ clicks
5472
+ }
5473
+ };
5474
+ })() : {};
5208
5475
  return {
5209
5476
  ...base,
5210
- key: o.key,
5477
+ ...lm ? { kind: lmImage ? "image" : "video" } : {},
5478
+ key: lm ? lm.videoKey : o.key,
5479
+ ...lm && lm.crop ? { crop: lm.crop } : {},
5480
+ ...lmCard ? { card: lmCard } : {},
5481
+ ...lmFacts,
5211
5482
  w: round6(o.width ?? OVERLAY_MEDIA_DEFAULT_WIDTH),
5212
5483
  radius: o.radius ?? OVERLAY_MEDIA_DEFAULT_RADIUS,
5213
5484
  opacity: o.opacity ?? 1,
@@ -8522,11 +8793,13 @@ export {
8522
8793
  FREEZE_SECONDS_MIN,
8523
8794
  HOUSE_GRADIENT,
8524
8795
  IDLE_KINDS,
8796
+ LAYER_CARD_SHADOW,
8525
8797
  LAYOUT_FRAME_FIELDS,
8526
8798
  LOOK_KINDS,
8527
8799
  MEDIA_ENTER_KINDS,
8528
8800
  MEDIA_EXIT_KINDS,
8529
8801
  MEDIA_FRAME_KEYS,
8802
+ MEDIA_KEY_PREFIX,
8530
8803
  MINIMAL_BAR_THEMES,
8531
8804
  MOTION_DELTA,
8532
8805
  MOTION_EASE,
@@ -8673,6 +8946,7 @@ export {
8673
8946
  isStageClip,
8674
8947
  isUnitStep,
8675
8948
  lastLayerEnd,
8949
+ layerMedia,
8676
8950
  layoutOf,
8677
8951
  levelForFocusFraction,
8678
8952
  lookFromBrand,
@@ -8684,6 +8958,8 @@ export {
8684
8958
  mediaDuration,
8685
8959
  mediaFrame,
8686
8960
  mediaKey,
8961
+ mediaRef,
8962
+ mediaRefId,
8687
8963
  mediaSource,
8688
8964
  micLane,
8689
8965
  migrateHostedDoc,