@bendyline/squisq-editor-react 2.3.4 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,11 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { MediaProvider, Doc, Theme } from '@bendyline/squisq/schemas';
2
+ import { Doc, Theme, MediaProvider } from '@bendyline/squisq/schemas';
3
3
  import { ContentContainer } from '@bendyline/squisq/storage';
4
- import { NarrationTrace, NarrationAlignment, NarrationScript, NarrationTimingJsonV3, VadConfig } from '@bendyline/squisq/narration';
5
- import { KeyboardEvent, ReactNode } from 'react';
4
+ import { i as TeleprompterRecordingDeps, f as NarrationStageHandle, T as TeleprompterController, b as FloatingWindowHandle } from '../useNarrationStage-Bqo18PBw.js';
5
+ export { C as CanvasSink, D as DEFAULT_TELEPROMPTER_PREFS, F as FloatOpenOptions, a as FloatTier, c as FloatingWindowManager, M as MicAnalysisHandle, d as MicAnalysisStatus, N as NarrationRecorderController, e as NarrationRecorderState, g as NarrationTake, P as PrompterTransport, p as TELEPROMPTER_PREF_LIMITS, h as TeleprompterPrefs, U as UseNarrationStageOptions, j as createFloatingWindowManager, k as detectFloatTiers, q as normalizeTeleprompterPrefs, u as useFloatingWindow, l as useMicAnalysis, m as useNarrationRecorder, n as useNarrationStage, o as useTeleprompter, v as vadConfigForSensitivity } from '../useNarrationStage-Bqo18PBw.js';
6
+ import { NarrationTimingJsonV3, NarrationScript, NarrationAlignment } from '@bendyline/squisq/narration';
7
+ import { ReactNode } from 'react';
6
8
 
7
- /** Editor plumbing the recording flow needs; omit for prompter-only use. */
8
- interface TeleprompterRecordingDeps {
9
- mediaProvider: MediaProvider;
10
- container: ContentContainer | null;
11
- markdownSource: string;
12
- setMarkdownSource: (next: string) => void;
13
- bumpMediaRevision: () => void;
14
- }
15
9
  interface TeleprompterViewProps {
16
10
  doc: Doc | null;
17
11
  theme: Theme;
@@ -30,96 +24,45 @@ interface TeleprompterViewProps {
30
24
  }
31
25
  declare function TeleprompterView(props: TeleprompterViewProps): react_jsx_runtime.JSX.Element;
32
26
 
33
- /**
34
- * Mic capture + PCM transport for the teleprompter.
35
- *
36
- * getUserMedia → AudioContext → AudioWorklet tap → subscriber callbacks
37
- * with 1024-sample Float32Array hops on the main thread. The DSP itself
38
- * lives in core (`@bendyline/squisq/narration`) — this hook only moves
39
- * samples. Falls back to a ScriptProcessorNode when `audioWorklet` is
40
- * unavailable (same push model, so pacing still isn't tied to rAF).
41
- */
42
- type MicAnalysisStatus = 'idle' | 'starting' | 'live' | 'error';
43
- type PcmHopListener = (pcm: Float32Array, audioTimeSec: number) => void;
44
- interface MicAnalysisHandle {
45
- status: MicAnalysisStatus;
46
- error: Error | null;
47
- /** The live mic stream (feeds the narration recorder too), or null. */
48
- stream: MediaStream | null;
49
- /** AudioContext sample rate once live. */
50
- sampleRate: number | null;
51
- /** Known audio inputs (labels appear after the first grant). */
52
- devices: MediaDeviceInfo[];
27
+ interface NarrationStageProps {
28
+ stage: NarrationStageHandle;
29
+ theme: Theme;
53
30
  /**
54
- * Start (or restart) capture. Resolves with the live stream — callers
55
- * that need it immediately must use the return value, not the `stream`
56
- * state field, which only updates on the NEXT render (stale-closure
57
- * hazard right after an await). Null on failure/supersession.
31
+ * Optional audience-window portal owned by the editor's Presentation mode.
32
+ * The main controller remains authoritative; only this live surface is
33
+ * mirrored into the target.
58
34
  */
59
- start: (deviceId: string | null) => Promise<MediaStream | null>;
60
- stop: () => void;
61
- /** Subscribe to PCM hops; returns an unsubscribe. */
62
- subscribeHop: (listener: PcmHopListener) => () => void;
63
- }
64
- declare function useMicAnalysis(): MicAnalysisHandle;
65
-
66
- /**
67
- * Narration recorder for the teleprompter an in-place (no modal)
68
- * capture flow.
69
- *
70
- * Audio records THE MIC ANALYSIS STREAM (what paces the prompter is
71
- * exactly what lands in the take); the optional camera is a separate
72
- * video-only capture whose start skew vs the audio recorder is measured
73
- * and persisted (`cameraOffsetSec`) — the audio file is the doc clock,
74
- * so camera skew never affects narration timing. While recording, a
75
- * sparse live trace of the prompter position is sampled; after stop,
76
- * the take is decoded and run through core's offline aligner
77
- * (`alignNarration`) to produce word/block timestamps for the sidecar.
78
- * Decode/alignment failure degrades gracefully: saving still works,
79
- * just without re-timing.
80
- */
81
-
82
- type NarrationRecorderState = 'idle' | 'starting' | 'recording' | 'processing' | 'review' | 'saving' | 'error';
83
- interface NarrationTake {
84
- audioBlob: Blob;
85
- audioMime: string;
86
- audioExt: string;
87
- cameraBlob: Blob | null;
88
- cameraMime: string | null;
89
- cameraExt: string | null;
90
- durationSec: number;
91
- cameraOffsetSec: number | undefined;
92
- trace: NarrationTrace;
93
- alignment: NarrationAlignment | null;
94
- script: NarrationScript;
95
- }
96
- interface UseNarrationRecorderOptions {
97
- mic: MicAnalysisHandle;
98
- getScript: () => NarrationScript | null;
99
- /** Live prompter position, sampled into the trace while recording. */
100
- getWordPos: () => number;
101
- getMicDeviceId: () => string | null;
102
- /** Fired when capture actually starts (View starts the prompter). */
103
- onRecordingStart?: () => void;
104
- onRecordingStop?: () => void;
105
- }
106
- interface NarrationRecorderController {
107
- state: NarrationRecorderState;
108
- error: Error | null;
109
- withCamera: boolean;
110
- setWithCamera: (on: boolean) => void;
111
- /** Live camera stream for the self-view while recording. */
112
- cameraStream: MediaStream | null;
113
- take: NarrationTake | null;
114
- start: () => Promise<void>;
115
- stop: () => Promise<void>;
116
- retake: () => void;
117
- discard: () => void;
118
- /** Transition into/out of 'saving'; the View owns the actual I/O. */
119
- beginSave: () => void;
120
- finishSave: (ok: boolean, error?: Error) => void;
35
+ presentationTarget?: HTMLElement | null;
36
+ /**
37
+ * Camera corner-overlay on the prompter. Hosts that show the camera stream
38
+ * in their own chrome (the recorder dialog's preview box) pass false.
39
+ */
40
+ showSelfView?: boolean;
41
+ /**
42
+ * The record slot's own "camera" checkbox. Hosts whose chrome already owns
43
+ * the camera toggle (the recorder dialog's Camera source button) pass false.
44
+ */
45
+ showCameraToggleInRecordSlot?: boolean;
46
+ /**
47
+ * The whole Record/Stop slot in the control rail. Hosts that surface their
48
+ * own record affordance (the recorder dialog's Start preview Record flow)
49
+ * pass false; the review bar still renders.
50
+ */
51
+ showRecordSlot?: boolean;
52
+ /**
53
+ * The control rail's Start/Pause transport button. Hosts whose record flow
54
+ * drives the prompter (recording start = prompter start) pass false;
55
+ * Restart and Countdown remain.
56
+ */
57
+ showTransportPlay?: boolean;
58
+ /**
59
+ * The review bar's Save/Retake/Discard buttons. Hosts that render those in
60
+ * their own action row (the recorder dialog) pass false; the take info and
61
+ * the prompter-scrubbing audio playback stay.
62
+ */
63
+ showReviewActions?: boolean;
121
64
  }
122
- declare function useNarrationRecorder(options: UseNarrationRecorderOptions): NarrationRecorderController;
65
+ declare function NarrationStage(props: NarrationStageProps): react_jsx_runtime.JSX.Element;
123
66
 
124
67
  /**
125
68
  * Pure save-plan builder for a narration take, plus its executor.
@@ -147,6 +90,8 @@ interface NarrationSavePlanArgs {
147
90
  cameraExt: string | null;
148
91
  baseWpm: number;
149
92
  cameraOffsetSec?: number;
93
+ /** User-chosen filename base for the audio file; default timestamped `narration-…`. */
94
+ audioBasename?: string;
150
95
  }
151
96
  interface NarrationSavePlan {
152
97
  audioRelativeName: string;
@@ -265,162 +210,28 @@ interface TeleprompterSurfaceProps {
265
210
  theme: Theme;
266
211
  /** Hide block markers / tighten padding for small float windows. */
267
212
  compact?: boolean;
268
- /** Click a word to jump the prompter there. */
213
+ /** Double-click a word to jump the prompter there. */
269
214
  onSeekToken?: (tokenIndex: number) => void;
215
+ /** Move by whole words in response to direct surface input. */
216
+ onNudge?: (deltaTokens: number) => void;
217
+ /** Pause or resume voice/constant-rate automatic advancement. */
218
+ onToggleAutoAdvance?: () => void;
270
219
  }
271
- declare function TeleprompterSurface({ script, wordPos, fontSizePx, mirrored, lineGuide, countdownRemaining, recordingIndicator, theme, compact, onSeekToken, }: TeleprompterSurfaceProps): react_jsx_runtime.JSX.Element;
272
-
273
- /**
274
- * Shared types for the Narrate (teleprompter) display mode.
275
- */
276
- interface TeleprompterPrefs {
277
- /** Prompter type size in px (28–96). */
278
- fontSizePx: number;
279
- /** Beam-splitter mirror flip. */
280
- mirrored: boolean;
281
- /** Base speaking rate in words per minute (80–260). */
282
- baseWpm: number;
283
- /** Voice-adaptive pacing on/off; off = constant-rate manual mode. */
284
- voiceTracking: boolean;
285
- /** VAD sensitivity 0–1 (0.5 = engine defaults). */
286
- vadSensitivity: number;
287
- /** Countdown before the prompter starts rolling. */
288
- countdownSec: 0 | 3 | 5 | 10;
289
- /** Eye-line chevrons + focus band. */
290
- lineGuide: boolean;
291
- /** Preferred mic device id (null = system default). */
292
- micDeviceId: string | null;
293
- }
294
- declare const DEFAULT_TELEPROMPTER_PREFS: TeleprompterPrefs;
295
- type PrompterTransport = 'stopped' | 'countdown' | 'rolling' | 'paused' | 'finished';
296
- /** Floating-surface tier, best first. */
297
- type FloatTier = 'document-pip' | 'video-pip' | 'popup' | 'docked';
298
-
299
- /**
300
- * The teleprompter controller — single source of truth, always in the
301
- * MAIN window (floats are render targets only).
302
- *
303
- * Loop-ownership rule: **the audio worklet owns time; the main window
304
- * owns state; the visible surface owns pixels.** Voice-mode position
305
- * advances on worklet PCM hops (immune to rAF/timer throttling while
306
- * the browser is occluded by recording software); manual constant-rate
307
- * mode uses rAF and is documented as best-effort under occlusion. React
308
- * state publishes at ~15 Hz; per-hop subscribers (`subscribeTick`)
309
- * exist for the video-PiP canvas pump.
310
- */
311
-
312
- interface TeleprompterController {
313
- script: NarrationScript | null;
314
- transport: PrompterTransport;
315
- countdownRemaining: number | null;
316
- /** Fractional word position (published ~15 Hz). */
317
- wordPos: number;
318
- /** Smoothed mic level 0–1 for the meter. */
319
- micLevel: number;
320
- /** VAD flag for the meter tint. */
321
- voiceActive: boolean;
322
- mic: MicAnalysisHandle;
323
- prefs: TeleprompterPrefs;
324
- setPrefs: (patch: Partial<TeleprompterPrefs>) => void;
325
- play: () => void;
326
- pause: () => void;
327
- restart: () => void;
328
- /** Move the prompter by whole tokens and re-anchor voice tracking. */
329
- nudge: (deltaTokens: number) => void;
330
- /** Jump to an absolute token index (e.g. click a block marker). */
331
- seekToToken: (tokenIndex: number) => void;
332
- /** Per-analysis-tick subscription (video-PiP pump). Not throttled. */
333
- subscribeTick: (cb: (wordPos: number) => void) => () => void;
334
- handleKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
335
- }
336
- /** Map the 0–1 sensitivity pref onto VAD thresholds (0.5 = engine defaults). */
337
- declare function vadConfigForSensitivity(sensitivity: number): Partial<VadConfig>;
338
- declare function useTeleprompter(opts: {
339
- doc: Doc | null;
340
- }): TeleprompterController;
341
-
342
- /**
343
- * Floating-teleprompter window manager — framework-free.
344
- *
345
- * Capability ladder, best first:
346
- * 1. `document-pip` — Document Picture-in-Picture (Chromium 116+,
347
- * Firefox 151+): a true always-on-top window hosting live DOM; the
348
- * React surface portals into it.
349
- * 2. `video-pip` — canvas → `captureStream(0)` → `<video>` →
350
- * `requestPictureInPicture()` (Safari's only always-on-top path;
351
- * `webkitSetPresentationMode` fallback). Read-only: the main
352
- * window draws frames and calls `requestFrame()` on analysis
353
- * ticks — never rAF, which throttles under occlusion.
354
- * 3. `popup` — `window.open` (positionable, not always-on-top).
355
- * 4. `docked` — no float.
356
- *
357
- * Every tier is feature-detected at open time and falls through to the
358
- * next on ANY failure. All interactive state stays in the main window;
359
- * floats are render targets only.
360
- */
361
-
362
- interface FloatOpenOptions {
363
- width: number;
364
- height: number;
365
- /** Try this tier first; the ladder continues below it on failure. */
366
- preferredTier?: FloatTier;
367
- title: string;
368
- }
369
- interface CanvasSink {
370
- canvas: HTMLCanvasElement;
371
- width: number;
372
- height: number;
373
- /** Push the freshly drawn canvas frame into the PiP video. */
374
- requestFrame: () => void;
375
- }
376
- type FloatEvent = 'closed' | 'tierchange';
377
- interface FloatingWindowManager {
378
- readonly tier: FloatTier;
379
- readonly isOpen: boolean;
380
- /** Resolves with the tier that actually opened ('docked' if none could). */
381
- open(opts: FloatOpenOptions): Promise<FloatTier>;
382
- /** Idempotent; restores docked and emits 'closed'. */
383
- close(): void;
384
- /** Portal container for 'document-pip' | 'popup'; null otherwise. */
385
- getPortalTarget(): HTMLElement | null;
386
- /** Canvas sink for 'video-pip'; null otherwise. */
387
- getCanvasSink(): CanvasSink | null;
388
- on(event: FloatEvent, cb: (tier: FloatTier) => void): () => void;
389
- /** Tear everything down (unmount); like close() but silent-safe. */
390
- dispose(): void;
391
- }
392
- /** Feature-detect the available float tiers, best first. */
393
- declare function detectFloatTiers(): FloatTier[];
394
- declare function createFloatingWindowManager(deps: {
395
- styleCss: string;
396
- }): FloatingWindowManager;
397
-
398
- /**
399
- * React binding over {@link createFloatingWindowManager}: exposes the
400
- * current tier + portal target as state and guarantees the float closes
401
- * when the owning view unmounts (mode switch, Use-tab exit, shell
402
- * teardown).
403
- */
404
-
405
- interface FloatingWindowHandle {
406
- tier: FloatTier;
407
- isOpen: boolean;
408
- /** Non-docked tiers this browser supports, best first. */
409
- supportedTiers: FloatTier[];
410
- portalTarget: HTMLElement | null;
411
- canvasSink: CanvasSink | null;
412
- open: (preferredTier?: FloatTier) => Promise<void>;
413
- close: () => void;
414
- }
415
- declare function useFloatingWindow(styleCss: string): FloatingWindowHandle;
220
+ declare function TeleprompterSurface({ script, wordPos, fontSizePx, mirrored, lineGuide, countdownRemaining, recordingIndicator, theme, compact, onSeekToken, onNudge, onToggleAutoAdvance, }: TeleprompterSurfaceProps): react_jsx_runtime.JSX.Element;
416
221
 
417
222
  interface TeleprompterControlsProps {
418
223
  controller: TeleprompterController;
419
224
  float: FloatingWindowHandle;
420
225
  /** Phase B mounts the narration Record button here. */
421
226
  recordSlot?: ReactNode;
227
+ /**
228
+ * Show the Start/Pause transport button (default true). Hosts whose own
229
+ * chrome drives playback — the Record media dialog starts the prompter
230
+ * through its Record button — pass false; Restart and Countdown stay.
231
+ */
232
+ showPlayPause?: boolean;
422
233
  }
423
- declare function TeleprompterControls({ controller, float, recordSlot }: TeleprompterControlsProps): react_jsx_runtime.JSX.Element;
234
+ declare function TeleprompterControls({ controller, float, recordSlot, showPlayPause, }: TeleprompterControlsProps): react_jsx_runtime.JSX.Element;
424
235
 
425
236
  /**
426
237
  * The PCM tap AudioWorklet, shipped as an inline source string.
@@ -494,4 +305,4 @@ declare function prompterVarsFromTheme(theme: Theme): Record<string, string>;
494
305
  declare function ensureTeleprompterStyles(doc: Document): void;
495
306
  declare const TELEPROMPTER_CSS = "\n.squisq-teleprompter-root {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 0;\n background: var(--squisq-bg, #f5f5f5);\n outline: none;\n}\n.squisq-teleprompter-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n display: flex;\n}\n\n.squisq-teleprompter-surface {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background: var(--squisq-prompter-bg, #101014);\n color: var(--squisq-prompter-text, #f5f5f2);\n font-family: var(--squisq-prompter-font, system-ui, sans-serif);\n user-select: none;\n}\n.squisq-teleprompter-flip {\n position: absolute;\n inset: 0;\n}\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-flip {\n transform: scaleX(-1);\n}\n.squisq-teleprompter-scroll {\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n will-change: transform;\n padding: 40vh 8% 60vh;\n box-sizing: border-box;\n}\n.squisq-teleprompter-line-guide {\n position: absolute;\n left: 0;\n right: 0;\n pointer-events: none;\n z-index: 2;\n}\n.squisq-teleprompter-line-guide::before,\n.squisq-teleprompter-line-guide::after {\n content: '';\n position: absolute;\n top: 50%;\n border: 9px solid transparent;\n transform: translateY(-50%);\n}\n.squisq-teleprompter-line-guide::before {\n left: 6px;\n border-left-color: var(--squisq-prompter-accent, #e8b64c);\n}\n.squisq-teleprompter-line-guide::after {\n right: 6px;\n border-right-color: var(--squisq-prompter-accent, #e8b64c);\n}\n.squisq-teleprompter-guide-band {\n position: absolute;\n left: 0;\n right: 0;\n background: color-mix(in srgb, var(--squisq-prompter-accent, #e8b64c) 9%, transparent);\n pointer-events: none;\n z-index: 1;\n}\n\n.squisq-teleprompter-block-marker {\n display: block;\n margin: 1.1em 0 0.35em;\n font-size: 0.38em;\n font-weight: 600;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--squisq-prompter-muted, #9a9aa0);\n border-top: 1px solid color-mix(in srgb, var(--squisq-prompter-muted, #9a9aa0) 35%, transparent);\n padding-top: 0.6em;\n}\n.squisq-teleprompter-para {\n margin: 0 0 0.55em;\n line-height: 1.4;\n font-weight: 500;\n}\n.squisq-teleprompter-word {\n opacity: 0.45;\n transition: color 0.1s linear, opacity 0.1s linear;\n}\n.squisq-teleprompter-word--read {\n opacity: 0.9;\n}\n.squisq-teleprompter-word--active {\n opacity: 1;\n font-weight: 800;\n color: var(--squisq-prompter-accent, #e8b64c);\n}\n\n.squisq-teleprompter-countdown {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 3;\n background: color-mix(in srgb, var(--squisq-prompter-bg, #101014) 72%, transparent);\n}\n.squisq-teleprompter-countdown-digit {\n font-size: 18vmin;\n font-weight: 800;\n color: var(--squisq-prompter-accent, #e8b64c);\n animation: squisq-prompter-pulse 1s ease-in-out infinite;\n}\n.squisq-teleprompter-recdot {\n position: absolute;\n top: 14px;\n right: 16px;\n z-index: 4;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #e5484d;\n box-shadow: 0 0 0 3px color-mix(in srgb, #e5484d 30%, transparent);\n animation: squisq-prompter-pulse 1.4s ease-in-out infinite;\n}\n/* Counter-flip overlays so they stay readable in mirror mode. */\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-countdown,\n.squisq-teleprompter-surface--mirrored .squisq-teleprompter-recdot {\n transform: scaleX(-1);\n}\n\n.squisq-teleprompter-selfview {\n position: absolute;\n right: 14px;\n bottom: 14px;\n width: 160px;\n border-radius: 8px;\n border: 2px solid color-mix(in srgb, #e5484d 60%, transparent);\n z-index: 5;\n background: #000;\n}\n\n.squisq-teleprompter-review {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n border-top: 1px solid var(--squisq-border, #e5e7eb);\n background: var(--squisq-surface, var(--squisq-bg, #fff));\n color: var(--squisq-text, #111827);\n font-size: 12.5px;\n}\n.squisq-teleprompter-review audio {\n height: 28px;\n max-width: 260px;\n}\n.squisq-teleprompter-review button {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n\n.squisq-teleprompter-float-note {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n color: var(--squisq-text-muted, #6b7280);\n font-size: 14px;\n}\n\n.squisq-teleprompter-controls {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 10px;\n padding: 8px 12px;\n border-top: 1px solid var(--squisq-border, #e5e7eb);\n background: var(--squisq-surface, var(--squisq-bg, #fff));\n color: var(--squisq-text, #111827);\n font-size: 12.5px;\n}\n.squisq-teleprompter-controls .squisq-teleprompter-group {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n white-space: nowrap;\n}\n.squisq-teleprompter-controls button {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.squisq-teleprompter-controls button:hover {\n background: var(--squisq-surface-hover, #f3f4f6);\n}\n.squisq-teleprompter-controls button[aria-pressed='true'] {\n background: var(--squisq-text, #111827);\n color: var(--squisq-bg, #fff);\n border-color: var(--squisq-text, #111827);\n}\n.squisq-teleprompter-controls select,\n.squisq-teleprompter-controls input[type='range'] {\n font: inherit;\n color: inherit;\n background: var(--squisq-input-bg, #fff);\n border: 1px solid var(--squisq-border, #d1d5db);\n border-radius: 6px;\n max-width: 170px;\n}\n.squisq-teleprompter-controls input[type='range'] {\n border: none;\n background: transparent;\n width: 110px;\n}\n.squisq-teleprompter-meter {\n position: relative;\n width: 64px;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: var(--squisq-border, #e5e7eb);\n}\n.squisq-teleprompter-meter-fill {\n position: absolute;\n inset: 0 auto 0 0;\n background: #9ca3af;\n transition: width 0.08s linear;\n}\n.squisq-teleprompter-meter--voice .squisq-teleprompter-meter-fill {\n background: #30a46c;\n}\n\n@keyframes squisq-prompter-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.45; }\n}\n@media (prefers-reduced-motion: reduce) {\n .squisq-teleprompter-countdown-digit,\n .squisq-teleprompter-recdot {\n animation: none;\n }\n .squisq-teleprompter-word {\n transition: none;\n }\n}\n";
496
307
 
497
- export { type CanvasSink, DEFAULT_TELEPROMPTER_PREFS, EYE_LINE_FRACTION, type FloatOpenOptions, type FloatTier, type FloatingWindowHandle, type FloatingWindowManager, type MicAnalysisHandle, type MicAnalysisStatus, type NarrationRecorderController, type NarrationRecorderState, type NarrationSavePlan, type NarrationSaveResult, type NarrationTake, PCM_WORKLET_NAME, PCM_WORKLET_SOURCE, type PrompterTransport, TELEPROMPTER_CSS, type TeleprompterController, TeleprompterControls, type TeleprompterControlsProps, type TeleprompterPrefs, type TeleprompterRecordingDeps, TeleprompterSurface, type TeleprompterSurfaceProps, TeleprompterView, type TeleprompterViewProps, type TokenLineMap, buildNarrationSavePlan, cameraVideoLine, createFloatingWindowManager, detectFloatTiers, ensureTeleprompterStyles, executeNarrationSave, insertNarrationPreamble, measureTokenLines, narrationAnnotationLine, prompterVarsFromTheme, registerPcmWorklet, stepScroll, targetOffsetFor, useFloatingWindow, useMicAnalysis, useNarrationRecorder, useTeleprompter, vadConfigForSensitivity };
308
+ export { EYE_LINE_FRACTION, FloatingWindowHandle, type NarrationSavePlan, type NarrationSaveResult, NarrationStage, NarrationStageHandle, type NarrationStageProps, PCM_WORKLET_NAME, PCM_WORKLET_SOURCE, TELEPROMPTER_CSS, TeleprompterController, TeleprompterControls, type TeleprompterControlsProps, TeleprompterRecordingDeps, TeleprompterSurface, type TeleprompterSurfaceProps, TeleprompterView, type TeleprompterViewProps, type TokenLineMap, buildNarrationSavePlan, cameraVideoLine, ensureTeleprompterStyles, executeNarrationSave, insertNarrationPreamble, measureTokenLines, narrationAnnotationLine, prompterVarsFromTheme, registerPcmWorklet, stepScroll, targetOffsetFor };
@@ -1,12 +1,16 @@
1
+ import {
2
+ TeleprompterView
3
+ } from "../chunk-V6Z7GG55.js";
1
4
  import {
2
5
  DEFAULT_TELEPROMPTER_PREFS,
3
6
  EYE_LINE_FRACTION,
7
+ NarrationStage,
4
8
  PCM_WORKLET_NAME,
5
9
  PCM_WORKLET_SOURCE,
6
10
  TELEPROMPTER_CSS,
11
+ TELEPROMPTER_PREF_LIMITS,
7
12
  TeleprompterControls,
8
13
  TeleprompterSurface,
9
- TeleprompterView,
10
14
  buildNarrationSavePlan,
11
15
  cameraVideoLine,
12
16
  createFloatingWindowManager,
@@ -16,6 +20,7 @@ import {
16
20
  insertNarrationPreamble,
17
21
  measureTokenLines,
18
22
  narrationAnnotationLine,
23
+ normalizeTeleprompterPrefs,
19
24
  prompterVarsFromTheme,
20
25
  registerPcmWorklet,
21
26
  stepScroll,
@@ -23,16 +28,18 @@ import {
23
28
  useFloatingWindow,
24
29
  useMicAnalysis,
25
30
  useNarrationRecorder,
31
+ useNarrationStage,
26
32
  useTeleprompter,
27
33
  vadConfigForSensitivity
28
- } from "../chunk-5JMHFAVW.js";
29
- import "../chunk-5Q4JN4I5.js";
34
+ } from "../chunk-PDCKJCOS.js";
30
35
  export {
31
36
  DEFAULT_TELEPROMPTER_PREFS,
32
37
  EYE_LINE_FRACTION,
38
+ NarrationStage,
33
39
  PCM_WORKLET_NAME,
34
40
  PCM_WORKLET_SOURCE,
35
41
  TELEPROMPTER_CSS,
42
+ TELEPROMPTER_PREF_LIMITS,
36
43
  TeleprompterControls,
37
44
  TeleprompterSurface,
38
45
  TeleprompterView,
@@ -45,6 +52,7 @@ export {
45
52
  insertNarrationPreamble,
46
53
  measureTokenLines,
47
54
  narrationAnnotationLine,
55
+ normalizeTeleprompterPrefs,
48
56
  prompterVarsFromTheme,
49
57
  registerPcmWorklet,
50
58
  stepScroll,
@@ -52,6 +60,7 @@ export {
52
60
  useFloatingWindow,
53
61
  useMicAnalysis,
54
62
  useNarrationRecorder,
63
+ useNarrationStage,
55
64
  useTeleprompter,
56
65
  vadConfigForSensitivity
57
66
  };