auricui-skill 0.0.0-stage → 0.1.2

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 (362) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -2
  3. package/bin/install.mjs +66 -0
  4. package/package.json +29 -4
  5. package/skill/SKILL.md +64 -0
  6. package/skill/agents/openai.yaml +4 -0
  7. package/skill/assets/examples/ApprovalCard.tsx +8 -0
  8. package/skill/assets/examples/BasicApp.tsx +38 -0
  9. package/skill/assets/examples/BriefingCard.tsx +8 -0
  10. package/skill/assets/examples/ChatControls.tsx +49 -0
  11. package/skill/assets/examples/ChatPanel.tsx +73 -0
  12. package/skill/assets/examples/Checkbox.tsx +13 -0
  13. package/skill/assets/examples/Combobox.tsx +14 -0
  14. package/skill/assets/examples/Drawer.tsx +10 -0
  15. package/skill/assets/examples/MemoryCard.tsx +9 -0
  16. package/skill/assets/examples/MessageList.tsx +8 -0
  17. package/skill/assets/examples/Theme.tsx +14 -0
  18. package/skill/assets/examples/ToolCall.tsx +8 -0
  19. package/skill/assets/examples/Tree.tsx +15 -0
  20. package/skill/assets/examples/examples.css +11 -0
  21. package/skill/assets/library/auricui-0.1.0-preview.1.tgz +0 -0
  22. package/skill/assets/library/auricui-0.1.0-preview.3.tgz +0 -0
  23. package/skill/assets/library/auricui-0.1.0-preview.4.tgz +0 -0
  24. package/skill/assets/upstream/LICENSE +21 -0
  25. package/skill/assets/upstream/apps/desktop/chat/src/App.tsx +1099 -0
  26. package/skill/assets/upstream/apps/desktop/chat/src/app-bridge.ts +154 -0
  27. package/skill/assets/upstream/apps/desktop/chat/src/chat.css +1812 -0
  28. package/skill/assets/upstream/apps/desktop/chat/src/components/AddSkill.tsx +223 -0
  29. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentActivityRow.tsx +716 -0
  30. package/skill/assets/upstream/apps/desktop/chat/src/components/AgentIcons.tsx +46 -0
  31. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentCard.tsx +34 -0
  32. package/skill/assets/upstream/apps/desktop/chat/src/components/AttachmentPreview.tsx +331 -0
  33. package/skill/assets/upstream/apps/desktop/chat/src/components/BalanceNotice.tsx +22 -0
  34. package/skill/assets/upstream/apps/desktop/chat/src/components/BrandLogo.tsx +35 -0
  35. package/skill/assets/upstream/apps/desktop/chat/src/components/ComposerCard.tsx +182 -0
  36. package/skill/assets/upstream/apps/desktop/chat/src/components/ConversationSessionBar.tsx +292 -0
  37. package/skill/assets/upstream/apps/desktop/chat/src/components/CopyDiagnostics.tsx +24 -0
  38. package/skill/assets/upstream/apps/desktop/chat/src/components/Divider.tsx +47 -0
  39. package/skill/assets/upstream/apps/desktop/chat/src/components/FileChangesList.tsx +119 -0
  40. package/skill/assets/upstream/apps/desktop/chat/src/components/Markdown.tsx +198 -0
  41. package/skill/assets/upstream/apps/desktop/chat/src/components/ModelPopover.tsx +280 -0
  42. package/skill/assets/upstream/apps/desktop/chat/src/components/NoticeCard.tsx +87 -0
  43. package/skill/assets/upstream/apps/desktop/chat/src/components/Popover.tsx +195 -0
  44. package/skill/assets/upstream/apps/desktop/chat/src/components/PreviewFrame.tsx +129 -0
  45. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectConversation.tsx +852 -0
  46. package/skill/assets/upstream/apps/desktop/chat/src/components/ProjectStarters.tsx +118 -0
  47. package/skill/assets/upstream/apps/desktop/chat/src/components/Tooltip.tsx +191 -0
  48. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnLog.tsx +583 -0
  49. package/skill/assets/upstream/apps/desktop/chat/src/components/TurnQuestionCard.tsx +133 -0
  50. package/skill/assets/upstream/apps/desktop/chat/src/components/dock-pane-bar.ts +14 -0
  51. package/skill/assets/upstream/apps/desktop/chat/src/components/project-version.ts +37 -0
  52. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/MentionMenuPlugin.tsx +372 -0
  53. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptEditor.tsx +476 -0
  54. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptPill.tsx +399 -0
  55. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/PromptReferenceNode.tsx +164 -0
  56. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/index.ts +31 -0
  57. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/mention-menu.ts +206 -0
  58. package/skill/assets/upstream/apps/desktop/chat/src/components/prompt-editor/prompt-text.ts +171 -0
  59. package/skill/assets/upstream/apps/desktop/chat/src/components/useStickToBottom.ts +320 -0
  60. package/skill/assets/upstream/apps/desktop/chat/src/components/useTicker.ts +55 -0
  61. package/skill/assets/upstream/apps/desktop/chat/src/i18n/index.ts +42 -0
  62. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/en-US.ts +408 -0
  63. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/es.ts +407 -0
  64. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ja.ts +407 -0
  65. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/ko.ts +403 -0
  66. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/words.ts +6 -0
  67. package/skill/assets/upstream/apps/desktop/chat/src/i18n/locales/zh-CN.ts +407 -0
  68. package/skill/assets/upstream/apps/desktop/chat/src/i18n/lookup.ts +12 -0
  69. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-activity-runs.ts +154 -0
  70. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-menu-rows.ts +41 -0
  71. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-refs.ts +105 -0
  72. package/skill/assets/upstream/apps/desktop/chat/src/lib/agent-transcript.ts +440 -0
  73. package/skill/assets/upstream/apps/desktop/chat/src/lib/asset-lists.ts +75 -0
  74. package/skill/assets/upstream/apps/desktop/chat/src/lib/attachment-place.ts +17 -0
  75. package/skill/assets/upstream/apps/desktop/chat/src/lib/balance.ts +16 -0
  76. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-layer.ts +10 -0
  77. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-menu.ts +18 -0
  78. package/skill/assets/upstream/apps/desktop/chat/src/lib/chat-store.ts +101 -0
  79. package/skill/assets/upstream/apps/desktop/chat/src/lib/composer-attachments.ts +189 -0
  80. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-attachments.ts +178 -0
  81. package/skill/assets/upstream/apps/desktop/chat/src/lib/desktop-bridge.ts +291 -0
  82. package/skill/assets/upstream/apps/desktop/chat/src/lib/developer.ts +39 -0
  83. package/skill/assets/upstream/apps/desktop/chat/src/lib/esc-stop.ts +23 -0
  84. package/skill/assets/upstream/apps/desktop/chat/src/lib/file-changes.ts +32 -0
  85. package/skill/assets/upstream/apps/desktop/chat/src/lib/live-activity.ts +20 -0
  86. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-agent-run.ts +1174 -0
  87. package/skill/assets/upstream/apps/desktop/chat/src/lib/local-sessions.ts +25 -0
  88. package/skill/assets/upstream/apps/desktop/chat/src/lib/model-calls.ts +87 -0
  89. package/skill/assets/upstream/apps/desktop/chat/src/lib/pretty-json.ts +12 -0
  90. package/skill/assets/upstream/apps/desktop/chat/src/lib/preview-top.ts +15 -0
  91. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-resources.ts +66 -0
  92. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-sessions.ts +66 -0
  93. package/skill/assets/upstream/apps/desktop/chat/src/lib/project-versions.ts +116 -0
  94. package/skill/assets/upstream/apps/desktop/chat/src/lib/ref-pictures.ts +119 -0
  95. package/skill/assets/upstream/apps/desktop/chat/src/lib/relative-time.ts +46 -0
  96. package/skill/assets/upstream/apps/desktop/chat/src/lib/skill-pages.ts +29 -0
  97. package/skill/assets/upstream/apps/desktop/chat/src/lib/step-file.ts +23 -0
  98. package/skill/assets/upstream/apps/desktop/chat/src/lib/structural-share.ts +52 -0
  99. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-film.ts +44 -0
  100. package/skill/assets/upstream/apps/desktop/chat/src/lib/studio-transfer.ts +40 -0
  101. package/skill/assets/upstream/apps/desktop/chat/src/lib/unprompted.ts +27 -0
  102. package/skill/assets/upstream/apps/desktop/chat/src/lib/upload-limits.ts +53 -0
  103. package/skill/assets/upstream/apps/desktop/chat/src/mount.tsx +19 -0
  104. package/skill/assets/upstream/apps/desktop/chat/src/state/local-agent-store.ts +296 -0
  105. package/skill/assets/upstream/apps/desktop/chat/src/studio-refs.ts +307 -0
  106. package/skill/assets/upstream/apps/desktop/package.json +51 -0
  107. package/skill/assets/upstream/apps/desktop/shell/bridge.ts +415 -0
  108. package/skill/assets/upstream/apps/desktop/shell/index.html +27 -0
  109. package/skill/assets/upstream/apps/desktop/shell/main.tsx +17 -0
  110. package/skill/assets/upstream/apps/desktop/shell/shell.css +17 -0
  111. package/skill/assets/upstream/apps/desktop/shell/vite.config.ts +65 -0
  112. package/skill/assets/upstream/apps/desktop/src/agent-definitions.mjs +31 -0
  113. package/skill/assets/upstream/packages/openfilm/LICENSE +21 -0
  114. package/skill/assets/upstream/packages/openfilm/package.json +88 -0
  115. package/skill/assets/upstream/packages/openfilm/src/film-doc.d.mts +125 -0
  116. package/skill/assets/upstream/packages/openfilm/src/film-doc.mjs +986 -0
  117. package/skill/assets/upstream/packages/openfilm/src/overrides.mjs +73 -0
  118. package/skill/assets/upstream/packages/openfilm/src/timeline.mjs +1280 -0
  119. package/skill/assets/upstream/packages/openfilm/studio/server/bridge.js +209 -0
  120. package/skill/assets/upstream/packages/openfilm/studio/server/film-subtitle.mjs +785 -0
  121. package/skill/assets/upstream/packages/openfilm/studio/server/ops.mjs +403 -0
  122. package/skill/assets/upstream/packages/openfilm/studio/server/stage.js +1649 -0
  123. package/skill/assets/upstream/packages/openfilm/studio/server/track-layout.mjs +43 -0
  124. package/skill/assets/upstream/packages/openfilm/studio/ui/src/App.tsx +156 -0
  125. package/skill/assets/upstream/packages/openfilm/studio/ui/src/api.ts +328 -0
  126. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/AudioMeters.tsx +188 -0
  127. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockThumbs.tsx +84 -0
  128. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BlockWaveform.tsx +201 -0
  129. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLoadingScreen.tsx +56 -0
  130. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/BrandLogo.tsx +25 -0
  131. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Card.tsx +89 -0
  132. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ColorPicker.tsx +204 -0
  133. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ConfirmDialog.tsx +96 -0
  134. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ContextMenu.tsx +422 -0
  135. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/DesktopDialog.tsx +271 -0
  136. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ExamplesGallery.tsx +372 -0
  137. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmFullscreenBar.tsx +147 -0
  138. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmInspector.tsx +2310 -0
  139. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FilmStageSelect.tsx +3296 -0
  140. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FocusNav.tsx +28 -0
  141. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FontFamilyPicker.tsx +293 -0
  142. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/FramePicker.tsx +69 -0
  143. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/InspectorPanel.tsx +25 -0
  144. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Launcher.tsx +38 -0
  145. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LayersPanel.tsx +277 -0
  146. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/LiveTime.tsx +101 -0
  147. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPool.tsx +1413 -0
  148. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MediaPreview.tsx +188 -0
  149. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MenuSelect.tsx +161 -0
  150. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/MgOutline.tsx +24 -0
  151. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/OperationHistory.tsx +184 -0
  152. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PaneDivider.tsx +292 -0
  153. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PasteAttrsDialog.tsx +69 -0
  154. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PlaybackMenu.tsx +110 -0
  155. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Popover.tsx +159 -0
  156. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectCover.tsx +60 -0
  157. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectHistoryControls.tsx +748 -0
  158. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectLayoutMenu.tsx +82 -0
  159. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectMenu.tsx +111 -0
  160. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectSources.tsx +587 -0
  161. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectView.tsx +3222 -0
  162. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsLibrary.tsx +567 -0
  163. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ProjectsView.tsx +1593 -0
  164. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/PropertiesSection.tsx +418 -0
  165. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ResourceInspector.tsx +100 -0
  166. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SelectionToolbar.tsx +157 -0
  167. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SkillsLibrary.tsx +504 -0
  168. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SourcePreview.tsx +464 -0
  169. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SpendAsks.tsx +88 -0
  170. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageMaskOverlay.tsx +299 -0
  171. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/StageTransformFrame.tsx +246 -0
  172. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.module.css +46 -0
  173. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleInspector.tsx +884 -0
  174. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleOverlay.tsx +148 -0
  175. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/SubtitleRow.tsx +419 -0
  176. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TabStrip.tsx +188 -0
  177. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Timeline.tsx +4911 -0
  178. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/TimelineMarkers.tsx +329 -0
  179. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Toast.tsx +55 -0
  180. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/Tooltip.tsx +193 -0
  181. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/UpdateButton.tsx +104 -0
  182. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/ViewerBar.tsx +98 -0
  183. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/WhatsNew.tsx +147 -0
  184. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/dock-pane-bar.ts +16 -0
  185. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportCenter.tsx +1502 -0
  186. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/export/ExportTray.tsx +284 -0
  187. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/film-stage-hit.ts +18 -0
  188. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-fields.tsx +863 -0
  189. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/inspector-look.tsx +1357 -0
  190. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/controls-bar.tsx +397 -0
  191. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/player-controls/transport.tsx +294 -0
  192. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/replace-drop.ts +20 -0
  193. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/AgentsSection.tsx +613 -0
  194. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/CustomServices.tsx +104 -0
  195. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/DiagnosticsGroup.tsx +202 -0
  196. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ModelsSection.tsx +179 -0
  197. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProviderMark.tsx +24 -0
  198. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ProvidersSection.tsx +177 -0
  199. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsModal.tsx +511 -0
  200. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/SettingsSelect.tsx +84 -0
  201. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ShortcutsSection.tsx +291 -0
  202. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/StorageGroup.tsx +119 -0
  203. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/theme.ts +47 -0
  204. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/ui.tsx +139 -0
  205. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/settings/use-settings.ts +10 -0
  206. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/stage-menu.tsx +283 -0
  207. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/timeline-head-extras.tsx +91 -0
  208. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/typing-target.ts +45 -0
  209. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-editor-tools.tsx +408 -0
  210. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-page-changes.ts +48 -0
  211. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/use-project-history.ts +113 -0
  212. package/skill/assets/upstream/packages/openfilm/studio/ui/src/components/useBlockDrag.ts +639 -0
  213. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/Preview.tsx +426 -0
  214. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/player.ts +558 -0
  215. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/preview-frames.ts +36 -0
  216. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/steps.ts +36 -0
  217. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/undo.ts +156 -0
  218. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-film.ts +212 -0
  219. package/skill/assets/upstream/packages/openfilm/studio/ui/src/editor/use-preview-renders.ts +89 -0
  220. package/skill/assets/upstream/packages/openfilm/studio/ui/src/globals.css +559 -0
  221. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/en.ts +1901 -0
  222. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/es.ts +1900 -0
  223. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/index.ts +130 -0
  224. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ja.ts +1900 -0
  225. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/ko.ts +1896 -0
  226. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/words.ts +6 -0
  227. package/skill/assets/upstream/packages/openfilm/studio/ui/src/i18n/zh-CN.ts +1901 -0
  228. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/agent-row.ts +21 -0
  229. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/asset-timeline.ts +469 -0
  230. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/audio-levels.ts +60 -0
  231. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/batch-map.ts +28 -0
  232. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/browser.ts +10 -0
  233. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/canvas-fit.ts +116 -0
  234. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/chat-marks.ts +114 -0
  235. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-attrs.ts +102 -0
  236. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-fade.ts +70 -0
  237. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-look.ts +520 -0
  238. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-mask.ts +536 -0
  239. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/clip-replace.ts +60 -0
  240. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/component-props.ts +128 -0
  241. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/context-length.ts +12 -0
  242. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/desktop-build.ts +44 -0
  243. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/diagnostics.ts +68 -0
  244. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/double-press.ts +22 -0
  245. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/dropped-overrides.ts +139 -0
  246. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-jobs.ts +191 -0
  247. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/export-spec.ts +261 -0
  248. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/field-input.ts +155 -0
  249. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-clip-edits.ts +82 -0
  250. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-frame.ts +134 -0
  251. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-overrides.ts +107 -0
  252. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film-shape.ts +172 -0
  253. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/film.ts +80 -0
  254. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-menu.ts +236 -0
  255. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/font-styles.ts +129 -0
  256. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/freeze-frame.ts +49 -0
  257. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/host.ts +438 -0
  258. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/inspect-scrub.ts +26 -0
  259. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap-store.ts +74 -0
  260. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/keymap.ts +529 -0
  261. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/layers-tree.ts +133 -0
  262. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/look.ts +83 -0
  263. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/loudness.ts +58 -0
  264. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/markers.ts +152 -0
  265. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-bar.ts +58 -0
  266. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-folders.ts +45 -0
  267. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-place.ts +102 -0
  268. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-queue.ts +107 -0
  269. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/media-selection.ts +64 -0
  270. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/menu-position.ts +77 -0
  271. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mg-transform.ts +111 -0
  272. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/mixed.ts +3 -0
  273. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/page-changes.ts +45 -0
  274. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/picture-files.ts +19 -0
  275. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-prefs.ts +46 -0
  276. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/preview-renders.ts +71 -0
  277. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-delete.ts +20 -0
  278. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-folders.ts +144 -0
  279. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-history.ts +188 -0
  280. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/project-sources.ts +283 -0
  281. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/projects-page.ts +40 -0
  282. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/provider-marks.ts +35 -0
  283. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/providers.ts +163 -0
  284. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/resource-drag.ts +131 -0
  285. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/retry-image.ts +17 -0
  286. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved-insert.ts +40 -0
  287. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/saved.ts +125 -0
  288. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/selection-toolbar.ts +158 -0
  289. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/shortcut-hint.ts +21 -0
  290. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/similar-layers.ts +46 -0
  291. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/solo.ts +19 -0
  292. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-clip.ts +60 -0
  293. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-crop.ts +232 -0
  294. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-gesture.ts +277 -0
  295. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-layers.ts +71 -0
  296. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-mask.ts +176 -0
  297. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-measure.ts +53 -0
  298. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/stage-types.ts +155 -0
  299. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-refs.ts +248 -0
  300. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/studio-version.ts +41 -0
  301. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitle-cues.ts +238 -0
  302. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/subtitles.ts +263 -0
  303. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/tab-menu.ts +6 -0
  304. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/template-controls.ts +131 -0
  305. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/theme-changes.ts +31 -0
  306. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cache.ts +63 -0
  307. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-cell.ts +8 -0
  308. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-frame.ts +76 -0
  309. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/thumb-pick.ts +42 -0
  310. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timecode.ts +161 -0
  311. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-clipboard.ts +279 -0
  312. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-drag.ts +589 -0
  313. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-edit.ts +1138 -0
  314. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-keys.ts +63 -0
  315. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-layout.ts +1038 -0
  316. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-nav.ts +160 -0
  317. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-prefs.ts +73 -0
  318. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-split.ts +40 -0
  319. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/timeline-view.ts +105 -0
  320. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/title.ts +39 -0
  321. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-heights.ts +55 -0
  322. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/track-lock.ts +27 -0
  323. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/transitions.ts +459 -0
  324. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/type-metrics.ts +83 -0
  325. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-film-subtitles.ts +111 -0
  326. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-fonts.ts +181 -0
  327. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-loaded-thumbs.ts +91 -0
  328. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-edits.ts +134 -0
  329. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-font.ts +51 -0
  330. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/use-subtitle-style.ts +68 -0
  331. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/waveform-columns.ts +48 -0
  332. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/web-update.ts +41 -0
  333. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/whats-new.ts +401 -0
  334. package/skill/assets/upstream/packages/openfilm/studio/ui/src/lib/workspace-resources.ts +373 -0
  335. package/skill/assets/upstream/packages/openfilm/studio/ui/src/main.tsx +10 -0
  336. package/skill/assets/upstream/packages/openfilm/studio/ui/vite.config.ts +12 -0
  337. package/skill/assets/upstream/packages/shared/package.json +45 -0
  338. package/skill/assets/upstream/packages/shared/src/agent-activity.ts +263 -0
  339. package/skill/assets/upstream/packages/shared/src/contract.ts +285 -0
  340. package/skill/assets/upstream/packages/shared/src/index.ts +17 -0
  341. package/skill/assets/upstream/packages/shared/src/markdown-blocks.ts +252 -0
  342. package/skill/assets/upstream/packages/shared/src/modality.ts +4 -0
  343. package/skill/assets/upstream/packages/shared/src/prompt-reference.ts +113 -0
  344. package/skill/assets/upstream/packages/shared/src/title.ts +76 -0
  345. package/skill/assets/upstream/packages/shared/src/turn.ts +68 -0
  346. package/skill/assets/upstream/packages/shared/src/video.ts +26 -0
  347. package/skill/references/agent-components.md +33 -0
  348. package/skill/references/api-index.json +2693 -0
  349. package/skill/references/chat-components.md +65 -0
  350. package/skill/references/components.md +23 -0
  351. package/skill/references/core-components.md +44 -0
  352. package/skill/references/dependencies.md +22 -0
  353. package/skill/references/esm-package.md +48 -0
  354. package/skill/references/integration.md +43 -0
  355. package/skill/references/library-manifest.json +12 -0
  356. package/skill/references/source-manifest.json +325 -0
  357. package/skill/references/studio-components.md +24 -0
  358. package/skill/references/theme-motion.md +49 -0
  359. package/skill/references/troubleshooting.md +27 -0
  360. package/skill/references/verification.md +13 -0
  361. package/skill/scripts/check_dependencies.py +109 -0
  362. package/skill/scripts/inspect_api.py +60 -0
@@ -0,0 +1,271 @@
1
+ /**
2
+ * What the desktop app adds, opened from the top bar's Desktop button (never on its own). Two halves: on the left a
3
+ * picture of its window (Studio with the person's agent beside the film) over the agents it works with; on the right
4
+ * what it does, in eight points, and the download for this computer's system and chip (lib/desktop-build), beside
5
+ * the version. "Download"
6
+ * has Studio open the installer in the person's browser (the website when this build names no place for
7
+ * installers); Studio sends nothing else.
8
+ */
9
+ import React from 'react';
10
+ import { createPortal } from 'react-dom';
11
+ import { ArrowLeftRight, Bot, FileDown, Gauge, History, MonitorPlay, PackageCheck, RefreshCw, X } from 'lucide-react';
12
+ import { useT } from '@/i18n';
13
+ import { desktopBuild, desktopSystem } from '@/lib/desktop-build';
14
+ import { BrandLogo } from './BrandLogo';
15
+ import { ProviderMark } from './settings/ProviderMark';
16
+ import { Tooltip } from './Tooltip';
17
+
18
+ /* what the app does: the chat and the editor together, the agents, the engine, no setup, and staying current */
19
+ const FEATURES: { key: string; icon: React.ReactNode }[] = [
20
+ { key: 'sync', icon: <ArrowLeftRight size={15} strokeWidth={1.75} /> },
21
+ { key: 'live', icon: <MonitorPlay size={15} strokeWidth={1.75} /> },
22
+ { key: 'drop', icon: <FileDown size={15} strokeWidth={1.75} /> },
23
+ { key: 'agents', icon: <Bot size={15} strokeWidth={1.75} /> },
24
+ { key: 'speed', icon: <Gauge size={15} strokeWidth={1.75} /> },
25
+ { key: 'setup', icon: <PackageCheck size={15} strokeWidth={1.75} /> },
26
+ { key: 'updates', icon: <RefreshCw size={15} strokeWidth={1.75} /> },
27
+ { key: 'history', icon: <History size={15} strokeWidth={1.75} /> },
28
+ ];
29
+
30
+ /** The agents it finds on the computer, by their marks (Codex's is OpenAI's). */
31
+ const AGENTS: { id: string; name: string }[] = [
32
+ { id: 'claude', name: 'Claude Code' },
33
+ { id: 'openai', name: 'Codex' },
34
+ { id: 'copilot', name: 'GitHub Copilot' },
35
+ { id: 'cursor', name: 'Cursor' },
36
+ { id: 'opencode', name: 'OpenCode' },
37
+ { id: 'qwen', name: 'Qwen Code' },
38
+ { id: 'kimi', name: 'Kimi' },
39
+ { id: 'minimax', name: 'MiniMax Code' },
40
+ { id: 'deepseek', name: 'DeepSeek Harness' },
41
+ ];
42
+
43
+ const SYSTEM_NAMES = { mac: 'macOS', win: 'Windows' } as const;
44
+
45
+ /**
46
+ * The picture half is dark in either theme (a window on a stage): Studio's dark colors, set on it alone, so the drawn
47
+ * window and the agents' marks read the same in a light Studio.
48
+ */
49
+ const DARK: React.CSSProperties = {
50
+ '--text': '#ededed',
51
+ '--text-muted': '#a3a3a3',
52
+ '--text-faint': '#6f6f6f',
53
+ '--surface': '#1d1f1f',
54
+ '--surface-2': '#272a2a',
55
+ '--border': 'rgba(255, 255, 255, 0.08)',
56
+ '--dock-shell': '#121414',
57
+ '--tl-playhead': '#ffffff',
58
+ '--tl-accent': '#45c2b4',
59
+ '--tl-wave': 'rgba(12, 168, 164, 0.9)',
60
+ background: [
61
+ 'radial-gradient(90% 70% at 0% 0%, color-mix(in srgb, #5ecb92 34%, transparent), transparent 70%)',
62
+ 'radial-gradient(80% 60% at 100% 100%, color-mix(in srgb, #7fd3e4 26%, transparent), transparent 70%)',
63
+ '#0d1110',
64
+ ].join(', '),
65
+ } as React.CSSProperties;
66
+
67
+ const APPLE = 'M16.37 12.6c-.02-2.3 1.88-3.4 1.97-3.46-1.07-1.57-2.74-1.79-3.33-1.81-1.42-.14-2.77.84-3.49.84-.72 0-1.83-.82-3.01-.8-1.55.02-2.98.9-3.78 2.29-1.61 2.8-.41 6.94 1.16 9.2.77 1.11 1.68 2.36 2.88 2.31 1.16-.05 1.59-.75 2.99-.75 1.39 0 1.79.75 3.01.72 1.24-.02 2.03-1.13 2.79-2.25.88-1.29 1.24-2.54 1.26-2.6-.03-.01-2.42-.93-2.45-3.69ZM14.07 5.85c.64-.77 1.07-1.85.95-2.92-.92.04-2.03.61-2.69 1.38-.59.68-1.11 1.78-.97 2.83 1.02.08 2.07-.52 2.71-1.29Z';
68
+ const WINDOWS = 'M3 5.1 10.4 4v7.1H3V5.1Zm8.4-1.2L21 2.5v8.6h-9.6V3.9ZM3 12.9h7.4V20L3 18.9v-6Zm8.4 0H21v8.6l-9.6-1.4v-7.2Z';
69
+
70
+ export function DesktopDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
71
+ const t = useT();
72
+ const dialog = React.useRef<HTMLDivElement>(null);
73
+ const download = React.useRef<HTMLButtonElement>(null);
74
+ /* the latest onClose, so a parent's re-render does not move focus back to the first button */
75
+ const close = React.useRef(onClose);
76
+ close.current = onClose;
77
+ const system = desktopSystem();
78
+ /* the version: Studio's, which the app shares (one version for all of OpenFilm) */
79
+ const [version, setVersion] = React.useState<string | null>(null);
80
+ React.useEffect(() => {
81
+ if (!open || version) return;
82
+ void fetch('/api/health', { cache: 'no-store' }).then((r) => r.json())
83
+ .then((h: { version?: unknown }) => { if (typeof h?.version === 'string') setVersion(h.version); })
84
+ .catch(() => { /* no version shown */ });
85
+ }, [open, version]);
86
+
87
+ React.useEffect(() => {
88
+ if (!open) return;
89
+ const previousFocus = document.activeElement as HTMLElement | null;
90
+ download.current?.focus();
91
+ const onKey = (e: KeyboardEvent) => {
92
+ if (e.key === 'Escape') { e.stopPropagation(); close.current(); return; }
93
+ if (e.key !== 'Tab' || !dialog.current) return;
94
+ const focusable = Array.from(dialog.current.querySelectorAll<HTMLElement>('button:not(:disabled), a[href]'));
95
+ const first = focusable[0];
96
+ const last = focusable[focusable.length - 1];
97
+ if (!dialog.current.contains(document.activeElement)) { e.preventDefault(); first?.focus(); }
98
+ else if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last?.focus(); }
99
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first?.focus(); }
100
+ };
101
+ window.addEventListener('keydown', onKey);
102
+ return () => {
103
+ window.removeEventListener('keydown', onKey);
104
+ if (previousFocus?.isConnected) previousFocus.focus();
105
+ };
106
+ }, [open]);
107
+
108
+ const openDownload = async () => {
109
+ const build = await desktopBuild();
110
+ void fetch('/api/desktop', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(build ?? {}) })
111
+ .catch(() => { /* Studio is gone: nothing to open */ });
112
+ onClose();
113
+ };
114
+
115
+ if (!open) return null;
116
+
117
+ return createPortal(
118
+ <div
119
+ className="fixed inset-0 z-[10100] flex items-center justify-center bg-black/55 p-4 backdrop-blur-[3px]"
120
+ style={{ animation: 'openfilm-rise 0.18s ease-out both' }}
121
+ onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}
122
+ >
123
+ <div
124
+ ref={dialog}
125
+ role="dialog"
126
+ aria-modal="true"
127
+ aria-labelledby="desktop-title"
128
+ className="relative grid max-h-[calc(100vh-32px)] w-full max-w-[1040px] overflow-hidden rounded-[18px] border border-[var(--card-border)] bg-[var(--card-bg)] shadow-[0_32px_80px_-24px_rgba(0,0,0,0.6)] md:grid-cols-[minmax(0,0.72fr)_minmax(0,1.28fr)]"
129
+ >
130
+ <button
131
+ type="button"
132
+ onClick={onClose}
133
+ aria-label={t('desktop.close')}
134
+ className="absolute right-3 top-3 z-10 flex h-8 w-8 items-center justify-center rounded-full text-[var(--text-muted)] transition hover:bg-[var(--bg-hover)] hover:text-[var(--text)]"
135
+ >
136
+ <X size={16} />
137
+ </button>
138
+
139
+ {/* the picture: the window, then the agents it works with */}
140
+ <div className="flex min-w-0 flex-col gap-6 p-6 md:p-7" style={DARK}>
141
+ <div className="flex flex-1 items-center">
142
+ <AppPicture />
143
+ </div>
144
+ <div>
145
+ <div className="text-[11px] font-medium uppercase tracking-[0.1em] text-[var(--text-faint)]">{t('desktop.worksWith')}</div>
146
+ <div className="mt-2.5 flex flex-wrap items-center gap-1.5">
147
+ {AGENTS.map(({ id, name }) => (
148
+ <Tooltip key={id} label={name}>
149
+ <span aria-label={name} className="flex h-8 w-8 items-center justify-center rounded-[9px] border border-white/10 bg-white/[0.06] transition hover:border-white/25 hover:bg-white/[0.12]">
150
+ <ProviderMark id={id} size={15} />
151
+ </span>
152
+ </Tooltip>
153
+ ))}
154
+ </div>
155
+ <div className="mt-2.5 text-[12px] text-[var(--text-muted)]">{t('desktop.orOwnKey')}</div>
156
+ </div>
157
+ </div>
158
+
159
+ {/* what it does, and the download */}
160
+ <div className="flex min-h-0 min-w-0 flex-col">
161
+ <div className="min-h-0 flex-1 overflow-y-auto px-7 pb-5 pt-7 md:px-8">
162
+ <div className="flex items-center gap-2 text-[var(--text-muted)]">
163
+ <BrandLogo size={14} />
164
+ <span className="text-[11.5px] font-medium uppercase tracking-[0.1em]">{t('desktop.title')}</span>
165
+ </div>
166
+ <h2 id="desktop-title" className="mt-3 pr-8 text-[24px] font-semibold leading-[1.2] tracking-[-0.015em] text-[var(--text)]">
167
+ {t('desktop.tagline')}
168
+ </h2>
169
+ <p className="mt-2.5 text-[13.5px] leading-relaxed text-[var(--text-muted)]">{t('desktop.intro')}</p>
170
+
171
+ <ul className="mt-6 grid gap-x-6 gap-y-5 sm:grid-cols-2">
172
+ {FEATURES.map(({ key, icon }) => (
173
+ <li key={key} className="min-w-0">
174
+ <span className="flex items-center gap-2.5">
175
+ <span
176
+ className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[8px]"
177
+ style={{ background: 'color-mix(in srgb, var(--brand-wind-mid) 14%, transparent)', color: 'var(--brand-wind-from)' }}
178
+ >
179
+ {icon}
180
+ </span>
181
+ <span className="min-w-0 text-[13.5px] font-medium leading-snug text-[var(--text)]">{t(`desktop.features.${key}.title`)}</span>
182
+ </span>
183
+ <span className="mt-1.5 block text-[12.5px] leading-relaxed text-[var(--text-muted)]">{t(`desktop.features.${key}.body`)}</span>
184
+ </li>
185
+ ))}
186
+ </ul>
187
+ </div>
188
+
189
+ <div className="flex shrink-0 flex-wrap items-center justify-between gap-x-4 gap-y-3 border-t border-[var(--border)] px-7 py-4 md:px-8">
190
+ <span className="text-[12px] text-[var(--text-faint)]">
191
+ {[t('desktop.status'), t('desktop.otherSystems'), version ? `v${version}` : null].filter(Boolean).join(' · ')}
192
+ </span>
193
+ <button
194
+ ref={download}
195
+ type="button"
196
+ onClick={() => void openDownload()}
197
+ className="inline-flex h-10 items-center gap-2 rounded-full bg-[var(--text)] px-5 text-[13.5px] font-medium text-[var(--card-bg)] transition hover:opacity-90"
198
+ >
199
+ {system ? (
200
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden><path d={system === 'mac' ? APPLE : WINDOWS} /></svg>
201
+ ) : null}
202
+ {system ? t('desktop.downloadFor').replaceAll('{system}', SYSTEM_NAMES[system]) : t('desktop.getIt')}
203
+ </button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </div>,
208
+ document.body,
209
+ );
210
+ }
211
+
212
+ /**
213
+ * The app's window, drawn: Studio's picture and timeline on the left, the chat on the right, and the clip selected on
214
+ * the timeline sitting in the person's message, in the same color and glowing together. Decoration only (hidden
215
+ * from screen readers).
216
+ */
217
+ function AppPicture() {
218
+ const t = useT();
219
+ return (
220
+ <div aria-hidden className="w-full overflow-hidden rounded-[12px] border border-white/10 bg-[var(--dock-shell)] shadow-[0_24px_48px_-20px_rgba(0,0,0,0.7)]">
221
+ {/* the window's top bar */}
222
+ <div className="flex h-[22px] items-center gap-[5px] border-b border-[var(--border)] px-2.5">
223
+ <span className="h-[7px] w-[7px] rounded-full bg-[#ff5f57]" />
224
+ <span className="h-[7px] w-[7px] rounded-full bg-[#febc2e]" />
225
+ <span className="h-[7px] w-[7px] rounded-full bg-[#28c840]" />
226
+ </div>
227
+ <div className="grid grid-cols-[1fr_40%] gap-[5px] p-[5px]">
228
+ {/* Studio: the picture, then the timeline */}
229
+ <div className="flex min-w-0 flex-col gap-[5px]">
230
+ <div className="flex aspect-[16/10] items-center justify-center rounded-[6px] bg-[var(--surface)]">
231
+ <div
232
+ className="flex aspect-video w-[78%] flex-col justify-end rounded-[4px] p-[8%]"
233
+ style={{ background: 'linear-gradient(135deg, #5ecb92, #7fd3e4)' }}
234
+ >
235
+ <span className="openfilm-desktop-pulse h-[7px] w-[58%] rounded-full bg-white/90" />
236
+ <span className="mt-[4px] h-[4px] w-[36%] rounded-full bg-white/60" />
237
+ </div>
238
+ </div>
239
+ <div className="relative rounded-[6px] bg-[var(--surface)] px-[6px] py-[7px]">
240
+ <div className="flex flex-col gap-[4px]">
241
+ <div className="flex gap-[3px]">
242
+ <span className="h-[10px] w-[30%] rounded-[2px] bg-white/[0.13]" />
243
+ <span className="openfilm-desktop-pulse h-[10px] w-[28%] rounded-[2px] outline outline-[1.5px] outline-offset-[1px]" style={{ background: 'color-mix(in srgb, var(--tl-accent) 45%, transparent)', outlineColor: 'var(--tl-accent)' }} />
244
+ <span className="h-[10px] w-[34%] rounded-[2px] bg-white/[0.13]" />
245
+ </div>
246
+ <div className="flex gap-[3px] pl-[18%]">
247
+ <span className="h-[10px] w-[48%] rounded-[2px] bg-[color-mix(in_srgb,#66d8b8_40%,transparent)]" />
248
+ </div>
249
+ <span className="h-[8px] w-[92%] rounded-[2px] bg-[color-mix(in_srgb,var(--tl-wave)_55%,transparent)]" />
250
+ </div>
251
+ <span className="openfilm-desktop-playhead absolute bottom-[4px] top-[3px] w-[1.5px] rounded-full bg-[var(--tl-playhead)]" />
252
+ </div>
253
+ </div>
254
+ {/* the chat beside it */}
255
+ <div className="flex min-w-0 flex-col gap-[6px] rounded-[6px] bg-[var(--surface)] p-[6px]">
256
+ <div className="ml-auto max-w-[96%] rounded-[6px] bg-[var(--surface-2)] px-[6px] py-[5px]">
257
+ <span
258
+ className="openfilm-desktop-pulse mb-[4px] inline-flex max-w-full items-center truncate rounded-[3px] px-[4px] py-[1px] text-[7.5px] font-medium leading-tight"
259
+ style={{ background: 'color-mix(in srgb, var(--tl-accent) 18%, transparent)', color: 'var(--tl-accent)', boxShadow: 'inset 0 0 0 1px var(--tl-accent)' }}
260
+ >
261
+ {t('desktop.picture.clip')}
262
+ </span>
263
+ <span className="block text-[8px] leading-snug text-[var(--text)]">{t('desktop.picture.ask')}</span>
264
+ </div>
265
+ <div className="max-w-[96%] text-[8px] leading-snug text-[var(--text-muted)]">{t('desktop.picture.reply')}</div>
266
+ <div className="mt-auto h-[18px] rounded-[5px] border border-[var(--border)] bg-[var(--surface-2)]" />
267
+ </div>
268
+ </div>
269
+ </div>
270
+ );
271
+ }
@@ -0,0 +1,372 @@
1
+ /**
2
+ * The examples (films made with OpenFilm) and the skills, to start from or to learn from. One view, two places:
3
+ *
4
+ * · start the projects window's "Examples": the examples only; one is started from (copied into a new project)
5
+ * · reference the Community, over a film, from the app's chat (lib/host.ts `onCommand` `show-examples`): two views
6
+ * switched in its title row, Skills first (SkillsLibrary: those installed, and OpenFilm's to install),
7
+ * then Examples (one, or a part of it, a segment or a stretch dragged on its strip, is pointed at in the
8
+ * message; starting from one is still there)
9
+ *
10
+ * Examples: the first wide, the others in a grid; cards are posters that play their preview under the pointer. One
11
+ * opened, an example or a skill, is a page of two columns in place of the list: its preview on the left, what it is
12
+ * and what to do with it on the right; Back (or Esc) returns to the list as it was. The title row is the projects
13
+ * window's own, so switching views moves nothing.
14
+ */
15
+ import React from 'react';
16
+ import { createPortal } from 'react-dom';
17
+ import { ArrowLeft, Clapperboard, ExternalLink, Play, X } from 'lucide-react';
18
+ import { useT } from '@/i18n';
19
+ import { studioHost, type CommunityItem } from '@/lib/host';
20
+ import { seenExamples, seenSkills, sourcesApi, watchSkills, type Example, type SkillEntry } from '@/lib/project-sources';
21
+ import { GitHubDialog } from './ProjectSources';
22
+ import { SkillsLibrary } from './SkillsLibrary';
23
+
24
+ export type ExamplesMode = 'start' | 'reference';
25
+ /** A part of an example picked to point at: none for the whole film. */
26
+ export type ExamplePart = { from: number; to: number; segment?: string } | null;
27
+
28
+ type Opened = { kind: 'example'; example: Example } | { kind: 'skill'; skill: SkillEntry };
29
+
30
+ const clock = (s: number) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`;
31
+
32
+ /** Two titles in the title row, the one shown bright (the projects window's switch is the same). */
33
+ export function TitleSwitch<T extends string>({ items, value, onChange }: { items: Array<{ id: T; label: string }>; value: T; onChange: (id: T) => void }) {
34
+ return (
35
+ <div role="tablist" className="mr-auto flex items-baseline gap-5">
36
+ {items.map((item) => (
37
+ <button key={item.id} type="button" role="tab" aria-selected={value === item.id} onClick={() => onChange(item.id)}
38
+ className={`text-[17px] font-semibold tracking-[-0.01em] transition ${value === item.id ? 'text-[var(--text)]' : 'text-[var(--text-faint)] hover:text-[var(--text-muted)]'}`}>
39
+ {item.label}
40
+ </button>
41
+ ))}
42
+ </div>
43
+ );
44
+ }
45
+
46
+ export function ExamplesGallery({ mode, projectId = null, heading, initial, onClose, onStart, onReference, onTrySkill }: {
47
+ mode: ExamplesMode;
48
+ /** open on this skill's or example's page (a pill of one clicked in the chat) */
49
+ initial?: CommunityItem;
50
+ /** the project shown, for its own skills */
51
+ projectId?: string | null;
52
+ /** the title row's left (the projects window's switch); none: the Community's own (Skill, Examples) */
53
+ heading?: React.ReactNode;
54
+ onClose?: () => void;
55
+ onStart: (example: Example) => void;
56
+ onReference?: (example: Example, part: ExamplePart) => void;
57
+ /** Try now on a skill: a new conversation in the app's chat with it named */
58
+ onTrySkill?: (skill: SkillEntry) => void;
59
+ }) {
60
+ const t = useT();
61
+ /* the Community (over a film) opens on its skills; the projects window shows the examples only */
62
+ const community = mode === 'reference';
63
+ const [view, setView] = React.useState<'skills' | 'examples'>(community ? 'skills' : 'examples');
64
+ /* what was read before shows at once; what is read now replaces it */
65
+ const [examples, setExamples] = React.useState<Example[] | null>(() => seenExamples());
66
+ const [skills, setSkills] = React.useState<SkillEntry[]>(() => seenSkills(projectId) ?? []);
67
+ const [opened, setOpened] = React.useState<Opened | null>(null);
68
+ /* opening on one item: once what it is in has been read */
69
+ const pending = React.useRef(initial);
70
+ React.useEffect(() => {
71
+ const want = pending.current;
72
+ if (!want) return;
73
+ if (want.kind === 'skill') {
74
+ const skill = skills.find((k) => k.name === want.name);
75
+ if (skill) { pending.current = undefined; setView('skills'); setOpened({ kind: 'skill', skill }); }
76
+ } else if (examples) {
77
+ pending.current = undefined;
78
+ const example = examples.find((e) => e.id === want.id);
79
+ setView('examples');
80
+ if (example) setOpened({ kind: 'example', example });
81
+ }
82
+ }, [skills, examples]);
83
+
84
+ React.useEffect(() => {
85
+ let alive = true;
86
+ void sourcesApi.examples().then((list) => { if (alive) setExamples(list); });
87
+ const stop = community ? watchSkills(projectId, setSkills) : undefined;
88
+ return () => { alive = false; stop?.(); };
89
+ }, [projectId, community]);
90
+
91
+ React.useEffect(() => {
92
+ const onKey = (e: KeyboardEvent) => {
93
+ if (e.key !== 'Escape' || e.defaultPrevented) return;
94
+ if (opened) { e.preventDefault(); e.stopPropagation(); setOpened(null); } else if (onClose) { e.preventDefault(); onClose(); }
95
+ };
96
+ window.addEventListener('keydown', onKey, true);
97
+ return () => window.removeEventListener('keydown', onKey, true);
98
+ }, [opened, onClose]);
99
+
100
+ /* a page opened starts at its top; Back returns to the list where it was left */
101
+ const scroller = React.useRef<HTMLDivElement>(null);
102
+ const listTop = React.useRef(0);
103
+ const wasOpen = React.useRef(false);
104
+ React.useLayoutEffect(() => {
105
+ const el = scroller.current;
106
+ const open = opened != null;
107
+ if (!el || open === wasOpen.current) return;
108
+ if (open) { listTop.current = el.scrollTop; el.scrollTop = 0; } else el.scrollTop = listTop.current;
109
+ wasOpen.current = open;
110
+ }, [opened]);
111
+
112
+ const kindLabel = (c: string) => { const key = `gallery.cat.${c}`; const label = t(key); return label === key ? c : label; };
113
+ const show = (next: 'skills' | 'examples') => { setView(next); setOpened(null); };
114
+
115
+ let body: React.ReactNode;
116
+ if (view === 'skills') {
117
+ body = <SkillsLibrary skills={skills} setSkills={setSkills} projectId={projectId} opened={opened?.kind === 'skill' ? opened.skill : null}
118
+ onOpen={(skill) => setOpened({ kind: 'skill', skill })} onBack={() => setOpened(null)} {...(onTrySkill ? { onTry: onTrySkill } : {})} />;
119
+ } else if (examples == null) {
120
+ body = <Empty text={t('gallery.loading')} />;
121
+ } else if (!examples.length) {
122
+ body = <Empty text={t('gallery.empty')} />;
123
+ } else {
124
+ /* the first wide, the others in a grid */
125
+ const [hero, ...rest] = examples;
126
+ body = (
127
+ <Body note={community ? t('gallery.referenceNote') : t('gallery.discoverNote')}>
128
+ <Hero example={hero!} mode={mode} onOpen={() => setOpened({ kind: 'example', example: hero! })} onStart={() => onStart(hero!)} />
129
+ {rest.length ? (
130
+ <div className="mt-8">
131
+ <Grid>{rest.map((e) => <ExampleCard key={e.id} example={e} label={e.category ? kindLabel(e.category) : null} onOpen={() => setOpened({ kind: 'example', example: e })} />)}</Grid>
132
+ </div>
133
+ ) : null}
134
+ </Body>
135
+ );
136
+ }
137
+ /* an example opened: its page in place of the list, the list kept (hidden) so Back finds it where it was */
138
+ if (opened?.kind === 'example') {
139
+ body = (
140
+ <>
141
+ <ExampleDetail example={opened.example} mode={mode} onStart={onStart} onReference={onReference} categoryLabel={kindLabel} />
142
+ <div hidden>{body}</div>
143
+ </>
144
+ );
145
+ }
146
+
147
+ return (
148
+ <div className="flex min-h-0 min-w-0 flex-1 flex-col">
149
+ {/* the same row as the projects' (ProjectsView): switching between them moves nothing */}
150
+ <header className="flex shrink-0 flex-col px-6 pb-4 pt-5">
151
+ <div className="flex min-h-8 items-center gap-1">
152
+ {/* an example or a skill opened has its way back here, where the switch was */}
153
+ {opened ? (
154
+ <button type="button" onClick={() => setOpened(null)} className="mr-auto flex h-8 items-center gap-1.5 rounded-lg pl-1 pr-2.5 text-[13.5px] font-medium text-[var(--text-muted)] transition hover:bg-[var(--bg-hover)] hover:text-[var(--text)]">
155
+ <ArrowLeft size={16} strokeWidth={1.75} />{t('gallery.back')}
156
+ </button>
157
+ ) : heading ?? <TitleSwitch items={[{ id: 'skills' as const, label: t('gallery.skills') }, { id: 'examples' as const, label: t('gallery.title') }]} value={view} onChange={show} />}
158
+ {onClose ? (
159
+ <button type="button" onClick={onClose} aria-label={t('project.close')}
160
+ className="ml-1 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-[var(--text-faint)] transition hover:bg-[var(--bg-hover)] hover:text-[var(--text)]">
161
+ <X size={16} strokeWidth={1.75} />
162
+ </button>
163
+ ) : null}
164
+ </div>
165
+ </header>
166
+ <div ref={scroller} className="min-h-0 min-w-0 flex-1 overflow-y-auto [scrollbar-gutter:stable]">{body}</div>
167
+ </div>
168
+ );
169
+ }
170
+
171
+ /** A view's content: a line saying what it is, then its sections. */
172
+ function Body({ note, children }: { note?: string; children: React.ReactNode }) {
173
+ return (
174
+ <div className="px-6 pb-10">
175
+ {note ? <p className="mb-5 text-[12.5px] text-[var(--text-muted)]">{note}</p> : null}
176
+ {children}
177
+ </div>
178
+ );
179
+ }
180
+
181
+ function Empty({ text }: { text: string }) {
182
+ return <p className="py-14 text-center text-[13px] text-[var(--text-muted)]">{text}</p>;
183
+ }
184
+
185
+ function Grid({ children }: { children: React.ReactNode }) {
186
+ return <div className="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-x-6 gap-y-8">{children}</div>;
187
+ }
188
+
189
+ /** A poster that plays the preview while the pointer is on it; a clapperboard when there is neither. */
190
+ function Picture({ example, playing, fit = 'cover' }: { example: Example; playing: boolean; fit?: 'cover' | 'contain' }) {
191
+ const [failed, setFailed] = React.useState(false);
192
+ const fitClass = fit === 'contain' ? 'object-contain' : 'object-cover';
193
+ return (
194
+ <>
195
+ {example.poster && !failed ? <img src={example.poster} alt="" loading="lazy" draggable={false} onError={() => setFailed(true)} className={`absolute inset-0 h-full w-full ${fitClass}`} />
196
+ : <span className="absolute inset-0 flex items-center justify-center bg-[linear-gradient(135deg,var(--surface-2),var(--surface-3))] text-[var(--text-faint)]"><Clapperboard size={24} strokeWidth={1.5} /></span>}
197
+ {playing && example.video ? <video src={example.video} autoPlay muted loop playsInline className={`absolute inset-0 h-full w-full ${fitClass}`} /> : null}
198
+ </>
199
+ );
200
+ }
201
+
202
+ /** The first example, wide: its preview under the pointer, its title over it, and how to start from it. */
203
+ function Hero({ example, mode, onOpen, onStart }: { example: Example; mode: ExamplesMode; onOpen: () => void; onStart: () => void }) {
204
+ const t = useT();
205
+ const [hover, setHover] = React.useState(false);
206
+ return (
207
+ <div role="button" tabIndex={0} onClick={onOpen} onKeyDown={(e) => { if (e.key === 'Enter') onOpen(); }}
208
+ onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
209
+ className="relative block aspect-[2.2/1] w-full cursor-pointer overflow-hidden rounded-[14px] bg-black outline-none ring-1 ring-[var(--border)] transition hover:ring-[var(--border-strong)] focus-visible:ring-2 focus-visible:ring-[var(--text-muted)]">
210
+ <Picture example={example} playing={hover} />
211
+ <div className="absolute inset-0 bg-[linear-gradient(to_top,rgba(0,0,0,0.8)_0%,rgba(0,0,0,0.3)_45%,rgba(0,0,0,0)_75%)]" />
212
+ <div className="absolute inset-x-0 bottom-0 flex items-end gap-6 p-6">
213
+ <div className="min-w-0 flex-1">
214
+ <div className="text-[22px] font-semibold leading-tight tracking-[-0.015em] text-white">{example.title}</div>
215
+ {example.description ? <div className="mt-1.5 line-clamp-2 max-w-[520px] text-[13px] leading-[19px] text-white/80">{example.description}</div> : null}
216
+ {example.duration ? <div className="mt-1.5 text-[12px] text-white/55">{clock(example.duration)}</div> : null}
217
+ </div>
218
+ <button type="button" onClick={(e) => { e.stopPropagation(); if (mode === 'reference') onOpen(); else onStart(); }}
219
+ className="shrink-0 rounded-[10px] bg-white px-4 py-2.5 text-[13px] font-medium text-black transition hover:bg-white/90">
220
+ {mode === 'reference' ? t('gallery.reference') : t('gallery.start')}
221
+ </button>
222
+ </div>
223
+ </div>
224
+ );
225
+ }
226
+
227
+ function ExampleCard({ example, label, onOpen }: { example: Example; label: string | null; onOpen: () => void }) {
228
+ const [hover, setHover] = React.useState(false);
229
+ return (
230
+ <button type="button" onClick={onOpen} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} className="group flex min-w-0 flex-col text-left outline-none">
231
+ <div className="relative aspect-video w-full overflow-hidden rounded-[10px] bg-[var(--surface-2)] ring-1 ring-[var(--border)] transition group-hover:ring-[var(--border-strong)] group-focus-visible:ring-2 group-focus-visible:ring-[var(--text-muted)]">
232
+ <Picture example={example} playing={hover} />
233
+ {example.duration ? <span className="absolute bottom-2 right-2 rounded bg-black/60 px-1.5 py-[1px] font-mono text-[10.5px] text-white">{clock(example.duration)}</span> : null}
234
+ </div>
235
+ <div className="mt-2.5 flex items-baseline gap-2">
236
+ <span className="min-w-0 flex-1 truncate text-[13.5px] font-medium text-[var(--text)]">{example.title}</span>
237
+ {label ? <span className="shrink-0 text-[11.5px] text-[var(--text-faint)]">{label}</span> : null}
238
+ </div>
239
+ {example.description ? <div className="mt-0.5 line-clamp-2 text-[12px] leading-[17px] text-[var(--text-muted)]">{example.description}</div> : null}
240
+ </button>
241
+ );
242
+ }
243
+
244
+ /**
245
+ * An example on its own: its preview with sound, its parts on a strip (one picked by a click, a stretch by dragging
246
+ * across them; none: the whole film), and what to do with it.
247
+ */
248
+ function ExampleDetail({ example, mode, onStart, onReference, categoryLabel }: {
249
+ example: Example; mode: ExamplesMode; onStart: (example: Example) => void;
250
+ onReference?: (example: Example, part: ExamplePart) => void; categoryLabel: (c: string) => string;
251
+ }) {
252
+ const t = useT();
253
+ const video = React.useRef<HTMLVideoElement>(null);
254
+ const [part, setPart] = React.useState<ExamplePart>(null);
255
+ const duration = example.duration ?? example.segments.at(-1)?.to ?? 0;
256
+ const [drag, setDrag] = React.useState<number | null>(null);
257
+ const strip = React.useRef<HTMLDivElement>(null);
258
+ const timeAt = (clientX: number) => {
259
+ const box = strip.current?.getBoundingClientRect();
260
+ return box && duration ? Math.max(0, Math.min(duration, ((clientX - box.left) / box.width) * duration)) : 0;
261
+ };
262
+ const seek = (s: number) => { if (video.current) { video.current.currentTime = s; void video.current.play().catch(() => {}); } };
263
+ const pick = (next: ExamplePart) => { setPart(next); if (next) seek(next.from); };
264
+ const primary = mode === 'reference' && onReference
265
+ ? { label: part ? t('gallery.referencePart') : t('gallery.reference'), run: () => onReference(example, part) }
266
+ : { label: t('gallery.start'), run: () => onStart(example) };
267
+ const secondary = mode === 'reference' ? { label: t('gallery.startNew'), run: () => onStart(example) } : null;
268
+
269
+ return (
270
+ <div className="px-6 pb-10">
271
+ <div className="grid grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)] gap-7">
272
+ <div>
273
+ <div className="relative aspect-video overflow-hidden rounded-[12px] bg-black ring-1 ring-[var(--border)]">
274
+ {example.video ? <video ref={video} src={example.video} poster={example.poster ?? undefined} controls playsInline className="absolute inset-0 h-full w-full object-contain" />
275
+ : <Picture example={example} playing={false} fit="contain" />}
276
+ </div>
277
+ {duration ? (
278
+ <div className="mt-3">
279
+ <div className="mb-1.5 flex items-center justify-between text-[11.5px] text-[var(--text-faint)]">
280
+ <span>{t('gallery.parts')}</span>
281
+ <span>{part ? `${clock(part.from)}–${clock(part.to)}` : t('gallery.whole')}</span>
282
+ </div>
283
+ <div ref={strip}
284
+ onMouseDown={(e) => { if (e.button === 0) { setDrag(timeAt(e.clientX)); setPart(null); } }}
285
+ onMouseMove={(e) => { if (drag != null) { const at = timeAt(e.clientX); if (Math.abs(at - drag) > duration / 200) setPart({ from: Math.min(drag, at), to: Math.max(drag, at) }); } }}
286
+ onMouseUp={() => { if (drag != null && part) seek(part.from); setDrag(null); }}
287
+ onMouseLeave={() => setDrag(null)}
288
+ className="relative flex h-9 cursor-crosshair select-none overflow-hidden rounded-[8px] bg-[var(--surface-2)] ring-1 ring-[var(--border)]">
289
+ {(example.segments.length ? example.segments : [{ title: example.title, from: 0, to: duration }]).map((g) => (
290
+ <button key={`${g.from}-${g.title}`} type="button" title={g.title}
291
+ onMouseDown={(e) => e.stopPropagation()}
292
+ onClick={() => pick(example.segments.length ? { from: g.from, to: g.to, segment: g.title } : null)}
293
+ style={{ left: `${(g.from / duration) * 100}%`, width: `${((g.to - g.from) / duration) * 100}%` }}
294
+ className={`absolute inset-y-1 truncate rounded-[5px] border-l-2 border-[var(--bg)] px-1.5 text-left text-[11px] transition ${part?.segment === g.title ? 'bg-[var(--text)] text-[var(--bg)]' : 'text-[var(--text-muted)] hover:bg-[var(--bg-hover)]'}`}>
295
+ {g.title}
296
+ </button>
297
+ ))}
298
+ {part && !part.segment ? (
299
+ <div className="pointer-events-none absolute inset-y-0 rounded-[4px] ring-2 ring-inset ring-[var(--text)]"
300
+ style={{ left: `${(part.from / duration) * 100}%`, width: `${((part.to - part.from) / duration) * 100}%`, background: 'color-mix(in srgb, var(--text) 18%, transparent)' }} />
301
+ ) : null}
302
+ </div>
303
+ <p className="mt-1.5 text-[11.5px] text-[var(--text-faint)]">{t('gallery.partsHint')}</p>
304
+ </div>
305
+ ) : null}
306
+ </div>
307
+ <div className="flex min-w-0 flex-col">
308
+ {example.category ? <div className="mb-1 text-[11.5px] font-medium text-[var(--text-muted)]">{categoryLabel(example.category)}</div> : null}
309
+ <h2 className="text-[20px] font-semibold leading-tight tracking-[-0.01em] text-[var(--text)]">{example.title}</h2>
310
+ {example.description ? <p className="mt-2 text-[13.5px] leading-[20px] text-[var(--text-muted)]">{example.description}</p> : null}
311
+ {example.duration ? <p className="mt-2 text-[12px] text-[var(--text-faint)]">{clock(example.duration)}</p> : null}
312
+ <div className="mt-5 flex flex-col gap-2">
313
+ <button type="button" onClick={primary.run}
314
+ className="flex h-10 items-center justify-center gap-1.5 rounded-[10px] bg-[var(--text)] text-[13.5px] font-medium text-[var(--bg)] transition hover:opacity-90">
315
+ {mode === 'reference' ? null : <Play size={13} />}{primary.label}
316
+ </button>
317
+ {secondary ? (
318
+ <button type="button" onClick={secondary.run}
319
+ className="h-10 rounded-[10px] border border-[var(--border)] text-[13.5px] text-[var(--text)] transition hover:bg-[var(--bg-hover)]">{secondary.label}</button>
320
+ ) : null}
321
+ </div>
322
+ <p className="mt-3 text-[12px] leading-[17px] text-[var(--text-faint)]">{mode === 'reference' ? t('gallery.referenceHow') : t('gallery.startHow')}</p>
323
+ <a href={example.url} target="_blank" rel="noreferrer" className="mt-auto flex items-center gap-1.5 pt-6 text-[12px] text-[var(--text-dim)] hover:text-[var(--text)]"><ExternalLink size={13} />{t('gallery.viewSource')}</a>
324
+ </div>
325
+ </div>
326
+ </div>
327
+ );
328
+ }
329
+
330
+ /**
331
+ * The examples over a film, opened from the app's chat (`show-examples`): an example or a part of it becomes a
332
+ * reference in the message being written at once (its folder, for the agent to read, fetched meanwhile and by the app
333
+ * when the message is sent), a skill is named in it (lib/host.ts `useSkill`), and an example can still be started
334
+ * from, as a new project (`onStarted`).
335
+ */
336
+ export function ExamplesDialog({ projectId, open, onClose, onStarted }: { projectId: string; open?: CommunityItem; onClose: () => void; onStarted: (projectId: string) => void }) {
337
+ const t = useT();
338
+ const [starting, setStarting] = React.useState<Example | null>(null);
339
+ const reference = (example: Example, part: ExamplePart) => {
340
+ /* fetched meanwhile, so the message goes as soon as it is sent */
341
+ void sourcesApi.exampleFolder(example.id).catch(() => {});
342
+ studioHost?.refer?.({
343
+ kind: 'example', id: example.id, title: example.title, url: example.url, ...(example.description ? { description: example.description } : {}),
344
+ ...(part ? { start: part.from, end: part.to, ...(part.segment ? { segment: part.segment } : {}) } : {}),
345
+ });
346
+ onClose();
347
+ };
348
+ return createPortal(
349
+ <>
350
+ <div className="fixed inset-0 z-[10040] bg-black/60" onMouseDown={onClose} />
351
+ <div role="dialog" aria-modal="true" aria-label={t('gallery.community')}
352
+ className="fixed left-1/2 top-1/2 z-[10050] flex -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-2xl border border-[var(--border-strong)] bg-[var(--app-content-bg)] shadow-[0_24px_64px_rgba(0,0,0,0.35)]"
353
+ style={{ width: 'min(1200px, calc(100vw - 64px))', height: 'min(800px, calc(100vh - 80px))' }}>
354
+ <ExamplesGallery
355
+ mode="reference"
356
+ projectId={projectId}
357
+ {...(open ? { initial: open } : {})}
358
+ onClose={onClose}
359
+ onStart={setStarting}
360
+ onReference={reference}
361
+ onTrySkill={(skill) => { studioHost?.useSkill?.({ name: skill.name, description: skill.description, newConversation: true }); onClose(); }}
362
+ />
363
+ </div>
364
+ {starting ? (
365
+ <GitHubDialog url={starting.url} start title={starting.title}
366
+ onDone={(id) => { setStarting(null); onStarted(id); onClose(); }}
367
+ onClose={() => setStarting(null)} />
368
+ ) : null}
369
+ </>,
370
+ document.body,
371
+ );
372
+ }