@vosjs/studio-core 0.24.1 → 0.25.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
@@ -1894,6 +1894,126 @@ type Media = ProjectDoc['source'] & {
1894
1894
  frame?: MediaFrame;
1895
1895
  };
1896
1896
 
1897
+ /**
1898
+ * The callout grammar — three shapes a viewer learns once, composed from the
1899
+ * product's own register so the card is unmistakably the video's and still
1900
+ * the product's.
1901
+ *
1902
+ * The rule set (the distinguishability principle: share the product's hue
1903
+ * and type, contrast in value, elevation, scale and place):
1904
+ * - VALUE INVERSION: a light app gets a dark card in the product's hue, a
1905
+ * dark app a light one; the accent is kept for the kicker and the dot.
1906
+ * - ELEVATION: a real shadow plus a hairline of light on the edge.
1907
+ * - SCALE: the title is set for the render, CALLOUT_TYPE_RATIO times the
1908
+ * app's body as it appears on screen at the layer's start (the camera
1909
+ * scales the app, so the caller passes that scale), never the app's own
1910
+ * size.
1911
+ * - MOTION: a rise in, a fade out.
1912
+ * - PLACE is the pin's (lower/pin.ts), not the card's.
1913
+ *
1914
+ * Pure: hex in, markup and CSS out. The starters are this grammar in the
1915
+ * house register; `vos callout` is it in the take's BRAND.md.
1916
+ */
1917
+
1918
+ type CalloutShape = 'tag' | 'note' | 'code';
1919
+ declare const CALLOUT_SHAPES: readonly CalloutShape[];
1920
+ /** The title's size over the app's body size as seen on screen. */
1921
+ declare const CALLOUT_TYPE_RATIO = 1.35;
1922
+ /** The app's body size when nothing says otherwise (CSS px at the viewport). */
1923
+ declare const CALLOUT_DEFAULT_BODY_PX = 14;
1924
+ /** The product's register: what a card shares with the app. */
1925
+ interface CalloutRegister {
1926
+ /** The app's ground (its page background), a hex. Decides the inversion. */
1927
+ ground: string;
1928
+ /** The product's accent, a hex: the kicker, the dot, the bar. */
1929
+ accent: string;
1930
+ /** The words face: a hosted catalog family or a system stack. */
1931
+ face?: string;
1932
+ /** The code face. */
1933
+ mono?: string;
1934
+ /** The app's body type size in CSS px at the recorded viewport. */
1935
+ body?: number;
1936
+ }
1937
+ interface CalloutWords {
1938
+ /** A short label above the title, set in the accent (a tag is only this). */
1939
+ kicker?: string;
1940
+ title?: string;
1941
+ /** One or two lines under the title. */
1942
+ body?: string;
1943
+ /** The payload, kept as typed (lines break on \n). */
1944
+ code?: string;
1945
+ }
1946
+ /** The resolved look: colours and sizes in design px. */
1947
+ interface CalloutLook {
1948
+ light: boolean;
1949
+ cardGround: string;
1950
+ cardInk: string;
1951
+ cardMuted: string;
1952
+ hairline: string;
1953
+ accent: string;
1954
+ shadow: string;
1955
+ face: string;
1956
+ mono: string;
1957
+ kickerPx: number;
1958
+ titlePx: number;
1959
+ bodyPx: number;
1960
+ codePx: number;
1961
+ radius: number;
1962
+ padX: number;
1963
+ padY: number;
1964
+ }
1965
+ declare const HOUSE_REGISTER: CalloutRegister;
1966
+ /**
1967
+ * Resolve a register into a look. `scale` is design px per CSS px of the
1968
+ * app as it appears at the layer's start (the card's footage scale times
1969
+ * the camera's level); 1 when unknown.
1970
+ */
1971
+ declare function calloutLook(register: CalloutRegister, scale?: number): CalloutLook;
1972
+ interface CalloutBox {
1973
+ width: number;
1974
+ height: number;
1975
+ }
1976
+ /**
1977
+ * The design box a shape needs for its words: one line for a tag, the
1978
+ * kicker, title and wrapped body for a note, the lines for code. Widths
1979
+ * estimate glyphs at 0.56 em for words and 0.62 em for mono.
1980
+ */
1981
+ declare function calloutBox(shape: CalloutShape, look: CalloutLook, words: CalloutWords): CalloutBox;
1982
+ /** The markup and CSS for a shape in a look, sized to a box. */
1983
+ declare function calloutSource(shape: CalloutShape, look: CalloutLook, words: CalloutWords, box: CalloutBox): {
1984
+ html: string;
1985
+ css: string;
1986
+ };
1987
+ interface CalloutClipOptions {
1988
+ id: string;
1989
+ /** OUTPUT seconds. */
1990
+ start: number;
1991
+ duration: number;
1992
+ /** Design px per CSS px of the app on screen at `start` (1 = unknown). */
1993
+ scale?: number;
1994
+ pin?: OverlayPin;
1995
+ /** The fallback place when the pin cannot resolve (frame fractions). */
1996
+ at?: {
1997
+ x: number;
1998
+ y: number;
1999
+ };
2000
+ }
2001
+ /** A callout as an html overlay clip: the grammar applied to a register. */
2002
+ declare function calloutClip(shape: CalloutShape, register: CalloutRegister, words: CalloutWords, opts: CalloutClipOptions): HtmlOverlayClip;
2003
+ /**
2004
+ * The card's ground as its CSS declares it (the root rule's background),
2005
+ * for the picture check that compares it with the footage it covers. Null
2006
+ * when the CSS names none in a form a hex can be read from.
2007
+ */
2008
+ declare function calloutGroundOf(css: string): string | null;
2009
+ type Rgb = [number, number, number];
2010
+ declare function hexToRgb(hex: string): Rgb | null;
2011
+ declare function rgbToHex([r, g, b]: Rgb): string;
2012
+ declare function parseCssColour(s: string): Rgb | null;
2013
+ declare function relativeLuminance([r, g, b]: Rgb): number;
2014
+ /** CIE76 colour difference between two sRGB colours. */
2015
+ declare function deltaE(a: Rgb, b: Rgb): number;
2016
+
1897
2017
  /**
1898
2018
  * Capture-space normalization — the single seam that makes window/monitor
1899
2019
  * recordings look like tab recordings to everything downstream.
@@ -2992,6 +3112,19 @@ interface PinReferent {
2992
3112
  * second. A `rect` is taken as written. Null when nothing names a rect.
2993
3113
  */
2994
3114
  declare function pinReferent(doc: ProjectDoc, pin: OverlayPin): PinReferent | null;
3115
+ /** A step a layer could be pinned to: it has an element, and its window overlaps the layer's. */
3116
+ interface PinCandidate {
3117
+ step: string | number;
3118
+ do: string;
3119
+ selector?: string;
3120
+ /** The step's OUTPUT window. */
3121
+ output: {
3122
+ start: number;
3123
+ end: number;
3124
+ };
3125
+ /** Seconds of the layer's window the step's covers. */
3126
+ overlap: number;
3127
+ }
2995
3128
  /** The step a pin names: its id first, then its record-time index. */
2996
3129
  declare function findStep(steps: readonly StepSpan[], ref: string | number): StepSpan | undefined;
2997
3130
  /**
@@ -3299,6 +3432,16 @@ declare function docZoomTrack(input: ProjectDoc): KeyframeTrack<number[]> | unde
3299
3432
  * and the layer keeps its `transform` — the lint says why.
3300
3433
  */
3301
3434
  declare function resolvePins(doc: ProjectDoc, layout: CardLayout, camera: CameraModel, zoomTrack: KeyframeTrack<number[]> | undefined): Map<string, PinPlacement>;
3435
+ /**
3436
+ * The steps an unpinned layer could name: every step with an element (a
3437
+ * rect, or presses inside its window) whose output window overlaps the
3438
+ * layer's, most overlap first. The studio's Pin-to select and the lint's
3439
+ * advice read this.
3440
+ */
3441
+ declare function pinCandidates(doc: ProjectDoc, clip: {
3442
+ start: number;
3443
+ duration: number;
3444
+ }): PinCandidate[];
3302
3445
  /**
3303
3446
  * A pinned layer's effective [x, y, scale, rotation, opacity] at CLIP-LOCAL
3304
3447
  * time t — the host-side mirror the picking layer substitutes into the
@@ -3645,22 +3788,8 @@ declare function htmlLayerPayload(clip: HtmlOverlayClip): {
3645
3788
  html: HtmlLayerPayload;
3646
3789
  };
3647
3790
 
3648
- /**
3649
- * The house starters for an HTML layer: what one press of the studio's HTML
3650
- * verb puts on the canvas, and what its panel's Starter select swaps in.
3651
- * Product defaults the way the three text presets are (code, not shelf
3652
- * data), five and not fifty: a LIBRARY of components is an official project
3653
- * on the shelf that an agent applies as a template.
3654
- *
3655
- * Each is real, readable markup and CSS a person can retune, in the house
3656
- * look (a dark card, Inter for words, JetBrains Mono for code, both hosted so
3657
- * the faces resolve from the CSS alone), with a `box-shadow` so the derived
3658
- * bleed is exercised. `htmlStarters.test.ts` proves every one passes the
3659
- * gate with no problem and no warning.
3660
- */
3661
-
3662
3791
  interface HtmlStarter {
3663
- id: 'callout' | 'code' | 'terminal' | 'keys' | 'badge';
3792
+ id: 'note' | 'tag' | 'code' | 'terminal' | 'keys' | 'badge';
3664
3793
  name: string;
3665
3794
  html: string;
3666
3795
  css: string;
@@ -4694,4 +4823,4 @@ declare function rejectSpan(rejected: RejectedSpan[] | undefined, lane: Rejected
4694
4823
  anchor?: StepAnchor;
4695
4824
  }, note?: string): RejectedSpan;
4696
4825
 
4697
- export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimSide, 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_CENTRE_RAMP, CAMERA_COVER_MIN, 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 CameraModel, 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, DESIGN_FRAME_WIDTH, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, DRAG_MIN_S, DRAG_MIN_TRAVEL_FRAC, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type Drag, 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, HTML_LAYER_FOREIGN_FIELDS, HTML_LAYER_MAX_BYTES, HTML_LAYER_SVG_CODE, HTML_LAYER_WARN_BYTES, HTML_STARTERS, type HtmlLayerFace, type HtmlLayerInlineFace, type HtmlLayerPayload, type HtmlLayerProblem, type HtmlLayerProblemCode, type HtmlLayerSource, type HtmlOverlayClip, type HtmlOverlayFont, type HtmlStarter, 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 OverlayPin, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PIN_GAP, PIN_MARGIN, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, type PinCarry, type PinMark, type PinPlacement, type PinPlacementInput, type PinReferent, type PinSide, type PinSideResolved, 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, SEGMENT_ENTER_KINDS, SEGMENT_EXIT_KINDS, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SegmentExtent, 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_SECONDS, TRANSITION_SECONDS_MAX, TRANSITION_SECONDS_MIN, 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 Transition, type TransitionKind, type TransitionMove, type TransitionOut, 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 ZoomView, type ZoomWindow, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, buildDigest, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cameraCentring, cameraModel, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cssFamilies, cssWeights, cursorIdleFade, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, docTransitions, docZoomTrack, dragsFromTrack, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, findStep, focusBounds, focusForViewportCentre, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgbTriplet, houseLook, htmlLayerBleed, htmlLayerBleedFor, htmlLayerFaces, htmlLayerKey, htmlLayerLabel, htmlLayerPayload, htmlLayerPictureBox, htmlLayerProblems, htmlLayerSvg, htmlLayerText, htmlLayerUnhostedFamilies, htmlLayerWidth, htmlStarterClip, idleGaps, idleKey, isKeyedOverlay, 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, overlayPinPoseAt, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parsePoseId, pickStyle, pinBox, pinPlacement, pinRectOnScreen, pinReferent, pinReferentAt, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolvePins, resolveText3dAsset, resolveZoomStyle, restLeanOf, restSpansThroughTransitions, sameMedia, sceneChanges, scrollRuns, segmentOutputExtents, outputLen as segmentOutputLen, segmentStarts, setSpeedInRange, slideSide, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, transitionSeconds, videoLane, voiceKey, wellFormedXml, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomView, zoomViewport, zoomWindow };
4826
+ export { ASPECT_RATIOS, AUDIO_PLAN_STEP, type AnchorKind, type Anim, type AnimKind, type AnimSide, 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, CALLOUT_DEFAULT_BODY_PX, CALLOUT_SHAPES, CALLOUT_TYPE_RATIO, CAMERA_CENTRE_RAMP, CAMERA_COVER_MIN, 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 CalloutBox, type CalloutClipOptions, type CalloutLook, type CalloutRegister, type CalloutShape, type CalloutWords, type CamBubbleRect, type CamPoseSpan, type CamStyle, type CameraModel, 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, DESIGN_FRAME_WIDTH, DESTINATIONS, DIGEST_CROP_MAX, DIGEST_FULL_MAX, DIGEST_VERSION, DOC_SCHEMA_VERSION, DRAG_FIT_LEVEL, DRAG_MIN_S, DRAG_MIN_TRAVEL_FRAC, type Destination, type Digest, type DigestImageRef, type DigestPlan, type DigestTakeFacts, type DocSegment, type Drag, 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, HOUSE_REGISTER, HTML_LAYER_FOREIGN_FIELDS, HTML_LAYER_MAX_BYTES, HTML_LAYER_SVG_CODE, HTML_LAYER_WARN_BYTES, HTML_STARTERS, type HtmlLayerFace, type HtmlLayerInlineFace, type HtmlLayerPayload, type HtmlLayerProblem, type HtmlLayerProblemCode, type HtmlLayerSource, type HtmlOverlayClip, type HtmlOverlayFont, type HtmlStarter, 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 OverlayPin, type OverlayPresetStyle, type OverlayRect, type OverlayTransform, type OverlayTransition, PIN_GAP, PIN_MARGIN, PLATE_GROUND, PLAYBACK_ACTIVITY, PRIMARY_MEDIA, PROGRAM_RETIME, type PinCandidate, type PinCarry, type PinMark, type PinPlacement, type PinPlacementInput, type PinReferent, type PinSide, type PinSideResolved, 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, type Rgb, SCENE_MOTION, SCENE_QUIET, SEGMENT_ENTER_KINDS, SEGMENT_EXIT_KINDS, SPEED_RATE_MAX, SPEED_RATE_MIN, SPEED_SPAN_MIN, STAGE_CLIP_PREFIX, STILL_DEFAULT, STUDIO_ENTRY_ID, STYLE_FIELDS, type SegmentExtent, 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_SECONDS, TRANSITION_SECONDS_MAX, TRANSITION_SECONDS_MIN, 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 Transition, type TransitionKind, type TransitionMove, type TransitionOut, 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 ZoomView, type ZoomWindow, anchorKindOf, anchorSourceDuration, animStep, applyLook, applyTemplate, aspectRatioValue, audioLane, backdropMedia, buildDigest, calloutBox, calloutClip, calloutGroundOf, calloutLook, calloutSource, camBubbleRect, camBubbleRectAt, camLane, camMoveLane, camRectFromPose, camRestPose, camTrackFromDoc, cameraCentring, cameraModel, cardEnter, cardExit, cardFields, cardInset, cardPointToScreen, cardPoseTrack, clampCamFrac, clampCamSize, clampFocus, clampFreezeSeconds, clampSpeedRate, clampTiltDeg, clampZoomLevel, clipEnvelope, clipLength, clipsFrom, computeCardLayout, computeMicRms, computePeaks, copyLayout, copyStyle, cropBox, cssFamilies, cssWeights, cursorIdleFade, deltaE, deriveViewportCrop, destinationById, destinationsForChannel, docCardLayout, docFreezes, docMediaList, docOutputDuration, docRestTime, docStillTime, docToCropSpace, docToFullSpace, docTransitions, docZoomTrack, dragsFromTrack, dropTemplate, duckCurve, effectiveSegments, endCardClips, enterKey, enterOf, entranceSeconds, envelopeAt, envelopeValueAt, exitKey, exitOf, exitSeconds, expandEndCard, expectedFrameSize, exportSizeFor, extractClicks, findStep, focusBounds, focusForViewportCentre, followFocusEvents, frameGeometry, freezeLane, freezeOutputExtents, hexToRgb, hexToRgbTriplet, houseLook, htmlLayerBleed, htmlLayerBleedFor, htmlLayerFaces, htmlLayerKey, htmlLayerLabel, htmlLayerPayload, htmlLayerPictureBox, htmlLayerProblems, htmlLayerSvg, htmlLayerText, htmlLayerUnhostedFamilies, htmlLayerWidth, htmlStarterClip, idleGaps, idleKey, isKeyedOverlay, 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, overlayPinPoseAt, overlayRect, overlaysLane, ownFreezes, pageDisplayUrl, parseCssColour, parsePoseId, pickStyle, pinBox, pinCandidates, pinPlacement, pinRectOnScreen, pinReferent, pinReferentAt, placeFreezes, planAutoSpeed, planAutoTilt, planAutoZoom, planForDigest, planeSizeAtDepth, platformBarKind, programDuration, projectFromArtifact, ratedSegments, recommendedExportResolution, refillAudioBeds, rejectSpan, relativeLuminance, removeSourceRange, removeSpeedInRange, resolveExportSize, resolveOverlayBox, resolveOverlayStyle, resolvePins, resolveText3dAsset, resolveZoomStyle, restLeanOf, restSpansThroughTransitions, rgbToHex, sameMedia, sceneChanges, scrollRuns, segmentOutputExtents, outputLen as segmentOutputLen, segmentStarts, setSpeedInRange, slideSide, smoothCursor, spanOutputExtent, speedLane, studioAudioPlan, studioEntry, studioLayerData, tiltLane, tiltTrackFromDoc, transitionMult, transitionSeconds, videoLane, voiceKey, wellFormedXml, withBackdrop, withFreezes, withHolds, withoutRejected, wrapProgramLength, zoomCoversRect, zoomLane, zoomSpanForRange, zoomTrackFromDoc, zoomView, zoomViewport, zoomWindow };
package/dist/index.js CHANGED
@@ -236,6 +236,210 @@ var MEDIA_FRAME_KEYS = [
236
236
  "focusFollow"
237
237
  ];
238
238
 
239
+ // src/callout.ts
240
+ var CALLOUT_SHAPES = ["tag", "note", "code"];
241
+ var CALLOUT_TYPE_RATIO = 1.35;
242
+ var CALLOUT_DEFAULT_BODY_PX = 14;
243
+ var HOUSE_REGISTER = {
244
+ ground: "#ffffff",
245
+ accent: "#7c3aed"
246
+ };
247
+ var DEFAULT_FACE = "'Inter',-apple-system,system-ui,sans-serif";
248
+ var DEFAULT_MONO = "'JetBrains Mono',ui-monospace,monospace";
249
+ function calloutLook(register, scale = 1) {
250
+ const ground = hexToRgb(register.ground) ?? [255, 255, 255];
251
+ const accent = hexToRgb(register.accent) ?? [124, 58, 237];
252
+ const light = relativeLuminance(ground) > 0.4;
253
+ const [h, s] = rgbToHsl(accent);
254
+ const cardGround = light ? hslToHex(h, Math.min(0.35, s * 0.6), 0.07) : hslToHex(h, Math.min(0.25, s * 0.35), 0.97);
255
+ const bodyPx = register.body ?? CALLOUT_DEFAULT_BODY_PX;
256
+ const titlePx = Math.max(20, Math.round(CALLOUT_TYPE_RATIO * bodyPx * scale));
257
+ return {
258
+ light,
259
+ cardGround,
260
+ cardInk: light ? "#fafafa" : "#101012",
261
+ cardMuted: light ? "rgba(255,255,255,0.64)" : "rgba(16,16,18,0.6)",
262
+ hairline: light ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.1)",
263
+ accent: rgbToHex(accent),
264
+ shadow: "0 24px 60px rgba(0,0,0,0.35), 0 2px 6px rgba(0,0,0,0.16)",
265
+ face: register.face ? quoteFamily(register.face) : DEFAULT_FACE,
266
+ mono: register.mono ? quoteFamily(register.mono) : DEFAULT_MONO,
267
+ kickerPx: Math.max(11, Math.round(titlePx * 0.55)),
268
+ titlePx,
269
+ bodyPx: Math.max(15, Math.round(titlePx * 0.75)),
270
+ codePx: Math.max(14, Math.round(titlePx * 0.68)),
271
+ radius: 16,
272
+ padX: 24,
273
+ padY: 20
274
+ };
275
+ }
276
+ function calloutBox(shape, look, words) {
277
+ const em = (px, chars, adv) => Math.ceil(px * adv * chars);
278
+ if (shape === "tag") {
279
+ const text = words.kicker ?? words.title ?? "";
280
+ const w = em(look.kickerPx * 1.1, text.length, 0.62) + 14 + 18 * 2;
281
+ return {
282
+ width: clamp(w, 120, 720),
283
+ height: Math.max(36, Math.round(look.kickerPx * 1.1 * 2.6))
284
+ };
285
+ }
286
+ if (shape === "code") {
287
+ const lines = (words.code ?? "").split("\n");
288
+ const longest = Math.max(1, ...lines.map((l) => l.length));
289
+ const lineH = Math.round(look.codePx * 1.7);
290
+ const w = em(look.codePx, longest, 0.62) + look.padX * 2;
291
+ const h = lines.length * lineH + look.padY * 2 + (words.kicker ? look.kickerPx * 2 : 0);
292
+ return { width: clamp(w, 360, 820), height: clamp(h, 72, 640) };
293
+ }
294
+ const title = words.title ?? "";
295
+ const body = words.body ?? "";
296
+ const titleW = em(look.titlePx, title.length, 0.56);
297
+ const width = clamp(Math.max(titleW, 320) + look.padX * 2, 360, 640);
298
+ const inner = width - look.padX * 2;
299
+ const bodyLines = body ? Math.max(1, Math.ceil(em(look.bodyPx, body.length, 0.54) / inner)) : 0;
300
+ const titleLines = Math.max(1, Math.ceil(titleW / inner));
301
+ const height = look.padY * 2 + (words.kicker ? Math.round(look.kickerPx * 1.6) : 0) + titleLines * Math.round(look.titlePx * 1.25) + (bodyLines ? 8 + bodyLines * Math.round(look.bodyPx * 1.5) : 0);
302
+ return { width, height: clamp(height, 64, 480) };
303
+ }
304
+ function calloutSource(shape, look, words, box) {
305
+ const base = `box-sizing:border-box;width:${box.width}px;height:${box.height}px;background:${look.cardGround};color:${look.cardInk};border:1px solid ${look.hairline};border-radius:${look.radius}px;box-shadow:${look.shadow};-webkit-font-smoothing:antialiased;`;
306
+ if (shape === "tag") {
307
+ const text = esc(words.kicker ?? words.title ?? "");
308
+ return {
309
+ html: `<div class="tag"><span class="dot"></span><span class="t">${text}</span></div>`,
310
+ css: `.tag{font-family:${look.face};display:flex;align-items:center;gap:10px;${base}border-radius:999px;padding:0 18px;background:${look.accent};color:#fff;border-color:rgba(255,255,255,0.18);font-size:${Math.round(look.kickerPx * 1.1)}px;font-weight:600;letter-spacing:0.02em;text-transform:uppercase;white-space:nowrap}.dot{width:8px;height:8px;border-radius:99px;background:#fff;opacity:0.9;flex:none}`
311
+ };
312
+ }
313
+ if (shape === "code") {
314
+ const lines = (words.code ?? "").split("\n").map(
315
+ (l) => /^\s*(\/\/|#|\$)/.test(l) ? `<span class="mut">${esc(l)}</span>` : esc(l)
316
+ ).join("\n");
317
+ const kicker2 = words.kicker ? `<div class="k">${esc(words.kicker)}</div>` : "";
318
+ return {
319
+ html: `<div class="code">${kicker2}<pre class="p">${lines}</pre></div>`,
320
+ css: `.code{font-family:${look.mono};${base}padding:${look.padY}px ${look.padX}px;font-size:${look.codePx}px;line-height:1.7;letter-spacing:-0.01em}.k{font-family:${look.face};font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin:0 0 ${Math.round(look.kickerPx * 0.9)}px}.p{margin:0;white-space:pre;font:inherit}.mut{color:${look.cardMuted}}`
321
+ };
322
+ }
323
+ const kicker = words.kicker ? `<div class="k"><span class="dot"></span>${esc(words.kicker)}</div>` : "";
324
+ const body = words.body ? `<div class="b">${esc(words.body)}</div>` : "";
325
+ return {
326
+ html: `<div class="note">${kicker}<div class="t">${esc(words.title ?? "")}</div>${body}</div>`,
327
+ css: `.note{font-family:${look.face};${base}padding:${look.padY}px ${look.padX}px;display:flex;flex-direction:column;justify-content:center}.k{display:flex;align-items:center;gap:8px;font-size:${look.kickerPx}px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:${look.accent};margin-bottom:${Math.round(look.kickerPx * 0.6)}px}.dot{width:7px;height:7px;border-radius:99px;background:${look.accent};box-shadow:0 0 10px ${look.accent}}.t{font-size:${look.titlePx}px;font-weight:600;line-height:1.25;letter-spacing:-0.01em}.b{margin-top:8px;font-size:${look.bodyPx}px;line-height:1.5;color:${look.cardMuted}}`
328
+ };
329
+ }
330
+ function calloutClip(shape, register, words, opts) {
331
+ const look = calloutLook(register, opts.scale ?? 1);
332
+ const box = calloutBox(shape, look, words);
333
+ const { html, css } = calloutSource(shape, look, words, box);
334
+ return {
335
+ id: opts.id,
336
+ kind: "html",
337
+ start: opts.start,
338
+ duration: opts.duration,
339
+ html,
340
+ css,
341
+ box,
342
+ transform: {
343
+ x: opts.at?.x ?? 0.5,
344
+ y: opts.at?.y ?? 0.5,
345
+ scale: 1,
346
+ rotation: 0
347
+ },
348
+ anim: { enter: "rise", exit: "fade" },
349
+ ...opts.pin ? { pin: opts.pin } : {}
350
+ };
351
+ }
352
+ function calloutGroundOf(css) {
353
+ const m = /background(?:-color)?\s*:\s*(#[0-9a-fA-F]{3,8}|rgba?\([^)]*\))/.exec(css);
354
+ if (!m) return null;
355
+ const rgb = parseCssColour(m[1]);
356
+ return rgb ? rgbToHex(rgb) : null;
357
+ }
358
+ function hexToRgb(hex) {
359
+ const m = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(hex.trim());
360
+ if (!m) return null;
361
+ let h = m[1];
362
+ if (h.length === 3)
363
+ h = h.split("").map((c) => c + c).join("");
364
+ return [
365
+ parseInt(h.slice(0, 2), 16),
366
+ parseInt(h.slice(2, 4), 16),
367
+ parseInt(h.slice(4, 6), 16)
368
+ ];
369
+ }
370
+ function rgbToHex([r, g, b]) {
371
+ const c = (v) => Math.round(clamp(v, 0, 255)).toString(16).padStart(2, "0");
372
+ return `#${c(r)}${c(g)}${c(b)}`;
373
+ }
374
+ function parseCssColour(s) {
375
+ const hex = hexToRgb(s);
376
+ if (hex) return hex;
377
+ const m = /rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/.exec(s);
378
+ if (!m) return null;
379
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
380
+ }
381
+ function relativeLuminance([r, g, b]) {
382
+ const lin = (v) => {
383
+ const c = v / 255;
384
+ return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
385
+ };
386
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
387
+ }
388
+ function rgbToHsl([r, g, b]) {
389
+ const rr = r / 255;
390
+ const gg = g / 255;
391
+ const bb = b / 255;
392
+ const max = Math.max(rr, gg, bb);
393
+ const min = Math.min(rr, gg, bb);
394
+ const l = (max + min) / 2;
395
+ if (max === min) return [0, 0, l];
396
+ const d = max - min;
397
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
398
+ let h = 0;
399
+ if (max === rr) h = (gg - bb) / d + (gg < bb ? 6 : 0);
400
+ else if (max === gg) h = (bb - rr) / d + 2;
401
+ else h = (rr - gg) / d + 4;
402
+ return [h / 6, s, l];
403
+ }
404
+ function hslToHex(h, s, l) {
405
+ const f = (n) => {
406
+ const k = (n + h * 12) % 12;
407
+ const a = s * Math.min(l, 1 - l);
408
+ return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
409
+ };
410
+ return rgbToHex([f(0) * 255, f(8) * 255, f(4) * 255]);
411
+ }
412
+ function deltaE(a, b) {
413
+ const la = rgbToLab(a);
414
+ const lb = rgbToLab(b);
415
+ return Math.hypot(la[0] - lb[0], la[1] - lb[1], la[2] - lb[2]);
416
+ }
417
+ function rgbToLab([r, g, b]) {
418
+ const lin = (v) => {
419
+ const c = v / 255;
420
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
421
+ };
422
+ const R = lin(r);
423
+ const G = lin(g);
424
+ const B = lin(b);
425
+ const x = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047;
426
+ const y = R * 0.2126 + G * 0.7152 + B * 0.0722;
427
+ const z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 1.08883;
428
+ const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
429
+ return [116 * f(y) - 16, 500 * (f(x) - f(y)), 200 * (f(y) - f(z))];
430
+ }
431
+ function quoteFamily(family) {
432
+ const first = family.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
433
+ const rest = family.includes(",") ? family.slice(family.indexOf(",") + 1).trim() : /mono/i.test(first) ? "ui-monospace,monospace" : "-apple-system,system-ui,sans-serif";
434
+ return /^[a-z-]+$/i.test(first) && /^(ui-|system-ui|sans-serif|serif|monospace)/.test(first) ? `${first},${rest}` : `'${first}',${rest}`;
435
+ }
436
+ function esc(s) {
437
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
438
+ }
439
+ function clamp(v, lo, hi) {
440
+ return Math.max(lo, Math.min(hi, v));
441
+ }
442
+
239
443
  // src/capture.ts
240
444
  var CHROME_TOP_MIN = 20;
241
445
  var CHROME_TOP_MAX = 220;
@@ -1102,7 +1306,7 @@ function sampleAt(points, t) {
1102
1306
  return { x: last.x, y: last.y };
1103
1307
  }
1104
1308
  function smoothCursor(track, options = {}) {
1105
- const factor = clamp(options.factor ?? 0.15, 0.01, 1);
1309
+ const factor = clamp2(options.factor ?? 0.15, 0.01, 1);
1106
1310
  const fps = options.fps ?? 60;
1107
1311
  const raw = track.filter((e) => e.type === "move" || e.type === "down" || e.type === "up").map((e) => ({ t: e.t / 1e3, x: e.x, y: e.y }));
1108
1312
  if (raw.length === 0) return [];
@@ -1121,7 +1325,7 @@ function smoothCursor(track, options = {}) {
1121
1325
  if (clicks.length) {
1122
1326
  while (ci < clicks.length - 1 && t > clicks[ci].t + SNAP_AFTER) ci++;
1123
1327
  const ck = clicks[ci];
1124
- const u = clamp(
1328
+ const u = clamp2(
1125
1329
  (t - (ck.t - SNAP_BEFORE)) / SNAP_BEFORE,
1126
1330
  0,
1127
1331
  t <= ck.t + SNAP_AFTER ? 1 : 0
@@ -1136,7 +1340,7 @@ function smoothCursor(track, options = {}) {
1136
1340
  }
1137
1341
  return out;
1138
1342
  }
1139
- function clamp(v, lo, hi) {
1343
+ function clamp2(v, lo, hi) {
1140
1344
  return Math.max(lo, Math.min(hi, v));
1141
1345
  }
1142
1346
 
@@ -1782,7 +1986,7 @@ function focusFor(cluster, width, height, targetFill, minLevel, maxLevel) {
1782
1986
  const cx = clamp012(sx / n / width);
1783
1987
  const cy = clamp012(sy / n / height);
1784
1988
  const level = fitLevel({ x: 0, y: 0, w: maxW, h: maxH }, width, height, targetFill) ?? maxLevel;
1785
- return { cx, cy, level: clamp2(level, minLevel, maxLevel) };
1989
+ return { cx, cy, level: clamp3(level, minLevel, maxLevel) };
1786
1990
  }
1787
1991
  function clusterFocus(events, width, height) {
1788
1992
  let sx = 0;
@@ -1815,11 +2019,11 @@ function clusterFocus(events, width, height) {
1815
2019
  } : null;
1816
2020
  return { cx: clamp012(sx / n / width), cy: clamp012(sy / n / height), rect };
1817
2021
  }
1818
- function clamp2(v, lo, hi) {
2022
+ function clamp3(v, lo, hi) {
1819
2023
  return Math.max(lo, Math.min(hi, v));
1820
2024
  }
1821
2025
  function clamp012(v) {
1822
- return clamp2(v, 0, 1);
2026
+ return clamp3(v, 0, 1);
1823
2027
  }
1824
2028
  function round(v) {
1825
2029
  return Math.round(v * 1e3) / 1e3;
@@ -7532,6 +7736,29 @@ function resolvePins(doc, layout, camera, zoomTrack) {
7532
7736
  }
7533
7737
  return out;
7534
7738
  }
7739
+ function pinCandidates(doc, clip) {
7740
+ const rated = ratedSegments(doc);
7741
+ const out = [];
7742
+ const a0 = clip.start;
7743
+ const a1 = clip.start + clip.duration;
7744
+ for (const s of doc.source.meta.steps ?? []) {
7745
+ if (s.skipped) continue;
7746
+ const ref = pinReferent(doc, { step: s.id ?? s.step });
7747
+ if (!ref) continue;
7748
+ const ext = spanOutputExtent(rated, s.tStart, s.tEnd);
7749
+ if (!ext) continue;
7750
+ const overlap = Math.min(a1, ext.end) - Math.max(a0, ext.start);
7751
+ if (overlap <= 0) continue;
7752
+ out.push({
7753
+ step: s.id ?? s.step,
7754
+ do: s.do,
7755
+ ...s.selector ? { selector: s.selector } : {},
7756
+ output: { start: round7(ext.start), end: round7(ext.end) },
7757
+ overlap: round7(overlap)
7758
+ });
7759
+ }
7760
+ return out.sort((x, y) => y.overlap - x.overlap);
7761
+ }
7535
7762
  function overlayPinPoseAt(doc, o, t) {
7536
7763
  if (!o.pin) return null;
7537
7764
  const layout = docCardLayout(doc);
@@ -9079,24 +9306,31 @@ function destinationsForChannel(channel) {
9079
9306
  }
9080
9307
 
9081
9308
  // src/htmlStarters.ts
9309
+ function grammarStarter(id, name, words) {
9310
+ const clip = calloutClip(id, HOUSE_REGISTER, words, {
9311
+ id,
9312
+ start: 0,
9313
+ duration: 1,
9314
+ scale: 1.4
9315
+ });
9316
+ return { id, name, html: clip.html, css: clip.css ?? "", box: clip.box };
9317
+ }
9082
9318
  var WORDS = "font-family:'Inter',-apple-system,system-ui,sans-serif;color:#fafafa;-webkit-font-smoothing:antialiased;";
9083
9319
  var MONO = "font-family:'JetBrains Mono',ui-monospace,monospace;";
9084
9320
  var CARD = "background:#0b0b0d;border:1px solid rgba(255,255,255,.08);border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45);";
9085
9321
  var HTML_STARTERS = [
9086
- {
9087
- id: "callout",
9088
- name: "Callout",
9089
- box: { width: 420, height: 132 },
9090
- html: '<div class="callout"><div class="head"><span class="dot"></span><span class="title">Title</span></div><div class="body">One line that says what this is.</div></div>',
9091
- css: `.callout{${WORDS}${CARD}box-sizing:border-box;width:420px;height:132px;padding:22px 24px}.head{display:flex;align-items:center;gap:10px}.dot{width:8px;height:8px;border-radius:99px;background:#4ade80;box-shadow:0 0 10px rgba(74,222,128,.9)}.title{font-size:20px;font-weight:600;letter-spacing:-0.01em}.body{margin-top:12px;font-size:15px;line-height:1.5;color:#8b8b94}`
9092
- },
9093
- {
9094
- id: "code",
9095
- name: "Code",
9096
- box: { width: 460, height: 168 },
9097
- html: '<div class="code"><span class="mut">// button.tsx</span>\n<span class="kw">export function</span> <span class="fn">Button</span>({ variant }) {\n <span class="kw">return</span> <span class="tag">&lt;button</span> className={<span class="fn">cn</span>(variant)} <span class="tag">/&gt;</span>\n}</div>',
9098
- css: `.code{${MONO}${CARD}color:#e8e8ec;box-sizing:border-box;width:460px;height:168px;padding:22px 24px;font-size:14px;line-height:1.8;white-space:pre;letter-spacing:-0.01em}.kw{color:#c4b5fd}.fn{color:#7dd3fc}.tag{color:#f0abfc}.mut{color:#5b5b66}`
9099
- },
9322
+ // The callout grammar (three shapes a viewer learns once), in the house
9323
+ // register: a dark card in the house hue over a light app.
9324
+ grammarStarter("note", "Note", {
9325
+ kicker: "Presets",
9326
+ title: "One of 43, picked here.",
9327
+ body: "One line that says what this is about."
9328
+ }),
9329
+ grammarStarter("tag", "Tag", { kicker: "New in 2.0" }),
9330
+ grammarStarter("code", "Code", {
9331
+ kicker: "index.css",
9332
+ code: ":root {\n --primary: oklch(0.63 0.06 4.6);\n --radius: 2.25rem;\n}"
9333
+ }),
9100
9334
  {
9101
9335
  id: "terminal",
9102
9336
  name: "Terminal",
@@ -10637,6 +10871,9 @@ export {
10637
10871
  AUDIO_PLAN_STEP,
10638
10872
  BACKGROUND_Z,
10639
10873
  BASE_FRAME_STYLE,
10874
+ CALLOUT_DEFAULT_BODY_PX,
10875
+ CALLOUT_SHAPES,
10876
+ CALLOUT_TYPE_RATIO,
10640
10877
  CAMERA_CENTRE_RAMP,
10641
10878
  CAMERA_COVER_MIN,
10642
10879
  CAMERA_FAR,
@@ -10711,6 +10948,7 @@ export {
10711
10948
  FREEZE_SECONDS_MAX,
10712
10949
  FREEZE_SECONDS_MIN,
10713
10950
  HOUSE_GRADIENT,
10951
+ HOUSE_REGISTER,
10714
10952
  HTML_LAYER_FOREIGN_FIELDS,
10715
10953
  HTML_LAYER_MAX_BYTES,
10716
10954
  HTML_LAYER_SVG_CODE,
@@ -10802,6 +11040,11 @@ export {
10802
11040
  audioLane,
10803
11041
  backdropMedia,
10804
11042
  buildDigest,
11043
+ calloutBox,
11044
+ calloutClip,
11045
+ calloutGroundOf,
11046
+ calloutLook,
11047
+ calloutSource,
10805
11048
  camBubbleRect,
10806
11049
  camBubbleRectAt,
10807
11050
  camLane,
@@ -10836,6 +11079,7 @@ export {
10836
11079
  cssFamilies,
10837
11080
  cssWeights,
10838
11081
  cursorIdleFade,
11082
+ deltaE,
10839
11083
  deriveViewportCrop,
10840
11084
  destinationById,
10841
11085
  destinationsForChannel,
@@ -10873,6 +11117,7 @@ export {
10873
11117
  frameGeometry,
10874
11118
  freezeLane,
10875
11119
  freezeOutputExtents,
11120
+ hexToRgb,
10876
11121
  hexToRgbTriplet,
10877
11122
  houseLook,
10878
11123
  htmlLayerBleed,
@@ -10943,9 +11188,11 @@ export {
10943
11188
  overlaysLane,
10944
11189
  ownFreezes,
10945
11190
  pageDisplayUrl,
11191
+ parseCssColour,
10946
11192
  parsePoseId,
10947
11193
  pickStyle,
10948
11194
  pinBox,
11195
+ pinCandidates,
10949
11196
  pinPlacement,
10950
11197
  pinRectOnScreen,
10951
11198
  pinReferent,
@@ -10963,6 +11210,7 @@ export {
10963
11210
  recommendedExportResolution,
10964
11211
  refillAudioBeds,
10965
11212
  rejectSpan,
11213
+ relativeLuminance,
10966
11214
  removeSourceRange,
10967
11215
  removeSpeedInRange,
10968
11216
  resolveExportSize,
@@ -10973,6 +11221,7 @@ export {
10973
11221
  resolveZoomStyle,
10974
11222
  restLeanOf,
10975
11223
  restSpansThroughTransitions,
11224
+ rgbToHex,
10976
11225
  sameMedia,
10977
11226
  sceneChanges,
10978
11227
  scrollRuns,