@vosjs/studio-core 0.5.0 → 0.6.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
@@ -1050,6 +1050,12 @@ interface TextOverlayClip extends OverlayClipBase {
1050
1050
  letterSpacing?: number;
1051
1051
  /** Line height multiplier (default OVERLAY_LINE_HEIGHT). */
1052
1052
  lineHeight?: number;
1053
+ /**
1054
+ * Legibility shadow strength 0..1 (the preset's when absent; 0 = none).
1055
+ * Words on a plate ground carry none: the shadow exists for words over
1056
+ * footage.
1057
+ */
1058
+ shadow?: number;
1053
1059
  /** Text outline, drawn under the fill. */
1054
1060
  stroke?: TextOverlayStroke;
1055
1061
  /** Background pill behind the text block (absent = none). */
@@ -1426,6 +1432,19 @@ declare const FRAME_BORDER_DEFAULT = 0.35;
1426
1432
  */
1427
1433
  declare const FRAME_BORDER_WIDTH_DEFAULT = 1.5;
1428
1434
  declare const FRAME_BORDER_COLOR_DEFAULT = "#ffffff";
1435
+ /**
1436
+ * How far past the card's rounded clip the footage and the browser-bar fill
1437
+ * are drawn, in device pixels, so the clip is the ONE edge the eye sees.
1438
+ * A clipped drawImage whose destination rect lands exactly on the clip path
1439
+ * anti-aliases that edge twice wherever it falls on a fractional device
1440
+ * pixel (the studio canvas is the fitted box at device pixel ratio, and any
1441
+ * zoom rescales everything about its focus), leaving a partially covered
1442
+ * seam pixel along every straight edge that the arc, drawn by the clip
1443
+ * alone, never has. One pixel covers the seam and changes the footage scale
1444
+ * by less than a part in a thousand. Read by ON_FRAME; the layout tests
1445
+ * expect the destination rect to be the mirror's grown by this much.
1446
+ */
1447
+ declare const CARD_EDGE_OVERDRAW = 1;
1429
1448
  /**
1430
1449
  * The realistic browser-bar kind matching the recorder's OS — seeds Default
1431
1450
  * mode so the synthetic chrome looks native to where the take was recorded
@@ -3316,4 +3335,4 @@ declare function rejectSpan(rejected: RejectedSpan[] | undefined, lane: Rejected
3316
3335
  anchor?: StepAnchor;
3317
3336
  }, note?: string): RejectedSpan;
3318
3337
 
3319
- export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, 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_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 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_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, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, HOUSE_GRADIENT, type IngestOptions, LOOK_KINDS, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type MediaOverlayClip, 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, PROGRAM_RETIME, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, 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, 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_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 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, applyLook, aspectRatioValue, audioLane, backdropMedia, buildDigest, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, computeCardLayout, computeMicRms, computePeaks, copyStyle, cropBox, cursorIdleFade, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docOutputDuration, docToCropSpace, docToFullSpace, duckCurve, effectiveSegments, entranceSeconds, envelopeAt, envelopeValueAt, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, focusBounds, followFocusEvents, frameGeometry, hexToRgbTriplet, houseLook, idleGaps, isLookKind, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, levelForFocusFraction, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, micLane, migrateHostedDoc, momentsFromDoc, motionTrack, musicBedClip, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayRect, overlaysLane, pageDisplayUrl, parsePoseId, pickStyle, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolveText3dAsset, resolveZoomStyle, sceneChanges, scrollRuns, setSpeedInRange, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, videoLane, voiceKey, withBackdrop, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomWindow };
3338
+ export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, 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_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 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_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, type FocusBounds, type FollowEvent, type FollowOptions, type FrameEntrance, type FrameGeometry, type FrameStyle, HOUSE_GRADIENT, type IngestOptions, LOOK_KINDS, type Look, type LookBrand, type LookKind, type LookPlacement, type LowerProgramOptions, type LoweredAudioClip, type LoweredClick, type LoweredComposition, type LoweredZoomSpan, MINIMAL_BAR_THEMES, MOTION_DELTA, MOTION_EASE, type MediaOverlayClip, 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, PROGRAM_RETIME, type PlanOptions, type PlanTiltOptions, type PlaneSize, type ProgramAnchorDoc, type ProgramTweenEdit, type ProjectDoc, type PxRect, REJECT_OVERLAP, 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, 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_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 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, applyLook, aspectRatioValue, audioLane, backdropMedia, buildDigest, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, computeCardLayout, computeMicRms, computePeaks, copyStyle, cropBox, cursorIdleFade, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docOutputDuration, docToCropSpace, docToFullSpace, duckCurve, effectiveSegments, entranceSeconds, envelopeAt, envelopeValueAt, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, focusBounds, followFocusEvents, frameGeometry, hexToRgbTriplet, houseLook, idleGaps, isLookKind, isMusicBed, isPlayback, isProgramDoc, isRecordingDoc, isRejected, levelForFocusFraction, lookFromBrand, lookKindForGround, lowerProgramDoc, lowerStudioDoc, lowerToComposition, micLane, migrateHostedDoc, momentsFromDoc, motionTrack, musicBedClip, normalizeCaptureSpace, objectMotionBase, objectMotionPoseAt, objectsLane, outputDurationOf, outputRangeToSource, overlapFraction, overlayFaceFor, overlayFontFaces, overlayFontString, overlayHit, overlayLines, overlayMotionBase, overlayMotionPoseAt, overlayRect, overlaysLane, pageDisplayUrl, parsePoseId, pickStyle, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolveText3dAsset, resolveZoomStyle, sceneChanges, scrollRuns, setSpeedInRange, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, videoLane, voiceKey, withBackdrop, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomWindow };
package/dist/index.js CHANGED
@@ -153,6 +153,7 @@ var DEFAULT_FRAME_STYLE = BASE_FRAME_STYLE;
153
153
  var FRAME_BORDER_DEFAULT = 0.35;
154
154
  var FRAME_BORDER_WIDTH_DEFAULT = 1.5;
155
155
  var FRAME_BORDER_COLOR_DEFAULT = "#ffffff";
156
+ var CARD_EDGE_OVERDRAW = 1;
156
157
  function platformBarKind(platform) {
157
158
  return platform === "windows" || platform === "linux" ? "windows-light" : "mac-light";
158
159
  }
@@ -1265,6 +1266,7 @@ var CARD_FOV = 30;
1265
1266
  var OVERLAY_Z = -2;
1266
1267
  var CARD_Z = -4;
1267
1268
  var BACKGROUND_Z = -6;
1269
+ var CARD_OVERSCAN = 1.25;
1268
1270
  var CAMERA_NEAR = 0.1;
1269
1271
  var CAMERA_FAR = 100;
1270
1272
  function planeSizeAtDepth(distance, fovDeg, aspect) {
@@ -1360,6 +1362,7 @@ function resolveOverlayStyle(clip) {
1360
1362
  )
1361
1363
  } : {},
1362
1364
  ...clip.color ? { color: clip.color } : {},
1365
+ ...clip.shadow !== void 0 ? { shadow: Math.min(1, Math.max(0, clip.shadow)) } : {},
1363
1366
  fontStyle: clip.italic ? "italic" : "normal",
1364
1367
  align: clip.align ?? "center",
1365
1368
  letterSpacing: clip.letterSpacing ?? 0,
@@ -2849,11 +2852,15 @@ function expandEndCard(doc, outputDuration) {
2849
2852
  enter: "rise",
2850
2853
  exit: "none",
2851
2854
  align: "center",
2852
- ...card.ink ? { color: card.ink } : {}
2855
+ ...card.ink ? { color: card.ink } : {},
2856
+ shadow: 0
2853
2857
  });
2854
- if (card.headline?.trim()) clips.push(text("endcard-title", card.headline.trim(), "title", 0.44, 0.35));
2855
- if (card.sub?.trim()) clips.push(text("endcard-sub", card.sub.trim(), "caption", 0.57, 0.5));
2856
- if (card.wordmark?.trim()) clips.push(text("endcard-mark", card.wordmark.trim(), "label", 0.88, 0.65));
2858
+ if (card.headline?.trim())
2859
+ clips.push(text("endcard-title", card.headline.trim(), "title", 0.44, 0.35));
2860
+ if (card.sub?.trim())
2861
+ clips.push(text("endcard-sub", card.sub.trim(), "caption", 0.57, 0.5));
2862
+ if (card.wordmark?.trim())
2863
+ clips.push(text("endcard-mark", card.wordmark.trim(), "label", 0.88, 0.65));
2857
2864
  const overlays = [...doc.overlays ?? [], ...clips];
2858
2865
  return { doc: { ...doc, segments, overlays }, endStart, seconds };
2859
2866
  }
@@ -3525,24 +3532,33 @@ var ON_FRAME = `(ctx, content, dt) => {
3525
3532
  // the GPU texture allocated at the original dims and re-uploads the new canvas
3526
3533
  // against stale dims \u2192 stretch/duplicate; dispose() forces a full realloc) and
3527
3534
  // rebuilding each frustum-filling plane at the new aspect + syncing the camera.
3528
- if (cv.width !== W || cv.height !== H) {
3535
+ // Card OVERSCAN (stage.ts CARD_OVERSCAN): a frame whose inset bleeds the
3536
+ // card past an edge paints it on a canvas and a plane larger than the
3537
+ // frame by ck about the centre, so a tilt that turns the bled edge back
3538
+ // toward the viewer shows card there, not the texture's edge. Every doc
3539
+ // that keeps its card inside the frame (and the flat stub, which has no
3540
+ // card layer) stays at 1: byte- and pixel-identical.
3541
+ var d = ctx.data || {}
3542
+ var ckIns = (d.frame && d.frame.inset) || {}
3543
+ var ck = r.card && (ckIns.left < 0 || ckIns.right < 0 || ckIns.top < 0 || ckIns.bottom < 0) ? ${CARD_OVERSCAN} : 1
3544
+ var cW = Math.round(W * ck), cH = Math.round(H * ck)
3545
+ if (cv.width !== cW || cv.height !== cH) {
3529
3546
  var THREE = ctx.THREE
3530
3547
  var aspect = W / H
3531
3548
  var cm = ctx.camera
3532
3549
  if (cm && cm.isPerspectiveCamera) { cm.aspect = aspect; cm.updateProjectionMatrix() }
3533
- var lyr = [[bg, ${BACKGROUND_Z}], [card, ${CARD_Z}], [ov, ${OVERLAY_Z}]]
3550
+ var lyr = [[bg, ${BACKGROUND_Z}, 1], [card, ${CARD_Z}, ck], [ov, ${OVERLAY_Z}, 1]]
3534
3551
  for (var Li = 0; Li < lyr.length; Li++) {
3535
- var Ly = lyr[Li][0], Lz = lyr[Li][1]
3536
- if (Ly.canvas) { Ly.canvas.width = W; Ly.canvas.height = H }
3552
+ var Ly = lyr[Li][0], Lz = lyr[Li][1], Lk = lyr[Li][2]
3553
+ if (Ly.canvas) { Ly.canvas.width = Math.round(W * Lk); Ly.canvas.height = Math.round(H * Lk) }
3537
3554
  if (Ly.texture && Ly.texture.dispose) Ly.texture.dispose()
3538
3555
  if (Ly.mesh && THREE) {
3539
3556
  if (Ly.mesh.geometry && Ly.mesh.geometry.dispose) Ly.mesh.geometry.dispose()
3540
- var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2)
3557
+ var Lh = 2 * Math.abs(Lz) * Math.tan(${CARD_FOV} * Math.PI / 180 / 2) * Lk
3541
3558
  Ly.mesh.geometry = new THREE.PlaneGeometry(Lh * aspect, Lh)
3542
3559
  }
3543
3560
  }
3544
3561
  }
3545
- var d = ctx.data || {}
3546
3562
  var frame = d.frame || {}
3547
3563
  var TL = globalThis.__vosTimeline
3548
3564
  // Output-timeline seconds (engine-fed master clock) \u2192 source seconds on screen.
@@ -3861,7 +3877,15 @@ var ON_FRAME = `(ctx, content, dt) => {
3861
3877
 
3862
3878
  // The CARD layer canvas starts transparent each frame \u2014 the padding around
3863
3879
  // the contain-fit card shows the background layer through the plane's alpha.
3864
- c.clearRect(0, 0, W, H)
3880
+ // Under overscan the canvas is larger than the frame and every frame-space
3881
+ // draw below rides a base translation to its centre.
3882
+ if (ck !== 1) c.setTransform(1, 0, 0, 1, 0, 0)
3883
+ c.clearRect(0, 0, cv.width, cv.height)
3884
+ if (ck !== 1) c.setTransform(1, 0, 0, 1, (cW - W) / 2, (cH - H) / 2)
3885
+ // Footage is nearly always drawn SMALLER than it was recorded (a 1920
3886
+ // recording in a padded 1080 card, or a 440-wide tile): the default
3887
+ // bilinear tap aliases and blurs a large downscale; 'high' resamples.
3888
+ c.imageSmoothingQuality = 'high'
3865
3889
 
3866
3890
  // video destination rect (contain within the padded area). The optional browser-bar
3867
3891
  // strip is part of the card: it takes barH from the available height and the video
@@ -3953,28 +3977,56 @@ var ON_FRAME = `(ctx, content, dt) => {
3953
3977
  c.save()
3954
3978
  // d.zoomSuppressed = editor aiming mode: the host merges it into ctx.data
3955
3979
  // while the focus overlay is up so the full frame renders. Never persisted.
3980
+ // ceZs = the zoom transform's device scale, for the two values below that
3981
+ // are sized in DEVICE pixels inside a scaled user space.
3982
+ var ceZs = 1
3956
3983
  if (lvl > 1.001 && !d.zoomSuppressed) {
3957
3984
  var fx = dx + zx * dw, fy = dy + zy * dh
3958
3985
  c.translate(fx, fy); c.scale(lvl, lvl); c.translate(-fx, -fy)
3959
- }
3960
- // soft shadow behind the card (bar strip + video)
3986
+ ceZs = lvl
3987
+ }
3988
+ // The card's shadows are cast by a body drawn OFF-CANVAS and brought back
3989
+ // by shadowOffsetX alone. Shadow offsets are device space, untouched by
3990
+ // the transform, so the body moves by shD / ceZs in user space and the
3991
+ // offset is shD; shD clears the canvas by the card's own device size, so
3992
+ // no part of the body lands on it at any zoom. Painting the body in place
3993
+ // (opaque black under the footage) left a 1px dark seam along every
3994
+ // straight card edge at a fractional coordinate: the fill's anti-aliased
3995
+ // rim showed through where the clipped footage did not cover it, and the
3996
+ // corners, drawn by the clip alone, hid theirs inside the arc.
3997
+ var shD = ceZs * (cardW + cardH) + 2 * (W + H)
3998
+ var shX = cardX - shD / ceZs
3999
+ // The card shadow is LAYERED: three shadows whose blur and offset grow
4000
+ // while each stays at a low alpha (shadow is the strength they share),
4001
+ // so the card reads as lifted a little off the ground. One wide layer at
4002
+ // the full strength was the old paint, and at the default 0.4 it read as
4003
+ // a dark pool under the card.
3961
4004
  if (shadow > 0) {
3962
- c.save()
3963
- c.shadowColor = 'rgba(' + shRgb + ',' + shadow + ')'
3964
- c.shadowBlur = 60 * s2; c.shadowOffsetY = 24 * s2
3965
- c.fillStyle = '#000'
3966
- rr(cardX, cardY, cardW, cardH, radius); c.fill()
3967
- c.restore()
4005
+ var shL = [[3, 1, 0.2], [14, 6, 0.22], [48, 22, 0.3]]
4006
+ for (var shI = 0; shI < shL.length; shI++) {
4007
+ c.save()
4008
+ c.shadowColor = 'rgba(' + shRgb + ',' + +(shadow * shL[shI][2]).toFixed(3) + ')'
4009
+ c.shadowBlur = shL[shI][0] * s2; c.shadowOffsetX = shD; c.shadowOffsetY = shL[shI][1] * s2
4010
+ c.fillStyle = '#000'
4011
+ rr(shX, cardY, cardW, cardH, radius); c.fill()
4012
+ c.restore()
4013
+ }
3968
4014
  }
3969
4015
  // contact shadow: tight and close, over the ambient layer
3970
4016
  if (shC > 0) {
3971
4017
  c.save()
3972
4018
  c.shadowColor = 'rgba(' + shRgb + ',' + shC + ')'
3973
- c.shadowBlur = 10 * s2; c.shadowOffsetY = 3 * s2
4019
+ c.shadowBlur = 10 * s2; c.shadowOffsetX = shD; c.shadowOffsetY = 3 * s2
3974
4020
  c.fillStyle = '#000'
3975
- rr(cardX, cardY, cardW, cardH, radius); c.fill()
4021
+ rr(shX, cardY, cardW, cardH, radius); c.fill()
3976
4022
  c.restore()
3977
4023
  }
4024
+ // The footage and the bar fill overdraw the clip by one device pixel
4025
+ // (CARD_EDGE_OVERDRAW) so the clip is the only edge: a destination rect
4026
+ // that lands exactly on the clip path anti-aliases the edge twice at a
4027
+ // fractional coordinate and leaves a half-covered seam pixel.
4028
+ var ceOv = ${CARD_EDGE_OVERDRAW} / ceZs
4029
+ var ceX = dx - ceOv, ceY = dy - ceOv, ceW = dw + 2 * ceOv, ceH = dh + 2 * ceOv
3978
4030
  // video, then bar, clipped to the card's rounded corners. The bar draws
3979
4031
  // AFTER the footage: under cover the video rect can overflow ABOVE the
3980
4032
  // bar strip (a vertical crop), and bar-first let the footage paint over
@@ -3987,9 +4039,9 @@ var ON_FRAME = `(ctx, content, dt) => {
3987
4039
  // dimension-checked at attach); false \u21D2 element path (pre-first-seek,
3988
4040
  // or the provider never attached).
3989
4041
  var wcp3 = video.__voilaWc
3990
- if (!(wcp3 && wcp3.draw(c, crp, dx, dy, dw, dh))) {
3991
- if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, dx, dy, dw, dh)
3992
- else c.drawImage(video, dx, dy, dw, dh)
4042
+ if (!(wcp3 && wcp3.draw(c, crp, ceX, ceY, ceW, ceH))) {
4043
+ if (crp) c.drawImage(video, crp.x, crp.y, crp.w, crp.h, ceX, ceY, ceW, ceH)
4044
+ else c.drawImage(video, ceX, ceY, ceW, ceH)
3993
4045
  }
3994
4046
  } catch (e) {}
3995
4047
  if (barH > 0) {
@@ -3999,7 +4051,7 @@ var ON_FRAME = `(ctx, content, dt) => {
3999
4051
  // absent = the built-in graphite look
4000
4052
  var thm = (minimal && bar.theme) || null
4001
4053
  c.fillStyle = minimal ? (thm ? thm.bar : '#141417') : dark ? '#2a2a2e' : '#e9e9eb'
4002
- c.fillRect(cardX, cardY, cardW, barH)
4054
+ c.fillRect(cardX - ceOv, cardY - ceOv, cardW + 2 * ceOv, barH + ceOv)
4003
4055
  c.fillStyle = dark || (minimal && !(thm && thm.light)) ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'
4004
4056
  c.fillRect(cardX, cardY + barH - s2, cardW, s2) // hairline above the video
4005
4057
  var midY = cardY + barH / 2
@@ -5862,8 +5914,8 @@ var LOOKS = {
5862
5914
  cardWidth: 0.84,
5863
5915
  headroom: 0.16,
5864
5916
  radius: 12,
5865
- shadow: 0.22,
5866
- shadowContact: 0.16,
5917
+ shadow: 0.42,
5918
+ shadowContact: 0,
5867
5919
  border: 0.08,
5868
5920
  borderColor: "#000000"
5869
5921
  },
@@ -5873,8 +5925,8 @@ var LOOKS = {
5873
5925
  cardWidth: 0.84,
5874
5926
  headroom: 0.16,
5875
5927
  radius: 12,
5876
- shadow: 0.35,
5877
- shadowContact: 0.2,
5928
+ shadow: 0.5,
5929
+ shadowContact: 0,
5878
5930
  border: 0
5879
5931
  },
5880
5932
  dark: {
@@ -5883,8 +5935,8 @@ var LOOKS = {
5883
5935
  cardWidth: 0.86,
5884
5936
  headroom: 0.14,
5885
5937
  radius: 12,
5886
- shadow: 0.5,
5887
- shadowContact: 0.3,
5938
+ shadow: 0.7,
5939
+ shadowContact: 0,
5888
5940
  shadowColor: "#000000",
5889
5941
  border: 0.12,
5890
5942
  borderColor: "#ffffff"
@@ -7231,6 +7283,7 @@ export {
7231
7283
  CAM_SIZE_MIN,
7232
7284
  CAM_SPAN_MIN,
7233
7285
  CAPTURE_COVERAGE_MIN,
7286
+ CARD_EDGE_OVERDRAW,
7234
7287
  CARD_FOV,
7235
7288
  CARD_Z,
7236
7289
  CHANNEL_SPECS_HASH,