@scenar/remotion 0.11.0 → 0.12.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.
@@ -0,0 +1,69 @@
1
+ import { type ReactNode } from "react";
2
+ import { type NarrationManifest, type ScenarioBundle } from "@scenar/core";
3
+ /**
4
+ * The canonical viewport a scenario lays out at inside the video frame.
5
+ * Mirrors the embed's `ScenarEmbedViewport` vocabulary (`widthPx` /
6
+ * `heightPx`) — the same numbers a bundle would announce over the embed
7
+ * handshake.
8
+ */
9
+ export interface RenderViewport {
10
+ readonly widthPx: number;
11
+ readonly heightPx: number;
12
+ }
13
+ /**
14
+ * The contain-fit placement of a canonical box inside a video frame: one
15
+ * uniform scale factor (the DD-008 rule — the frame boundary owns the only
16
+ * scale), centered with letterbox/pillarbox margins on the shorter axis.
17
+ * Exported for tests.
18
+ */
19
+ export declare function computeContainFit(frameWidth: number, frameHeight: number, boxWidth: number, boxHeight: number): {
20
+ scale: number;
21
+ offsetX: number;
22
+ offsetY: number;
23
+ };
24
+ interface PresentationStackProps<T> {
25
+ /** The applied bundle (cards synthesized) the player renders. */
26
+ bundle: ScenarioBundle<T> & {
27
+ narrationManifest?: NarrationManifest;
28
+ };
29
+ captions: boolean;
30
+ viewport: RenderViewport;
31
+ /** Wrap each step's content in `<ScenarioStage>` (staged bundles). */
32
+ stage: boolean;
33
+ /** Render the cursor overlay for interactions. */
34
+ cursor: boolean;
35
+ children: (data: T, stepIndex: number) => ReactNode;
36
+ }
37
+ /**
38
+ * The full presentation stack for the video-export path — the same stack
39
+ * the packed embed entry mounts around the player (scenar#35), assembled
40
+ * from the pieces that are already frame-driven under a TimeSource:
41
+ *
42
+ * fixed canonical box (geometry the embed's DemoViewport provides)
43
+ * └ ViewportTransformLayer(camera — its export path interpolates
44
+ * per frame with `interpolateViewportTransform`)
45
+ * └ ScenarioPlayer
46
+ * └ Cursor ← child of the camera, so it scales and
47
+ * pans with the content under a zoom
48
+ * + useStepInteractions ← switches to the frame-driven scheduler
49
+ * whenever a TimeSource is present
50
+ *
51
+ * Geometry: `DemoViewport` is deliberately a passthrough under
52
+ * `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
53
+ * definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
54
+ * `--scenar-shell-height` variable the shells consume, contain-fit into
55
+ * the composition frame with exactly one scale factor (DD-008). Without a
56
+ * definite box, surfaces that size through the shell-geometry contract
57
+ * collapse to zero height — the scenar#35 black frames.
58
+ *
59
+ * Interaction state assumes frames render in timeline order — the same
60
+ * sequential model `ScenarioCaptureMount.walkTo` documents. The render
61
+ * pipeline enforces it by rendering with concurrency 1.
62
+ *
63
+ * Must render below the composition's `TimeSourceProvider`, or
64
+ * `useStepInteractions` silently selects the wall-clock browser path
65
+ * (the capture mount's load-bearing lesson).
66
+ */
67
+ export declare function PresentationStack<T>({ bundle, captions, viewport, stage, cursor, children, }: PresentationStackProps<T>): import("react/jsx-runtime").JSX.Element;
68
+ export {};
69
+ //# sourceMappingURL=PresentationStack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresentationStack.d.ts","sourceRoot":"","sources":["../src/PresentationStack.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,OAAO,CAAC;AAEtE,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEpB,MAAM,cAAc,CAAC;AAStB;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAOrD;AAED,UAAU,sBAAsB,CAAC,CAAC;IAChC,iEAAiE;IACjE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG;QAAE,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;IACtE,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;IACzB,sEAAsE;IACtE,KAAK,EAAE,OAAO,CAAC;IACf,kDAAkD;IAClD,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,EACnC,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAQ,GACT,EAAE,sBAAsB,CAAC,CAAC,CAAC,2CA+F3B"}
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useRef, useState } from "react";
3
+ import { useVideoConfig } from "remotion";
4
+ import { VIEWPORT_TRANSFORM_IDENTITY, } from "@scenar/core";
5
+ import { Cursor, ScenarioPlayer, ScenarioStage, ViewportTransformLayer, useStepInteractions, } from "@scenar/react";
6
+ /**
7
+ * The contain-fit placement of a canonical box inside a video frame: one
8
+ * uniform scale factor (the DD-008 rule — the frame boundary owns the only
9
+ * scale), centered with letterbox/pillarbox margins on the shorter axis.
10
+ * Exported for tests.
11
+ */
12
+ export function computeContainFit(frameWidth, frameHeight, boxWidth, boxHeight) {
13
+ const scale = Math.min(frameWidth / boxWidth, frameHeight / boxHeight);
14
+ return {
15
+ scale,
16
+ offsetX: (frameWidth - boxWidth * scale) / 2,
17
+ offsetY: (frameHeight - boxHeight * scale) / 2,
18
+ };
19
+ }
20
+ /**
21
+ * The full presentation stack for the video-export path — the same stack
22
+ * the packed embed entry mounts around the player (scenar#35), assembled
23
+ * from the pieces that are already frame-driven under a TimeSource:
24
+ *
25
+ * fixed canonical box (geometry the embed's DemoViewport provides)
26
+ * └ ViewportTransformLayer(camera — its export path interpolates
27
+ * per frame with `interpolateViewportTransform`)
28
+ * └ ScenarioPlayer
29
+ * └ Cursor ← child of the camera, so it scales and
30
+ * pans with the content under a zoom
31
+ * + useStepInteractions ← switches to the frame-driven scheduler
32
+ * whenever a TimeSource is present
33
+ *
34
+ * Geometry: `DemoViewport` is deliberately a passthrough under
35
+ * `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
36
+ * definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
37
+ * `--scenar-shell-height` variable the shells consume, contain-fit into
38
+ * the composition frame with exactly one scale factor (DD-008). Without a
39
+ * definite box, surfaces that size through the shell-geometry contract
40
+ * collapse to zero height — the scenar#35 black frames.
41
+ *
42
+ * Interaction state assumes frames render in timeline order — the same
43
+ * sequential model `ScenarioCaptureMount.walkTo` documents. The render
44
+ * pipeline enforces it by rendering with concurrency 1.
45
+ *
46
+ * Must render below the composition's `TimeSourceProvider`, or
47
+ * `useStepInteractions` silently selects the wall-clock browser path
48
+ * (the capture mount's load-bearing lesson).
49
+ */
50
+ export function PresentationStack({ bundle, captions, viewport, stage, cursor, children, }) {
51
+ const { width: frameWidth, height: frameHeight } = useVideoConfig();
52
+ const containerRef = useRef(null);
53
+ const cameraRef = useRef(null);
54
+ const [stepIndex, setStepIndex] = useState(0);
55
+ const [cursorTarget, setCursorTarget] = useState(undefined);
56
+ const [showRipple, setShowRipple] = useState(true);
57
+ const [dragging, setDragging] = useState(false);
58
+ const [cameraTransform, setCameraTransform] = useState(VIEWPORT_TRANSFORM_IDENTITY);
59
+ // Reset the cursor when the step changes so a prior step's target never
60
+ // lingers; the new step's interactions re-set it at their scheduled time.
61
+ // One handler feeds both step-change callbacks: authored steps deliver
62
+ // data, card steps deliver the card — the wiring needs only the index,
63
+ // and it must track BOTH so card-step interactions (the outro's
64
+ // cursor-clear and viewport-reset housekeeping) reach the scheduler.
65
+ // Mirrors the packed embed entry's handler exactly, so the two outputs
66
+ // cannot diverge on cursor semantics.
67
+ const handleStepChange = useCallback((_dataOrCard, index) => {
68
+ setStepIndex(index);
69
+ setCursorTarget(undefined);
70
+ }, []);
71
+ useStepInteractions({
72
+ stepIndex,
73
+ narrationManifest: bundle.narrationManifest,
74
+ containerRef,
75
+ cameraRef,
76
+ setCursorTarget,
77
+ setShowRipple,
78
+ setDragging,
79
+ setViewportTransform: setCameraTransform,
80
+ steps: bundle.steps,
81
+ });
82
+ const fit = computeContainFit(frameWidth, frameHeight, viewport.widthPx, viewport.heightPx);
83
+ return (
84
+ // The letterbox/pillarbox canvas: painted with the theme surface token
85
+ // so uncovered margins read as an intentional page, not dead pixels.
86
+ _jsx("div", { style: {
87
+ position: "absolute",
88
+ inset: 0,
89
+ background: "var(--scenar-surface)",
90
+ }, children: _jsx("div", { ref: containerRef, style: {
91
+ position: "absolute",
92
+ left: fit.offsetX,
93
+ top: fit.offsetY,
94
+ width: viewport.widthPx,
95
+ height: viewport.heightPx,
96
+ transform: `scale(${fit.scale})`,
97
+ transformOrigin: "top left",
98
+ overflow: "hidden",
99
+ "--scenar-shell-height": `${viewport.heightPx}px`,
100
+ }, children: _jsxs(ViewportTransformLayer, { transform: cameraTransform, contentRef: cameraRef, children: [_jsx(ScenarioPlayer, { bundle: bundle, captions: captions, onStepChange: handleStepChange, onCardStepChange: handleStepChange, children: stage
101
+ ? (data, i) => _jsx(ScenarioStage, { children: children(data, i) })
102
+ : children }), cursor && (_jsx(Cursor, { target: cursorTarget, containerRef: cameraRef, showRipple: showRipple, isDragging: dragging }))] }) }) }));
103
+ }
104
+ //# sourceMappingURL=PresentationStack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PresentationStack.js","sourceRoot":"","sources":["../src/PresentationStack.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAkB,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAGL,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,MAAM,EACN,cAAc,EACd,aAAa,EACb,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAavB;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAkB,EAClB,WAAmB,EACnB,QAAgB,EAChB,SAAiB;IAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,QAAQ,EAAE,WAAW,GAAG,SAAS,CAAC,CAAC;IACvE,OAAO;QACL,KAAK;QACL,OAAO,EAAE,CAAC,UAAU,GAAG,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC;QAC5C,OAAO,EAAE,CAAC,WAAW,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC;KAC/C,CAAC;AACJ,CAAC;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,iBAAiB,CAAI,EACnC,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,MAAM,EACN,QAAQ,GACkB;IAC1B,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,CAAC;IACpE,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC/C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAqB,SAAS,CAAC,CAAC;IAChF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAC,2BAA2B,CAAC,CAAC;IAEpF,wEAAwE;IACxE,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,gEAAgE;IAChE,qEAAqE;IACrE,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,WAAoB,EAAE,KAAa,EAAE,EAAE;QAC3E,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,mBAAmB,CAAC;QAClB,SAAS;QACT,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,YAAY;QACZ,SAAS;QACT,eAAe;QACf,aAAa;QACb,WAAW;QACX,oBAAoB,EAAE,kBAAkB;QACxC,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,iBAAiB,CAC3B,UAAU,EACV,WAAW,EACX,QAAQ,CAAC,OAAO,EAChB,QAAQ,CAAC,QAAQ,CAClB,CAAC;IAEF,OAAO;IACL,uEAAuE;IACvE,qEAAqE;IACrE,cACE,KAAK,EAAE;YACL,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,CAAC;YACR,UAAU,EAAE,uBAAuB;SACpC,YAMD,cACE,GAAG,EAAE,YAAY,EACjB,KAAK,EACH;gBACE,QAAQ,EAAE,UAAU;gBACpB,IAAI,EAAE,GAAG,CAAC,OAAO;gBACjB,GAAG,EAAE,GAAG,CAAC,OAAO;gBAChB,KAAK,EAAE,QAAQ,CAAC,OAAO;gBACvB,MAAM,EAAE,QAAQ,CAAC,QAAQ;gBACzB,SAAS,EAAE,SAAS,GAAG,CAAC,KAAK,GAAG;gBAChC,eAAe,EAAE,UAAU;gBAC3B,QAAQ,EAAE,QAAQ;gBAClB,uBAAuB,EAAE,GAAG,QAAQ,CAAC,QAAQ,IAAI;aAC3B,YAG1B,MAAC,sBAAsB,IAAC,SAAS,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,aACvE,KAAC,cAAc,IACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,gBAAgB,EAC9B,gBAAgB,EAAE,gBAAgB,YAEjC,KAAK;4BACJ,CAAC,CAAC,CAAC,IAAO,EAAE,CAAS,EAAE,EAAE,CAAC,KAAC,aAAa,cAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAiB;4BAC5E,CAAC,CAAC,QAAQ,GACG,EAChB,MAAM,IAAI,CACT,KAAC,MAAM,IACL,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,SAAS,EACvB,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,QAAQ,GACpB,CACH,IACsB,GACrB,GACF,CACP,CAAC;AACJ,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { type ScenarioBundle, type SfxSound } from "@scenar/core";
3
+ import { type RenderViewport } from "./PresentationStack.js";
3
4
  interface ScenarioCompositionProps<T> {
4
5
  /** Self-contained scenario bundle with steps and optional narration. */
5
6
  bundle: ScenarioBundle<T>;
@@ -28,6 +29,29 @@ interface ScenarioCompositionProps<T> {
28
29
  * render` stages them at (`soundtrack/sfx/<name>.mp3`).
29
30
  */
30
31
  sfxSrcs?: Record<SfxSound, string>;
32
+ /**
33
+ * Mount the full presentation stack around the player — the same stack
34
+ * packed embeds mount: a definite canonical box contain-fit into the
35
+ * video frame (the geometry `DemoViewport` provides in browsers but
36
+ * deliberately passes through under `isVideoExport`), the
37
+ * `ViewportTransformLayer` camera, the `Cursor`, and TimeSource-driven
38
+ * step interactions. Without it, only step content renders:
39
+ * interactions' visible effects are absent from the MP4 and surfaces
40
+ * that size through the shell-geometry contract
41
+ * (`--scenar-shell-height`) collapse (scenar#35). Omitted, the
42
+ * composition keeps its prior bare-player output.
43
+ */
44
+ viewport?: RenderViewport;
45
+ /**
46
+ * Wrap each step's content in `<ScenarioStage>` (staged bundles — the
47
+ * `scenar pack --stage` presentation). Only meaningful with `viewport`.
48
+ */
49
+ stage?: boolean;
50
+ /**
51
+ * Render the cursor overlay for interactions. Defaults to true; only
52
+ * meaningful with `viewport`.
53
+ */
54
+ cursor?: boolean;
31
55
  }
32
56
  /**
33
57
  * Root component for rendering a Scenar scenario as a Remotion video.
@@ -71,6 +95,6 @@ interface ScenarioCompositionProps<T> {
71
95
  * );
72
96
  * ```
73
97
  */
74
- export declare function ScenarioComposition<T>({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp, captions, sfxSrcs, }: ScenarioCompositionProps<T>): import("react/jsx-runtime").JSX.Element;
98
+ export declare function ScenarioComposition<T>({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp, captions, sfxSrcs, viewport, stage, cursor, }: ScenarioCompositionProps<T>): import("react/jsx-runtime").JSX.Element;
75
99
  export {};
76
100
  //# sourceMappingURL=ScenarioComposition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ScenarioComposition.d.ts","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAShD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EAId,MAAM,cAAc,CAAC;AA4BtB,UAAU,wBAAwB,CAAC,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;IACpD,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,EACrC,MAAM,EACN,QAAQ,EACR,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,iBAAwB,EACvC,QAAgB,EAChB,OAA0B,GAC3B,EAAE,wBAAwB,CAAC,CAAC,CAAC,2CA6I7B"}
1
+ {"version":3,"file":"ScenarioComposition.d.ts","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,OAAO,CAAC;AAShD,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EAId,MAAM,cAAc,CAAC;AAOtB,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAsBhF,UAAU,wBAAwB,CAAC,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC;IACpD,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,EACrC,MAAM,EACN,QAAQ,EACR,GAAG,EAAE,OAAO,EACZ,aAAa,EAAE,iBAAwB,EACvC,QAAgB,EAChB,OAA0B,EAC1B,QAAQ,EACR,KAAa,EACb,MAAa,GACd,EAAE,wBAAwB,CAAC,CAAC,CAAC,2CA6J7B"}
@@ -3,6 +3,7 @@ import { useMemo } from "react";
3
3
  import { Audio, OffthreadVideo, Sequence, staticFile, useCurrentFrame, useVideoConfig, } from "remotion";
4
4
  import { computeMusicEnvelope, deriveSfxTimeline, musicGainAt, } from "@scenar/core";
5
5
  import { TimeSourceProvider, VideoExportProvider, ScenarioPlayer, } from "@scenar/react";
6
+ import { PresentationStack } from "./PresentationStack.js";
6
7
  import { msToFrames, useScenarioTimeline } from "./useScenarioTimeline.js";
7
8
  const DEFAULT_FPS = 30;
8
9
  /**
@@ -62,7 +63,7 @@ const DEFAULT_SFX_SRCS = {
62
63
  * );
63
64
  * ```
64
65
  */
65
- export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp = true, captions = false, sfxSrcs = DEFAULT_SFX_SRCS, }) {
66
+ export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticFile: useStaticFileProp = true, captions = false, sfxSrcs = DEFAULT_SFX_SRCS, viewport, stage = false, cursor = true, }) {
66
67
  const videoConfig = useVideoConfig();
67
68
  const fps = fpsProp ?? videoConfig.fps ?? DEFAULT_FPS;
68
69
  const frame = useCurrentFrame();
@@ -122,7 +123,12 @@ export function ScenarioComposition({ bundle, children, fps: fpsProp, useStaticF
122
123
  src: sfxSrcs[event.sound],
123
124
  }));
124
125
  }, [soundtrack, bundle.steps, bundle.narrationManifest, timeline, fps, sfxSrcs]);
125
- return (_jsxs(_Fragment, { children: [_jsx(TimeSourceProvider, { currentTimeMs: currentTimeMs, stepStartTimesMs: timeline.stepStartTimesMs, children: _jsx(VideoExportProvider, { presenterMedia: presenterMedia, children: _jsx(ScenarioPlayer, { bundle: playerBundle, captions: captions, children: children }) }) }), timeline.audioClips.map((clip) => (_jsx(Sequence, { from: clip.startFrame, durationInFrames: clip.durationFrames, children: _jsx(Audio, { src: resolveAssetSrc(clip.src, useStaticFileProp) }) }, clip.stepIndex))), soundtrack?.musicSrc && (
126
+ return (_jsxs(_Fragment, { children: [_jsx(TimeSourceProvider, { currentTimeMs: currentTimeMs, stepStartTimesMs: timeline.stepStartTimesMs, children: _jsx(VideoExportProvider, { presenterMedia: presenterMedia, children: viewport ? (
127
+ // The full presentation stack (scenar#35). Rendered below the
128
+ // TimeSourceProvider on purpose: its useStepInteractions call
129
+ // must see the time source or it silently selects the
130
+ // wall-clock browser scheduler (the capture mount's lesson).
131
+ _jsx(PresentationStack, { bundle: playerBundle, captions: captions, viewport: viewport, stage: stage, cursor: cursor, children: children })) : (_jsx(ScenarioPlayer, { bundle: playerBundle, captions: captions, children: children })) }) }), timeline.audioClips.map((clip) => (_jsx(Sequence, { from: clip.startFrame, durationInFrames: clip.durationFrames, children: _jsx(Audio, { src: resolveAssetSrc(clip.src, useStaticFileProp) }) }, clip.stepIndex))), soundtrack?.musicSrc && (
126
132
  // The music spans the whole composition: `loop` folds a shorter
127
133
  // asset seamlessly; the volume function carries fade-in, ducking,
128
134
  // and the closing fade-out, so no trim math is needed here.
@@ -1 +1 @@
1
- {"version":3,"file":"ScenarioComposition.js","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ScenarioComposition.js","sourceRoot":"","sources":["../src/ScenarioComposition.tsx"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export type { ScenarioBundle, NarrationManifest, ScenarioStep, StepTimeline, TitleCard, TitleCards, StepCard, AppliedTitleCards, } from "@scenar/core";
2
2
  export { applyTitleCards } from "@scenar/core";
3
3
  export { ScenarioComposition } from "./ScenarioComposition.js";
4
+ export type { RenderViewport } from "./PresentationStack.js";
5
+ export { PresentationStack, computeContainFit } from "./PresentationStack.js";
4
6
  export type { ScenarioTimeline, AudioClip } from "./useScenarioTimeline.js";
5
7
  export { useScenarioTimeline, calculateScenarioTimeline, msToFrames, } from "./useScenarioTimeline.js";
6
8
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG/D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,iBAAiB,GAClB,MAAM,cAAc,CAAC;AAItB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAK/D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG9E,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
package/index.js CHANGED
@@ -6,5 +6,6 @@
6
6
  export { applyTitleCards } from "@scenar/core";
7
7
  // Composition
8
8
  export { ScenarioComposition } from "./ScenarioComposition.js";
9
+ export { PresentationStack, computeContainFit } from "./PresentationStack.js";
9
10
  export { useScenarioTimeline, calculateScenarioTimeline, msToFrames, } from "./useScenarioTimeline.js";
10
11
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,iEAAiE;AAajE,sEAAsE;AACtE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc;AACd,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAI/D,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,iEAAiE;AAajE,sEAAsE;AACtE,sEAAsE;AACtE,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,cAAc;AACd,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAI9E,OAAO,EACL,mBAAmB,EACnB,yBAAyB,EACzB,UAAU,GACX,MAAM,0BAA0B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenar/remotion",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Remotion integration for Scenar — render scenario demos as pixel-perfect MP4 videos.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -29,8 +29,8 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@scenar/core": "0.11.0",
33
- "@scenar/react": "0.11.0"
32
+ "@scenar/core": "0.12.0",
33
+ "@scenar/react": "0.12.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@remotion/renderer": "4.0.448",
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { computeContainFit } from "./PresentationStack.js";
3
+
4
+ /**
5
+ * The contain-fit math is the presentation stack's one piece of geometry:
6
+ * a single uniform scale factor (DD-008 — the frame boundary owns the only
7
+ * scale), centered margins on the shorter axis.
8
+ */
9
+ describe("computeContainFit", () => {
10
+ it("pillarboxes a 16:10 canonical box in a 16:9 frame (the render default)", () => {
11
+ const fit = computeContainFit(1920, 1080, 1440, 900);
12
+ expect(fit.scale).toBeCloseTo(1080 / 900); // height-limited: 1.2
13
+ expect(fit.offsetY).toBe(0);
14
+ expect(fit.offsetX).toBeCloseTo((1920 - 1440 * 1.2) / 2); // 96px pillars
15
+ });
16
+
17
+ it("letterboxes a wide box in a tall frame", () => {
18
+ const fit = computeContainFit(1080, 1920, 1440, 900);
19
+ expect(fit.scale).toBeCloseTo(1080 / 1440); // width-limited: 0.75
20
+ expect(fit.offsetX).toBe(0);
21
+ expect(fit.offsetY).toBeCloseTo((1920 - 900 * 0.75) / 2);
22
+ });
23
+
24
+ it("is the identity when the box matches the frame", () => {
25
+ const fit = computeContainFit(1440, 900, 1440, 900);
26
+ expect(fit).toEqual({ scale: 1, offsetX: 0, offsetY: 0 });
27
+ });
28
+
29
+ it("scales up as well as down (a small box fills a large frame)", () => {
30
+ const fit = computeContainFit(2880, 1800, 1440, 900);
31
+ expect(fit).toEqual({ scale: 2, offsetX: 0, offsetY: 0 });
32
+ });
33
+ });
@@ -0,0 +1,191 @@
1
+ import { type ReactNode, useCallback, useRef, useState } from "react";
2
+ import { useVideoConfig } from "remotion";
3
+ import {
4
+ type NarrationManifest,
5
+ type ScenarioBundle,
6
+ VIEWPORT_TRANSFORM_IDENTITY,
7
+ } from "@scenar/core";
8
+ import {
9
+ Cursor,
10
+ ScenarioPlayer,
11
+ ScenarioStage,
12
+ ViewportTransformLayer,
13
+ useStepInteractions,
14
+ } from "@scenar/react";
15
+
16
+ /**
17
+ * The canonical viewport a scenario lays out at inside the video frame.
18
+ * Mirrors the embed's `ScenarEmbedViewport` vocabulary (`widthPx` /
19
+ * `heightPx`) — the same numbers a bundle would announce over the embed
20
+ * handshake.
21
+ */
22
+ export interface RenderViewport {
23
+ readonly widthPx: number;
24
+ readonly heightPx: number;
25
+ }
26
+
27
+ /**
28
+ * The contain-fit placement of a canonical box inside a video frame: one
29
+ * uniform scale factor (the DD-008 rule — the frame boundary owns the only
30
+ * scale), centered with letterbox/pillarbox margins on the shorter axis.
31
+ * Exported for tests.
32
+ */
33
+ export function computeContainFit(
34
+ frameWidth: number,
35
+ frameHeight: number,
36
+ boxWidth: number,
37
+ boxHeight: number,
38
+ ): { scale: number; offsetX: number; offsetY: number } {
39
+ const scale = Math.min(frameWidth / boxWidth, frameHeight / boxHeight);
40
+ return {
41
+ scale,
42
+ offsetX: (frameWidth - boxWidth * scale) / 2,
43
+ offsetY: (frameHeight - boxHeight * scale) / 2,
44
+ };
45
+ }
46
+
47
+ interface PresentationStackProps<T> {
48
+ /** The applied bundle (cards synthesized) the player renders. */
49
+ bundle: ScenarioBundle<T> & { narrationManifest?: NarrationManifest };
50
+ captions: boolean;
51
+ viewport: RenderViewport;
52
+ /** Wrap each step's content in `<ScenarioStage>` (staged bundles). */
53
+ stage: boolean;
54
+ /** Render the cursor overlay for interactions. */
55
+ cursor: boolean;
56
+ children: (data: T, stepIndex: number) => ReactNode;
57
+ }
58
+
59
+ /**
60
+ * The full presentation stack for the video-export path — the same stack
61
+ * the packed embed entry mounts around the player (scenar#35), assembled
62
+ * from the pieces that are already frame-driven under a TimeSource:
63
+ *
64
+ * fixed canonical box (geometry the embed's DemoViewport provides)
65
+ * └ ViewportTransformLayer(camera — its export path interpolates
66
+ * per frame with `interpolateViewportTransform`)
67
+ * └ ScenarioPlayer
68
+ * └ Cursor ← child of the camera, so it scales and
69
+ * pans with the content under a zoom
70
+ * + useStepInteractions ← switches to the frame-driven scheduler
71
+ * whenever a TimeSource is present
72
+ *
73
+ * Geometry: `DemoViewport` is deliberately a passthrough under
74
+ * `isVideoExport` (see ScenarioCaptureMount), so this stack owns it — a
75
+ * definite canonical box (`viewport.widthPx` × `heightPx`) exposing the
76
+ * `--scenar-shell-height` variable the shells consume, contain-fit into
77
+ * the composition frame with exactly one scale factor (DD-008). Without a
78
+ * definite box, surfaces that size through the shell-geometry contract
79
+ * collapse to zero height — the scenar#35 black frames.
80
+ *
81
+ * Interaction state assumes frames render in timeline order — the same
82
+ * sequential model `ScenarioCaptureMount.walkTo` documents. The render
83
+ * pipeline enforces it by rendering with concurrency 1.
84
+ *
85
+ * Must render below the composition's `TimeSourceProvider`, or
86
+ * `useStepInteractions` silently selects the wall-clock browser path
87
+ * (the capture mount's load-bearing lesson).
88
+ */
89
+ export function PresentationStack<T>({
90
+ bundle,
91
+ captions,
92
+ viewport,
93
+ stage,
94
+ cursor,
95
+ children,
96
+ }: PresentationStackProps<T>) {
97
+ const { width: frameWidth, height: frameHeight } = useVideoConfig();
98
+ const containerRef = useRef<HTMLDivElement>(null);
99
+ const cameraRef = useRef<HTMLDivElement>(null);
100
+ const [stepIndex, setStepIndex] = useState(0);
101
+ const [cursorTarget, setCursorTarget] = useState<string | undefined>(undefined);
102
+ const [showRipple, setShowRipple] = useState(true);
103
+ const [dragging, setDragging] = useState(false);
104
+ const [cameraTransform, setCameraTransform] = useState(VIEWPORT_TRANSFORM_IDENTITY);
105
+
106
+ // Reset the cursor when the step changes so a prior step's target never
107
+ // lingers; the new step's interactions re-set it at their scheduled time.
108
+ // One handler feeds both step-change callbacks: authored steps deliver
109
+ // data, card steps deliver the card — the wiring needs only the index,
110
+ // and it must track BOTH so card-step interactions (the outro's
111
+ // cursor-clear and viewport-reset housekeeping) reach the scheduler.
112
+ // Mirrors the packed embed entry's handler exactly, so the two outputs
113
+ // cannot diverge on cursor semantics.
114
+ const handleStepChange = useCallback((_dataOrCard: unknown, index: number) => {
115
+ setStepIndex(index);
116
+ setCursorTarget(undefined);
117
+ }, []);
118
+
119
+ useStepInteractions({
120
+ stepIndex,
121
+ narrationManifest: bundle.narrationManifest,
122
+ containerRef,
123
+ cameraRef,
124
+ setCursorTarget,
125
+ setShowRipple,
126
+ setDragging,
127
+ setViewportTransform: setCameraTransform,
128
+ steps: bundle.steps,
129
+ });
130
+
131
+ const fit = computeContainFit(
132
+ frameWidth,
133
+ frameHeight,
134
+ viewport.widthPx,
135
+ viewport.heightPx,
136
+ );
137
+
138
+ return (
139
+ // The letterbox/pillarbox canvas: painted with the theme surface token
140
+ // so uncovered margins read as an intentional page, not dead pixels.
141
+ <div
142
+ style={{
143
+ position: "absolute",
144
+ inset: 0,
145
+ background: "var(--scenar-surface)",
146
+ }}
147
+ >
148
+ {/* The canonical box, contain-fit into the frame. `transform` does
149
+ not affect layout, so the box is positioned at its scaled offset
150
+ and scaled from its top-left corner — one scale factor, owned by
151
+ this boundary. */}
152
+ <div
153
+ ref={containerRef}
154
+ style={
155
+ {
156
+ position: "absolute",
157
+ left: fit.offsetX,
158
+ top: fit.offsetY,
159
+ width: viewport.widthPx,
160
+ height: viewport.heightPx,
161
+ transform: `scale(${fit.scale})`,
162
+ transformOrigin: "top left",
163
+ overflow: "hidden",
164
+ "--scenar-shell-height": `${viewport.heightPx}px`,
165
+ } as React.CSSProperties
166
+ }
167
+ >
168
+ <ViewportTransformLayer transform={cameraTransform} contentRef={cameraRef}>
169
+ <ScenarioPlayer
170
+ bundle={bundle}
171
+ captions={captions}
172
+ onStepChange={handleStepChange}
173
+ onCardStepChange={handleStepChange}
174
+ >
175
+ {stage
176
+ ? (data: T, i: number) => <ScenarioStage>{children(data, i)}</ScenarioStage>
177
+ : children}
178
+ </ScenarioPlayer>
179
+ {cursor && (
180
+ <Cursor
181
+ target={cursorTarget}
182
+ containerRef={cameraRef}
183
+ showRipple={showRipple}
184
+ isDragging={dragging}
185
+ />
186
+ )}
187
+ </ViewportTransformLayer>
188
+ </div>
189
+ </div>
190
+ );
191
+ }
@@ -20,6 +20,7 @@ import {
20
20
  VideoExportProvider,
21
21
  ScenarioPlayer,
22
22
  } from "@scenar/react";
23
+ import { PresentationStack, type RenderViewport } from "./PresentationStack.js";
23
24
  import { msToFrames, useScenarioTimeline } from "./useScenarioTimeline.js";
24
25
 
25
26
  const DEFAULT_FPS = 30;
@@ -69,6 +70,29 @@ interface ScenarioCompositionProps<T> {
69
70
  * render` stages them at (`soundtrack/sfx/<name>.mp3`).
70
71
  */
71
72
  sfxSrcs?: Record<SfxSound, string>;
73
+ /**
74
+ * Mount the full presentation stack around the player — the same stack
75
+ * packed embeds mount: a definite canonical box contain-fit into the
76
+ * video frame (the geometry `DemoViewport` provides in browsers but
77
+ * deliberately passes through under `isVideoExport`), the
78
+ * `ViewportTransformLayer` camera, the `Cursor`, and TimeSource-driven
79
+ * step interactions. Without it, only step content renders:
80
+ * interactions' visible effects are absent from the MP4 and surfaces
81
+ * that size through the shell-geometry contract
82
+ * (`--scenar-shell-height`) collapse (scenar#35). Omitted, the
83
+ * composition keeps its prior bare-player output.
84
+ */
85
+ viewport?: RenderViewport;
86
+ /**
87
+ * Wrap each step's content in `<ScenarioStage>` (staged bundles — the
88
+ * `scenar pack --stage` presentation). Only meaningful with `viewport`.
89
+ */
90
+ stage?: boolean;
91
+ /**
92
+ * Render the cursor overlay for interactions. Defaults to true; only
93
+ * meaningful with `viewport`.
94
+ */
95
+ cursor?: boolean;
72
96
  }
73
97
 
74
98
  /**
@@ -120,6 +144,9 @@ export function ScenarioComposition<T>({
120
144
  useStaticFile: useStaticFileProp = true,
121
145
  captions = false,
122
146
  sfxSrcs = DEFAULT_SFX_SRCS,
147
+ viewport,
148
+ stage = false,
149
+ cursor = true,
123
150
  }: ScenarioCompositionProps<T>) {
124
151
  const videoConfig = useVideoConfig();
125
152
  const fps = fpsProp ?? videoConfig.fps ?? DEFAULT_FPS;
@@ -223,9 +250,25 @@ export function ScenarioComposition<T>({
223
250
  stepStartTimesMs={timeline.stepStartTimesMs}
224
251
  >
225
252
  <VideoExportProvider presenterMedia={presenterMedia}>
226
- <ScenarioPlayer bundle={playerBundle} captions={captions}>
227
- {children}
228
- </ScenarioPlayer>
253
+ {viewport ? (
254
+ // The full presentation stack (scenar#35). Rendered below the
255
+ // TimeSourceProvider on purpose: its useStepInteractions call
256
+ // must see the time source or it silently selects the
257
+ // wall-clock browser scheduler (the capture mount's lesson).
258
+ <PresentationStack
259
+ bundle={playerBundle}
260
+ captions={captions}
261
+ viewport={viewport}
262
+ stage={stage}
263
+ cursor={cursor}
264
+ >
265
+ {children}
266
+ </PresentationStack>
267
+ ) : (
268
+ <ScenarioPlayer bundle={playerBundle} captions={captions}>
269
+ {children}
270
+ </ScenarioPlayer>
271
+ )}
229
272
  </VideoExportProvider>
230
273
  </TimeSourceProvider>
231
274
 
package/src/index.ts CHANGED
@@ -20,6 +20,12 @@ export { applyTitleCards } from "@scenar/core";
20
20
  // Composition
21
21
  export { ScenarioComposition } from "./ScenarioComposition.js";
22
22
 
23
+ // The video-export presentation stack (scenar#35). The composition mounts
24
+ // it when given `viewport`; the pieces are exported for custom entries
25
+ // that assemble their own tree.
26
+ export type { RenderViewport } from "./PresentationStack.js";
27
+ export { PresentationStack, computeContainFit } from "./PresentationStack.js";
28
+
23
29
  // Timeline
24
30
  export type { ScenarioTimeline, AudioClip } from "./useScenarioTimeline.js";
25
31
  export {