@adminide-stack/yantra-mobile 12.0.121 → 12.0.123

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.
Files changed (175) hide show
  1. package/lib/features/apps/appArt.js +1 -1
  2. package/lib/features/apps/appArt.js.map +1 -1
  3. package/lib/features/apps/mobileAppCatalog.js +15 -11
  4. package/lib/features/apps/mobileAppCatalog.js.map +1 -1
  5. package/lib/features/canvas/CanvasAgentChatPane.js +82 -20
  6. package/lib/features/canvas/CanvasAgentChatPane.js.map +1 -1
  7. package/lib/features/canvas/CanvasBoardsGrid.js +291 -100
  8. package/lib/features/canvas/CanvasBoardsGrid.js.map +1 -1
  9. package/lib/features/canvas/CanvasEditToolsPanel.js +105 -12
  10. package/lib/features/canvas/CanvasEditToolsPanel.js.map +1 -1
  11. package/lib/features/canvas/CanvasExperience.js +266 -68
  12. package/lib/features/canvas/CanvasExperience.js.map +1 -1
  13. package/lib/features/canvas/CanvasGeneratePanel.js +204 -66
  14. package/lib/features/canvas/CanvasGeneratePanel.js.map +1 -1
  15. package/lib/features/canvas/CanvasLibrarySheet.js +31 -13
  16. package/lib/features/canvas/CanvasLibrarySheet.js.map +1 -1
  17. package/lib/features/canvas/CanvasModelPicker.js +176 -0
  18. package/lib/features/canvas/CanvasModelPicker.js.map +1 -0
  19. package/lib/features/canvas/CanvasTasksPanel.js +325 -29
  20. package/lib/features/canvas/CanvasTasksPanel.js.map +1 -1
  21. package/lib/features/canvas/CanvasTemplatesPanel.js +5 -4
  22. package/lib/features/canvas/CanvasTemplatesPanel.js.map +1 -1
  23. package/lib/features/canvas/CanvasToolDock.js +137 -61
  24. package/lib/features/canvas/CanvasToolDock.js.map +1 -1
  25. package/lib/features/canvas/FrameExtractorExperience.js +33 -2
  26. package/lib/features/canvas/FrameExtractorExperience.js.map +1 -1
  27. package/lib/features/canvas/GeneratorReferenceSlots.js +197 -0
  28. package/lib/features/canvas/GeneratorReferenceSlots.js.map +1 -0
  29. package/lib/features/canvas/NativeCanvasViewer.js +89 -11
  30. package/lib/features/canvas/NativeCanvasViewer.js.map +1 -1
  31. package/lib/features/canvas/PromoContextFields.js +55 -2
  32. package/lib/features/canvas/PromoContextFields.js.map +1 -1
  33. package/lib/features/canvas/SetStudioExperience.js +323 -13
  34. package/lib/features/canvas/SetStudioExperience.js.map +1 -1
  35. package/lib/features/canvas/VideoEditExperience.js +1 -53
  36. package/lib/features/canvas/VideoEditExperience.js.map +1 -1
  37. package/lib/features/canvas/VideoEditorExperience.js +697 -506
  38. package/lib/features/canvas/VideoEditorExperience.js.map +1 -1
  39. package/lib/features/canvas/VideoGeneratorExperience.js +22 -5
  40. package/lib/features/canvas/VideoGeneratorExperience.js.map +1 -1
  41. package/lib/features/canvas/canvasAgentChat.js +125 -11
  42. package/lib/features/canvas/canvasAgentChat.js.map +1 -1
  43. package/lib/features/canvas/canvasCore.js +117 -12
  44. package/lib/features/canvas/canvasCore.js.map +1 -1
  45. package/lib/features/canvas/canvasEditOps.js +148 -111
  46. package/lib/features/canvas/canvasEditOps.js.map +1 -1
  47. package/lib/features/canvas/canvasTaskSync.js +235 -0
  48. package/lib/features/canvas/canvasTaskSync.js.map +1 -0
  49. package/lib/features/canvas/canvasTasks.js +98 -11
  50. package/lib/features/canvas/canvasTasks.js.map +1 -1
  51. package/lib/features/canvas/createTiles.js +247 -0
  52. package/lib/features/canvas/createTiles.js.map +1 -0
  53. package/lib/features/canvas/deepLinks.js +83 -0
  54. package/lib/features/canvas/deepLinks.js.map +1 -0
  55. package/lib/features/canvas/frameExtract.js +13 -1
  56. package/lib/features/canvas/frameExtract.js.map +1 -1
  57. package/lib/features/canvas/generationModels.js +36 -8
  58. package/lib/features/canvas/generationModels.js.map +1 -1
  59. package/lib/features/canvas/generatorCommands.js +106 -0
  60. package/lib/features/canvas/generatorCommands.js.map +1 -0
  61. package/lib/features/canvas/generatorReferences.js +82 -0
  62. package/lib/features/canvas/generatorReferences.js.map +1 -0
  63. package/lib/features/canvas/homeWork.js +74 -0
  64. package/lib/features/canvas/homeWork.js.map +1 -0
  65. package/lib/features/canvas/linksRpc.js +130 -0
  66. package/lib/features/canvas/linksRpc.js.map +1 -0
  67. package/lib/features/canvas/mediaRecheck.js +176 -0
  68. package/lib/features/canvas/mediaRecheck.js.map +1 -0
  69. package/lib/features/canvas/mediaUrls.js +258 -0
  70. package/lib/features/canvas/mediaUrls.js.map +1 -0
  71. package/lib/features/canvas/nativeViewerRegistry.js.map +1 -1
  72. package/lib/features/canvas/promoProjectImport.js +80 -13
  73. package/lib/features/canvas/promoProjectImport.js.map +1 -1
  74. package/lib/features/canvas/promoVideo.js +106 -15
  75. package/lib/features/canvas/promoVideo.js.map +1 -1
  76. package/lib/features/canvas/reframe.js +228 -0
  77. package/lib/features/canvas/reframe.js.map +1 -0
  78. package/lib/features/canvas/serverTasks.js +697 -0
  79. package/lib/features/canvas/serverTasks.js.map +1 -0
  80. package/lib/features/canvas/set/SetViewportWebView.js +207 -28
  81. package/lib/features/canvas/set/SetViewportWebView.js.map +1 -1
  82. package/lib/features/canvas/set/agent/prompt.js +66 -0
  83. package/lib/features/canvas/set/agent/prompt.js.map +1 -0
  84. package/lib/features/canvas/set/agent/setOps.js +203 -0
  85. package/lib/features/canvas/set/agent/setOps.js.map +1 -0
  86. package/lib/features/canvas/set/agent/setTurn.js +128 -0
  87. package/lib/features/canvas/set/agent/setTurn.js.map +1 -0
  88. package/lib/features/canvas/set/agent/snapshot.js +43 -0
  89. package/lib/features/canvas/set/agent/snapshot.js.map +1 -0
  90. package/lib/features/canvas/set/agent/tools.js +580 -0
  91. package/lib/features/canvas/set/agent/tools.js.map +1 -0
  92. package/lib/features/canvas/set/setProject.js +43 -4
  93. package/lib/features/canvas/set/setProject.js.map +1 -1
  94. package/lib/features/canvas/set/supplyLibrary.js +397 -0
  95. package/lib/features/canvas/set/supplyLibrary.js.map +1 -0
  96. package/lib/features/canvas/storageLibrary.js +113 -52
  97. package/lib/features/canvas/storageLibrary.js.map +1 -1
  98. package/lib/features/canvas/studioCatalog.js +15 -2
  99. package/lib/features/canvas/studioCatalog.js.map +1 -1
  100. package/lib/features/canvas/studioLooks.js +1 -8
  101. package/lib/features/canvas/studioLooks.js.map +1 -1
  102. package/lib/features/canvas/useBoardMediaRefresh.js +66 -0
  103. package/lib/features/canvas/useBoardMediaRefresh.js.map +1 -0
  104. package/lib/features/canvas/useMediaSource.js +84 -0
  105. package/lib/features/canvas/useMediaSource.js.map +1 -0
  106. package/lib/features/canvas/video3d.js +11 -10
  107. package/lib/features/canvas/video3d.js.map +1 -1
  108. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js +46 -0
  109. package/lib/features/canvas/videoEditor/BlurBoxOverlay.js.map +1 -0
  110. package/lib/features/canvas/videoEditor/ExportTaskCard.js +134 -0
  111. package/lib/features/canvas/videoEditor/ExportTaskCard.js.map +1 -0
  112. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js +45 -7
  113. package/lib/features/canvas/videoEditor/agent/AgentCutPane.js.map +1 -1
  114. package/lib/features/canvas/videoEditor/agent/agentLoop.js +4 -2
  115. package/lib/features/canvas/videoEditor/agent/agentLoop.js.map +1 -1
  116. package/lib/features/canvas/videoEditor/agent/attachments.js +304 -0
  117. package/lib/features/canvas/videoEditor/agent/attachments.js.map +1 -0
  118. package/lib/features/canvas/videoEditor/agent/brainClient.js +3 -2
  119. package/lib/features/canvas/videoEditor/agent/brainClient.js.map +1 -1
  120. package/lib/features/canvas/videoEditor/agent/cutTools.js +207 -7
  121. package/lib/features/canvas/videoEditor/agent/cutTools.js.map +1 -1
  122. package/lib/features/canvas/videoEditor/blurBox.js +34 -0
  123. package/lib/features/canvas/videoEditor/blurBox.js.map +1 -0
  124. package/lib/features/canvas/videoEditor/compileTimeline.js +668 -0
  125. package/lib/features/canvas/videoEditor/compileTimeline.js.map +1 -0
  126. package/lib/features/canvas/videoEditor/editorModel.js +56 -22
  127. package/lib/features/canvas/videoEditor/editorModel.js.map +1 -1
  128. package/lib/features/canvas/videoEditor/exportPipeline.js +77 -0
  129. package/lib/features/canvas/videoEditor/exportPipeline.js.map +1 -0
  130. package/lib/features/canvas/videoEditor/exportRenderer.js +19 -12
  131. package/lib/features/canvas/videoEditor/exportRenderer.js.map +1 -1
  132. package/lib/features/canvas/videoEditor/exportTask.js +216 -0
  133. package/lib/features/canvas/videoEditor/exportTask.js.map +1 -0
  134. package/lib/features/canvas/videoEditor/formWidgets.js +142 -0
  135. package/lib/features/canvas/videoEditor/formWidgets.js.map +1 -0
  136. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js +195 -0
  137. package/lib/features/canvas/videoEditor/produce/ProduceVideoSheet.js.map +1 -0
  138. package/lib/features/canvas/videoEditor/produce/produceScenes.js +158 -0
  139. package/lib/features/canvas/videoEditor/produce/produceScenes.js.map +1 -0
  140. package/lib/features/canvas/videoEditor/remoteMediaClient.js +17 -5
  141. package/lib/features/canvas/videoEditor/remoteMediaClient.js.map +1 -1
  142. package/lib/features/canvas/videoEditor/renderTimeline.js +207 -0
  143. package/lib/features/canvas/videoEditor/renderTimeline.js.map +1 -0
  144. package/lib/features/canvas/videoEditor/renewLinks.js +64 -0
  145. package/lib/features/canvas/videoEditor/renewLinks.js.map +1 -0
  146. package/lib/features/canvas/videoEditor/sceneLayers.js +101 -0
  147. package/lib/features/canvas/videoEditor/sceneLayers.js.map +1 -0
  148. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js +283 -0
  149. package/lib/features/canvas/videoEditor/tools/ToolsDrawer.js.map +1 -0
  150. package/lib/features/canvas/videoEditor/tools/catalog.js +576 -0
  151. package/lib/features/canvas/videoEditor/tools/catalog.js.map +1 -0
  152. package/lib/features/canvas/videoEditor/videoProjectsStore.js +27 -3
  153. package/lib/features/canvas/videoEditor/videoProjectsStore.js.map +1 -1
  154. package/lib/features/webbuilder/WebBuilderExperience.js +29 -1
  155. package/lib/features/webbuilder/WebBuilderExperience.js.map +1 -1
  156. package/lib/features/webbuilder/buildFragments.js +8 -4
  157. package/lib/features/webbuilder/buildFragments.js.map +1 -1
  158. package/lib/features/webbuilder/codezenHandoff.js +108 -0
  159. package/lib/features/webbuilder/codezenHandoff.js.map +1 -0
  160. package/lib/features/webbuilder/workflow.js.map +1 -1
  161. package/lib/hooks/useChatApi.js +1 -2
  162. package/lib/hooks/useChatApi.js.map +1 -1
  163. package/lib/screens/CanvasBoard/index.js +12 -1
  164. package/lib/screens/CanvasBoard/index.js.map +1 -1
  165. package/lib/screens/Home/HomeScreen.js.map +1 -1
  166. package/lib/screens/Home/components/HomeSuggestions.js +3 -3
  167. package/lib/screens/Home/components/HomeSuggestions.js.map +1 -1
  168. package/lib/screens/WebBuilder/index.js +4 -2
  169. package/lib/screens/WebBuilder/index.js.map +1 -1
  170. package/lib/screens/WebBuilderNative/index.js +29 -2
  171. package/lib/screens/WebBuilderNative/index.js.map +1 -1
  172. package/lib/screens/WebBuilderNative/useWebBuilderHost.js +30 -6
  173. package/lib/screens/WebBuilderNative/useWebBuilderHost.js.map +1 -1
  174. package/package.json +3 -3
  175. /package/lib/assets/apps/{paper2poster.webp → poster.webp} +0 -0
@@ -1,4 +1,4 @@
1
- import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {useState,useMemo,useRef,useEffect,useCallback}from'react';import {StyleSheet,useWindowDimensions,Platform,Alert,View,Text,Pressable,FlatList,ActivityIndicator,TextInput,ScrollView}from'react-native';import {KeyboardAwareScreen,KeyboardLiftSpacer}from'../../components/KeyboardComposerDock.js';import {dismissKeyboard}from'../../utils/keyboardController.js';import {GestureHandlerRootView,Gesture,GestureDetector}from'react-native-gesture-handler';import {MaterialCommunityIcons}from'@expo/vector-icons';import {Image}from'expo-image';import {getVideoPreviewRenderer}from'./videoRenderer.js';import {friendlyMediaError,resultUrl,generationMediaUrl}from'./videoEditor/mediaActions.js';import {clipFromItem,emptyTimeline,timelineDuration,updateClip,clipFromColor,textClipAt,newSegment,buildExportSpec,removeClip,moveClip,trimClip,SPEED_PRESETS,SPEED_CURVES,TEXT_PRESETS,updateText,removeText,MOTION_MOVES,MOTION_STRENGTHS,CLIP_TRANSITIONS,moveIdOf,kenBurnsFor,DEFAULT_IMAGE_SEC,clipTimelineSec}from'./videoEditor/editorModel.js';import {filterById,FILTER_PRESETS}from'./videoEditor/filters.js';import {useSafeInsets}from'./withRemoteSafeArea.js';import {buildVideoGenerateParams,STATIC_VIDEO_MODELS}from'./generationModels.js';import {mapDesignCorpus,playableTemplatePreviewUrl}from'./canvasChatTemplates.js';import {config}from'../../config/env-config.js';import {useStudioCatalogTemplates}from'./useStudioCatalogTemplates.js';import {DESIGN_PROMPT_TEMPLATES}from'./designPromptTemplates.js';import {templateHasUsableConfig,loadTemplateTimeline}from'./templateImport/loadTemplate.js';import {loadVideoProjects,projectHasWork,newVideoProjectId,snapshotProject,upsertVideoProject,persistVideoProjects,relativeProjectTime}from'./videoEditor/videoProjectsStore.js';import {AgentCutPane}from'./videoEditor/agent/AgentCutPane.js';var __defProp = Object.defineProperty;
1
+ import {jsxs,jsx,Fragment}from'react/jsx-runtime';import {useState,useMemo,useRef,useCallback,useEffect}from'react';import {StyleSheet,useWindowDimensions,Platform,Alert,View,Text,Pressable,FlatList,ActivityIndicator,TextInput,ScrollView}from'react-native';import {KeyboardAwareScreen,KeyboardLiftSpacer}from'../../components/KeyboardComposerDock.js';import {dismissKeyboard}from'../../utils/keyboardController.js';import {GestureHandlerRootView,Gesture,GestureDetector}from'react-native-gesture-handler';import {MaterialCommunityIcons}from'@expo/vector-icons';import {Image}from'expo-image';import {getVideoPreviewRenderer}from'./videoRenderer.js';import {friendlyMediaError,resultUrl,generationMediaUrl}from'./videoEditor/mediaActions.js';import {clipFromItem,emptyTimeline,timelineDuration,updateClip,clipFromColor,textClipAt,newSegment,buildExportSpec,removeClip,moveClip,trimClip,SPEED_PRESETS,SPEED_CURVES,TEXT_PRESETS,updateText,removeText,MOTION_MOVES,MOTION_STRENGTHS,CLIP_TRANSITIONS,moveIdOf,kenBurnsFor,DEFAULT_IMAGE_SEC,clipTimelineSec}from'./videoEditor/editorModel.js';import {filterById,FILTER_PRESETS}from'./videoEditor/filters.js';import {useSafeInsets}from'./withRemoteSafeArea.js';import {STATIC_VIDEO_MODELS,buildVideoGenerateParams}from'./generationModels.js';import {mapDesignCorpus,playableTemplatePreviewUrl}from'./canvasChatTemplates.js';import {config}from'../../config/env-config.js';import {useStudioCatalogTemplates}from'./useStudioCatalogTemplates.js';import {DESIGN_PROMPT_TEMPLATES}from'./designPromptTemplates.js';import {templateHasUsableConfig,loadTemplateTimeline}from'./templateImport/loadTemplate.js';import {loadVideoProjects,projectHasWork,newVideoProjectId,snapshotProject,upsertVideoProject,persistVideoProjects,relativeProjectTime}from'./videoEditor/videoProjectsStore.js';import {AgentCutPane}from'./videoEditor/agent/AgentCutPane.js';import {canvasForRatio,canvasForAspect}from'./videoEditor/compileTimeline.js';import {reflowSceneChildren,sceneSpans,stampScene,layerInView}from'./videoEditor/sceneLayers.js';import {BlurBoxOverlay}from'./videoEditor/BlurBoxOverlay.js';import {ToolsDrawer}from'./videoEditor/tools/ToolsDrawer.js';import {staleMediaUrls,withRenewedUrls}from'./videoEditor/renewLinks.js';import {ensureFetchableMediaUrl}from'./mediaUrls.js';import {ProduceVideoSheet}from'./videoEditor/produce/ProduceVideoSheet.js';import {sceneFootagePatch,produceScenes,produceJob,runProduce}from'./videoEditor/produce/produceScenes.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -90,6 +90,11 @@ const SEGMENT_KINDS = [{
90
90
  label: "Trim",
91
91
  hint: "Skip this range in playback and export",
92
92
  icon: "content-cut"
93
+ }, {
94
+ kind: "blur",
95
+ label: "Blur",
96
+ hint: "Blur a box (a face, a plate) for this range",
97
+ icon: "blur"
93
98
  }];
94
99
  const SCENE_COLORS = ["#101014", "#f4f4f5", "#1d4ed8", "#7c2d12"];
95
100
  const SPEED_CURVE_LABEL = {
@@ -186,7 +191,7 @@ function VideoEditorExperience({
186
191
  indexUrl,
187
192
  initialRailTab
188
193
  }) {
189
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
194
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
190
195
  const insets = useSafeInsets();
191
196
  const {
192
197
  width: windowWidth
@@ -195,7 +200,8 @@ function VideoEditorExperience({
195
200
  const [deviceMedia, setDeviceMedia] = useState([]);
196
201
  const [logoSrc, setLogoSrc] = useState(initialLogoSrc != null ? initialLogoSrc : null);
197
202
  const media = useMemo(() => [...deviceMedia, ...items].filter((i) => i.type === "image" || i.type === "video" || i.type === "audio"), [items, deviceMedia]);
198
- const [timeline, setTimeline] = useState(() => {
203
+ const sourceSecRef = useRef({});
204
+ const [timeline, setTimelineRaw] = useState(() => {
199
205
  if (initialTimeline && (initialTimeline.clips.length > 0 || initialTimeline.texts.length > 0)) {
200
206
  return initialTimeline;
201
207
  }
@@ -212,7 +218,28 @@ function VideoEditorExperience({
212
218
  texts: []
213
219
  };
214
220
  });
221
+ const setTimeline = useCallback((update) => {
222
+ if (typeof update !== "function") {
223
+ setTimelineRaw(update);
224
+ return;
225
+ }
226
+ setTimelineRaw((prev) => reflowSceneChildren(prev, update(prev), sourceSecRef.current));
227
+ }, []);
215
228
  const [selectedClipId, setSelectedClipId] = useState((_b = (_a = timeline.clips[0]) == null ? void 0 : _a.id) != null ? _b : null);
229
+ useEffect(() => {
230
+ if (!runAction) return void 0;
231
+ const stale = staleMediaUrls(timeline);
232
+ if (!stale.length) return void 0;
233
+ let live = true;
234
+ void Promise.all(stale.map(async (url) => [url, await ensureFetchableMediaUrl(runAction, url).catch(() => url)])).then((pairs) => {
235
+ if (!live) return;
236
+ const fresh = new Map(pairs.filter(([a, b]) => a !== b));
237
+ if (fresh.size) setTimelineRaw((prev) => withRenewedUrls(prev, fresh));
238
+ });
239
+ return () => {
240
+ live = false;
241
+ };
242
+ }, [runAction]);
216
243
  const [selectedTextId, setSelectedTextId] = useState(null);
217
244
  const [tab, setTab] = useState("media");
218
245
  const [railTab, setRailTab] = useState(() => {
@@ -233,9 +260,12 @@ function VideoEditorExperience({
233
260
  const [aiBusy, setAiBusy] = useState(false);
234
261
  const [usingTemplateId, setUsingTemplateId] = useState(null);
235
262
  const [notice, setNotice] = useState(null);
263
+ const [toolsOpen, setToolsOpen] = useState(false);
264
+ const [produceOpen, setProduceOpen] = useState(false);
236
265
  const [playing, setPlaying] = useState(false);
237
266
  const [playhead, setPlayhead] = useState(0);
238
267
  const [sourceSec, setSourceSec] = useState({});
268
+ sourceSecRef.current = sourceSec;
239
269
  const [scrub, setScrub] = useState({
240
270
  sec: 0,
241
271
  nonce: 0
@@ -353,13 +383,18 @@ function VideoEditorExperience({
353
383
  const openSavedProject = useCallback((row) => {
354
384
  var _a2, _b2, _c2, _d2;
355
385
  flushProject();
356
- setTimeline({
386
+ setTimeline(__spreadProps(__spreadValues(__spreadValues({
357
387
  clips: [...row.timeline.clips],
358
388
  texts: [...row.timeline.texts],
359
- segments: row.timeline.segments ? [...row.timeline.segments] : [],
389
+ segments: row.timeline.segments ? [...row.timeline.segments] : []
390
+ }, row.timeline.sceneAudio ? {
391
+ sceneAudio: [...row.timeline.sceneAudio]
392
+ } : {}), row.timeline.aspect ? {
393
+ aspect: row.timeline.aspect
394
+ } : {}), {
360
395
  voiceover: (_a2 = row.timeline.voiceover) != null ? _a2 : null,
361
396
  soundtrack: (_b2 = row.timeline.soundtrack) != null ? _b2 : null
362
- });
397
+ }));
363
398
  setLogoSrc(row.logoSrc);
364
399
  setActiveProjectId(row.id);
365
400
  activeIdRef.current = row.id;
@@ -429,16 +464,24 @@ function VideoEditorExperience({
429
464
  }, []);
430
465
  const addBlankScene = useCallback(() => {
431
466
  const clip = clipFromColor("#101014");
432
- const title = textClipAt(0, {
467
+ const title = __spreadProps(__spreadValues({}, textClipAt(0, {
433
468
  text: "Build your own scene",
434
469
  x: 0.5,
435
470
  y: 0.46
471
+ })), {
472
+ sceneId: clip.id
436
473
  });
437
474
  setDirty(true);
438
- setTimeline((t) => __spreadProps(__spreadValues({}, t), {
439
- clips: [...t.clips, clip],
440
- texts: [...t.texts, title]
441
- }));
475
+ setTimeline((t) => {
476
+ var _a2, _b2;
477
+ const startSec = (_b2 = (_a2 = sceneSpans(t.clips, sourceSecRef.current).at(-1)) == null ? void 0 : _a2.endSec) != null ? _b2 : 0;
478
+ return __spreadProps(__spreadValues({}, t), {
479
+ clips: [...t.clips, clip],
480
+ texts: [...t.texts, __spreadProps(__spreadValues({}, title), {
481
+ startSec
482
+ })]
483
+ });
484
+ });
442
485
  setSelectedClipId(clip.id);
443
486
  setSelectedTextId(title.id);
444
487
  setRailTab(null);
@@ -478,7 +521,9 @@ function VideoEditorExperience({
478
521
  try {
479
522
  const imported = await loadTemplateTimeline(row);
480
523
  setDirty(true);
481
- setTimeline(imported.timeline);
524
+ setTimeline(__spreadValues(__spreadValues({}, imported.timeline), imported.aspect ? {
525
+ aspect: imported.aspect
526
+ } : {}));
482
527
  setSelectedClipId((_b2 = (_a2 = imported.timeline.clips[0]) == null ? void 0 : _a2.id) != null ? _b2 : null);
483
528
  setSelectedTextId((_d2 = (_c2 = imported.timeline.texts[0]) == null ? void 0 : _c2.id) != null ? _d2 : null);
484
529
  if (imported.logoSrc) setLogoSrc(imported.logoSrc);
@@ -493,7 +538,7 @@ function VideoEditorExperience({
493
538
  const addText = useCallback((preset) => {
494
539
  const text = textClipAt(playhead, preset);
495
540
  setTimeline((t) => __spreadProps(__spreadValues({}, t), {
496
- texts: [...t.texts, text]
541
+ texts: [...t.texts, stampScene(text, t.clips, sourceSecRef.current)]
497
542
  }));
498
543
  setSelectedTextId(text.id);
499
544
  setTab("text");
@@ -618,6 +663,91 @@ function VideoEditorExperience({
618
663
  }
619
664
  audioRecorder.start().then(() => setMicTakeOn(true)).catch((err) => setRecordError(friendlyMediaError(err, "record")));
620
665
  }, [audioRecorder, uploadMedia, micTakeOn]);
666
+ const addSceneAudio = useCallback((audio) => {
667
+ if (!selectedClipId) return;
668
+ setDirty(true);
669
+ setTimeline((t) => {
670
+ var _a2, _b2, _c2, _d2;
671
+ const span = sceneSpans(t.clips, sourceSecRef.current).find((s) => s.id === selectedClipId);
672
+ const own = ((_a2 = t.sceneAudio) != null ? _a2 : []).filter((a) => a.sceneId === selectedClipId);
673
+ const startSec = Math.max((_b2 = span == null ? void 0 : span.startSec) != null ? _b2 : 0, ...own.map((a) => {
674
+ var _a3;
675
+ return a.startSec + ((_a3 = a.durationSec) != null ? _a3 : 0);
676
+ }));
677
+ return __spreadProps(__spreadValues({}, t), {
678
+ sceneAudio: [...(_c2 = t.sceneAudio) != null ? _c2 : [], {
679
+ id: `sa-${Date.now().toString(36)}`,
680
+ audioUrl: audio.audioUrl,
681
+ startSec,
682
+ durationSec: null,
683
+ volume: 1,
684
+ label: (_d2 = audio.label) == null ? void 0 : _d2.slice(0, 60),
685
+ sceneId: selectedClipId
686
+ }]
687
+ });
688
+ });
689
+ }, [selectedClipId, setTimeline]);
690
+ const selectedBlur = ((_e = timeline.segments) != null ? _e : []).find((s) => s.id === selectedSegmentId && s.kind === "blur");
691
+ const patchBlur = useCallback((patch) => {
692
+ if (!selectedSegmentId) return;
693
+ setDirty(true);
694
+ setTimeline((t) => {
695
+ var _a2;
696
+ return __spreadProps(__spreadValues({}, t), {
697
+ segments: ((_a2 = t.segments) != null ? _a2 : []).map((s) => s.id === selectedSegmentId && s.blur ? __spreadProps(__spreadValues({}, s), {
698
+ blur: __spreadValues(__spreadValues({}, s.blur), patch)
699
+ }) : s)
700
+ });
701
+ });
702
+ }, [selectedSegmentId, setTimeline]);
703
+ const applyFootage = useCallback((sceneId, footage) => {
704
+ setDirty(true);
705
+ setSourceSec((prev) => __spreadProps(__spreadValues({}, prev), {
706
+ [sceneId]: footage.sourceSec
707
+ }));
708
+ setTimeline((t) => {
709
+ const scene = t.clips.find((c) => c.id === sceneId);
710
+ if (!scene) return t;
711
+ return updateClip(t, sceneId, sceneFootagePatch(scene, footage, sourceSecRef.current[sceneId]));
712
+ });
713
+ }, [setTimeline]);
714
+ const produceAspect = useMemo(() => {
715
+ const c = canvasForRatio(timeline.aspect);
716
+ return !c ? "16:9" : c.height > c.width ? "9:16" : c.height === c.width ? "1:1" : "16:9";
717
+ }, [timeline.aspect]);
718
+ const filmScenes = useCallback((sceneIds) => {
719
+ var _a2;
720
+ if (!runAction) return;
721
+ const jobs = produceScenes(timelineRef.current, sourceSecRef.current).filter((s) => sceneIds.includes(s.sceneId)).map((s) => {
722
+ var _a3, _b2;
723
+ return produceJob(s, (_b2 = (_a3 = STATIC_VIDEO_MODELS[0]) == null ? void 0 : _a3.supportedDurationsSeconds) != null ? _b2 : [4, 6, 8]);
724
+ });
725
+ if (!jobs.length) return;
726
+ setNotice(`Filming ${jobs.length} scene${jobs.length === 1 ? "" : "s"} - they land in place in a few minutes.`);
727
+ void runProduce(jobs, runAction, {
728
+ aspectRatio: produceAspect,
729
+ model: (_a2 = STATIC_VIDEO_MODELS[0]) == null ? void 0 : _a2.id,
730
+ onStatus: (sceneId, status) => {
731
+ if (status.state === "done") applyFootage(sceneId, status.footage);
732
+ if (status.state === "failed") setNotice(status.message);
733
+ }
734
+ });
735
+ }, [runAction, produceAspect, applyFootage]);
736
+ const addLibraryMedia = useCallback((asset) => {
737
+ const id = `lib-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
738
+ setDeviceMedia((prev) => [{
739
+ id,
740
+ type: asset.type,
741
+ src: asset.src,
742
+ x: 0,
743
+ y: 0,
744
+ width: 640,
745
+ height: 360,
746
+ zIndex: 1,
747
+ generatedAssetName: asset.name
748
+ }, ...prev]);
749
+ return id;
750
+ }, []);
621
751
  const dropSegment = useCallback((kind) => {
622
752
  if (timeline.clips.length === 0) {
623
753
  setNotice("Add a scene first \u2014 segments sit across the cut.");
@@ -627,7 +757,7 @@ function VideoEditorExperience({
627
757
  setTimeline((t) => {
628
758
  var _a2;
629
759
  return __spreadProps(__spreadValues({}, t), {
630
- segments: [...(_a2 = t.segments) != null ? _a2 : [], seg]
760
+ segments: [...(_a2 = t.segments) != null ? _a2 : [], stampScene(seg, t.clips, sourceSecRef.current)]
631
761
  });
632
762
  });
633
763
  setSelectedSegmentId(seg.id);
@@ -706,15 +836,23 @@ function VideoEditorExperience({
706
836
  }
707
837
  }, [selectedClipId]);
708
838
  const exportTimeline = useCallback(() => {
839
+ var _a2;
709
840
  if (timeline.clips.length === 0) {
710
841
  setNotice("Add clips to the timeline first.");
711
842
  return;
712
843
  }
713
844
  flushProject();
714
845
  setRailTab(null);
715
- if (onExport) onExport(buildExportSpec(timeline));
846
+ const first = timeline.clips[0];
847
+ const firstItem = media.find((m) => m.id === first.itemId);
848
+ const canvas = (_a2 = canvasForRatio(timeline.aspect)) != null ? _a2 : firstItem ? canvasForAspect(firstItem.width, firstItem.height) : void 0;
849
+ if (onExport) onExport(buildExportSpec(timeline, __spreadValues({
850
+ sourceSec
851
+ }, canvas ? {
852
+ canvas
853
+ } : {})));
716
854
  else setNotice("Export renders through the yantra-video service - not wired in this host yet.");
717
- }, [flushProject, timeline, onExport]);
855
+ }, [flushProject, timeline, onExport, media, sourceSec]);
718
856
  const previewPlay = useCallback(() => {
719
857
  if (timeline.clips.length === 0) {
720
858
  setNotice("Add a clip from Media, then tap Preview.");
@@ -724,7 +862,7 @@ function VideoEditorExperience({
724
862
  setPlaying((on) => !on);
725
863
  }, [timeline.clips.length]);
726
864
  const VideoPreview = getVideoPreviewRenderer();
727
- const selectedFilter = filterById((_e = selected == null ? void 0 : selected.filter) != null ? _e : "none");
865
+ const selectedFilter = filterById((_f = selected == null ? void 0 : selected.filter) != null ? _f : "none");
728
866
  const selectedSource = selected ? sourceSec[selected.id] : void 0;
729
867
  const projectList = useMemo(() => {
730
868
  if (activeProjectId && projectRows.some((row) => row.id === activeProjectId)) return projectRows;
@@ -736,504 +874,538 @@ function VideoEditorExperience({
736
874
  updatedAt: Date.now()
737
875
  }, ...projectRows];
738
876
  }, [activeProjectId, localTitle, timeline, logoSrc, projectRows]);
739
- return /* @__PURE__ */ jsx(GestureHandlerRootView, { style: styles.root, children: /* @__PURE__ */ jsxs(KeyboardAwareScreen, { style: styles.root, testID: "video-editor", children: [
740
- /* @__PURE__ */ jsxs(View, { style: styles.header, children: [
741
- /* @__PURE__ */ jsxs(View, { style: styles.headerCopy, children: [
742
- /* @__PURE__ */ jsx(Text, { style: styles.headerProject, numberOfLines: 1, children: localTitle }),
743
- /* @__PURE__ */ jsxs(Text, { style: styles.headerSaved, children: [
744
- dirty ? "Unsaved" : "Saved",
745
- /* @__PURE__ */ jsx(Text, { style: styles.headerSavedDim, children: " \xB7 30 fps MP4" })
746
- ] })
747
- ] }),
748
- /* @__PURE__ */ jsx(Pressable, { onPress: previewPlay, disabled: timeline.clips.length === 0, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: playing ? "Pause preview" : "Preview", style: timeline.clips.length === 0 ? styles.disabled : styles.headerGhostBtn, children: /* @__PURE__ */ jsx(Text, { style: styles.headerGhost, children: playing ? "Pause" : "Preview" }) }),
749
- /* @__PURE__ */ jsx(Pressable, { onPress: exportTimeline, disabled: timeline.clips.length === 0, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Export video", accessibilityHint: timeline.clips.length === 0 ? "Add a scene or clip before exporting" : void 0, style: [styles.exportBtn, timeline.clips.length === 0 ? styles.disabled : null], children: /* @__PURE__ */ jsx(Text, { style: styles.exportBtnLabel, children: "Export" }) }),
750
- /* @__PURE__ */ jsx(Pressable, { onPress: () => {
751
- flushProject();
752
- dismissKeyboard();
753
- onClose();
754
- }, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close editor", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 22, color: "#E8E8EE" }) })
755
- ] }),
756
- /* @__PURE__ */ jsxs(View, { style: styles.workspace, children: [
757
- railTab || recordOpen ? /* @__PURE__ */ jsxs(View, { style: [styles.sheet, railTab === "templates" && !recordOpen ? styles.sheetTall : null], children: [
758
- /* @__PURE__ */ jsxs(View, { style: styles.sheetHead, children: [
759
- /* @__PURE__ */ jsx(Text, { style: styles.sheetTitle, children: recordOpen ? "Record" : (_g = (_f = TABS.find((entry) => entry.id === railTab)) == null ? void 0 : _f.label) != null ? _g : "Tools" }),
760
- railTab === "projects" && !recordOpen ? /* @__PURE__ */ jsxs(Pressable, { onPress: startNewProject, style: styles.sheetNewBtn, accessibilityRole: "button", accessibilityLabel: "New project", children: [
761
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 14, color: "#101013" }),
762
- /* @__PURE__ */ jsx(Text, { style: styles.sheetNewBtnLabel, children: "New" })
763
- ] }) : null,
764
- /* @__PURE__ */ jsx(Pressable, { onPress: () => {
765
- setRailTab(null);
766
- setRecordOpen(false);
767
- setMicTakeOn(false);
768
- }, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close tools", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 18, color: "#F2F2F5" }) })
877
+ return /* @__PURE__ */ jsxs(GestureHandlerRootView, { style: styles.root, children: [
878
+ /* @__PURE__ */ jsx(ToolsDrawer, { visible: toolsOpen, onClose: () => setToolsOpen(false), media, selectedSrc: (_g = selected == null ? void 0 : selected.src) != null ? _g : null, runAction, uploadMedia, onAddMedia: (url, type, name) => {
879
+ setDeviceMedia((prev) => [{
880
+ id: `tool-${Date.now().toString(36)}`,
881
+ type,
882
+ src: url,
883
+ x: 0,
884
+ y: 0,
885
+ width: 640,
886
+ height: 360,
887
+ zIndex: 1,
888
+ generatedAssetName: name
889
+ }, ...prev]);
890
+ setNotice(`${name} added to the library`);
891
+ } }),
892
+ /* @__PURE__ */ jsx(ProduceVideoSheet, { visible: produceOpen, onClose: () => setProduceOpen(false), timeline, sourceSec, aspectRatio: produceAspect, runAction, onFootage: applyFootage }),
893
+ /* @__PURE__ */ jsxs(KeyboardAwareScreen, { style: styles.root, testID: "video-editor", children: [
894
+ /* @__PURE__ */ jsxs(View, { style: styles.header, children: [
895
+ /* @__PURE__ */ jsxs(View, { style: styles.headerCopy, children: [
896
+ /* @__PURE__ */ jsx(Text, { style: styles.headerProject, numberOfLines: 1, children: localTitle }),
897
+ /* @__PURE__ */ jsxs(Text, { style: styles.headerSaved, children: [
898
+ dirty ? "Unsaved" : "Saved",
899
+ /* @__PURE__ */ jsx(Text, { style: styles.headerSavedDim, children: " \xB7 30 fps MP4" })
900
+ ] })
769
901
  ] }),
770
- railTab === "templates" && !recordOpen ? /* @__PURE__ */ jsx(View, { style: styles.templateSheetBody, children: /* @__PURE__ */ jsx(FlatList, { data: templateGrid, keyExtractor: (pair) => pair.map((row) => row.id).join(":"), keyboardShouldPersistTaps: "handled", style: styles.templateList, contentContainerStyle: styles.templateListInner, extraData: `${templatesLoadingMore}:${templates.length}`, ListHeaderComponent: /* @__PURE__ */ jsxs(View, { style: styles.templatesPane, children: [
771
- /* @__PURE__ */ jsxs(View, { style: styles.searchField, children: [
772
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "magnify", size: 20, color: "#8A8A94" }),
773
- /* @__PURE__ */ jsx(TextInput, { value: templateQuery, onChangeText: setTemplateQuery, placeholder: "Search templates\u2026", placeholderTextColor: "#6A6A74", style: styles.searchInput, autoCorrect: false, autoCapitalize: "none", autoFocus: false, returnKeyType: "search", accessibilityLabel: "Search templates", onSubmitEditing: () => setTemplateSearch(templateQuery.trim()) }),
774
- templateQuery.length > 0 ? /* @__PURE__ */ jsx(Pressable, { onPress: () => {
775
- setTemplateQuery("");
776
- setTemplateSearch("");
777
- dismissKeyboard();
778
- }, style: styles.searchClear, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Clear search", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close-circle", size: 18, color: "#8A8A94" }) }) : null
902
+ timeline.clips.some((c) => c.fillColor) ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setProduceOpen(true), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Produce video - film the colour scenes", style: styles.headerGhostBtn, testID: "video-editor-produce", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "movie-open-play-outline", size: 18, color: "#F7F7FA" }) }) : null,
903
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setToolsOpen(true), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Tools", style: styles.headerGhostBtn, testID: "video-editor-tools", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "toolbox-outline", size: 18, color: "#F7F7FA" }) }),
904
+ /* @__PURE__ */ jsx(Pressable, { onPress: previewPlay, disabled: timeline.clips.length === 0, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: playing ? "Pause preview" : "Preview", style: timeline.clips.length === 0 ? styles.disabled : styles.headerGhostBtn, children: /* @__PURE__ */ jsx(Text, { style: styles.headerGhost, children: playing ? "Pause" : "Preview" }) }),
905
+ /* @__PURE__ */ jsx(Pressable, { onPress: exportTimeline, disabled: timeline.clips.length === 0, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Export video", accessibilityHint: timeline.clips.length === 0 ? "Add a scene or clip before exporting" : void 0, style: [styles.exportBtn, timeline.clips.length === 0 ? styles.disabled : null], children: /* @__PURE__ */ jsx(Text, { style: styles.exportBtnLabel, children: "Export" }) }),
906
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => {
907
+ flushProject();
908
+ dismissKeyboard();
909
+ onClose();
910
+ }, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close editor", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 22, color: "#E8E8EE" }) })
911
+ ] }),
912
+ /* @__PURE__ */ jsxs(View, { style: styles.workspace, children: [
913
+ railTab || recordOpen ? /* @__PURE__ */ jsxs(View, { style: [styles.sheet, railTab === "templates" && !recordOpen ? styles.sheetTall : null], children: [
914
+ /* @__PURE__ */ jsxs(View, { style: styles.sheetHead, children: [
915
+ /* @__PURE__ */ jsx(Text, { style: styles.sheetTitle, children: recordOpen ? "Record" : (_i = (_h = TABS.find((entry) => entry.id === railTab)) == null ? void 0 : _h.label) != null ? _i : "Tools" }),
916
+ railTab === "projects" && !recordOpen ? /* @__PURE__ */ jsxs(Pressable, { onPress: startNewProject, style: styles.sheetNewBtn, accessibilityRole: "button", accessibilityLabel: "New project", children: [
917
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 14, color: "#101013" }),
918
+ /* @__PURE__ */ jsx(Text, { style: styles.sheetNewBtnLabel, children: "New" })
919
+ ] }) : null,
920
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => {
921
+ setRailTab(null);
922
+ setRecordOpen(false);
923
+ setMicTakeOn(false);
924
+ }, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close tools", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 18, color: "#F2F2F5" }) })
779
925
  ] }),
780
- templatesLoading && templates.length === 0 ? /* @__PURE__ */ jsx(View, { style: styles.emptyPanel, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: "#E8E8EE" }) }) : templates.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyPanel, children: [
781
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "view-grid-outline", size: 22, color: "#6A6A74" }),
782
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "No templates" }),
783
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: templateSearch ? "No templates match that search." : "The studio catalog is empty in this environment." })
784
- ] }) : null
785
- ] }), ListFooterComponent: templates.length === 0 ? null : /* @__PURE__ */ jsx(View, { style: styles.templateLoadBar, children: /* @__PURE__ */ jsx(Pressable, { onPress: loadMoreTemplates, disabled: templatesLoadingMore, style: [styles.loadMoreBtn, templatesLoadingMore ? styles.disabled : null], accessibilityRole: "button", accessibilityLabel: "Load more templates", children: templatesLoadingMore ? /* @__PURE__ */ jsx(ActivityIndicator, { color: "#101013" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
786
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "chevron-down", size: 20, color: "#101013" }),
787
- /* @__PURE__ */ jsx(Text, { style: styles.loadMoreBtnLabel, children: "Load more" })
788
- ] }) }) }), renderItem: ({
789
- item: pair
790
- }) => /* @__PURE__ */ jsxs(View, { style: styles.templateRow, children: [
791
- pair.map((row) => /* @__PURE__ */ jsx(TemplateCatalogCard, { row, generating: aiBusy, using: usingTemplateId === row.id, onGenerate: (item) => generateAiScene(item.prompt, {
792
- stayOnTemplates: true,
793
- aspect: item.aspect
794
- }), onPreview: previewStudioTemplate, onUse: (item) => void useStudioTemplate(item) }, row.id)),
795
- pair.length < templateCols ? /* @__PURE__ */ jsx(View, { style: {
796
- flex: 1
797
- } }) : null
798
- ] }) }) }) : /* @__PURE__ */ jsx(ScrollView, { style: styles.sidePaneScroll, contentContainerStyle: styles.sidePaneInner, keyboardShouldPersistTaps: "always", children: /* @__PURE__ */ jsx(View, { style: styles.panel, children: recordOpen ? /* @__PURE__ */ jsxs(View, { style: styles.recordPane, children: [
799
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Web records the screen, a window, or the camera, then drops the take on the timeline. On the phone that is camera, mic, or the library \u2014 screen capture stays on desktop." }),
800
- recordError ? /* @__PURE__ */ jsx(Text, { style: styles.recordError, children: recordError }) : null,
801
- [{
802
- id: "camera-video",
803
- icon: "video-outline",
804
- label: "Record video",
805
- hint: "Rear camera, up to 2 minutes"
806
- }, {
807
- id: "camera-photo",
808
- icon: "camera-outline",
809
- label: "Take photo",
810
- hint: "Still lands as a scene"
811
- }, {
812
- id: "library",
813
- icon: "image-multiple-outline",
814
- label: "Photo Library",
815
- hint: "Same as Open Media"
816
- }, {
817
- id: "file",
818
- icon: "folder-outline",
819
- label: "Choose file",
820
- hint: "Video, image, or audio"
821
- }].map((row) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => captureFromRecord(row.id), disabled: recordOn || !onPickLocalMedia, style: [styles.recordRow, (recordOn || !onPickLocalMedia) && styles.disabled], accessibilityRole: "button", children: [
822
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: row.icon, size: 22, color: "#F2F2F5" }),
823
- /* @__PURE__ */ jsxs(View, { style: styles.segmentMenuCopy, children: [
824
- /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuLabel, children: row.label }),
825
- /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuHint, children: row.hint })
826
- ] })
827
- ] }, row.id)),
828
- /* @__PURE__ */ jsxs(Pressable, { onPress: toggleMicTake, disabled: !audioRecorder, style: [styles.recordRow, !audioRecorder && styles.disabled], accessibilityRole: "button", children: [
829
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: micTakeOn ? "stop-circle-outline" : "microphone-outline", size: 22, color: micTakeOn ? "#F87171" : "#F2F2F5" }),
830
- /* @__PURE__ */ jsxs(View, { style: styles.segmentMenuCopy, children: [
831
- /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuLabel, children: micTakeOn ? "Stop voice take" : "Record voice" }),
832
- /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuHint, children: audioRecorder ? "Adds a voiceover on the Audio lane" : "Host microphone is not wired in this build" })
926
+ railTab === "templates" && !recordOpen ? /* @__PURE__ */ jsx(View, { style: styles.templateSheetBody, children: /* @__PURE__ */ jsx(FlatList, { data: templateGrid, keyExtractor: (pair) => pair.map((row) => row.id).join(":"), keyboardShouldPersistTaps: "handled", style: styles.templateList, contentContainerStyle: styles.templateListInner, extraData: `${templatesLoadingMore}:${templates.length}`, ListHeaderComponent: /* @__PURE__ */ jsxs(View, { style: styles.templatesPane, children: [
927
+ /* @__PURE__ */ jsxs(View, { style: styles.searchField, children: [
928
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "magnify", size: 20, color: "#8A8A94" }),
929
+ /* @__PURE__ */ jsx(TextInput, { value: templateQuery, onChangeText: setTemplateQuery, placeholder: "Search templates\u2026", placeholderTextColor: "#6A6A74", style: styles.searchInput, autoCorrect: false, autoCapitalize: "none", autoFocus: false, returnKeyType: "search", accessibilityLabel: "Search templates", onSubmitEditing: () => setTemplateSearch(templateQuery.trim()) }),
930
+ templateQuery.length > 0 ? /* @__PURE__ */ jsx(Pressable, { onPress: () => {
931
+ setTemplateQuery("");
932
+ setTemplateSearch("");
933
+ dismissKeyboard();
934
+ }, style: styles.searchClear, hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Clear search", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close-circle", size: 18, color: "#8A8A94" }) }) : null
935
+ ] }),
936
+ templatesLoading && templates.length === 0 ? /* @__PURE__ */ jsx(View, { style: styles.emptyPanel, children: /* @__PURE__ */ jsx(ActivityIndicator, { color: "#E8E8EE" }) }) : templates.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyPanel, children: [
937
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "view-grid-outline", size: 22, color: "#6A6A74" }),
938
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "No templates" }),
939
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: templateSearch ? "No templates match that search." : "The studio catalog is empty in this environment." })
940
+ ] }) : null
941
+ ] }), ListFooterComponent: templates.length === 0 ? null : /* @__PURE__ */ jsx(View, { style: styles.templateLoadBar, children: /* @__PURE__ */ jsx(Pressable, { onPress: loadMoreTemplates, disabled: templatesLoadingMore, style: [styles.loadMoreBtn, templatesLoadingMore ? styles.disabled : null], accessibilityRole: "button", accessibilityLabel: "Load more templates", children: templatesLoadingMore ? /* @__PURE__ */ jsx(ActivityIndicator, { color: "#101013" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
942
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "chevron-down", size: 20, color: "#101013" }),
943
+ /* @__PURE__ */ jsx(Text, { style: styles.loadMoreBtnLabel, children: "Load more" })
944
+ ] }) }) }), renderItem: ({
945
+ item: pair
946
+ }) => /* @__PURE__ */ jsxs(View, { style: styles.templateRow, children: [
947
+ pair.map((row) => /* @__PURE__ */ jsx(TemplateCatalogCard, { row, generating: aiBusy, using: usingTemplateId === row.id, onGenerate: (item) => generateAiScene(item.prompt, {
948
+ stayOnTemplates: true,
949
+ aspect: item.aspect
950
+ }), onPreview: previewStudioTemplate, onUse: (item) => void useStudioTemplate(item) }, row.id)),
951
+ pair.length < templateCols ? /* @__PURE__ */ jsx(View, { style: {
952
+ flex: 1
953
+ } }) : null
954
+ ] }) }) }) : /* @__PURE__ */ jsx(ScrollView, { style: styles.sidePaneScroll, contentContainerStyle: styles.sidePaneInner, keyboardShouldPersistTaps: "always", children: /* @__PURE__ */ jsx(View, { style: styles.panel, children: recordOpen ? /* @__PURE__ */ jsxs(View, { style: styles.recordPane, children: [
955
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Web records the screen, a window, or the camera, then drops the take on the timeline. On the phone that is camera, mic, or the library \u2014 screen capture stays on desktop." }),
956
+ recordError ? /* @__PURE__ */ jsx(Text, { style: styles.recordError, children: recordError }) : null,
957
+ [{
958
+ id: "camera-video",
959
+ icon: "video-outline",
960
+ label: "Record video",
961
+ hint: "Rear camera, up to 2 minutes"
962
+ }, {
963
+ id: "camera-photo",
964
+ icon: "camera-outline",
965
+ label: "Take photo",
966
+ hint: "Still lands as a scene"
967
+ }, {
968
+ id: "library",
969
+ icon: "image-multiple-outline",
970
+ label: "Photo Library",
971
+ hint: "Same as Open Media"
972
+ }, {
973
+ id: "file",
974
+ icon: "folder-outline",
975
+ label: "Choose file",
976
+ hint: "Video, image, or audio"
977
+ }].map((row) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => captureFromRecord(row.id), disabled: recordOn || !onPickLocalMedia, style: [styles.recordRow, (recordOn || !onPickLocalMedia) && styles.disabled], accessibilityRole: "button", children: [
978
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: row.icon, size: 22, color: "#F2F2F5" }),
979
+ /* @__PURE__ */ jsxs(View, { style: styles.segmentMenuCopy, children: [
980
+ /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuLabel, children: row.label }),
981
+ /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuHint, children: row.hint })
982
+ ] })
983
+ ] }, row.id)),
984
+ /* @__PURE__ */ jsxs(Pressable, { onPress: toggleMicTake, disabled: !audioRecorder, style: [styles.recordRow, !audioRecorder && styles.disabled], accessibilityRole: "button", children: [
985
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: micTakeOn ? "stop-circle-outline" : "microphone-outline", size: 22, color: micTakeOn ? "#F87171" : "#F2F2F5" }),
986
+ /* @__PURE__ */ jsxs(View, { style: styles.segmentMenuCopy, children: [
987
+ /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuLabel, children: micTakeOn ? "Stop voice take" : "Record voice" }),
988
+ /* @__PURE__ */ jsx(Text, { style: styles.segmentMenuHint, children: audioRecorder ? "Adds a voiceover on the Audio lane" : "Host microphone is not wired in this build" })
989
+ ] })
833
990
  ] })
834
- ] })
835
- ] }) : railTab === "media" ? /* @__PURE__ */ jsxs(View, { style: styles.mediaPane, children: [
836
- onPickLocalMedia ? /* @__PURE__ */ jsx(View, { style: styles.pickRow, children: [{
837
- id: "library",
838
- icon: "image-multiple-outline",
839
- label: "Photos"
840
- }, {
841
- id: "camera",
842
- icon: "camera-outline",
843
- label: "Camera"
844
- }, {
845
- id: "file",
846
- icon: "folder-outline",
847
- label: "Files"
848
- }].map((source) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => pickIntoLibrary(source.id), disabled: mediaBusy, style: [styles.pickBtn, mediaBusy && styles.disabled], accessibilityRole: "button", accessibilityLabel: `Upload from ${source.label}`, children: [
849
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: source.icon, size: 18, color: "#E8E8EE" }),
850
- /* @__PURE__ */ jsx(Text, { style: styles.pickBtnLabel, children: source.label })
851
- ] }, source.id)) }) : null,
852
- media.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyPanel, children: [
853
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "image-plus", size: 22, color: "#6A6A74" }),
854
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "No media yet" }),
855
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Upload from this phone, or tap a clip from this canvas to add it to the timeline." })
856
- ] }) : /* @__PURE__ */ jsx(View, { style: styles.mediaGrid, children: media.map((item) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
857
- if (item.type === "audio") {
858
- setTimeline((t) => __spreadProps(__spreadValues({}, t), {
859
- soundtrack: {
860
- audioUrl: item.src,
861
- mode: "mix",
862
- title: item.generatedAssetName || "Audio"
863
- }
864
- }));
865
- setRailTab("music");
866
- setTab("music");
867
- return;
868
- }
869
- addClip(item);
870
- }, style: styles.mediaTile, children: [
871
- item.type === "image" ? /* @__PURE__ */ jsx(Image, { source: {
872
- uri: item.src
873
- }, style: styles.mediaThumb, contentFit: "cover" }) : /* @__PURE__ */ jsx(View, { style: [styles.mediaThumb, styles.mediaThumbVideo], children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: item.type === "audio" ? "music" : "play", size: 18, color: "#9A9AA5" }) }),
874
- /* @__PURE__ */ jsx(View, { style: styles.mediaAdd, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 12, color: "#0A0A0C" }) })
875
- ] }, item.id)) })
876
- ] }) : railTab === "projects" ? /* @__PURE__ */ jsxs(View, { style: styles.projectsPane, children: [
877
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "A project is a saved timeline. Create one, then add clips from Media." }),
878
- /* @__PURE__ */ jsxs(Pressable, { onPress: startNewProject, style: styles.solidBtn, accessibilityRole: "button", accessibilityLabel: "New project", children: [
879
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 16, color: "#101013" }),
880
- /* @__PURE__ */ jsx(Text, { style: styles.solidBtnLabel, children: "Create project" })
881
- ] }),
882
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: dirty ? "Unsaved changes \u2014 saves as soon as you edit." : projectHasWork(timeline) ? "All changes saved" : "Saves as soon as you edit." }),
883
- projectList.map((row) => {
884
- const current = row.id === "__current__" || row.id === activeProjectId;
885
- const clips = row.timeline.clips.length;
886
- const isDraft = row.id === "__current__";
887
- return /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
888
- if (isDraft) return;
889
- openSavedProject(row);
890
- }, style: [styles.projectRow, current && styles.projectRowOn], children: [
891
- renameId === row.id ? /* @__PURE__ */ jsx(TextInput, { value: renameDraft, onChangeText: setRenameDraft, onSubmitEditing: () => {
892
- const title = renameDraft.trim() || "Untitled project";
893
- const next = upsertVideoProject(projectRows, __spreadProps(__spreadValues({}, row), {
894
- title
991
+ ] }) : railTab === "media" ? /* @__PURE__ */ jsxs(View, { style: styles.mediaPane, children: [
992
+ onPickLocalMedia ? /* @__PURE__ */ jsx(View, { style: styles.pickRow, children: [{
993
+ id: "library",
994
+ icon: "image-multiple-outline",
995
+ label: "Photos"
996
+ }, {
997
+ id: "camera",
998
+ icon: "camera-outline",
999
+ label: "Camera"
1000
+ }, {
1001
+ id: "file",
1002
+ icon: "folder-outline",
1003
+ label: "Files"
1004
+ }].map((source) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => pickIntoLibrary(source.id), disabled: mediaBusy, style: [styles.pickBtn, mediaBusy && styles.disabled], accessibilityRole: "button", accessibilityLabel: `Upload from ${source.label}`, children: [
1005
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: source.icon, size: 18, color: "#E8E8EE" }),
1006
+ /* @__PURE__ */ jsx(Text, { style: styles.pickBtnLabel, children: source.label })
1007
+ ] }, source.id)) }) : null,
1008
+ media.length === 0 ? /* @__PURE__ */ jsxs(View, { style: styles.emptyPanel, children: [
1009
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "image-plus", size: 22, color: "#6A6A74" }),
1010
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "No media yet" }),
1011
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Upload from this phone, or tap a clip from this canvas to add it to the timeline." })
1012
+ ] }) : /* @__PURE__ */ jsx(View, { style: styles.mediaGrid, children: media.map((item) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1013
+ if (item.type === "audio") {
1014
+ setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1015
+ soundtrack: {
1016
+ audioUrl: item.src,
1017
+ mode: "mix",
1018
+ title: item.generatedAssetName || "Audio"
1019
+ }
895
1020
  }));
896
- persistVideoProjects(next);
897
- setProjectRows(next);
898
- if (row.id === activeProjectId) setLocalTitle(title);
899
- setRenameId(null);
900
- }, autoFocus: true, style: styles.renameInput }) : /* @__PURE__ */ jsxs(View, { style: styles.projectRowCopy, children: [
901
- /* @__PURE__ */ jsx(Text, { style: styles.projectRowTitle, numberOfLines: 1, children: row.title }),
902
- /* @__PURE__ */ jsxs(Text, { style: styles.projectRowMeta, children: [
903
- clips,
904
- " clip",
905
- clips === 1 ? "" : "s",
906
- " \xB7",
907
- " ",
908
- relativeProjectTime(row.updatedAt)
909
- ] })
910
- ] }),
911
- !isDraft ? /* @__PURE__ */ jsxs(Fragment, { children: [
912
- /* @__PURE__ */ jsx(Pressable, { onPress: () => {
913
- setRenameId(row.id);
914
- setRenameDraft(row.title);
915
- }, hitSlop: 8, accessibilityLabel: "Rename project", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "pencil-outline", size: 16, color: "#8B8B96" }) }),
916
- /* @__PURE__ */ jsx(Pressable, { onPress: () => deleteSavedProject(row.id), hitSlop: 8, accessibilityLabel: "Delete project", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "trash-can-outline", size: 16, color: "#FF6B6B" }) })
917
- ] }) : /* @__PURE__ */ jsx(Text, { style: styles.projectRowMeta, children: "Current" })
918
- ] }, row.id);
919
- })
920
- ] }) : tab === "ai" ? /* @__PURE__ */ jsxs(View, { children: [
921
- /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Create with AI" }),
922
- /* @__PURE__ */ jsx(TextInput, { value: aiPrompt, onChangeText: setAiPrompt, placeholder: "Continue the scene\u2026", placeholderTextColor: "#5A5A66", style: styles.textInput, multiline: true }),
923
- /* @__PURE__ */ jsx(Pressable, { onPress: () => generateAiScene(), disabled: aiBusy || !aiPrompt.trim(), style: [styles.chip, styles.chipActive, styles.chipWide], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: aiBusy ? "Generating\u2026" : "Generate scene" }) })
924
- ] }) : railTab === "elements" ? /* @__PURE__ */ jsxs(View, { children: [
925
- /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Blank scene" }),
926
- /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SCENE_COLORS.map((color) => /* @__PURE__ */ jsx(Pressable, { onPress: () => {
927
- const clip = clipFromColor(color);
928
- setDirty(true);
929
- setTimeline((t) => __spreadProps(__spreadValues({}, t), {
930
- clips: [...t.clips, clip]
931
- }));
932
- setSelectedClipId(clip.id);
933
- setNotice("Blank scene added");
934
- }, style: [styles.sceneSwatch, {
935
- backgroundColor: color
936
- }], accessibilityRole: "button", accessibilityLabel: `Add blank scene ${color}` }, color)) }),
937
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Adds a colored still at the end of the timeline \u2014 hold duration in clip tools." })
938
- ] }) : railTab === "logo" ? /* @__PURE__ */ jsxs(View, { children: [
939
- logoSrc ? /* @__PURE__ */ jsx(Image, { source: {
940
- uri: logoSrc
941
- }, style: styles.logoPreview, contentFit: "contain" }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "A brand mark that rides every frame and exports with the video." }),
942
- /* @__PURE__ */ jsxs(View, { style: styles.panelRow, children: [
943
- /* @__PURE__ */ jsx(Pressable, { onPress: pickLogo, disabled: mediaBusy, style: [styles.chip, styles.chipActive, mediaBusy && styles.disabled], accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: logoSrc ? "Replace logo" : "Upload logo" }) }),
944
- logoSrc ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setLogoSrc(null), style: styles.chip, children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipDanger], children: "Remove" }) }) : null
945
- ] })
946
- ] }) : tab === "trim" ? selected ? /* @__PURE__ */ jsx(TrimPanel, { clip: selected, sourceSec: selectedSource, onTrim: (start, end) => patchSelected(trimClip(selected, start, end)), onImageDuration: (sec) => patchSelected({
947
- durationSec: sec
948
- }), onMove: (dir) => setTimeline((t) => moveClip(t, selected.id, dir)), onRemove: () => {
949
- setTimeline((t) => removeClip(t, selected.id));
950
- setSelectedClipId(null);
951
- }, canMoveLeft: selectedIndex > 0, canMoveRight: selectedIndex >= 0 && selectedIndex < timeline.clips.length - 1 }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Pick a clip on the timeline to change its timing, trim, motion, look and audio." }) : tab === "filter" && selected ? /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.panelRow, children: FILTER_PRESETS.map((preset) => {
952
- const active = selected.filter === preset.id;
953
- return /* @__PURE__ */ jsxs(Pressable, { onPress: () => patchSelected({
954
- filter: preset.id
955
- }), style: styles.filterTile, accessibilityRole: "button", accessibilityState: {
956
- selected: active
957
- }, children: [
958
- /* @__PURE__ */ jsxs(View, { style: [styles.filterThumb, active && styles.filterThumbActive], children: [
959
- selected.type === "image" ? /* @__PURE__ */ jsx(Image, { source: {
960
- uri: selected.src
961
- }, style: StyleSheet.absoluteFill, contentFit: "cover" }) : /* @__PURE__ */ jsx(View, { style: [StyleSheet.absoluteFill, styles.mediaThumbVideo] }),
962
- preset.overlays.map(([color, opacity]) => /* @__PURE__ */ jsx(View, { style: [StyleSheet.absoluteFill, {
963
- backgroundColor: color,
964
- opacity
965
- }] }, `${color}-${opacity}`)),
966
- preset.id === "none" ? /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "cancel", size: 18, color: "#9A9AA5", style: styles.filterNoneGlyph }) : null
1021
+ setRailTab("music");
1022
+ setTab("music");
1023
+ return;
1024
+ }
1025
+ addClip(item);
1026
+ }, style: styles.mediaTile, children: [
1027
+ item.type === "image" ? /* @__PURE__ */ jsx(Image, { source: {
1028
+ uri: item.src
1029
+ }, style: styles.mediaThumb, contentFit: "cover" }) : /* @__PURE__ */ jsx(View, { style: [styles.mediaThumb, styles.mediaThumbVideo], children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: item.type === "audio" ? "music" : "play", size: 18, color: "#9A9AA5" }) }),
1030
+ /* @__PURE__ */ jsx(View, { style: styles.mediaAdd, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 12, color: "#0A0A0C" }) })
1031
+ ] }, item.id)) })
1032
+ ] }) : railTab === "projects" ? /* @__PURE__ */ jsxs(View, { style: styles.projectsPane, children: [
1033
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "A project is a saved timeline. Create one, then add clips from Media." }),
1034
+ /* @__PURE__ */ jsxs(Pressable, { onPress: startNewProject, style: styles.solidBtn, accessibilityRole: "button", accessibilityLabel: "New project", children: [
1035
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 16, color: "#101013" }),
1036
+ /* @__PURE__ */ jsx(Text, { style: styles.solidBtnLabel, children: "Create project" })
967
1037
  ] }),
968
- /* @__PURE__ */ jsx(Text, { style: [styles.filterLabel, active && styles.filterLabelActive], children: preset.label })
969
- ] }, preset.id);
970
- }) }) : tab === "speed" && selected ? /* @__PURE__ */ jsxs(View, { children: [
971
- /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SPEED_PRESETS.map((speed) => {
972
- const active = selected.speed === speed;
973
- return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
974
- speed
975
- }), style: [styles.chip, active && styles.chipActive], accessibilityRole: "button", accessibilityState: {
976
- selected: active
977
- }, children: /* @__PURE__ */ jsxs(Text, { style: [styles.chipLabel, active && styles.chipLabelActive], children: [
978
- speed,
979
- "\xD7"
980
- ] }) }, speed);
981
- }) }),
982
- /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SPEED_CURVES.map((curve) => {
983
- const active = selected.speedCurve === curve;
984
- return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
985
- speedCurve: curve
986
- }), style: [styles.chip, active && styles.chipActive], accessibilityRole: "button", accessibilityState: {
987
- selected: active
988
- }, children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, active && styles.chipLabelActive], children: SPEED_CURVE_LABEL[curve] }) }, curve);
989
- }) }),
990
- selected.speedCurve !== "constant" ? /* @__PURE__ */ jsxs(Text, { style: styles.panelHint, children: [
991
- "Curves render on export; preview plays ",
992
- selected.speed,
993
- "\xD7."
994
- ] }) : null
995
- ] }) : railTab === "text" ? /* @__PURE__ */ jsxs(View, { children: [
996
- /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.panelRow, children: TEXT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => addText({
997
- text: preset.text,
998
- x: preset.x,
999
- y: preset.y
1000
- }), style: styles.textPreset, accessibilityRole: "button", accessibilityLabel: `Add ${preset.label}`, children: [
1001
- /* @__PURE__ */ jsx(Text, { style: styles.textPresetSample, numberOfLines: 1, children: preset.text }),
1002
- /* @__PURE__ */ jsx(Text, { style: styles.textPresetLabel, children: preset.label })
1003
- ] }, preset.id)) }),
1004
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Lands at the playhead. Tap the preview to edit, drag to place." }),
1005
- selectedText ? /* @__PURE__ */ jsxs(Fragment, { children: [
1006
- /* @__PURE__ */ jsx(TextInput, { value: selectedText.text, onChangeText: (text) => setTimeline((t) => updateText(t, selectedText.id, {
1007
- text
1008
- })), style: styles.textInput, placeholder: "Title", placeholderTextColor: "#5A5A66", selectionColor: "#8B7CFF", selectTextOnFocus: true, returnKeyType: "done", onSubmitEditing: dismissKeyboard }),
1038
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: dirty ? "Unsaved changes \u2014 saves as soon as you edit." : projectHasWork(timeline) ? "All changes saved" : "Saves as soon as you edit." }),
1039
+ projectList.map((row) => {
1040
+ const current = row.id === "__current__" || row.id === activeProjectId;
1041
+ const clips = row.timeline.clips.length;
1042
+ const isDraft = row.id === "__current__";
1043
+ return /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1044
+ if (isDraft) return;
1045
+ openSavedProject(row);
1046
+ }, style: [styles.projectRow, current && styles.projectRowOn], children: [
1047
+ renameId === row.id ? /* @__PURE__ */ jsx(TextInput, { value: renameDraft, onChangeText: setRenameDraft, onSubmitEditing: () => {
1048
+ const title = renameDraft.trim() || "Untitled project";
1049
+ const next = upsertVideoProject(projectRows, __spreadProps(__spreadValues({}, row), {
1050
+ title
1051
+ }));
1052
+ persistVideoProjects(next);
1053
+ setProjectRows(next);
1054
+ if (row.id === activeProjectId) setLocalTitle(title);
1055
+ setRenameId(null);
1056
+ }, autoFocus: true, style: styles.renameInput }) : /* @__PURE__ */ jsxs(View, { style: styles.projectRowCopy, children: [
1057
+ /* @__PURE__ */ jsx(Text, { style: styles.projectRowTitle, numberOfLines: 1, children: row.title }),
1058
+ /* @__PURE__ */ jsxs(Text, { style: styles.projectRowMeta, children: [
1059
+ clips,
1060
+ " clip",
1061
+ clips === 1 ? "" : "s",
1062
+ " \xB7",
1063
+ " ",
1064
+ relativeProjectTime(row.updatedAt)
1065
+ ] })
1066
+ ] }),
1067
+ !isDraft ? /* @__PURE__ */ jsxs(Fragment, { children: [
1068
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1069
+ setRenameId(row.id);
1070
+ setRenameDraft(row.title);
1071
+ }, hitSlop: 8, accessibilityLabel: "Rename project", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "pencil-outline", size: 16, color: "#8B8B96" }) }),
1072
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => deleteSavedProject(row.id), hitSlop: 8, accessibilityLabel: "Delete project", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "trash-can-outline", size: 16, color: "#FF6B6B" }) })
1073
+ ] }) : /* @__PURE__ */ jsx(Text, { style: styles.projectRowMeta, children: "Current" })
1074
+ ] }, row.id);
1075
+ })
1076
+ ] }) : tab === "ai" ? /* @__PURE__ */ jsxs(View, { children: [
1077
+ /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Create with AI" }),
1078
+ /* @__PURE__ */ jsx(TextInput, { value: aiPrompt, onChangeText: setAiPrompt, placeholder: "Continue the scene\u2026", placeholderTextColor: "#5A5A66", style: styles.textInput, multiline: true }),
1079
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => generateAiScene(), disabled: aiBusy || !aiPrompt.trim(), style: [styles.chip, styles.chipActive, styles.chipWide], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: aiBusy ? "Generating\u2026" : "Generate scene" }) })
1080
+ ] }) : railTab === "elements" ? /* @__PURE__ */ jsxs(View, { children: [
1081
+ /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Blank scene" }),
1082
+ /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SCENE_COLORS.map((color) => /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1083
+ const clip = clipFromColor(color);
1084
+ setDirty(true);
1085
+ setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1086
+ clips: [...t.clips, clip]
1087
+ }));
1088
+ setSelectedClipId(clip.id);
1089
+ setNotice("Blank scene added");
1090
+ }, style: [styles.sceneSwatch, {
1091
+ backgroundColor: color
1092
+ }], accessibilityRole: "button", accessibilityLabel: `Add blank scene ${color}` }, color)) }),
1093
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Adds a colored still at the end of the timeline \u2014 hold duration in clip tools." })
1094
+ ] }) : railTab === "logo" ? /* @__PURE__ */ jsxs(View, { children: [
1095
+ logoSrc ? /* @__PURE__ */ jsx(Image, { source: {
1096
+ uri: logoSrc
1097
+ }, style: styles.logoPreview, contentFit: "contain" }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "A brand mark that rides every frame and exports with the video." }),
1009
1098
  /* @__PURE__ */ jsxs(View, { style: styles.panelRow, children: [
1010
- /* @__PURE__ */ jsx(Stepper, { label: "Start", value: `${selectedText.startSec.toFixed(1)}s`, onLess: () => setTimeline((t) => updateText(t, selectedText.id, {
1011
- startSec: Math.max(0, selectedText.startSec - 0.5)
1012
- })), onMore: () => setTimeline((t) => updateText(t, selectedText.id, {
1013
- startSec: selectedText.startSec + 0.5
1014
- })) }),
1015
- /* @__PURE__ */ jsx(Stepper, { label: "For", value: `${selectedText.durationSec.toFixed(1)}s`, onLess: () => setTimeline((t) => updateText(t, selectedText.id, {
1016
- durationSec: Math.max(0.5, selectedText.durationSec - 0.5)
1017
- })), onMore: () => setTimeline((t) => updateText(t, selectedText.id, {
1018
- durationSec: selectedText.durationSec + 0.5
1019
- })) }),
1020
- /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1021
- setTimeline((t) => removeText(t, selectedText.id));
1022
- setSelectedTextId(null);
1023
- }, style: styles.chip, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipDanger], children: "Remove" }) })
1099
+ /* @__PURE__ */ jsx(Pressable, { onPress: pickLogo, disabled: mediaBusy, style: [styles.chip, styles.chipActive, mediaBusy && styles.disabled], accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: logoSrc ? "Replace logo" : "Upload logo" }) }),
1100
+ logoSrc ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setLogoSrc(null), style: styles.chip, children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipDanger], children: "Remove" }) }) : null
1024
1101
  ] })
1025
- ] }) : null
1026
- ] }) : railTab === "music" ? /* @__PURE__ */ jsx(SoundtrackPanel, { runAction, soundtrack: (_h = timeline.soundtrack) != null ? _h : null, totalSec: total.knownSec, onSoundtrack: (soundtrack) => setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1027
- soundtrack
1028
- })), onNotice: setNotice }) : railTab === "voiceover" ? /* @__PURE__ */ jsx(VoicePanel, { runAction, uploadMedia, audioRecorder, voiceover: (_i = timeline.voiceover) != null ? _i : null, selectedClip: selected, onVoiceover: (voiceover) => setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1029
- voiceover
1030
- })), onNotice: setNotice }) : railTab === "agent" ? /* @__PURE__ */ jsx(AgentCutPane, { timeline, setTimeline, token, indexUrl }) : railTab === "captions" ? /* @__PURE__ */ jsxs(View, { children: [
1031
- /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Captions" }),
1032
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "One captions lane \u2014 add at the playhead, then tap a row to edit." }),
1033
- /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1034
- const preset = TEXT_PRESETS.find((p) => p.id === "caption");
1035
- addText(preset ? {
1102
+ ] }) : tab === "trim" ? selected ? /* @__PURE__ */ jsx(TrimPanel, { clip: selected, sourceSec: selectedSource, onTrim: (start, end) => patchSelected(trimClip(selected, start, end)), onImageDuration: (sec) => patchSelected({
1103
+ durationSec: sec
1104
+ }), onMove: (dir) => setTimeline((t) => moveClip(t, selected.id, dir)), onRemove: () => {
1105
+ setTimeline((t) => removeClip(t, selected.id));
1106
+ setSelectedClipId(null);
1107
+ }, canMoveLeft: selectedIndex > 0, canMoveRight: selectedIndex >= 0 && selectedIndex < timeline.clips.length - 1 }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Pick a clip on the timeline to change its timing, trim, motion, look and audio." }) : tab === "filter" && selected ? /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.panelRow, children: FILTER_PRESETS.map((preset) => {
1108
+ const active = selected.filter === preset.id;
1109
+ return /* @__PURE__ */ jsxs(Pressable, { onPress: () => patchSelected({
1110
+ filter: preset.id
1111
+ }), style: styles.filterTile, accessibilityRole: "button", accessibilityState: {
1112
+ selected: active
1113
+ }, children: [
1114
+ /* @__PURE__ */ jsxs(View, { style: [styles.filterThumb, active && styles.filterThumbActive], children: [
1115
+ selected.type === "image" ? /* @__PURE__ */ jsx(Image, { source: {
1116
+ uri: selected.src
1117
+ }, style: StyleSheet.absoluteFill, contentFit: "cover" }) : /* @__PURE__ */ jsx(View, { style: [StyleSheet.absoluteFill, styles.mediaThumbVideo] }),
1118
+ preset.overlays.map(([color, opacity]) => /* @__PURE__ */ jsx(View, { style: [StyleSheet.absoluteFill, {
1119
+ backgroundColor: color,
1120
+ opacity
1121
+ }] }, `${color}-${opacity}`)),
1122
+ preset.id === "none" ? /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "cancel", size: 18, color: "#9A9AA5", style: styles.filterNoneGlyph }) : null
1123
+ ] }),
1124
+ /* @__PURE__ */ jsx(Text, { style: [styles.filterLabel, active && styles.filterLabelActive], children: preset.label })
1125
+ ] }, preset.id);
1126
+ }) }) : tab === "speed" && selected ? /* @__PURE__ */ jsxs(View, { children: [
1127
+ /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SPEED_PRESETS.map((speed) => {
1128
+ const active = selected.speed === speed;
1129
+ return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1130
+ speed
1131
+ }), style: [styles.chip, active && styles.chipActive], accessibilityRole: "button", accessibilityState: {
1132
+ selected: active
1133
+ }, children: /* @__PURE__ */ jsxs(Text, { style: [styles.chipLabel, active && styles.chipLabelActive], children: [
1134
+ speed,
1135
+ "\xD7"
1136
+ ] }) }, speed);
1137
+ }) }),
1138
+ /* @__PURE__ */ jsx(View, { style: styles.panelRow, children: SPEED_CURVES.map((curve) => {
1139
+ const active = selected.speedCurve === curve;
1140
+ return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1141
+ speedCurve: curve
1142
+ }), style: [styles.chip, active && styles.chipActive], accessibilityRole: "button", accessibilityState: {
1143
+ selected: active
1144
+ }, children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, active && styles.chipLabelActive], children: SPEED_CURVE_LABEL[curve] }) }, curve);
1145
+ }) }),
1146
+ selected.speedCurve !== "constant" ? /* @__PURE__ */ jsxs(Text, { style: styles.panelHint, children: [
1147
+ "Curves render on export; preview plays ",
1148
+ selected.speed,
1149
+ "\xD7."
1150
+ ] }) : null
1151
+ ] }) : railTab === "text" ? /* @__PURE__ */ jsxs(View, { children: [
1152
+ /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.panelRow, children: TEXT_PRESETS.map((preset) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => addText({
1036
1153
  text: preset.text,
1037
1154
  x: preset.x,
1038
1155
  y: preset.y
1039
- } : {
1040
- text: "Caption",
1041
- x: 0.5,
1042
- y: 0.86
1043
- });
1044
- }, style: [styles.chip, styles.chipActive, styles.chipWide], accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: "Add caption at playhead" }) }),
1045
- timeline.texts.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "No captions yet." }) : timeline.texts.map((row) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1046
- setSelectedTextId(row.id);
1047
- setTab("text");
1048
- }, style: styles.captionRow, children: [
1049
- /* @__PURE__ */ jsx(Text, { style: styles.captionRowText, numberOfLines: 1, children: row.text || "Caption" }),
1050
- /* @__PURE__ */ jsxs(Text, { style: styles.captionRowMeta, children: [
1051
- row.startSec.toFixed(1),
1052
- "s"
1156
+ }), style: styles.textPreset, accessibilityRole: "button", accessibilityLabel: `Add ${preset.label}`, children: [
1157
+ /* @__PURE__ */ jsx(Text, { style: styles.textPresetSample, numberOfLines: 1, children: preset.text }),
1158
+ /* @__PURE__ */ jsx(Text, { style: styles.textPresetLabel, children: preset.label })
1159
+ ] }, preset.id)) }),
1160
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Lands at the playhead. Tap the preview to edit, drag to place." }),
1161
+ selectedText ? /* @__PURE__ */ jsxs(Fragment, { children: [
1162
+ /* @__PURE__ */ jsx(TextInput, { value: selectedText.text, onChangeText: (text) => setTimeline((t) => updateText(t, selectedText.id, {
1163
+ text
1164
+ })), style: styles.textInput, placeholder: "Title", placeholderTextColor: "#5A5A66", selectionColor: "#8B7CFF", selectTextOnFocus: true, returnKeyType: "done", onSubmitEditing: dismissKeyboard }),
1165
+ /* @__PURE__ */ jsxs(View, { style: styles.panelRow, children: [
1166
+ /* @__PURE__ */ jsx(Stepper, { label: "Start", value: `${selectedText.startSec.toFixed(1)}s`, onLess: () => setTimeline((t) => updateText(t, selectedText.id, {
1167
+ startSec: Math.max(0, selectedText.startSec - 0.5)
1168
+ })), onMore: () => setTimeline((t) => updateText(t, selectedText.id, {
1169
+ startSec: selectedText.startSec + 0.5
1170
+ })) }),
1171
+ /* @__PURE__ */ jsx(Stepper, { label: "For", value: `${selectedText.durationSec.toFixed(1)}s`, onLess: () => setTimeline((t) => updateText(t, selectedText.id, {
1172
+ durationSec: Math.max(0.5, selectedText.durationSec - 0.5)
1173
+ })), onMore: () => setTimeline((t) => updateText(t, selectedText.id, {
1174
+ durationSec: selectedText.durationSec + 0.5
1175
+ })) }),
1176
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1177
+ setTimeline((t) => removeText(t, selectedText.id));
1178
+ setSelectedTextId(null);
1179
+ }, style: styles.chip, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipDanger], children: "Remove" }) })
1180
+ ] })
1181
+ ] }) : null
1182
+ ] }) : railTab === "music" ? /* @__PURE__ */ jsx(SoundtrackPanel, { runAction, soundtrack: (_j = timeline.soundtrack) != null ? _j : null, totalSec: total.knownSec, onSoundtrack: (soundtrack) => setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1183
+ soundtrack
1184
+ })), onNotice: setNotice }) : railTab === "voiceover" ? /* @__PURE__ */ jsx(VoicePanel, { runAction, uploadMedia, audioRecorder, voiceover: (_k = timeline.voiceover) != null ? _k : null, selectedClip: selected, onVoiceover: (voiceover) => setTimeline((t) => __spreadProps(__spreadValues({}, t), {
1185
+ voiceover
1186
+ })), onSceneAudio: addSceneAudio, onNotice: setNotice }) : railTab === "agent" ? /* @__PURE__ */ jsx(AgentCutPane, { timeline, setTimeline, token, indexUrl, runAction, uploadMedia, onAddMedia: addLibraryMedia, onFilm: filmScenes }) : railTab === "captions" ? /* @__PURE__ */ jsxs(View, { children: [
1187
+ /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Captions" }),
1188
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "One captions lane \u2014 add at the playhead, then tap a row to edit." }),
1189
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1190
+ const preset = TEXT_PRESETS.find((p) => p.id === "caption");
1191
+ addText(preset ? {
1192
+ text: preset.text,
1193
+ x: preset.x,
1194
+ y: preset.y
1195
+ } : {
1196
+ text: "Caption",
1197
+ x: 0.5,
1198
+ y: 0.86
1199
+ });
1200
+ }, style: [styles.chip, styles.chipActive, styles.chipWide], accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: "Add caption at playhead" }) }),
1201
+ timeline.texts.length === 0 ? /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "No captions yet." }) : timeline.texts.map((row) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1202
+ setSelectedTextId(row.id);
1203
+ setTab("text");
1204
+ }, style: styles.captionRow, children: [
1205
+ /* @__PURE__ */ jsx(Text, { style: styles.captionRowText, numberOfLines: 1, children: row.text || "Caption" }),
1206
+ /* @__PURE__ */ jsxs(Text, { style: styles.captionRowMeta, children: [
1207
+ row.startSec.toFixed(1),
1208
+ "s"
1209
+ ] })
1210
+ ] }, row.id))
1211
+ ] }) : railTab === "audio" ? /* @__PURE__ */ jsxs(View, { children: [
1212
+ /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "AI audio" }),
1213
+ /* @__PURE__ */ jsx(TextInput, { value: audioPrompt, onChangeText: setAudioPrompt, placeholder: "Music or narration prompt\u2026", placeholderTextColor: "#5A5A66", style: styles.textInput, multiline: true }),
1214
+ /* @__PURE__ */ jsx(Pressable, { onPress: generateAiAudio, disabled: audioBusy || !audioPrompt.trim(), style: [styles.chip, styles.chipActive, styles.chipWide], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: audioBusy ? "Generating\u2026" : "Generate" }) }),
1215
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Lands in the media library \u2014 tap it there to add to the timeline. Mood music with auto-duck lives under Music." })
1216
+ ] }) : tab === "segment" ? /* @__PURE__ */ jsxs(View, { children: [
1217
+ /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Segment" }),
1218
+ (selectedBlur == null ? void 0 : selectedBlur.blur) ? /* @__PURE__ */ jsxs(View, { style: styles.panelRow, children: [
1219
+ /* @__PURE__ */ jsx(Stepper, { label: "Blur", value: `${selectedBlur.blur.radius}px`, onLess: () => patchBlur({
1220
+ radius: Math.max(1, selectedBlur.blur.radius - 1)
1221
+ }), onMore: () => patchBlur({
1222
+ radius: Math.min(40, selectedBlur.blur.radius + 1)
1223
+ }) }),
1224
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setTimeline((t) => {
1225
+ var _a2;
1226
+ return __spreadProps(__spreadValues({}, t), {
1227
+ segments: ((_a2 = t.segments) != null ? _a2 : []).filter((s) => s.id !== selectedBlur.id)
1228
+ });
1229
+ }), style: styles.chip, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles.chipLabel, children: "Remove" }) }),
1230
+ /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Drag the box on the preview; pull its corner to resize." })
1231
+ ] }) : null,
1232
+ selectedSegmentId && ((_l = timeline.segments) != null ? _l : []).find((s) => s.id === selectedSegmentId) ? /* @__PURE__ */ jsxs(Text, { style: styles.panelEmpty, children: [
1233
+ (_n = ((_m = timeline.segments) != null ? _m : []).find((s) => s.id === selectedSegmentId)) == null ? void 0 : _n.name,
1234
+ " ",
1235
+ "\xB7 2s range at the playhead. Hide the segment lane to switch it off."
1236
+ ] }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Speed, zoom, crop, trim or blur a 2s range at the playhead." })
1237
+ ] }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Pick a clip on the timeline to change its timing, trim, motion, look and audio." }) }) })
1238
+ ] }) : null,
1239
+ /* @__PURE__ */ jsxs(View, { style: styles.stageCol, children: [
1240
+ /* @__PURE__ */ jsx(View, { style: styles.stageWell, onTouchStart: dismissKeyboard, children: /* @__PURE__ */ jsx(View, { style: styles.stage, children: selected === null ? /* @__PURE__ */ jsxs(View, { style: styles.stageEmptyCopy, children: [
1241
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "filmstrip-box-multiple", size: 32, color: "#6A6A74" }),
1242
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "Start a scene" }),
1243
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Drop in a photo, a camera take, or a blank card \u2014 the same doors as the web studio." }),
1244
+ /* @__PURE__ */ jsxs(View, { style: styles.stageEmptyActions, children: [
1245
+ /* @__PURE__ */ jsxs(Pressable, { onPress: addBlankScene, style: styles.solidBtn, accessibilityRole: "button", accessibilityLabel: "Add blank scene", children: [
1246
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 16, color: "#101013" }),
1247
+ /* @__PURE__ */ jsx(Text, { style: styles.solidBtnLabel, children: "Blank scene" })
1248
+ ] }),
1249
+ /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1250
+ setRailTab("media");
1251
+ setTab("media");
1252
+ }, style: styles.ghostBtn, accessibilityRole: "button", children: [
1253
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "image-plus", size: 16, color: "#F2F2F5" }),
1254
+ /* @__PURE__ */ jsx(Text, { style: styles.ghostBtnLabel, children: "Open Media" })
1255
+ ] })
1053
1256
  ] })
1054
- ] }, row.id))
1055
- ] }) : railTab === "audio" ? /* @__PURE__ */ jsxs(View, { children: [
1056
- /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "AI audio" }),
1057
- /* @__PURE__ */ jsx(TextInput, { value: audioPrompt, onChangeText: setAudioPrompt, placeholder: "Music or narration prompt\u2026", placeholderTextColor: "#5A5A66", style: styles.textInput, multiline: true }),
1058
- /* @__PURE__ */ jsx(Pressable, { onPress: generateAiAudio, disabled: audioBusy || !audioPrompt.trim(), style: [styles.chip, styles.chipActive, styles.chipWide], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: audioBusy ? "Generating\u2026" : "Generate" }) }),
1059
- /* @__PURE__ */ jsx(Text, { style: styles.panelHint, children: "Lands in the media library \u2014 tap it there to add to the timeline. Mood music with auto-duck lives under Music." })
1060
- ] }) : tab === "segment" ? /* @__PURE__ */ jsxs(View, { children: [
1061
- /* @__PURE__ */ jsx(Text, { style: styles.panelBarTitle, children: "Segment" }),
1062
- selectedSegmentId && ((_j = timeline.segments) != null ? _j : []).find((s) => s.id === selectedSegmentId) ? /* @__PURE__ */ jsxs(Text, { style: styles.panelEmpty, children: [
1063
- (_l = ((_k = timeline.segments) != null ? _k : []).find((s) => s.id === selectedSegmentId)) == null ? void 0 : _l.name,
1064
- " ",
1065
- "\xB7 2s range at the playhead. Hide the segment lane to switch it off."
1066
- ] }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Speed, zoom, crop or trim a 2s range at the playhead." })
1067
- ] }) : /* @__PURE__ */ jsx(Text, { style: styles.panelEmpty, children: "Pick a clip on the timeline to change its timing, trim, motion, look and audio." }) }) })
1068
- ] }) : null,
1069
- /* @__PURE__ */ jsxs(View, { style: styles.stageCol, children: [
1070
- /* @__PURE__ */ jsx(View, { style: styles.stageWell, onTouchStart: dismissKeyboard, children: /* @__PURE__ */ jsx(View, { style: styles.stage, children: selected === null ? /* @__PURE__ */ jsxs(View, { style: styles.stageEmptyCopy, children: [
1071
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "filmstrip-box-multiple", size: 32, color: "#6A6A74" }),
1072
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyTitle, children: "Start a scene" }),
1073
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Drop in a photo, a camera take, or a blank card \u2014 the same doors as the web studio." }),
1074
- /* @__PURE__ */ jsxs(View, { style: styles.stageEmptyActions, children: [
1075
- /* @__PURE__ */ jsxs(Pressable, { onPress: addBlankScene, style: styles.solidBtn, accessibilityRole: "button", accessibilityLabel: "Add blank scene", children: [
1076
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus", size: 16, color: "#101013" }),
1077
- /* @__PURE__ */ jsx(Text, { style: styles.solidBtnLabel, children: "Blank scene" })
1078
- ] }),
1079
- /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1080
- setRailTab("media");
1081
- setTab("media");
1082
- }, style: styles.ghostBtn, accessibilityRole: "button", children: [
1083
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "image-plus", size: 16, color: "#F2F2F5" }),
1084
- /* @__PURE__ */ jsx(Text, { style: styles.ghostBtnLabel, children: "Open Media" })
1257
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1258
+ selected.type === "video" && VideoPreview ? /* @__PURE__ */ jsx(VideoPreview, { src: selected.src, playing, rate: selected.speed, volume: selected.volume, muted: selected.muted, trimStartSec: selected.trimStartSec, trimEndSec: selected.trimEndSec, scrubToSec: scrub.sec, scrubNonce: scrub.nonce, onTime: onPreviewTime }) : selected.type === "video" ? /* @__PURE__ */ jsxs(View, { style: styles.stageEmpty, children: [
1259
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "play-circle-outline", size: 40, color: "#4A4A55" }),
1260
+ /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Video preview needs the host's player - editing still works." })
1261
+ ] }) : /* @__PURE__ */ jsx(Image, { source: {
1262
+ uri: selected.src
1263
+ }, style: StyleSheet.absoluteFill, contentFit: "contain" }),
1264
+ selectedFilter.overlays.map(([color, opacity]) => /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: [StyleSheet.absoluteFill, {
1265
+ backgroundColor: color,
1266
+ opacity
1267
+ }] }, `${color}-${opacity}`)),
1268
+ (selectedBlur == null ? void 0 : selectedBlur.blur) && tab === "segment" ? /* @__PURE__ */ jsx(BlurBoxOverlay, { box: selectedBlur.blur, onChange: (box) => patchBlur(box) }) : null,
1269
+ timeline.texts.filter((t) => layerInView(t, selected.id)).map((t) => /* @__PURE__ */ jsx(DraggableText, { text: t.text, x: t.x, y: t.y, active: t.id === selectedTextId, onSelect: () => {
1270
+ setSelectedTextId(t.id);
1271
+ setTab("text");
1272
+ }, onMove: (x, y) => setTimeline((tl) => updateText(tl, t.id, {
1273
+ x,
1274
+ y
1275
+ })) }, t.id)),
1276
+ logoSrc ? /* @__PURE__ */ jsx(Image, { source: {
1277
+ uri: logoSrc
1278
+ }, style: styles.logoOverlay, contentFit: "contain", pointerEvents: "none" }) : null,
1279
+ /* @__PURE__ */ jsxs(View, { style: styles.stageToolbar, children: [
1280
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "animate" ? null : "animate"), style: [styles.stagePill, selected.kenBurns && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Animate" }) }),
1281
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "look" ? null : "look"), style: [styles.stagePill, selected.filter !== "none" && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Look" }) }),
1282
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "transition" ? null : "transition"), style: [styles.stagePill, selected.transitionIn && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Transition" }) })
1085
1283
  ] })
1086
- ] })
1087
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1088
- selected.type === "video" && VideoPreview ? /* @__PURE__ */ jsx(VideoPreview, { src: selected.src, playing, rate: selected.speed, volume: selected.volume, muted: selected.muted, trimStartSec: selected.trimStartSec, trimEndSec: selected.trimEndSec, scrubToSec: scrub.sec, scrubNonce: scrub.nonce, onTime: onPreviewTime }) : selected.type === "video" ? /* @__PURE__ */ jsxs(View, { style: styles.stageEmpty, children: [
1089
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "play-circle-outline", size: 40, color: "#4A4A55" }),
1090
- /* @__PURE__ */ jsx(Text, { style: styles.stageEmptyDetail, children: "Video preview needs the host's player - editing still works." })
1091
- ] }) : /* @__PURE__ */ jsx(Image, { source: {
1092
- uri: selected.src
1093
- }, style: StyleSheet.absoluteFill, contentFit: "contain" }),
1094
- selectedFilter.overlays.map(([color, opacity]) => /* @__PURE__ */ jsx(View, { pointerEvents: "none", style: [StyleSheet.absoluteFill, {
1095
- backgroundColor: color,
1096
- opacity
1097
- }] }, `${color}-${opacity}`)),
1098
- timeline.texts.map((t) => /* @__PURE__ */ jsx(DraggableText, { text: t.text, x: t.x, y: t.y, active: t.id === selectedTextId, onSelect: () => {
1099
- setSelectedTextId(t.id);
1284
+ ] }) }) }),
1285
+ selected && stageTool ? /* @__PURE__ */ jsxs(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.stageToolRow, children: [
1286
+ stageTool === "animate" ? MOTION_MOVES.map((m) => {
1287
+ const on = moveIdOf(selected.kenBurns) === m.id;
1288
+ return /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1289
+ var _a2, _b2, _c2, _d2;
1290
+ return patchSelected({
1291
+ kenBurns: kenBurnsFor(m.id, Math.max((_b2 = (_a2 = selected.kenBurns) == null ? void 0 : _a2.zoomStart) != null ? _b2 : 1.3, (_d2 = (_c2 = selected.kenBurns) == null ? void 0 : _c2.zoomEnd) != null ? _d2 : 1.3))
1292
+ });
1293
+ }, style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: m.label }) }, m.id);
1294
+ }) : null,
1295
+ stageTool === "animate" && selected.kenBurns ? MOTION_STRENGTHS.map((s) => /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1296
+ kenBurns: kenBurnsFor(moveIdOf(selected.kenBurns), s.zoom)
1297
+ }), style: styles.chip, children: /* @__PURE__ */ jsx(Text, { style: styles.chipLabel, children: s.label }) }, s.id)) : null,
1298
+ stageTool === "look" ? FILTER_PRESETS.map((preset) => {
1299
+ const on = selected.filter === preset.id;
1300
+ return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1301
+ filter: preset.id
1302
+ }), style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: preset.label }) }, preset.id);
1303
+ }) : null,
1304
+ stageTool === "transition" ? CLIP_TRANSITIONS.map((fx) => {
1305
+ var _a2;
1306
+ const on = ((_a2 = selected.transitionIn) == null ? void 0 : _a2.effect) === fx;
1307
+ return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1308
+ transitionIn: on ? null : {
1309
+ effect: fx,
1310
+ durationMs: 1e3
1311
+ }
1312
+ }), style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: fx }) }, fx);
1313
+ }) : null
1314
+ ] }) : null,
1315
+ /* @__PURE__ */ jsx(TimelineTrack, { timeline, selectedClipId, selectedTextId, selectedSegmentId, segmentMenuOpen, playing, playhead, totalSec: total.knownSec, onTogglePlay: previewPlay, onSkipStart: () => {
1316
+ setPlaying(false);
1317
+ setPlayhead(0);
1318
+ setScrub((s) => ({
1319
+ sec: 0,
1320
+ nonce: s.nonce + 1
1321
+ }));
1322
+ }, onSkipEnd: () => {
1323
+ setPlaying(false);
1324
+ setPlayhead(total.knownSec);
1325
+ setScrub((s) => ({
1326
+ sec: total.knownSec,
1327
+ nonce: s.nonce + 1
1328
+ }));
1329
+ }, onToggleSegmentMenu: () => setSegmentMenuOpen((v) => !v), onAddSegment: dropSegment, onSelectSegment: (id) => {
1330
+ setSelectedSegmentId(id);
1331
+ setTab("segment");
1332
+ }, compact: timeline.clips.length === 0, onAddScene: addBlankScene, onLayers: () => {
1333
+ setRailTab("elements");
1334
+ setTab("elements");
1335
+ }, onSelectClip: (id) => {
1336
+ setSelectedClipId(id);
1337
+ setPlaying(false);
1338
+ setStageTool(null);
1339
+ setRailTab(null);
1340
+ }, onSelectText: (id) => {
1341
+ setSelectedTextId(id);
1342
+ setRailTab("text");
1100
1343
  setTab("text");
1101
- }, onMove: (x, y) => setTimeline((tl) => updateText(tl, t.id, {
1102
- x,
1103
- y
1104
- })) }, t.id)),
1105
- logoSrc ? /* @__PURE__ */ jsx(Image, { source: {
1106
- uri: logoSrc
1107
- }, style: styles.logoOverlay, contentFit: "contain", pointerEvents: "none" }) : null,
1108
- /* @__PURE__ */ jsxs(View, { style: styles.stageToolbar, children: [
1109
- /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "animate" ? null : "animate"), style: [styles.stagePill, selected.kenBurns && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Animate" }) }),
1110
- /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "look" ? null : "look"), style: [styles.stagePill, selected.filter !== "none" && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Look" }) }),
1111
- /* @__PURE__ */ jsx(Pressable, { onPress: () => setStageTool((t) => t === "transition" ? null : "transition"), style: [styles.stagePill, selected.transitionIn && styles.stagePillOn], children: /* @__PURE__ */ jsx(Text, { style: styles.stagePillLabel, children: "Transition" }) })
1112
- ] })
1113
- ] }) }) }),
1114
- selected && stageTool ? /* @__PURE__ */ jsxs(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, contentContainerStyle: styles.stageToolRow, children: [
1115
- stageTool === "animate" ? MOTION_MOVES.map((m) => {
1116
- const on = moveIdOf(selected.kenBurns) === m.id;
1117
- return /* @__PURE__ */ jsx(Pressable, { onPress: () => {
1118
- var _a2, _b2, _c2, _d2;
1119
- return patchSelected({
1120
- kenBurns: kenBurnsFor(m.id, Math.max((_b2 = (_a2 = selected.kenBurns) == null ? void 0 : _a2.zoomStart) != null ? _b2 : 1.3, (_d2 = (_c2 = selected.kenBurns) == null ? void 0 : _c2.zoomEnd) != null ? _d2 : 1.3))
1121
- });
1122
- }, style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: m.label }) }, m.id);
1123
- }) : null,
1124
- stageTool === "animate" && selected.kenBurns ? MOTION_STRENGTHS.map((s) => /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1125
- kenBurns: kenBurnsFor(moveIdOf(selected.kenBurns), s.zoom)
1126
- }), style: styles.chip, children: /* @__PURE__ */ jsx(Text, { style: styles.chipLabel, children: s.label }) }, s.id)) : null,
1127
- stageTool === "look" ? FILTER_PRESETS.map((preset) => {
1128
- const on = selected.filter === preset.id;
1129
- return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1130
- filter: preset.id
1131
- }), style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: preset.label }) }, preset.id);
1132
- }) : null,
1133
- stageTool === "transition" ? CLIP_TRANSITIONS.map((fx) => {
1134
- var _a2;
1135
- const on = ((_a2 = selected.transitionIn) == null ? void 0 : _a2.effect) === fx;
1136
- return /* @__PURE__ */ jsx(Pressable, { onPress: () => patchSelected({
1137
- transitionIn: on ? null : {
1138
- effect: fx,
1139
- durationMs: 1e3
1140
- }
1141
- }), style: [styles.chip, on && styles.chipActive], children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, on && styles.chipLabelActive], children: fx }) }, fx);
1142
- }) : null
1143
- ] }) : null,
1144
- /* @__PURE__ */ jsx(TimelineTrack, { timeline, selectedClipId, selectedTextId, selectedSegmentId, segmentMenuOpen, playing, playhead, totalSec: total.knownSec, onTogglePlay: previewPlay, onSkipStart: () => {
1145
- setPlaying(false);
1146
- setPlayhead(0);
1147
- setScrub((s) => ({
1148
- sec: 0,
1149
- nonce: s.nonce + 1
1150
- }));
1151
- }, onSkipEnd: () => {
1152
- setPlaying(false);
1153
- setPlayhead(total.knownSec);
1154
- setScrub((s) => ({
1155
- sec: total.knownSec,
1156
- nonce: s.nonce + 1
1157
- }));
1158
- }, onToggleSegmentMenu: () => setSegmentMenuOpen((v) => !v), onAddSegment: dropSegment, onSelectSegment: (id) => {
1159
- setSelectedSegmentId(id);
1160
- setTab("segment");
1161
- }, compact: timeline.clips.length === 0, onAddScene: addBlankScene, onLayers: () => {
1162
- setRailTab("elements");
1163
- setTab("elements");
1164
- }, onSelectClip: (id) => {
1165
- setSelectedClipId(id);
1166
- setPlaying(false);
1167
- setStageTool(null);
1168
- setRailTab(null);
1169
- }, onSelectText: (id) => {
1170
- setSelectedTextId(id);
1171
- setRailTab("text");
1172
- setTab("text");
1173
- } }),
1174
- selected && !railTab ? /* @__PURE__ */ jsxs(View, { style: styles.clipInspector, children: [
1175
- /* @__PURE__ */ jsx(Text, { style: styles.clipInspectorTitle, numberOfLines: 1, children: selected.name }),
1176
- /* @__PURE__ */ jsx(TrimPanel, { clip: selected, sourceSec: selectedSource, onTrim: (start, end) => patchSelected(trimClip(selected, start, end)), onImageDuration: (sec) => patchSelected({
1177
- durationSec: sec
1178
- }), onMove: (dir) => setTimeline((t) => moveClip(t, selected.id, dir)), onRemove: () => {
1179
- setTimeline((t) => removeClip(t, selected.id));
1180
- setSelectedClipId(null);
1181
- }, canMoveLeft: selectedIndex > 0, canMoveRight: selectedIndex >= 0 && selectedIndex < timeline.clips.length - 1 })
1182
- ] }) : null
1183
- ] }),
1184
- /* @__PURE__ */ jsxs(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.dock, contentContainerStyle: [styles.dockInner, {
1185
- paddingBottom: Math.max(insets.bottom, 8)
1186
- }], keyboardShouldPersistTaps: "handled", children: [
1187
- /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1188
- setRailTab(null);
1189
- setRecordError(null);
1190
- setRecordOpen((open) => !open);
1191
- }, style: [styles.dockTab, styles.dockRecord, recordOpen && styles.dockRecordOn], accessibilityRole: "button", accessibilityLabel: "Record", accessibilityState: {
1192
- selected: recordOpen
1193
- }, children: [
1194
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "record-circle", size: 22, color: recordOpen || recordOn ? "#F87171" : "#8B8B96" }),
1195
- /* @__PURE__ */ jsx(Text, { style: [styles.dockLabel, recordOpen && styles.dockRecordLabel], children: "Record" })
1344
+ } }),
1345
+ selected && !railTab ? /* @__PURE__ */ jsxs(View, { style: styles.clipInspector, children: [
1346
+ /* @__PURE__ */ jsx(Text, { style: styles.clipInspectorTitle, numberOfLines: 1, children: selected.name }),
1347
+ /* @__PURE__ */ jsx(TrimPanel, { clip: selected, sourceSec: selectedSource, onTrim: (start, end) => patchSelected(trimClip(selected, start, end)), onImageDuration: (sec) => patchSelected({
1348
+ durationSec: sec
1349
+ }), onMove: (dir) => setTimeline((t) => moveClip(t, selected.id, dir)), onRemove: () => {
1350
+ setTimeline((t) => removeClip(t, selected.id));
1351
+ setSelectedClipId(null);
1352
+ }, canMoveLeft: selectedIndex > 0, canMoveRight: selectedIndex >= 0 && selectedIndex < timeline.clips.length - 1 })
1353
+ ] }) : null
1196
1354
  ] }),
1197
- /* @__PURE__ */ jsx(View, { style: styles.dockRule }),
1198
- TABS.map((entry) => {
1199
- const active = railTab === entry.id;
1200
- return /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1201
- setRecordOpen(false);
1202
- setRailTab((cur) => cur === entry.id ? null : entry.id);
1203
- setTab(entry.id);
1204
- }, style: [styles.dockTab, active && styles.dockTabOn], accessibilityRole: "tab", accessibilityState: {
1205
- selected: active
1206
- }, accessibilityLabel: entry.label, children: [
1207
- /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: entry.icon, size: 20, color: active ? "#F2F2F5" : "#8B8B96" }),
1208
- /* @__PURE__ */ jsx(Text, { style: [styles.dockLabel, active && styles.dockLabelOn], children: entry.label })
1209
- ] }, entry.id);
1210
- })
1211
- ] }),
1212
- templatePreview ? /* @__PURE__ */ jsxs(View, { style: styles.templatePreviewOverlay, children: [
1213
- /* @__PURE__ */ jsxs(View, { style: styles.templatePreviewBar, children: [
1214
- /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewKicker, children: "Template preview" }),
1215
- /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewBarTitle, numberOfLines: 1, children: templatePreview.title }),
1216
- templatePreview.aspect ? /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewAspect, children: templatePreview.aspect }) : null,
1217
- /* @__PURE__ */ jsx(Pressable, { onPress: () => setTemplatePreview(null), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close template preview", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 18, color: "#F2F2F5" }) })
1355
+ /* @__PURE__ */ jsxs(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.dock, contentContainerStyle: [styles.dockInner, {
1356
+ paddingBottom: Math.max(insets.bottom, 8)
1357
+ }], keyboardShouldPersistTaps: "handled", children: [
1358
+ /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1359
+ setRailTab(null);
1360
+ setRecordError(null);
1361
+ setRecordOpen((open) => !open);
1362
+ }, style: [styles.dockTab, styles.dockRecord, recordOpen && styles.dockRecordOn], accessibilityRole: "button", accessibilityLabel: "Record", accessibilityState: {
1363
+ selected: recordOpen
1364
+ }, children: [
1365
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "record-circle", size: 22, color: recordOpen || recordOn ? "#F87171" : "#8B8B96" }),
1366
+ /* @__PURE__ */ jsx(Text, { style: [styles.dockLabel, recordOpen && styles.dockRecordLabel], children: "Record" })
1367
+ ] }),
1368
+ /* @__PURE__ */ jsx(View, { style: styles.dockRule }),
1369
+ TABS.map((entry) => {
1370
+ const active = railTab === entry.id;
1371
+ return /* @__PURE__ */ jsxs(Pressable, { onPress: () => {
1372
+ setRecordOpen(false);
1373
+ setRailTab((cur) => cur === entry.id ? null : entry.id);
1374
+ setTab(entry.id);
1375
+ }, style: [styles.dockTab, active && styles.dockTabOn], accessibilityRole: "tab", accessibilityState: {
1376
+ selected: active
1377
+ }, accessibilityLabel: entry.label, children: [
1378
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: entry.icon, size: 20, color: active ? "#F2F2F5" : "#8B8B96" }),
1379
+ /* @__PURE__ */ jsx(Text, { style: [styles.dockLabel, active && styles.dockLabelOn], children: entry.label })
1380
+ ] }, entry.id);
1381
+ })
1218
1382
  ] }),
1219
- /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewHint, children: "Not part of your video \u2014 Generate adds a fresh clip" }),
1220
- /* @__PURE__ */ jsx(View, { style: styles.templatePreviewMedia, children: templatePreview.kind === "video" ? /* @__PURE__ */ jsx(TemplateWebPreview, { src: templatePreview.src, poster: templatePreview.poster }) : /* @__PURE__ */ jsx(Image, { source: {
1221
- uri: templatePreview.poster || templatePreview.src
1222
- }, style: styles.templatePreviewImage, contentFit: "contain", recyclingKey: templatePreview.src }) })
1223
- ] }) : null
1224
- ] }),
1225
- notice ? /* @__PURE__ */ jsxs(View, { style: styles.notice, children: [
1226
- /* @__PURE__ */ jsx(Text, { style: styles.noticeText, children: notice }),
1227
- /* @__PURE__ */ jsx(Pressable, { onPress: () => setNotice(null), hitSlop: 8, children: /* @__PURE__ */ jsx(Text, { style: styles.noticeDismiss, children: "OK" }) })
1228
- ] }) : null,
1229
- mediaBusy || aiBusy ? /* @__PURE__ */ jsx(View, { style: styles.busyOverlay, pointerEvents: "auto", children: /* @__PURE__ */ jsxs(View, { style: styles.busyCard, children: [
1230
- /* @__PURE__ */ jsx(ActivityIndicator, { color: "#F7F7FA" }),
1231
- /* @__PURE__ */ jsx(Text, { style: styles.busyTitle, children: aiBusy ? "Generating scene" : mediaBusy ? "Uploading" : "Working" }),
1232
- /* @__PURE__ */ jsx(Text, { style: styles.busyDetail, children: aiBusy ? "This can take a minute. Keep the editor open." : uploadStatus || "Preparing your media\u2026" }),
1233
- mediaBusy ? /* @__PURE__ */ jsx(View, { style: styles.busyTrack, children: /* @__PURE__ */ jsx(View, { style: styles.busyTrackFill }) }) : null
1234
- ] }) }) : null,
1235
- /* @__PURE__ */ jsx(KeyboardLiftSpacer, {})
1236
- ] }) });
1383
+ templatePreview ? /* @__PURE__ */ jsxs(View, { style: styles.templatePreviewOverlay, children: [
1384
+ /* @__PURE__ */ jsxs(View, { style: styles.templatePreviewBar, children: [
1385
+ /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewKicker, children: "Template preview" }),
1386
+ /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewBarTitle, numberOfLines: 1, children: templatePreview.title }),
1387
+ templatePreview.aspect ? /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewAspect, children: templatePreview.aspect }) : null,
1388
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setTemplatePreview(null), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: "Close template preview", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "close", size: 18, color: "#F2F2F5" }) })
1389
+ ] }),
1390
+ /* @__PURE__ */ jsx(Text, { style: styles.templatePreviewHint, children: "Not part of your video \u2014 Generate adds a fresh clip" }),
1391
+ /* @__PURE__ */ jsx(View, { style: styles.templatePreviewMedia, children: templatePreview.kind === "video" ? /* @__PURE__ */ jsx(TemplateWebPreview, { src: templatePreview.src, poster: templatePreview.poster }) : /* @__PURE__ */ jsx(Image, { source: {
1392
+ uri: templatePreview.poster || templatePreview.src
1393
+ }, style: styles.templatePreviewImage, contentFit: "contain", recyclingKey: templatePreview.src }) })
1394
+ ] }) : null
1395
+ ] }),
1396
+ notice ? /* @__PURE__ */ jsxs(View, { style: styles.notice, children: [
1397
+ /* @__PURE__ */ jsx(Text, { style: styles.noticeText, children: notice }),
1398
+ /* @__PURE__ */ jsx(Pressable, { onPress: () => setNotice(null), hitSlop: 8, children: /* @__PURE__ */ jsx(Text, { style: styles.noticeDismiss, children: "OK" }) })
1399
+ ] }) : null,
1400
+ mediaBusy || aiBusy ? /* @__PURE__ */ jsx(View, { style: styles.busyOverlay, pointerEvents: "auto", children: /* @__PURE__ */ jsxs(View, { style: styles.busyCard, children: [
1401
+ /* @__PURE__ */ jsx(ActivityIndicator, { color: "#F7F7FA" }),
1402
+ /* @__PURE__ */ jsx(Text, { style: styles.busyTitle, children: aiBusy ? "Generating scene" : mediaBusy ? "Uploading" : "Working" }),
1403
+ /* @__PURE__ */ jsx(Text, { style: styles.busyDetail, children: aiBusy ? "This can take a minute. Keep the editor open." : uploadStatus || "Preparing your media\u2026" }),
1404
+ mediaBusy ? /* @__PURE__ */ jsx(View, { style: styles.busyTrack, children: /* @__PURE__ */ jsx(View, { style: styles.busyTrackFill }) }) : null
1405
+ ] }) }) : null,
1406
+ /* @__PURE__ */ jsx(KeyboardLiftSpacer, {})
1407
+ ] })
1408
+ ] });
1237
1409
  }
1238
1410
  function formatSec(sec) {
1239
1411
  const m = Math.floor(sec / 60);
@@ -1297,9 +1469,11 @@ function TimelineTrack({
1297
1469
  onSkipStart,
1298
1470
  onSkipEnd
1299
1471
  }) {
1300
- var _a;
1301
- const segments = (_a = timeline.segments) != null ? _a : [];
1302
- const hasAudio = Boolean(timeline.voiceover || timeline.soundtrack);
1472
+ var _a, _b;
1473
+ const segments = ((_a = timeline.segments) != null ? _a : []).filter((seg) => layerInView(seg, selectedClipId));
1474
+ const texts = timeline.texts.filter((t) => layerInView(t, selectedClipId));
1475
+ const sceneAudio = ((_b = timeline.sceneAudio) != null ? _b : []).filter((a) => layerInView(a, selectedClipId));
1476
+ const hasAudio = Boolean(timeline.voiceover || timeline.soundtrack || sceneAudio.length);
1303
1477
  return /* @__PURE__ */ jsxs(View, { style: [styles.timeline, compact && styles.timelineCompact], children: [
1304
1478
  /* @__PURE__ */ jsxs(View, { style: styles.timelineBar, children: [
1305
1479
  /* @__PURE__ */ jsx(Pressable, { onPress: onLayers, hitSlop: 6, style: styles.timelineIconBtn, accessibilityRole: "button", accessibilityLabel: "Open layers", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "layers-outline", size: 20, color: "#F2F2F5" }) }),
@@ -1361,12 +1535,12 @@ function TimelineTrack({
1361
1535
  ] }),
1362
1536
  /* @__PURE__ */ jsx(View, { style: styles.laneBody })
1363
1537
  ] }),
1364
- compact && timeline.texts.length === 0 ? null : /* @__PURE__ */ jsxs(View, { style: styles.laneRow, children: [
1538
+ compact && texts.length === 0 ? null : /* @__PURE__ */ jsxs(View, { style: styles.laneRow, children: [
1365
1539
  /* @__PURE__ */ jsxs(View, { style: styles.laneGutter, children: [
1366
1540
  /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "format-title", size: 14, color: "#8B8B96" }),
1367
1541
  /* @__PURE__ */ jsx(Text, { style: styles.laneLabel, children: "Text" })
1368
1542
  ] }),
1369
- /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.laneBody, children: /* @__PURE__ */ jsx(View, { style: styles.textTrack, children: timeline.texts.map((t) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelectText(t.id), style: [styles.textPill, t.id === selectedTextId && styles.textPillActive], children: [
1543
+ /* @__PURE__ */ jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: styles.laneBody, children: /* @__PURE__ */ jsx(View, { style: styles.textTrack, children: texts.map((t) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelectText(t.id), style: [styles.textPill, t.id === selectedTextId && styles.textPillActive], children: [
1370
1544
  /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "format-text", size: 11, color: "#F7F7FA" }),
1371
1545
  /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.textPillLabel, children: t.text })
1372
1546
  ] }, t.id)) }) })
@@ -1384,7 +1558,11 @@ function TimelineTrack({
1384
1558
  timeline.soundtrack ? /* @__PURE__ */ jsxs(View, { style: styles.voTrack, children: [
1385
1559
  /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "music-note", size: 11, color: "#DCD6FF" }),
1386
1560
  /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.voTrackLabel, children: timeline.soundtrack.title || "Soundtrack" })
1387
- ] }) : null
1561
+ ] }) : null,
1562
+ sceneAudio.map((a) => /* @__PURE__ */ jsxs(View, { style: styles.voTrack, children: [
1563
+ /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "waveform", size: 11, color: "#DCD6FF" }),
1564
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: styles.voTrackLabel, children: a.label || "Scene audio" })
1565
+ ] }, a.id))
1388
1566
  ] })
1389
1567
  ] }),
1390
1568
  compact && segments.length === 0 ? null : /* @__PURE__ */ jsxs(View, { style: styles.laneRow, children: [
@@ -1511,9 +1689,11 @@ function VoicePanel({
1511
1689
  voiceover,
1512
1690
  selectedClip,
1513
1691
  onVoiceover,
1692
+ onSceneAudio,
1514
1693
  onNotice
1515
1694
  }) {
1516
1695
  var _a;
1696
+ const [sceneOnly, setSceneOnly] = useState(false);
1517
1697
  const [voices, setVoices] = useState([]);
1518
1698
  const [voice, setVoice] = useState(null);
1519
1699
  const [text, setText] = useState((_a = voiceover == null ? void 0 : voiceover.text) != null ? _a : "");
@@ -1620,6 +1800,14 @@ function VoicePanel({
1620
1800
  } : {})).then((result) => {
1621
1801
  var _a2;
1622
1802
  const audioUrl = resultUrl(result, "SPEAK");
1803
+ if (sceneOnly && selectedClip && onSceneAudio) {
1804
+ onSceneAudio({
1805
+ audioUrl,
1806
+ label: (voice == null ? void 0 : voice.label) ? `${voice.label} \xB7 ${text.trim()}` : text.trim()
1807
+ });
1808
+ onNotice("Narration added to this scene - it moves with the scene.");
1809
+ return;
1810
+ }
1623
1811
  onVoiceover({
1624
1812
  audioUrl,
1625
1813
  mode: (_a2 = voiceover == null ? void 0 : voiceover.mode) != null ? _a2 : "mix",
@@ -1647,6 +1835,9 @@ function VoicePanel({
1647
1835
  /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, recording === "dictate" && styles.chipDanger], children: recording === "dictate" ? "Stop" : "Speak" })
1648
1836
  ] }) : null,
1649
1837
  (selectedClip == null ? void 0 : selectedClip.type) === "video" ? /* @__PURE__ */ jsx(Pressable, { onPress: transcribeClip, style: styles.chip, accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: styles.chipLabel, children: busy === "Transcribing" ? "Transcribing\u2026" : "From clip" }) }) : null,
1838
+ onSceneAudio && selectedClip ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setSceneOnly((v) => !v), style: [styles.chip, sceneOnly && styles.chipActive], accessibilityRole: "switch", accessibilityState: {
1839
+ checked: sceneOnly
1840
+ }, children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, sceneOnly && styles.chipLabelActive], children: "This scene only" }) }) : null,
1650
1841
  /* @__PURE__ */ jsx(Pressable, { onPress: generate, disabled: !text.trim() || busy !== null, style: [styles.chip, styles.chipActive, (!text.trim() || busy !== null) && styles.chipDisabled], accessibilityRole: "button", children: /* @__PURE__ */ jsx(Text, { style: [styles.chipLabel, styles.chipLabelActive], children: busy === "Generating" ? "Generating\u2026" : voiceover ? "Regenerate" : "Generate voiceover" }) })
1651
1842
  ] }),
1652
1843
  canRecord ? /* @__PURE__ */ jsxs(View, { style: styles.cloneBox, children: [