@bendyline/squisq-editor-react 2.11.16 → 2.11.17

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.
@@ -19,7 +19,7 @@ import {
19
19
  timingPathFor,
20
20
  useNarrationStage,
21
21
  useStreamPreview
22
- } from "./chunk-34FRFDQ6.js";
22
+ } from "./chunk-XZVMKR5Q.js";
23
23
 
24
24
  // src/recorder/sources/screenRegion.ts
25
25
  function validateScreenCaptureRegion(region, width, height) {
@@ -1818,8 +1818,9 @@ function isExpandedPanel(mode) {
1818
1818
  return mode !== "none";
1819
1819
  }
1820
1820
  function panelModeAfterToggle(current, target, checked) {
1821
- if (checked) return target;
1822
- return current === target ? "none" : current;
1821
+ const narration = target === "narration" ? checked : current === "narration" || current === "both";
1822
+ const slides = target === "slides" ? checked : current === "slides" || current === "both";
1823
+ return narration ? slides ? "both" : "narration" : slides ? "slides" : "none";
1823
1824
  }
1824
1825
  function clampSlideIndex(index, count) {
1825
1826
  if (count <= 0) return 0;
@@ -2695,8 +2696,9 @@ function RecorderModal({
2695
2696
  const [cameraPlaybackUrl, setCameraPlaybackUrl] = useState3(null);
2696
2697
  const [playbackPositionMs, setPlaybackPositionMs] = useState3(0);
2697
2698
  const [panelMode, setPanelMode] = useState3("none");
2698
- const narrationOn = panelMode === "narration";
2699
- const slidesOn = panelMode === "slides";
2699
+ const narrationOn = panelMode === "narration" || panelMode === "both";
2700
+ const slidesOn = panelMode === "slides" || panelMode === "both";
2701
+ const narrationRecording = narrationOn && Boolean(narration?.recording);
2700
2702
  const expanded = isExpandedPanel(panelMode);
2701
2703
  const [narrationRequesting, setNarrationRequesting] = useState3(false);
2702
2704
  const [narrationPreview, setNarrationPreview] = useState3(null);
@@ -2757,13 +2759,16 @@ function RecorderModal({
2757
2759
  systemAudio: canIncludeSystemAudio ? includeSystemAudio : false
2758
2760
  });
2759
2761
  const filenameSeed = recordingFilenameSeed(source, recorder.stream, micOn || includeSystemAudio);
2760
- const narrationAvailable = Boolean(narration && narration.recording);
2762
+ const narrationAvailable = Boolean(narration?.doc);
2761
2763
  const basenameRef = useRef2(basename);
2762
2764
  basenameRef.current = basename;
2763
2765
  const stage = useNarrationStage({
2764
2766
  doc: narration?.doc ?? null,
2765
2767
  maxRecordingBytes,
2766
2768
  recording: narration?.recording ?? null,
2769
+ // A prompter-only stage borrows the take's audio. It never requests or
2770
+ // stops capture tracks; the recorder remains their sole owner.
2771
+ analysisStream: narration?.recording ? void 0 : narrationOn && recorder.state !== "stopped" ? isDual ? recorder.camera?.stream ?? null : recorder.stream : null,
2767
2772
  getAudioBasename: () => basenameRef.current.trim() || void 0,
2768
2773
  micConstraints: audioConstraints,
2769
2774
  cameraConstraints,
@@ -2818,35 +2823,41 @@ function RecorderModal({
2818
2823
  useEffect4(() => {
2819
2824
  setAdvanceLog(EMPTY_ADVANCE_LOG);
2820
2825
  setSlideIndex(0);
2821
- }, [panelMode, slideDeckKey]);
2826
+ }, [slidesOn, slideDeckKey]);
2827
+ useEffect4(() => {
2828
+ if (!narrationOn || narrationRecording) return;
2829
+ const controller = stageRef.current.controller;
2830
+ if (recorder.state === "recording") controller.play();
2831
+ else controller.pause();
2832
+ }, [narrationOn, narrationRecording, recorder.state]);
2822
2833
  const handleDeviceSettingsChange = useCallback4(
2823
2834
  (next) => {
2824
2835
  setDeviceSettings(next);
2825
- if (narrationOn) {
2836
+ if (narrationRecording) {
2826
2837
  stageRef.current.controller.setPrefs({
2827
2838
  micDeviceId: next.audio.deviceId || null
2828
2839
  });
2829
2840
  }
2830
2841
  },
2831
- [narrationOn]
2842
+ [narrationRecording]
2832
2843
  );
2833
2844
  const narrationMicDeviceId = stage.controller.prefs.micDeviceId ?? "";
2834
2845
  useEffect4(() => {
2835
- if (!narrationOn) return;
2846
+ if (!narrationRecording) return;
2836
2847
  setDeviceSettings(
2837
2848
  (current) => current.audio.deviceId === narrationMicDeviceId ? current : {
2838
2849
  ...current,
2839
2850
  audio: { ...current.audio, deviceId: narrationMicDeviceId }
2840
2851
  }
2841
2852
  );
2842
- }, [narrationMicDeviceId, narrationOn]);
2853
+ }, [narrationMicDeviceId, narrationRecording]);
2843
2854
  const prevNarrationOnRef = useRef2(narrationOn);
2844
2855
  useEffect4(() => {
2845
2856
  const was = prevNarrationOnRef.current;
2846
2857
  prevNarrationOnRef.current = narrationOn;
2847
2858
  if (was === narrationOn) return;
2848
2859
  if (narrationOn) {
2849
- recorder.cancel();
2860
+ if (narrationRecording) recorder.cancel();
2850
2861
  } else {
2851
2862
  const s = stageRef.current;
2852
2863
  s.controller.pause();
@@ -2855,7 +2866,7 @@ function RecorderModal({
2855
2866
  }
2856
2867
  if (s.float.isOpen) s.float.close();
2857
2868
  }
2858
- }, [narrationOn, recorder]);
2869
+ }, [narrationOn, narrationRecording, recorder]);
2859
2870
  useEffect4(() => {
2860
2871
  if (!narrationOn) return;
2861
2872
  const onKey = (event) => {
@@ -2867,7 +2878,7 @@ function RecorderModal({
2867
2878
  return () => document.removeEventListener("keydown", onKey, true);
2868
2879
  }, [narrationOn]);
2869
2880
  const narrationMicLive = stage.controller.mic.status === "live";
2870
- const narrationPreviewWanted = narrationOn && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
2881
+ const narrationPreviewWanted = narrationRecording && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
2871
2882
  const narrationPreviewRef = useRef2(null);
2872
2883
  useEffect4(() => {
2873
2884
  const existing = narrationPreviewRef.current;
@@ -2916,20 +2927,20 @@ function RecorderModal({
2916
2927
  const handleNarrationRecord = useCallback4(() => {
2917
2928
  void stageRef.current.recorder.start();
2918
2929
  }, []);
2919
- const narrationCameraStream = narrationOn ? stage.recorder.cameraStream ?? narrationPreview : null;
2920
- const activePrimaryStream = narrationOn ? stage.controller.mic.stream : recorder.stream;
2921
- const activeCameraStream = narrationOn ? narrationCameraStream : recorder.camera?.stream ?? null;
2930
+ const narrationCameraStream = narrationRecording ? stage.recorder.cameraStream ?? narrationPreview : null;
2931
+ const activePrimaryStream = narrationRecording ? stage.controller.mic.stream : recorder.stream;
2932
+ const activeCameraStream = narrationRecording ? narrationCameraStream : recorder.camera?.stream ?? null;
2922
2933
  const { devices: mediaDevices, refresh: refreshMediaDevices } = useMediaDevices();
2923
2934
  useEffect4(() => {
2924
2935
  if (activePrimaryStream || activeCameraStream) refreshMediaDevices();
2925
2936
  }, [activeCameraStream, activePrimaryStream, refreshMediaDevices]);
2926
2937
  useStreamPreview(
2927
2938
  previewRef,
2928
- narrationOn ? narrationCameraStream : recorder.state === "stopped" ? null : recorder.stream
2939
+ narrationRecording ? narrationCameraStream : recorder.state === "stopped" ? null : recorder.stream
2929
2940
  );
2930
2941
  useStreamPreview(
2931
2942
  cameraPreviewRef,
2932
- !narrationOn && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
2943
+ !narrationRecording && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
2933
2944
  );
2934
2945
  useEffect4(() => {
2935
2946
  setPlaybackPositionMs(0);
@@ -2967,7 +2978,7 @@ function RecorderModal({
2967
2978
  }
2968
2979
  }, [captureKey, recorder]);
2969
2980
  const handleClose = useCallback4(() => {
2970
- if (!narrationOn) {
2981
+ if (!narrationRecording) {
2971
2982
  if (isSaving || recorder.state === "stopping" || recorder.state === "requesting") return;
2972
2983
  if ((recorder.blob || recorder.camera?.blob || recorder.state === "recording") && !window.confirm(
2973
2984
  "Discard this recording? Download or save it before closing to keep a copy."
@@ -2975,7 +2986,7 @@ function RecorderModal({
2975
2986
  return;
2976
2987
  }
2977
2988
  const s = stageRef.current;
2978
- if (closeNeedsConfirm(narrationOn, s.recorder.state, s.recorder.take !== null)) {
2989
+ if (closeNeedsConfirm(narrationRecording, s.recorder.state, s.recorder.take !== null)) {
2979
2990
  if (!window.confirm("Discard the current narration take?")) return;
2980
2991
  s.handleDiscard();
2981
2992
  }
@@ -2987,7 +2998,7 @@ function RecorderModal({
2987
2998
  }
2988
2999
  recorder.cancel();
2989
3000
  onClose();
2990
- }, [narrationOn, recorder, onClose, isSaving]);
3001
+ }, [narrationOn, narrationRecording, recorder, onClose, isSaving]);
2991
3002
  useModalDialog({
2992
3003
  rootRef: overlayRef,
2993
3004
  dialogRef,
@@ -3197,7 +3208,8 @@ function RecorderModal({
3197
3208
  dualSaveProgressRef.current = {};
3198
3209
  setAdvanceLog(EMPTY_ADVANCE_LOG);
3199
3210
  recorder.reset();
3200
- }, [recorder]);
3211
+ if (narrationOn) stageRef.current.controller.restart();
3212
+ }, [narrationOn, recorder]);
3201
3213
  const handlePlaybackTimeUpdate = useCallback4(
3202
3214
  (media) => {
3203
3215
  const currentMs = Number.isFinite(media.currentTime) ? media.currentTime * 1e3 : 0;
@@ -3234,7 +3246,7 @@ function RecorderModal({
3234
3246
  );
3235
3247
  const timingDownloadFilename = `${downloadFilename}.timing.json`;
3236
3248
  useEffect4(() => {
3237
- if (narrationOn || !canSave) {
3249
+ if (narrationRecording || !canSave) {
3238
3250
  setTimingDownloadUrl(null);
3239
3251
  return;
3240
3252
  }
@@ -3250,7 +3262,7 @@ function RecorderModal({
3250
3262
  setTimingDownloadUrl(url);
3251
3263
  return () => URL.revokeObjectURL(url);
3252
3264
  }, [
3253
- narrationOn,
3265
+ narrationRecording,
3254
3266
  canSave,
3255
3267
  recorder.durationMs,
3256
3268
  slidesOn,
@@ -3293,9 +3305,9 @@ function RecorderModal({
3293
3305
  recorder.blob !== null || recorder.camera?.blob != null,
3294
3306
  stage.recorder.state
3295
3307
  );
3296
- const deviceSettingsLocked = narrationOn ? !narrationRecorderIdle : togglesLocked;
3297
- const microphoneEnabled = narrationOn ? true : micOn;
3298
- const cameraEnabled = narrationOn ? stage.recorder.withCamera : cameraOn;
3308
+ const deviceSettingsLocked = narrationRecording ? !narrationRecorderIdle : togglesLocked;
3309
+ const microphoneEnabled = narrationRecording ? true : micOn;
3310
+ const cameraEnabled = narrationRecording ? stage.recorder.withCamera : cameraOn;
3299
3311
  const narrationToggleFor = (key) => {
3300
3312
  switch (key) {
3301
3313
  case "mic":
@@ -3359,442 +3371,451 @@ function RecorderModal({
3359
3371
  children: [
3360
3372
  /* @__PURE__ */ jsx6("h2", { id: headingId, style: titleStyle, children: title }),
3361
3373
  /* @__PURE__ */ jsxs5("div", { style: expanded ? bodyRowStyle : void 0, children: [
3362
- /* @__PURE__ */ jsxs5("div", { style: expanded ? leftColStyle : void 0, children: [
3363
- /* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
3364
- groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
3365
- /* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3366
- if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
3367
- const props = narrationOn ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
3368
- return /* @__PURE__ */ jsx6(
3369
- "button",
3370
- {
3371
- type: "button",
3372
- "aria-pressed": props.active,
3373
- style: props.active ? toggleActive : toggleBase,
3374
- onClick: props.onClick,
3375
- disabled: props.disabled,
3376
- title: props.title,
3377
- children: t.label
3378
- },
3379
- t.key
3380
- );
3381
- }) })
3382
- ] }, group.label)) }),
3383
- /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationOn ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3384
- narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3385
- /* @__PURE__ */ jsx6(
3386
- "input",
3387
- {
3388
- type: "checkbox",
3389
- style: { accentColor: "var(--squisq-recorder-accent)" },
3390
- checked: narrationOn,
3391
- onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "narration", e.target.checked)),
3392
- disabled: narrationToggleDisabled,
3393
- title: narrationToggleDisabled ? narrationOn ? "Finish or discard the narration take first" : "Save or discard this recording before switching modes" : void 0
3394
- }
3395
- ),
3396
- "Show narration mode"
3397
- ] }),
3398
- slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3399
- /* @__PURE__ */ jsx6(
3400
- "input",
3401
- {
3402
- type: "checkbox",
3403
- style: { accentColor: "var(--squisq-recorder-accent)" },
3404
- checked: slidesOn,
3405
- onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
3406
- disabled: narrationToggleDisabled,
3407
- title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
3408
- }
3409
- ),
3410
- "Show slides mode"
3411
- ] }),
3412
- /* @__PURE__ */ jsx6(
3413
- RecorderDeviceQuickPicks,
3414
- {
3415
- devices: mediaDevices,
3416
- microphoneEnabled,
3417
- cameraEnabled,
3418
- audioDeviceId: deviceSettings.audio.deviceId,
3419
- cameraDeviceId: deviceSettings.camera.deviceId,
3420
- onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
3421
- ...deviceSettings,
3422
- audio: { ...deviceSettings.audio, deviceId }
3423
- }),
3424
- onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
3425
- ...deviceSettings,
3426
- camera: { ...deviceSettings.camera, deviceId }
3427
- }),
3428
- disabled: deviceSettingsLocked
3429
- }
3430
- ),
3431
- !narrationOn && screenOn && /* @__PURE__ */ jsx6(
3432
- RecorderScreenSettings,
3433
- {
3434
- value: deviceSettings.screen,
3435
- onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
3436
- disabled: deviceSettingsLocked,
3437
- hasPreview: recorder.state === "ready",
3438
- onChoose: () => {
3439
- recorder.cancel();
3440
- void handleRequest();
3441
- }
3442
- }
3443
- ),
3444
- /* @__PURE__ */ jsx6(
3445
- RecorderDeviceSettingsPanel,
3446
- {
3447
- value: deviceSettings,
3448
- onChange: handleDeviceSettingsChange,
3449
- disabled: deviceSettingsLocked,
3450
- microphoneEnabled,
3451
- cameraEnabled,
3452
- screenEnabled: narrationOn ? false : screenOn,
3453
- systemAudioEnabled: !narrationOn && includeSystemAudio,
3454
- separateAudioRecorder: narrationOn,
3455
- primaryStream: activePrimaryStream,
3456
- cameraStream: activeCameraStream
3457
- }
3458
- ),
3459
- !narrationOn && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
3460
- !narrationOn && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
3461
- saveError,
3462
- /* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3463
- ] }),
3464
- /* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
3465
- "Recording size:",
3466
- " ",
3467
- formatRecordingBytes(
3468
- narrationOn ? stage.recorder.recordedBytes : recorder.recordedBytes
3469
- ),
3470
- ". Automatically stops at",
3471
- " ",
3472
- formatRecordingBytes(
3473
- narrationOn ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
3474
- ),
3475
- " ",
3476
- "total. Final encoding may add more data."
3477
- ] }),
3478
- !narrationOn && recorder.limitReached && /* @__PURE__ */ jsx6("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3479
- narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3480
- narrationOn && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
3481
- "video",
3482
- {
3483
- ref: previewRef,
3484
- autoPlay: true,
3485
- muted: true,
3486
- playsInline: true,
3487
- style: { width: "100%", height: "100%", objectFit: "contain" }
3488
- }
3489
- ) }),
3490
- narrationOn && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3491
- narrationOn && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3492
- narrationOn && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3493
- /* @__PURE__ */ jsx6(
3494
- "div",
3495
- {
3496
- "aria-hidden": "true",
3497
- style: {
3498
- ...meterLevelStyle,
3499
- width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
3500
- }
3501
- }
3502
- ),
3503
- /* @__PURE__ */ jsx6("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3504
- ] }),
3505
- !narrationOn && !showPreview && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: screenOn ? "Choose a screen or window to preview what will be recorded." : "Click Start preview to prepare a recording." }) }),
3506
- !narrationOn && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3507
- /* @__PURE__ */ jsx6(
3508
- "video",
3509
- {
3510
- ref: previewRef,
3511
- autoPlay: true,
3512
- muted: true,
3513
- playsInline: true,
3514
- style: { width: "100%", height: "100%", objectFit: "contain" }
3515
- }
3516
- ),
3517
- isDual && /* @__PURE__ */ jsx6(
3518
- "video",
3519
- {
3520
- ref: cameraPreviewRef,
3521
- autoPlay: true,
3522
- muted: true,
3523
- playsInline: true,
3524
- "aria-label": "Camera preview",
3525
- style: {
3526
- position: "absolute",
3527
- right: "3%",
3528
- bottom: "6%",
3529
- width: "22%",
3530
- aspectRatio: "16 / 9",
3531
- objectFit: "cover",
3532
- background: "#000",
3533
- border: "1px solid rgba(255,255,255,0.6)"
3534
- }
3535
- }
3536
- )
3537
- ] }),
3538
- !narrationOn && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3539
- "\u25CF Recording ",
3540
- formatDurationMs(recorder.durationMs)
3541
- ] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
3542
- !narrationOn && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3543
- /* @__PURE__ */ jsx6(
3544
- "video",
3545
- {
3546
- src: playbackUrl,
3547
- controls: true,
3548
- playsInline: true,
3549
- onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3550
- onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3551
- onEnded: () => setPlaybackPositionMs(recorder.durationMs),
3552
- style: { width: "100%", height: "100%", objectFit: "contain" }
3553
- }
3554
- ),
3555
- /* @__PURE__ */ jsxs5(
3556
- "div",
3557
- {
3558
- role: "timer",
3559
- "aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
3560
- style: playbackTimeStyle,
3561
- children: [
3562
- formatDurationMs(playbackPositionMs),
3563
- " / ",
3564
- formatDurationMs(recorder.durationMs)
3565
- ]
3566
- }
3567
- )
3568
- ] }),
3569
- !narrationOn && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3570
- /* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3571
- /* @__PURE__ */ jsx6(
3572
- "video",
3573
- {
3574
- src: cameraPlaybackUrl,
3575
- controls: true,
3576
- playsInline: true,
3577
- "aria-label": "Camera recording",
3578
- style: {
3579
- width: "48%",
3580
- display: "block",
3581
- marginLeft: "auto",
3582
- background: "#000"
3583
- }
3584
- }
3585
- )
3586
- ] }),
3587
- !narrationOn && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3588
- /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3589
- "\u2713 Recorded ",
3590
- formatDurationMs(recorder.durationMs)
3591
- ] }),
3592
- /* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3593
- ] }),
3594
- !narrationOn && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
3595
- "div",
3596
- {
3597
- style: {
3598
- ...buttonRowStyle,
3599
- flexWrap: "wrap",
3600
- justifyContent: "flex-start",
3601
- marginBottom: 12
3602
- },
3603
- children: [
3604
- /* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3605
- isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
3606
- "a",
3374
+ /* @__PURE__ */ jsxs5(
3375
+ "div",
3376
+ {
3377
+ style: expanded ? {
3378
+ ...leftColStyle,
3379
+ ...panelMode === "both" ? { flex: "0 0 min(280px, 24vw)" } : {}
3380
+ } : void 0,
3381
+ children: [
3382
+ /* @__PURE__ */ jsx6("div", { style: toggleRowStyle, role: "group", "aria-label": "Capture sources", children: TOGGLE_GROUPS.map((group, groupIndex) => /* @__PURE__ */ jsxs5(Fragment2, { children: [
3383
+ groupIndex > 0 && /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", style: groupDividerStyle }),
3384
+ /* @__PURE__ */ jsx6("div", { style: toggleGroupStyle, children: group.toggles.map((t) => {
3385
+ if (t.key === "systemAudio" && !canIncludeSystemAudio) return null;
3386
+ const props = narrationRecording ? narrationToggleFor(t.key) : simpleToggleProps(t.key);
3387
+ return /* @__PURE__ */ jsx6(
3388
+ "button",
3389
+ {
3390
+ type: "button",
3391
+ "aria-pressed": props.active,
3392
+ style: props.active ? toggleActive : toggleBase,
3393
+ onClick: props.onClick,
3394
+ disabled: props.disabled,
3395
+ title: props.title,
3396
+ children: t.label
3397
+ },
3398
+ t.key
3399
+ );
3400
+ }) })
3401
+ ] }, group.label)) }),
3402
+ /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: narrationRecording ? narrationCaptureSummary(stage.recorder.withCamera) : captureSummary(micOn, cameraOn, screenOn, includeSystemAudio) }),
3403
+ narrationAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3404
+ /* @__PURE__ */ jsx6(
3405
+ "input",
3607
3406
  {
3608
- href: cameraPlaybackUrl,
3609
- download: buildFilename(
3610
- "video",
3611
- recorder.camera?.extension ?? ".webm",
3612
- basename.trim() ? `${basename.trim()}-camera` : void 0,
3613
- micOn ? "camera-audio" : "camera"
3614
- ),
3615
- style: btnSecondary,
3616
- children: "Download camera recording"
3407
+ type: "checkbox",
3408
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3409
+ checked: narrationOn,
3410
+ onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "narration", e.target.checked)),
3411
+ disabled: narrationToggleDisabled,
3412
+ title: narrationToggleDisabled ? narrationRecording ? "Finish or discard the narration take first" : "Save or discard this recording before switching modes" : void 0
3617
3413
  }
3618
3414
  ),
3619
- timingDownloadUrl && /* @__PURE__ */ jsx6(
3620
- "a",
3415
+ "Show narration mode"
3416
+ ] }),
3417
+ slidesAvailable && /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3418
+ /* @__PURE__ */ jsx6(
3419
+ "input",
3621
3420
  {
3622
- href: timingDownloadUrl,
3623
- download: timingDownloadFilename,
3624
- style: btnSecondary,
3625
- children: "Download recording timings"
3421
+ type: "checkbox",
3422
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3423
+ checked: slidesOn,
3424
+ onChange: (e) => setPanelMode(panelModeAfterToggle(panelMode, "slides", e.target.checked)),
3425
+ disabled: narrationToggleDisabled,
3426
+ title: narrationToggleDisabled ? "Save or discard this recording before switching modes" : void 0
3626
3427
  }
3627
- )
3628
- ]
3629
- }
3630
- ),
3631
- !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3632
- /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3633
- /* @__PURE__ */ jsx6(
3634
- "textarea",
3635
- {
3636
- id: "recorder-source-text",
3637
- style: textareaStyle,
3638
- placeholder: "Type the text you're going to read aloud.",
3639
- value: sourceText,
3640
- onChange: (e) => setSourceText(e.target.value),
3641
- disabled: recorder.state === "recording"
3642
- }
3643
- )
3644
- ] }),
3645
- /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3646
- /* @__PURE__ */ jsx6(
3647
- "input",
3648
- {
3649
- id: "recorder-basename",
3650
- type: "text",
3651
- style: inputStyle2,
3652
- placeholder: narrationOn ? "narration" : isDual ? "screen + camera" : filenameSeed,
3653
- value: basename,
3654
- onChange: (e) => setBasename(e.target.value),
3655
- disabled: narrationOn ? !narrationRecorderIdle : recorder.state === "recording"
3656
- }
3657
- ),
3658
- !narrationOn && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
3659
- "\u25CF Recording ",
3660
- formatDurationMs(recorder.durationMs)
3661
- ] }),
3662
- showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3663
- /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3428
+ ),
3429
+ "Show slides mode"
3430
+ ] }),
3664
3431
  /* @__PURE__ */ jsx6(
3665
- "input",
3432
+ RecorderDeviceQuickPicks,
3666
3433
  {
3667
- type: "checkbox",
3668
- style: { accentColor: "var(--squisq-recorder-accent)" },
3669
- checked: applySlideTimings,
3670
- onChange: (e) => setApplySlideTimings(e.target.checked),
3671
- disabled: isSaving
3434
+ devices: mediaDevices,
3435
+ microphoneEnabled,
3436
+ cameraEnabled,
3437
+ audioDeviceId: deviceSettings.audio.deviceId,
3438
+ cameraDeviceId: deviceSettings.camera.deviceId,
3439
+ onAudioDeviceChange: (deviceId) => handleDeviceSettingsChange({
3440
+ ...deviceSettings,
3441
+ audio: { ...deviceSettings.audio, deviceId }
3442
+ }),
3443
+ onCameraDeviceChange: (deviceId) => handleDeviceSettingsChange({
3444
+ ...deviceSettings,
3445
+ camera: { ...deviceSettings.camera, deviceId }
3446
+ }),
3447
+ disabled: deviceSettingsLocked
3672
3448
  }
3673
3449
  ),
3674
- SLIDE_TIMING_CHECKBOX_LABEL
3675
- ] }),
3676
- applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
3677
- ] }),
3678
- /* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
3679
- /* @__PURE__ */ jsx6(
3680
- "button",
3681
- {
3682
- type: "button",
3683
- style: btnSecondary,
3684
- onClick: handleClose,
3685
- disabled: !narrationOn && isBusy,
3686
- children: "Close"
3687
- }
3688
- ),
3689
- narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3690
- narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
3691
- "button",
3450
+ !narrationRecording && screenOn && /* @__PURE__ */ jsx6(
3451
+ RecorderScreenSettings,
3452
+ {
3453
+ value: deviceSettings.screen,
3454
+ onChange: (screen) => handleDeviceSettingsChange({ ...deviceSettings, screen }),
3455
+ disabled: deviceSettingsLocked,
3456
+ hasPreview: recorder.state === "ready",
3457
+ onChoose: () => {
3458
+ recorder.cancel();
3459
+ void handleRequest();
3460
+ }
3461
+ }
3462
+ ),
3463
+ /* @__PURE__ */ jsx6(
3464
+ RecorderDeviceSettingsPanel,
3692
3465
  {
3693
- type: "button",
3694
- style: btnPrimary,
3695
- onClick: () => void handleNarrationPreview(),
3696
- disabled: narrationRequesting,
3697
- children: narrationRequesting ? "Requesting\u2026" : "Start preview"
3466
+ value: deviceSettings,
3467
+ onChange: handleDeviceSettingsChange,
3468
+ disabled: deviceSettingsLocked,
3469
+ microphoneEnabled,
3470
+ cameraEnabled,
3471
+ screenEnabled: narrationRecording ? false : screenOn,
3472
+ systemAudioEnabled: !narrationRecording && includeSystemAudio,
3473
+ separateAudioRecorder: narrationRecording,
3474
+ primaryStream: activePrimaryStream,
3475
+ cameraStream: activeCameraStream
3698
3476
  }
3699
3477
  ),
3700
- narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3478
+ !narrationRecording && recorder.error && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: recorder.error.message }),
3479
+ !narrationRecording && saveError && /* @__PURE__ */ jsxs5("div", { role: "alert", style: errorStyle, children: [
3480
+ saveError,
3481
+ /* @__PURE__ */ jsx6("p", { style: { marginBottom: 0 }, children: "Your recording is still available below. Download a copy before closing, or try saving again." })
3482
+ ] }),
3483
+ /* @__PURE__ */ jsxs5("p", { style: summaryStyle, children: [
3484
+ "Recording size:",
3485
+ " ",
3486
+ formatRecordingBytes(
3487
+ narrationRecording ? stage.recorder.recordedBytes : recorder.recordedBytes
3488
+ ),
3489
+ ". Automatically stops at",
3490
+ " ",
3491
+ formatRecordingBytes(
3492
+ narrationRecording ? stage.recorder.maxRecordingBytes : recorder.maxRecordingBytes
3493
+ ),
3494
+ " ",
3495
+ "total. Final encoding may add more data."
3496
+ ] }),
3497
+ !narrationRecording && recorder.limitReached && /* @__PURE__ */ jsx6("p", { role: "status", style: recordingStatusStyle, children: "Recording stopped at the size limit. Your complete recording is retained; download a copy or save it to the document." }),
3498
+ narrationOn && (stage.recorder.error ?? stage.controller.mic.error) && /* @__PURE__ */ jsx6("div", { style: errorStyle, children: (stage.recorder.error ?? stage.controller.mic.error)?.message }),
3499
+ narrationRecording && narrationCameraStream && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6(
3500
+ "video",
3501
+ {
3502
+ ref: previewRef,
3503
+ autoPlay: true,
3504
+ muted: true,
3505
+ playsInline: true,
3506
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3507
+ }
3508
+ ) }),
3509
+ narrationRecording && !narrationCameraStream && narrationTakeDone && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: stage.recorder.take ? `\u2713 Recorded ${formatDurationMs(stage.recorder.take.durationSec * 1e3)}` : "\u25CF Processing take\u2026" }),
3510
+ narrationRecording && !narrationCameraStream && !narrationTakeDone && stage.recorder.withCamera && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: narrationPreviewWanted ? "Camera starting\u2026" : "Click Start preview to turn on your camera." }) }),
3511
+ narrationRecording && !narrationCameraStream && !narrationTakeDone && !stage.recorder.withCamera && /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, position: "relative", overflow: "hidden" }, children: [
3701
3512
  /* @__PURE__ */ jsx6(
3702
- "span",
3513
+ "div",
3703
3514
  {
3704
- className: "squisq-recorder-record-dot",
3705
- style: recordDotFrameStyle,
3706
3515
  "aria-hidden": "true",
3707
- children: /* @__PURE__ */ jsx6(
3708
- "span",
3709
- {
3710
- className: "squisq-recorder-record-dot-center",
3711
- style: recordDotStyle
3712
- }
3713
- )
3516
+ style: {
3517
+ ...meterLevelStyle,
3518
+ width: `${Math.round(Math.min(1, Math.max(0, stage.controller.micLevel)) * 100)}%`
3519
+ }
3714
3520
  }
3715
3521
  ),
3716
- "Record"
3522
+ /* @__PURE__ */ jsx6("span", { style: { position: "relative" }, children: stage.recorder.state === "recording" ? "\u25CF Recording narration" : narrationMicLive ? stage.controller.voiceActive ? "Voice detected" : "Microphone ready" : "Click Start preview to check your mic." })
3717
3523
  ] }),
3718
- (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
3719
- "button",
3720
- {
3721
- type: "button",
3722
- style: btnDanger,
3723
- onClick: () => void stage.recorder.stop(),
3724
- children: "Stop"
3725
- }
3726
- ),
3727
- stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3728
- stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3729
- stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3730
- /* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3524
+ !narrationRecording && !showPreview && /* @__PURE__ */ jsx6("div", { style: previewBoxStyle, children: /* @__PURE__ */ jsx6("span", { children: screenOn ? "Choose a screen or window to preview what will be recorded." : "Click Start preview to prepare a recording." }) }),
3525
+ !narrationRecording && showPreview && recorder.state !== "stopped" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: isDual ? { ...previewBoxStyle, position: "relative" } : previewBoxStyle, children: [
3731
3526
  /* @__PURE__ */ jsx6(
3732
- "button",
3527
+ "video",
3733
3528
  {
3734
- type: "button",
3735
- style: btnPrimary,
3736
- onClick: () => void stage.handleSave(),
3737
- children: "Save to document"
3529
+ ref: previewRef,
3530
+ autoPlay: true,
3531
+ muted: true,
3532
+ playsInline: true,
3533
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3534
+ }
3535
+ ),
3536
+ isDual && /* @__PURE__ */ jsx6(
3537
+ "video",
3538
+ {
3539
+ ref: cameraPreviewRef,
3540
+ autoPlay: true,
3541
+ muted: true,
3542
+ playsInline: true,
3543
+ "aria-label": "Camera preview",
3544
+ style: {
3545
+ position: "absolute",
3546
+ right: "3%",
3547
+ bottom: "6%",
3548
+ width: "22%",
3549
+ aspectRatio: "16 / 9",
3550
+ objectFit: "cover",
3551
+ background: "#000",
3552
+ border: "1px solid rgba(255,255,255,0.6)"
3553
+ }
3738
3554
  }
3739
3555
  )
3740
- ] })
3741
- ] }),
3742
- !narrationOn && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3743
- (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
3744
- "button",
3745
- {
3746
- type: "button",
3747
- style: btnPrimary,
3748
- onClick: handleRequest,
3749
- disabled: isBusy || !canCapture,
3750
- children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3751
- }
3752
- ),
3753
- canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3556
+ ] }),
3557
+ !narrationRecording && showPreview && recorder.state !== "stopped" && isAudioOnly && /* @__PURE__ */ jsx6("div", { style: audioMeterStyle, children: recorder.state === "recording" ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3558
+ "\u25CF Recording ",
3559
+ formatDurationMs(recorder.durationMs)
3560
+ ] }) : /* @__PURE__ */ jsx6(Fragment3, { children: "Microphone ready" }) }),
3561
+ !narrationRecording && recorder.state === "stopped" && playbackUrl && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { ...previewBoxStyle, position: "relative" }, children: [
3754
3562
  /* @__PURE__ */ jsx6(
3755
- "span",
3563
+ "video",
3756
3564
  {
3757
- className: "squisq-recorder-record-dot",
3758
- style: recordDotFrameStyle,
3759
- "aria-hidden": "true",
3760
- children: /* @__PURE__ */ jsx6(
3761
- "span",
3565
+ src: playbackUrl,
3566
+ controls: true,
3567
+ playsInline: true,
3568
+ onTimeUpdate: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3569
+ onSeeking: (event) => handlePlaybackTimeUpdate(event.currentTarget),
3570
+ onEnded: () => setPlaybackPositionMs(recorder.durationMs),
3571
+ style: { width: "100%", height: "100%", objectFit: "contain" }
3572
+ }
3573
+ ),
3574
+ /* @__PURE__ */ jsxs5(
3575
+ "div",
3576
+ {
3577
+ role: "timer",
3578
+ "aria-label": `Playback time: ${formatDurationMs(playbackPositionMs)} of ${formatDurationMs(recorder.durationMs)}`,
3579
+ style: playbackTimeStyle,
3580
+ children: [
3581
+ formatDurationMs(playbackPositionMs),
3582
+ " / ",
3583
+ formatDurationMs(recorder.durationMs)
3584
+ ]
3585
+ }
3586
+ )
3587
+ ] }),
3588
+ !narrationRecording && recorder.state === "stopped" && isDual && cameraPlaybackUrl && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3589
+ /* @__PURE__ */ jsx6("div", { style: summaryStyle, children: "Camera (picture-in-picture)" }),
3590
+ /* @__PURE__ */ jsx6(
3591
+ "video",
3592
+ {
3593
+ src: cameraPlaybackUrl,
3594
+ controls: true,
3595
+ playsInline: true,
3596
+ "aria-label": "Camera recording",
3597
+ style: {
3598
+ width: "48%",
3599
+ display: "block",
3600
+ marginLeft: "auto",
3601
+ background: "#000"
3602
+ }
3603
+ }
3604
+ )
3605
+ ] }),
3606
+ !narrationRecording && recorder.state === "stopped" && playbackUrl && isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: { marginBottom: 12 }, children: [
3607
+ /* @__PURE__ */ jsxs5("div", { style: { ...audioMeterStyle, marginBottom: 8 }, children: [
3608
+ "\u2713 Recorded ",
3609
+ formatDurationMs(recorder.durationMs)
3610
+ ] }),
3611
+ /* @__PURE__ */ jsx6("audio", { src: playbackUrl, controls: true, style: { width: "100%" } })
3612
+ ] }),
3613
+ !narrationRecording && canSave && playbackUrl && /* @__PURE__ */ jsxs5(
3614
+ "div",
3615
+ {
3616
+ style: {
3617
+ ...buttonRowStyle,
3618
+ flexWrap: "wrap",
3619
+ justifyContent: "flex-start",
3620
+ marginBottom: 12
3621
+ },
3622
+ children: [
3623
+ /* @__PURE__ */ jsx6("a", { href: playbackUrl, download: downloadFilename, style: btnSecondary, children: isDual ? "Download screen recording" : "Download recording" }),
3624
+ isDual && cameraPlaybackUrl && /* @__PURE__ */ jsx6(
3625
+ "a",
3762
3626
  {
3763
- className: "squisq-recorder-record-dot-center",
3764
- style: recordDotStyle
3627
+ href: cameraPlaybackUrl,
3628
+ download: buildFilename(
3629
+ "video",
3630
+ recorder.camera?.extension ?? ".webm",
3631
+ basename.trim() ? `${basename.trim()}-camera` : void 0,
3632
+ micOn ? "camera-audio" : "camera"
3633
+ ),
3634
+ style: btnSecondary,
3635
+ children: "Download camera recording"
3636
+ }
3637
+ ),
3638
+ timingDownloadUrl && /* @__PURE__ */ jsx6(
3639
+ "a",
3640
+ {
3641
+ href: timingDownloadUrl,
3642
+ download: timingDownloadFilename,
3643
+ style: btnSecondary,
3644
+ children: "Download recording timings"
3765
3645
  }
3766
3646
  )
3647
+ ]
3648
+ }
3649
+ ),
3650
+ !narrationOn && !slidesTimingCapture && source === "mic" && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3651
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-source-text", children: "Script (used to auto-match this narration to a block)" }),
3652
+ /* @__PURE__ */ jsx6(
3653
+ "textarea",
3654
+ {
3655
+ id: "recorder-source-text",
3656
+ style: textareaStyle,
3657
+ placeholder: "Type the text you're going to read aloud.",
3658
+ value: sourceText,
3659
+ onChange: (e) => setSourceText(e.target.value),
3660
+ disabled: recorder.state === "recording"
3767
3661
  }
3768
- ),
3769
- "Record"
3662
+ )
3770
3663
  ] }),
3771
- canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3772
- canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3664
+ /* @__PURE__ */ jsx6("label", { style: labelStyle, htmlFor: "recorder-basename", children: "Filename (optional)" }),
3665
+ /* @__PURE__ */ jsx6(
3666
+ "input",
3667
+ {
3668
+ id: "recorder-basename",
3669
+ type: "text",
3670
+ style: inputStyle2,
3671
+ placeholder: narrationRecording ? "narration" : isDual ? "screen + camera" : filenameSeed,
3672
+ value: basename,
3673
+ onChange: (e) => setBasename(e.target.value),
3674
+ disabled: narrationRecording ? !narrationRecorderIdle : recorder.state === "recording"
3675
+ }
3676
+ ),
3677
+ !narrationRecording && recorder.state === "recording" && !isAudioOnly && /* @__PURE__ */ jsxs5("div", { style: recordingStatusStyle, children: [
3678
+ "\u25CF Recording ",
3679
+ formatDurationMs(recorder.durationMs)
3680
+ ] }),
3681
+ showTimingCheckbox && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3682
+ /* @__PURE__ */ jsxs5("label", { style: checkboxRowStyle, children: [
3683
+ /* @__PURE__ */ jsx6(
3684
+ "input",
3685
+ {
3686
+ type: "checkbox",
3687
+ style: { accentColor: "var(--squisq-recorder-accent)" },
3688
+ checked: applySlideTimings,
3689
+ onChange: (e) => setApplySlideTimings(e.target.checked),
3690
+ disabled: isSaving
3691
+ }
3692
+ ),
3693
+ SLIDE_TIMING_CHECKBOX_LABEL
3694
+ ] }),
3695
+ applySlideTimings && slideCoverageWarning && /* @__PURE__ */ jsx6("p", { style: summaryStyle, children: slideCoverageWarning })
3696
+ ] }),
3697
+ /* @__PURE__ */ jsxs5("div", { style: buttonRowStyle, children: [
3773
3698
  /* @__PURE__ */ jsx6(
3774
3699
  "button",
3775
3700
  {
3776
3701
  type: "button",
3777
3702
  style: btnSecondary,
3778
- onClick: handleDiscard,
3779
- disabled: isBusy,
3780
- children: "Discard & re-record"
3703
+ onClick: handleClose,
3704
+ disabled: !narrationRecording && isBusy,
3705
+ children: "Close"
3781
3706
  }
3782
3707
  ),
3783
- /* @__PURE__ */ jsx6(
3784
- "button",
3785
- {
3786
- type: "button",
3787
- style: btnPrimary,
3788
- onClick: handleSave,
3789
- disabled: isBusy,
3790
- children: isSaving ? "Saving\u2026" : "Save to document"
3791
- }
3792
- )
3708
+ narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3709
+ narrationRecorderIdle && !narrationMicLive && /* @__PURE__ */ jsx6(
3710
+ "button",
3711
+ {
3712
+ type: "button",
3713
+ style: btnPrimary,
3714
+ onClick: () => void handleNarrationPreview(),
3715
+ disabled: narrationRequesting,
3716
+ children: narrationRequesting ? "Requesting\u2026" : "Start preview"
3717
+ }
3718
+ ),
3719
+ narrationRecorderIdle && narrationMicLive && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleNarrationRecord, children: [
3720
+ /* @__PURE__ */ jsx6(
3721
+ "span",
3722
+ {
3723
+ className: "squisq-recorder-record-dot",
3724
+ style: recordDotFrameStyle,
3725
+ "aria-hidden": "true",
3726
+ children: /* @__PURE__ */ jsx6(
3727
+ "span",
3728
+ {
3729
+ className: "squisq-recorder-record-dot-center",
3730
+ style: recordDotStyle
3731
+ }
3732
+ )
3733
+ }
3734
+ ),
3735
+ "Record"
3736
+ ] }),
3737
+ (stage.recorder.state === "recording" || stage.recorder.state === "starting") && /* @__PURE__ */ jsx6(
3738
+ "button",
3739
+ {
3740
+ type: "button",
3741
+ style: btnDanger,
3742
+ onClick: () => void stage.recorder.stop(),
3743
+ children: "Stop"
3744
+ }
3745
+ ),
3746
+ stage.recorder.state === "processing" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Aligning take\u2026" }),
3747
+ stage.recorder.state === "saving" && /* @__PURE__ */ jsx6("span", { style: recordingStatusStyle, children: "Saving\u2026" }),
3748
+ stage.recorder.state === "review" && stage.recorder.take && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3749
+ /* @__PURE__ */ jsx6("button", { type: "button", style: btnSecondary, onClick: stage.handleRetake, children: "Discard & re-record" }),
3750
+ /* @__PURE__ */ jsx6(
3751
+ "button",
3752
+ {
3753
+ type: "button",
3754
+ style: btnPrimary,
3755
+ onClick: () => void stage.handleSave(),
3756
+ children: "Save to document"
3757
+ }
3758
+ )
3759
+ ] })
3760
+ ] }),
3761
+ !narrationRecording && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3762
+ (recorder.state === "idle" || recorder.state === "error" || recorder.state === "requesting") && /* @__PURE__ */ jsx6(
3763
+ "button",
3764
+ {
3765
+ type: "button",
3766
+ style: btnPrimary,
3767
+ onClick: handleRequest,
3768
+ disabled: isBusy || !canCapture,
3769
+ children: recorder.state === "requesting" ? "Requesting\u2026" : "Start preview"
3770
+ }
3771
+ ),
3772
+ canRecord && /* @__PURE__ */ jsxs5("button", { type: "button", style: btnRecord, onClick: handleStart, disabled: isBusy, children: [
3773
+ /* @__PURE__ */ jsx6(
3774
+ "span",
3775
+ {
3776
+ className: "squisq-recorder-record-dot",
3777
+ style: recordDotFrameStyle,
3778
+ "aria-hidden": "true",
3779
+ children: /* @__PURE__ */ jsx6(
3780
+ "span",
3781
+ {
3782
+ className: "squisq-recorder-record-dot-center",
3783
+ style: recordDotStyle
3784
+ }
3785
+ )
3786
+ }
3787
+ ),
3788
+ "Record"
3789
+ ] }),
3790
+ canStop && /* @__PURE__ */ jsx6("button", { type: "button", style: btnDanger, onClick: handleStop, disabled: isBusy, children: "Stop" }),
3791
+ canSave && /* @__PURE__ */ jsxs5(Fragment3, { children: [
3792
+ /* @__PURE__ */ jsx6(
3793
+ "button",
3794
+ {
3795
+ type: "button",
3796
+ style: btnSecondary,
3797
+ onClick: handleDiscard,
3798
+ disabled: isBusy,
3799
+ children: "Discard & re-record"
3800
+ }
3801
+ ),
3802
+ /* @__PURE__ */ jsx6(
3803
+ "button",
3804
+ {
3805
+ type: "button",
3806
+ style: btnPrimary,
3807
+ onClick: handleSave,
3808
+ disabled: isBusy,
3809
+ children: isSaving ? "Saving\u2026" : "Save to document"
3810
+ }
3811
+ )
3812
+ ] })
3813
+ ] })
3793
3814
  ] })
3794
- ] })
3795
- ] })
3796
- ] }),
3797
- narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3815
+ ]
3816
+ }
3817
+ ),
3818
+ narrationOn && narration && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
3798
3819
  NarrationStage,
3799
3820
  {
3800
3821
  stage,
@@ -3806,7 +3827,7 @@ function RecorderModal({
3806
3827
  showReviewActions: false
3807
3828
  }
3808
3829
  ) }),
3809
- slidesOn && /* @__PURE__ */ jsx6("div", { style: rightColStyle, children: /* @__PURE__ */ jsx6(
3830
+ slidesOn && /* @__PURE__ */ jsx6("div", { style: { ...rightColStyle, flex: "1 1 0" }, children: /* @__PURE__ */ jsx6(
3810
3831
  RecorderSlidesPanel,
3811
3832
  {
3812
3833
  slides: slideDeck,
@@ -3815,7 +3836,7 @@ function RecorderModal({
3815
3836
  viewport: slidesViewport,
3816
3837
  basePath: slides?.basePath,
3817
3838
  mediaProvider: slides?.mediaProvider ?? mediaProvider,
3818
- recording: recorder.state === "recording",
3839
+ recording: narrationRecording ? stage.recorder.state === "recording" : recorder.state === "recording",
3819
3840
  shownBlockIds
3820
3841
  }
3821
3842
  ) })