cozyclay 2.0.2 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +17 -2
  3. package/THIRD_PARTY_NOTICES.md +8 -0
  4. package/bin/agent/agent-routes.mjs +211 -36
  5. package/bin/agent/agent-runner.mjs +98 -4
  6. package/bin/agent/codex-client.mjs +2 -1
  7. package/bin/agent/fal-contract.mjs +0 -17
  8. package/bin/agent/h3-preservation.mjs +6 -8
  9. package/bin/agent/provider-keys.mjs +1 -1
  10. package/bin/agent/providers.mjs +124 -3
  11. package/bin/agent/script-tool.mjs +74 -0
  12. package/bin/agent/session-store.mjs +5 -5
  13. package/bin/agent/studio-prompt.mjs +2 -1
  14. package/bin/agent/studio-rebase.mjs +314 -0
  15. package/bin/agent/studio-tools.mjs +21 -14
  16. package/bin/agent/supervisor.mjs +311 -0
  17. package/bin/agent/video-adapters.mjs +0 -7
  18. package/bin/codex-auth.mjs +4 -4
  19. package/bin/live/cli.mjs +23 -8
  20. package/bin/live/client.mjs +1 -1
  21. package/bin/live/studio.mjs +5 -2
  22. package/bin/mcp-runtime.mjs +2 -2
  23. package/bin/telemetry-state.mjs +3 -3
  24. package/dist/app/index.html +6 -6
  25. package/dist/assets/analytics-C9pMpW7s.js +1 -0
  26. package/dist/assets/app-B_2XB9rw.css +1 -0
  27. package/dist/assets/app-DAuvKPP8.js +4920 -0
  28. package/dist/assets/{demo-ByeYp-dY.js → demo-CvKuBgy5.js} +1 -1
  29. package/dist/assets/{first-shot-handoff-DgT28ZiV.js → first-shot-handoff-msLI71g1.js} +1 -1
  30. package/dist/assets/{landing-BaXkpFip.js → landing-ChUvB_zL.js} +1 -1
  31. package/dist/assets/scene-asset-sync-DKkpjKBV.js +19 -0
  32. package/dist/assets/{shot-prompt-CbqWcWYi.css → scene-asset-sync-DjJ_cJ2R.css} +1 -1
  33. package/dist/assets/{ticket-BcmsnHn5.js → ticket-BPk2Ni95.js} +1 -1
  34. package/dist/assets/workflow-jJRBR94Z.js +179 -0
  35. package/dist/cozyclay-package.json +1 -1
  36. package/dist/index.html +328 -222
  37. package/dist/llms.txt +14 -0
  38. package/dist/sitemap.xml +1 -1
  39. package/dist/workflow/index.html +4 -4
  40. package/mcp/LIVE-PROTOCOL.md +23 -2
  41. package/mcp/README.md +37 -2
  42. package/mcp/ardy-prompts.mjs +1 -1
  43. package/mcp/image-file.mjs +132 -0
  44. package/mcp/live-hub.mjs +2 -2
  45. package/mcp/package.json +1 -0
  46. package/mcp/runtime/package-lock.json +7 -0
  47. package/mcp/runtime/package.json +1 -0
  48. package/mcp/server.mjs +0 -6
  49. package/mcp/tool-handlers.mjs +289 -87
  50. package/package.json +11 -2
  51. package/src/App.jsx +813 -278
  52. package/src/agent-image-client.js +94 -0
  53. package/src/analytics.js +47 -3
  54. package/src/app-stage.jsx +129 -2745
  55. package/src/ardy/auto-physics.js +6 -16
  56. package/src/ardy/collision-blockers.js +3 -7
  57. package/src/ardy/fix-collisions.js +5 -35
  58. package/src/ardy/ik-drag.js +86 -5
  59. package/src/ardy/ik-key-json.js +3 -1
  60. package/src/ardy/ik.js +15 -13
  61. package/src/ardy/motion-calibration.js +1 -1
  62. package/src/ardy/npz.js +0 -8
  63. package/src/ardy/physics-panel.jsx +3 -2
  64. package/src/ardy/physics-review.js +3 -2
  65. package/src/ardy/physics-support.js +1 -1
  66. package/src/ardy/physics-surface.js +1 -1
  67. package/src/ardy/platform-fit-panel.jsx +2 -1
  68. package/src/ardy/playback.js +1 -5
  69. package/src/ardy/range-pin.js +2 -2
  70. package/src/ardy/root-drop.js +0 -1
  71. package/src/ardy/timeline.css +184 -6
  72. package/src/ardy/timeline.jsx +121 -36
  73. package/src/ardy/to-cskel27.js +0 -2
  74. package/src/ardy/waypoints.js +0 -3
  75. package/src/ardy-bridge-contract.js +281 -0
  76. package/src/asset-pane.jsx +19 -9
  77. package/src/auto-color.js +1 -1
  78. package/src/burn-in.js +1 -1
  79. package/src/camera-block.js +1 -1
  80. package/src/camera-follow.js +75 -32
  81. package/src/camera-move.js +0 -6
  82. package/src/camera-tutorial.jsx +6 -7
  83. package/src/character-kind.js +79 -0
  84. package/src/command-bus.js +76 -17
  85. package/src/commands/cast.js +31 -7
  86. package/src/commands/elements/object.js +1 -1
  87. package/src/commands/elements/shot.js +1 -1
  88. package/src/commands/elements.js +1 -1
  89. package/src/commands/export.js +3 -3
  90. package/src/commands/motion.js +2 -2
  91. package/src/commands/objects.js +105 -10
  92. package/src/commands/project.js +15 -2
  93. package/src/commands/sculpt-images.js +53 -0
  94. package/src/commands/shot.js +88 -10
  95. package/src/commands/view.js +10 -4
  96. package/src/coplanar-depth.js +216 -0
  97. package/src/cuts.js +7 -6
  98. package/src/domains/cast.js +60 -43
  99. package/src/domains/motion/motion-intent-codec.js +50 -0
  100. package/src/domains/motion/motion-range-pin-rules.js +23 -0
  101. package/src/domains/motion/motion-video-capture.js +664 -0
  102. package/src/domains/motion.js +253 -742
  103. package/src/domains/objects.js +106 -5
  104. package/src/domains/scenes.js +79 -28
  105. package/src/domains/shots.js +33 -7
  106. package/src/error-boundary.jsx +1 -1
  107. package/src/execution-telemetry.js +5 -5
  108. package/src/facing-marks.js +14 -1
  109. package/src/first-success-guide.jsx +35 -4
  110. package/src/gizmo-claim.js +3 -1
  111. package/src/grid-floor.jsx +1 -4
  112. package/src/hierarchy-model.js +48 -35
  113. package/src/hierarchy-panel.css +1 -0
  114. package/src/hierarchy-panel.jsx +54 -102
  115. package/src/history.js +0 -2
  116. package/src/i2v-motion-client.js +1 -4
  117. package/src/ime.js +6 -0
  118. package/src/keyframe-pack.js +15 -6
  119. package/src/line-edit.js +4 -4
  120. package/src/live-control.js +6 -6
  121. package/src/matte-editor.js +2 -8
  122. package/src/matte.js +0 -19
  123. package/src/mesh-graph-clone.js +16 -2
  124. package/src/mesh-sniff.js +0 -1
  125. package/src/monotone-cubic.js +40 -0
  126. package/src/motion-store.js +4 -4
  127. package/src/motion-trail.js +5 -11
  128. package/src/multimodel-ingest.js +1 -3
  129. package/src/object-catalog.jsx +7 -5
  130. package/src/object-gizmo.jsx +45 -31
  131. package/src/object-path.js +310 -67
  132. package/src/object-travel.js +224 -0
  133. package/src/otio.js +8 -3
  134. package/src/panels/CharacterTransformPanel.jsx +24 -6
  135. package/src/panels/ObjectTransformPanel.jsx +4 -3
  136. package/src/panels/PoseImagePanel.jsx +205 -0
  137. package/src/panels/PromptBlocksPanel.jsx +3 -1
  138. package/src/panels/ProxyFigurePanel.jsx +56 -0
  139. package/src/panels/ReferenceImageField.jsx +1 -1
  140. package/src/panels/RigControlPanel.jsx +24 -3
  141. package/src/panels/RigPanel.jsx +2 -1
  142. package/src/panels/SculptPanel.jsx +405 -0
  143. package/src/panels/SubjectBox.jsx +1 -1
  144. package/src/panels/SubjectsPanel.jsx +0 -2
  145. package/src/panels/VideoCapturePanel.jsx +2 -1
  146. package/src/panels/pose-image.css +42 -0
  147. package/src/panels/pose.css +3 -0
  148. package/src/panels/sculpt.css +162 -0
  149. package/src/plan-diagram.js +184 -0
  150. package/src/planview.jsx +127 -80
  151. package/src/playground.js +3 -5
  152. package/src/pose-fade.js +77 -0
  153. package/src/pose-gizmo.css +23 -0
  154. package/src/pose-gizmo.js +96 -0
  155. package/src/posestudio.jsx +134 -83
  156. package/src/project-browser.css +53 -0
  157. package/src/project-browser.jsx +49 -4
  158. package/src/project.js +55 -24
  159. package/src/props.jsx +177 -36
  160. package/src/proxy-figure.jsx +69 -0
  161. package/src/range-pin-panel.jsx +0 -1
  162. package/src/receipt-findings.js +20 -0
  163. package/src/render-passes.js +0 -1
  164. package/src/resource-status.jsx +1 -1
  165. package/src/root-path.js +48 -0
  166. package/src/route-owner.js +52 -0
  167. package/src/scene-asset-cache.js +1 -2
  168. package/src/scene-assets.js +12 -4
  169. package/src/scene-fbx.js +1 -1
  170. package/src/scene-mesh-cache.js +0 -1
  171. package/src/scene-objects.js +319 -19
  172. package/src/scenes.js +58 -9
  173. package/src/sculpt-blueprint.js +65 -0
  174. package/src/sculpt-compare.js +384 -0
  175. package/src/sculpt-geometry.js +141 -0
  176. package/src/sculpt-mode.js +168 -0
  177. package/src/sculpt-recipe.js +385 -0
  178. package/src/sculpt-run.js +351 -0
  179. package/src/sculpt-stage.jsx +221 -0
  180. package/src/settings-menu.jsx +1 -1
  181. package/src/shell/BoardSlot.jsx +341 -0
  182. package/src/shell/BottomDock.jsx +60 -16
  183. package/src/shell/CompositionGuideButton.jsx +20 -0
  184. package/src/shell/DetailsSlot.jsx +26 -6
  185. package/src/shell/MenuBar.jsx +78 -6
  186. package/src/shell/OutlinerSlot.jsx +3 -1
  187. package/src/shell/ShotPreviewHeader.jsx +17 -0
  188. package/src/shell/TopBar.jsx +18 -3
  189. package/src/shell/ViewportToolbar.jsx +36 -31
  190. package/src/shell/dock.css +239 -0
  191. package/src/shell/glass-regions.css +3 -2
  192. package/src/shell/log-store.js +4 -4
  193. package/src/shell/mode.css +20 -0
  194. package/src/shell/studio-shell-context.js +4 -1
  195. package/src/shell/viewport.css +12 -0
  196. package/src/shot-authoring.js +69 -6
  197. package/src/stable-items.js +1 -1
  198. package/src/stage-attach-frames.js +169 -0
  199. package/src/stage-canvas-rigs.jsx +402 -0
  200. package/src/stage-capture.jsx +216 -0
  201. package/src/stage-characters.jsx +302 -0
  202. package/src/stage-editor-gizmos.jsx +1229 -0
  203. package/src/stage-labels-ko.js +204 -0
  204. package/src/stage-startup-defaults.js +182 -0
  205. package/src/starter-scenes.js +1 -1
  206. package/src/storyboard.js +24 -5
  207. package/src/studio-actions.js +42 -9
  208. package/src/studio-agent-commands.js +213 -23
  209. package/src/studio-agent-context.js +11 -9
  210. package/src/studio-agent-motion.js +2 -1
  211. package/src/studio-agent-protocol.js +60 -24
  212. package/src/studio-app-binding.js +56 -23
  213. package/src/studio-contact-sheet.js +3 -3
  214. package/src/studio-elements.js +11 -2
  215. package/src/studio-geometry-facts.js +114 -0
  216. package/src/styles.css +92 -9
  217. package/src/ui-scrub.js +4 -4
  218. package/src/ui.jsx +6 -4
  219. package/src/use-asset-shelf.js +60 -0
  220. package/src/use-camera-tutorial.js +50 -0
  221. package/src/use-embed-bridge.js +12 -0
  222. package/src/use-first-success-prompt.js +7 -0
  223. package/src/use-i2v-capture-modal.js +15 -0
  224. package/src/use-sculpt-mode.js +95 -0
  225. package/src/workflow/AgentPanel.jsx +106 -18
  226. package/src/workflow/WorkflowBuilder.jsx +1 -1
  227. package/src/workflow/agent-client.js +113 -45
  228. package/src/workflow/agent-panel.css +35 -0
  229. package/src/workflow/attachment-image.js +1 -1
  230. package/src/workflow/canvas-commands.js +3 -3
  231. package/src/workflow/cozy-scene-node.js +3 -3
  232. package/src/workflow/local-workflow.js +2 -2
  233. package/src/workflow/motion-input.js +3 -3
  234. package/src/workflow/node-schema.js +1 -1
  235. package/src/workflow/scene-asset-sync.js +2 -9
  236. package/src/workflow/shot-prompt-node.js +1 -1
  237. package/src/workflow/video-contract.js +1 -1
  238. package/src/workflow/workflow-resources.js +0 -8
  239. package/src/workflow-send.js +87 -0
  240. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  241. package/tools/check-eslint.mjs +57 -0
  242. package/tools/check-knip.mjs +60 -0
  243. package/tools/check-loc.mjs +54 -0
  244. package/tools/demo-worker/api-client.mjs +7 -13
  245. package/tools/demo-worker/generate.mjs +2 -4
  246. package/tools/eslint-baseline.json +23 -0
  247. package/tools/gt-render/browser.mjs +23 -0
  248. package/tools/headless-studio.mjs +85 -0
  249. package/tools/kimodo/effector-constraints.mjs +4 -4
  250. package/tools/kimodo/generate.mjs +1 -1
  251. package/tools/knip-baseline.json +331 -0
  252. package/tools/loc-baseline.json +126 -0
  253. package/tools/process-supervisor.mjs +1 -1
  254. package/tools/projflow/replay.mjs +4 -4
  255. package/tools/projflow/runner.mjs +3 -3
  256. package/tools/qa/studio-control-count.mjs +2 -0
  257. package/tools/run-tests.mjs +95 -7
  258. package/dist/assets/analytics-MVZOEW8O.js +0 -1
  259. package/dist/assets/app-BxBLC6GT.js +0 -4876
  260. package/dist/assets/app-CJdxvnrl.css +0 -1
  261. package/dist/assets/shot-prompt-hN0j_O-p.js +0 -17
  262. package/dist/assets/workflow-B5XZwVfe.js +0 -179
@@ -170,9 +170,10 @@ const MENUS = [
170
170
  export default function MenuBar({ preferences }) {
171
171
  const shell = useStudioShell();
172
172
  const {
173
- runStudioAction, projectSaveState, recState, exportMenuTriggerRef, exportMenuOpen,
173
+ runStudioAction, projectSaveState, previsMode, recState, exportMenuTriggerRef, exportMenuOpen,
174
174
  exportShotIdRef, setExportMenuOpen, shots, exportKeyframePacks, hasCameraKeys, motion,
175
175
  exportRenderPasses, exportDepthVideo, exportStoryboard, downloadOtioCutList,
176
+ exportContactSheet, exportPanelPacks, exportAnimatic,
176
177
  castDomain, preferencesOpen, setPreferencesOpen, agentOpen, toggleAgent,
177
178
  setToast, embedMode, setProjectStartupOpen, setProjectBrowserOpen,
178
179
  } = shell;
@@ -187,7 +188,13 @@ export default function MenuBar({ preferences }) {
187
188
  const exportOpenRef = useRef(exportMenuOpen);
188
189
  exportOpenRef.current = exportMenuOpen;
189
190
  const recording = recState === "recording";
190
- const packReason = shots.length ? "" : ko("Add a shot first — a pack describes one cut", "샷을 먼저 추가하세요 — 팩은 컷 하나를 설명합니다");
191
+ // A storyboard project exports panels (its stills), never clip packs or shot video.
192
+ const storyboard = previsMode === "storyboard";
193
+ const panelCount = storyboard ? shots.filter((entry) => entry.kind === "still").length : 0;
194
+ const noPanels = ko("Add a panel first — storyboard exports describe panels", "패널을 먼저 추가하세요 — 스토리보드 내보내기는 패널을 담습니다");
195
+ const packReason = storyboard
196
+ ? panelCount ? "" : noPanels
197
+ : shots.length ? "" : ko("Add a shot first — a pack describes one cut", "샷을 먼저 추가하세요 — 팩은 컷 하나를 설명합니다");
191
198
 
192
199
  // The camera tutorial's handoff opens Export directly; its flyout lives in File.
193
200
  useEffect(() => {
@@ -223,7 +230,7 @@ export default function MenuBar({ preferences }) {
223
230
 
224
231
  // ⌘S saves and ⌘E exports the keyframe pack (G7), from anywhere in the studio.
225
232
  const keys = useRef(null);
226
- keys.current = { runStudioAction, projectSaveState, recording, packReason, exportKeyframePacks, setToast };
233
+ keys.current = { runStudioAction, projectSaveState, recording, packReason, exportKeyframePacks: storyboard ? exportPanelPacks : exportKeyframePacks, setToast };
227
234
  useEffect(() => {
228
235
  if (embedMode) return undefined;
229
236
  const onKeyDown = (event) => {
@@ -277,6 +284,42 @@ export default function MenuBar({ preferences }) {
277
284
  closeMenus(false);
278
285
  onSelect();
279
286
  };
287
+ const panelItem = (testId, label, title, onSelect, shortcut = null) => (
288
+ <button
289
+ type="button"
290
+ role="menuitem"
291
+ className="menubar-item"
292
+ data-testid={testId}
293
+ disabled={!panelCount || recording}
294
+ data-disabled-reason={panelCount ? undefined : "no-panels"}
295
+ title={panelCount ? title : noPanels}
296
+ onClick={() => {
297
+ const shotId = exportShotIdRef.current;
298
+ closeMenus(false);
299
+ onSelect(shotId);
300
+ }}
301
+ >
302
+ <span className="menubar-item-label">{label}</span>
303
+ {shortcut && <Shortcut keys={shortcut} />}
304
+ </button>
305
+ );
306
+ const storyboardExports = (
307
+ <>
308
+ {panelItem("export-contact-sheet", ko("Contact sheet (PNG)", "콘택트 시트 (PNG)"),
309
+ ko("Every panel with its caption on one sheet", "모든 패널과 캡션을 한 장에"), () => void exportContactSheet())}
310
+ {panelItem("export-panel-pack", ko("Panel pack (zip)", "패널 팩 (zip)"),
311
+ ko("The selected panel's frame, stylized image, camera and prompt as one zip", "선택한 패널의 프레임·스타일 이미지·카메라·프롬프트를 zip 하나로"),
312
+ (shotId) => void exportPanelPacks(false, shotId), `${MOD}E`)}
313
+ {panelItem("export-all-panels", ko("All panels (zip)", "모든 패널 (zip)"),
314
+ ko("One zip with a folder per panel", "패널마다 폴더 하나씩 담은 zip"), () => void exportPanelPacks(true, null))}
315
+ {panelItem("export-animatic", ko("Animatic (mp4)", "애니매틱 (mp4)"),
316
+ ko("Every panel held for its hold, as one MP4", "모든 패널을 홀드만큼 이어 MP4 하나로"), () => void exportAnimatic())}
317
+ {panelItem("export-otio", ko("Cut list (OTIO)", "컷 목록 (OTIO)"),
318
+ ko("Download the OTIO cut list; each panel is a clip as long as its hold", "OTIO 컷 목록 다운로드 — 패널마다 홀드 길이의 클립"), () => downloadOtioCutList())}
319
+ {panelItem("export-render-passes", ko("Depth + normal (PNG)", "뎄스 + 노멀 (PNG)"),
320
+ ko("Depth and normal conditioning plates of the current framing", "현재 프레이밍의 뎄스·노멀 컨디션 플레이트"), () => exportRenderPasses())}
321
+ </>
322
+ );
280
323
 
281
324
  const content = {
282
325
  file: (
@@ -303,6 +346,17 @@ export default function MenuBar({ preferences }) {
303
346
  <button type="button" role="menuitem" className="menubar-item" onClick={item(() => runStudioAction("project.saveAs"))}>
304
347
  <span className="menubar-item-label">{ko("Save As…", "다른 이름으로 저장…")}</span>
305
348
  </button>
349
+ {previsMode === "storyboard" && (
350
+ <button
351
+ type="button"
352
+ role="menuitem"
353
+ className="menubar-item"
354
+ data-testid="export-animation-project"
355
+ onClick={item(() => runStudioAction("project.exportAsAnimation"))}
356
+ >
357
+ <span className="menubar-item-label">{ko("Export as Animation project…", "애니메이션 프로젝트로 내보내기…")}</span>
358
+ </button>
359
+ )}
306
360
  <hr className="menubar-separator" />
307
361
  {/* One Export menu for every delivery this studio makes (#193,
308
362
  R4). The keyframe pack leads because it is the pack an AI video
@@ -330,6 +384,7 @@ export default function MenuBar({ preferences }) {
330
384
  </button>
331
385
  {exportMenuOpen && (
332
386
  <ExportFlyout anchorRef={exportMenuTriggerRef} autoFocus={flyoutFocus} onAutoFocused={focusedFlyout}>
387
+ {storyboard ? storyboardExports : (<>
333
388
  <button
334
389
  type="button"
335
390
  role="menuitem"
@@ -357,14 +412,30 @@ export default function MenuBar({ preferences }) {
357
412
  className="menubar-item"
358
413
  data-testid="export-video"
359
414
  disabled={recording}
360
- title={ko("Render the shot to an MP4 — camera move and character motion, no editor chrome", "샷을 MP4로 렌더링합니다 — 카메라 움직임과 캐릭터 모션만, 편집 UI는 제외")}
415
+ title={ko("Render the whole timeline to one MP4 — every shot back to back, camera and motion, no editor chrome", "타임라인 전체를 MP4 하나로 — 모든 샷을 이어서, 카메라·모션만, 편집 UI 제외")}
416
+ onClick={() => {
417
+ closeMenus(false);
418
+ void runStudioAction("export.shotVideo", { scope: "timeline" });
419
+ }}
420
+ >
421
+ <span className="menubar-item-label">{ko("Video — whole timeline (mp4)", "영상 — 타임라인 전체 (mp4)")}</span>
422
+ </button>
423
+ )}
424
+ {shots.length > 0 && (
425
+ <button
426
+ type="button"
427
+ role="menuitem"
428
+ className="menubar-item"
429
+ data-testid="export-video-shot"
430
+ disabled={recording}
431
+ title={ko("Render one shot to its own MP4 — the shot you opened the menu from, or the one under the playhead", "샷 하나만 MP4로 — 메뉴를 연 샷, 아니면 재생 위치의 샷")}
361
432
  onClick={() => {
362
433
  const shotId = exportShotIdRef.current;
363
434
  closeMenus(false);
364
- void runStudioAction("export.shotVideo", shotId ? { shotId } : {});
435
+ void runStudioAction("export.shotVideo", shotId ? { shotId, scope: "shot" } : { scope: "shot" });
365
436
  }}
366
437
  >
367
- <span className="menubar-item-label">{ko("Video (mp4)", "영상 (mp4)")}</span>
438
+ <span className="menubar-item-label">{ko("Video — this shot only (mp4)", "영상 — 이 샷만 (mp4)")}</span>
368
439
  </button>
369
440
  )}
370
441
  <button
@@ -427,6 +498,7 @@ export default function MenuBar({ preferences }) {
427
498
  : ko("Add a shot to export video or OTIO", "영상·OTIO를 내보내려면 샷을 추가하세요")}
428
499
  </p>
429
500
  )}
501
+ </>)}
430
502
  </ExportFlyout>
431
503
  )}
432
504
  </div>
@@ -9,7 +9,7 @@ export default function OutlinerSlot() {
9
9
  ikMode, rowIdForCharIndex, activeCharIndex, sceneObjects,
10
10
  scenes, activeSceneId, selectSceneDocument, createSceneDocumentFromUi, duplicateSceneDocumentFromUi,
11
11
  renameSceneDocumentFromUi, deleteSceneDocumentFromUi, addSceneObject, renameSceneObject, runStudioAction,
12
- deleteSceneObject, frameSelection, toggleHierarchyHidden, propsDrop, hierarchyReparent,
12
+ deleteSceneObject, frameSelection, groupObjectUnderNewEmpty, renameRequest, toggleHierarchyHidden, propsDrop, hierarchyReparent,
13
13
  agentTouchedRows,
14
14
  } = useStudioShell();
15
15
  return (
@@ -40,6 +40,8 @@ export default function OutlinerSlot() {
40
40
  onDuplicateObject={(objectId) => runStudioAction("object.duplicate", objectId ? { objectId } : {})}
41
41
  onDeleteObject={deleteSceneObject}
42
42
  onFrameObject={frameSelection}
43
+ onGroupObject={groupObjectUnderNewEmpty}
44
+ renameRequest={renameRequest}
43
45
  onToggleHidden={toggleHierarchyHidden}
44
46
  propsDrop={propsDrop}
45
47
  reparent={hierarchyReparent}
@@ -0,0 +1,17 @@
1
+ import { useLayoutEffect, useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ export default function ShotPreviewHeader({ name, lens }) {
5
+ const [host, setHost] = useState(null);
6
+ useLayoutEffect(() => {
7
+ setHost(document.querySelector(".vp-shot-preview .vp-shot-preview-tag"));
8
+ }, []);
9
+ if (!host) return null;
10
+ return createPortal(
11
+ <span className="vp-shot-preview-meta" data-testid="shot-preview-header">
12
+ <span className="vp-shot-preview-name">{name}</span>
13
+ <span className="vp-shot-preview-lens">{lens}</span>
14
+ </span>,
15
+ host,
16
+ );
17
+ }
@@ -10,7 +10,7 @@ import "./topbar.css";
10
10
  // primary action. Save and Export live in File (G7). #570: the project name
11
11
  // and its save state head the left column (ProjectHead below).
12
12
  export default function TopBar({ preferences }) {
13
- const { recState, liveHubStatus, liveWorkspaceHandle } = useStudioShell();
13
+ const { recState, liveHubStatus, liveWorkspaceHandle, previsMode } = useStudioShell();
14
14
  const connected = liveHubStatus === "connected";
15
15
  return (
16
16
  <header className="topbar v2-topbar" data-rec-state={recState}>
@@ -25,7 +25,7 @@ export default function TopBar({ preferences }) {
25
25
  <i className="topbar-mcp-dot" aria-hidden="true" />
26
26
  <span className="topbar-mcp-text">{connected ? ko("MCP connected", "MCP 연결됨") : ko("MCP offline", "MCP 꺼짐")}</span>
27
27
  </span>
28
- <GenerateMotion />
28
+ {previsMode !== "storyboard" && <GenerateMotion />}
29
29
  </header>
30
30
  );
31
31
  }
@@ -35,7 +35,7 @@ export default function TopBar({ preferences }) {
35
35
  export function ProjectHead() {
36
36
  const {
37
37
  projectMenuOpen, setProjectMenuOpen, projectDirty, projectName, projectStartupOpen,
38
- projectManifest, saveStatus,
38
+ projectManifest, saveStatus, previsMode,
39
39
  } = useStudioShell();
40
40
  return (
41
41
  <div className="topbar-project studio-project-head">
@@ -48,10 +48,24 @@ export function ProjectHead() {
48
48
  projectManifest={projectManifest}
49
49
  />
50
50
  <SaveState status={saveStatus} />
51
+ <PrevisModeBadge mode={previsMode} />
51
52
  </div>
52
53
  );
53
54
  }
54
55
 
56
+ // The project's previs mode, a quiet label under the save state.
57
+ function previsModeLabel(mode) {
58
+ return mode === "storyboard" ? ko("Storyboard", "스토리보드") : ko("Animation", "애니메이션");
59
+ }
60
+
61
+ function PrevisModeBadge({ mode }) {
62
+ return (
63
+ <span className="topbar-previs-mode" data-testid="topbar-previs-mode" data-previs-mode={mode}>
64
+ {previsModeLabel(mode)}
65
+ </span>
66
+ );
67
+ }
68
+
55
69
  // "Generate Motion" is today's Scene generate (G6) and the one home for
56
70
  // generation (IS-2): with authored prompt blocks it runs them all, as the
57
71
  // removed Details "Generate all blocks" did; otherwise it runs the prompt.
@@ -110,6 +124,7 @@ function GenerateMotion() {
110
124
  key={action.id}
111
125
  className="menubar-item"
112
126
  data-generate-action={action.id}
127
+ disabled={Boolean(action.reason)}
113
128
  aria-disabled={action.reason ? "true" : undefined}
114
129
  data-disabled-reason={action.reason || undefined}
115
130
  onClick={() => {
@@ -1,6 +1,6 @@
1
- import { useEffect, useLayoutEffect, useState } from "react";
2
- import { createPortal } from "react-dom";
1
+ import { useEffect, useState } from "react";
3
2
  import { useStudioShell } from "./studio-shell-context.js";
3
+ import ShotPreviewHeader from "./ShotPreviewHeader.jsx";
4
4
  import { ko } from "../locale.js";
5
5
  import { PRESETS, SHOT_ASPECT_PRESETS } from "../app-stage.jsx";
6
6
  import { CAMERA_PRESETS } from "../camera-move.js";
@@ -10,16 +10,21 @@ import { CHARACTER_MODEL_IDS } from "../scenes.js";
10
10
  import { elementByPath } from "../studio-elements.js";
11
11
  import { saveAutoColor } from "../auto-color.js";
12
12
  import { trackFeature } from "../analytics.js";
13
+ import { GUIDE_LABELS, nextGuideMode } from "../shot-guides.js";
13
14
  import "./viewport.css";
14
15
 
15
- // G1: the single mode+tool pill. Keys 1-4 and W/E/R are bound in App's
16
+ // G1: the single mode+tool pill. Keys 1-5 and W/E/R are bound in App's
16
17
  // keydown handler; these buttons are the same doors for the pointer.
17
18
  const MODES = [
18
19
  { id: "scene", key: "1", label: ko("Stage", "배치") },
19
20
  { id: "pose", key: "2", label: ko("Pose", "포즈") },
20
21
  { id: "camera", key: "3", label: ko("Camera", "카메라") },
21
22
  { id: "motion", key: "4", label: ko("Motion", "모션") },
23
+ // #739: an isolated turntable for one sculpt. Every previs mode has it.
24
+ { id: "sculpt", key: "5", label: ko("Sculpt", "조각") },
22
25
  ];
26
+ // A storyboard project has no Motion department: its pill reads 1 2 3 5.
27
+ const modesFor = (previsMode) => (previsMode === "storyboard" ? MODES.filter((mode) => mode.id !== "motion") : MODES);
23
28
 
24
29
  // #570 line icons (16px, 1.3 stroke) for the floating rails.
25
30
  const ICON = {
@@ -27,6 +32,10 @@ const ICON = {
27
32
  pose: "M8 2.25a1.5 1.5 0 1 1 0 3 1.5 1.5 0 1 1 0-3z M5 7.25h6 M8 7.25v3.5 M6.25 13.75 8 10.75l1.75 3",
28
33
  camera: "M2 5.75A1.5 1.5 0 0 1 3.5 4.25h6A1.5 1.5 0 0 1 11 5.75v4.5a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 2 10.25z M11 7l3-1.5v5L11 9",
29
34
  motion: "M1.75 9.5c1.5 0 2-4 3.25-4s1.75 5 3 5 1.75-5 3-5 1.75 4 3.25 4",
35
+ sculpt: "M3.75 11.75c0-3.25 1.9-5.5 4.25-5.5s4.25 2.25 4.25 5.5z M2 11.75h12 M5.25 14h5.5 M10 2.25 8.5 5.25",
36
+ front: "M8 2.5a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 1 1 0-3.5z M4.5 13.5v-2.75a3.5 3.5 0 0 1 7 0v2.75",
37
+ side: "M7.25 2.5a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 1 1 0-3.5z M9 4.25h1.5 M6 13.5v-2.75a2.5 2.5 0 0 1 4.5-1.5",
38
+ back: "M8 2.5a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 1 1 0-3.5z M4.5 13.5v-2.75a3.5 3.5 0 0 1 7 0v2.75 M8 9v4.5",
30
39
  move: "M8 2v12 M2 8h12 M6.25 3.75 8 2l1.75 1.75 M6.25 12.25 8 14l1.75-1.75 M3.75 6.25 2 8l1.75 1.75 M12.25 6.25 14 8l-1.75 1.75",
31
40
  rotate: "M13.25 8A5.25 5.25 0 1 1 11.7 4.3 M13.25 2.5v2.75H10.5",
32
41
  scale: "M2.5 8.5v4a1 1 0 0 0 1 1h4 M8.5 2.5h5v5 M13.5 2.5 7.5 8.5",
@@ -105,35 +114,18 @@ function MenuItem({ checked, role = "menuitemradio", className = "", children, .
105
114
  );
106
115
  }
107
116
 
108
- /** The inset card's header (violet dot, shot name, lens) lives in App's
109
- * `.vp-shot-preview-tag`; this region only fills in the words it shows. */
110
- function ShotPreviewHeader({ name, lens }) {
111
- const [host, setHost] = useState(null);
112
- useLayoutEffect(() => {
113
- setHost(document.querySelector(".vp-shot-preview .vp-shot-preview-tag"));
114
- }, []);
115
- if (!host) return null;
116
- return createPortal(
117
- <span className="vp-shot-preview-meta" data-testid="shot-preview-header">
118
- <span className="vp-shot-preview-name">{name}</span>
119
- <span className="vp-shot-preview-lens">{lens}</span>
120
- </span>,
121
- host,
122
- );
123
- }
124
-
125
117
  export default function ViewportToolbar() {
126
118
  const {
127
119
  workflowMode, selectWorkflowMode, poseRefusal, gizmoMode, setGizmoMode, snapEnabled,
128
120
  setSnapEnabled, preset, applyPreset, cameraPresetId, i2vMotionCameraLocked,
129
121
  runStudioAction, shotAspectKey, fovDeg, shotsDomain, shot, shots, activeShot,
130
122
  setNonce, workspaceLayout, viewMenuTriggerRef, viewMenuOpen,
131
- setViewMenuOpen, viewLooksActive, gridView, setGridView,
123
+ setViewMenuOpen, viewLooksActive, gridView, setGridView, guideMode,
132
124
  autoColor, setAutoColor, isCharacterSelection, partColoursChoice, embedMode,
133
125
  agentCollapsed, addSceneObject, spawnCharacter, characters, flySpeed,
134
126
  ikEditTool, setIkEditTool, showTrails, setShowTrails, motion, setRangePinPartPick,
135
127
  trailFalloffS, setTrailFalloffS, lineEditMode, exitLineEditMode, enterRefineMode,
136
- refineDisabledReason,
128
+ refineDisabledReason, previsMode, sculptView, requestSculptView,
137
129
  } = useStudioShell();
138
130
  const menu = useOverlayMenu();
139
131
 
@@ -178,6 +170,12 @@ export default function ViewportToolbar() {
178
170
  refused: refineReason, pick: () => { if (!lineEditMode) enterRefineMode(); },
179
171
  },
180
172
  ],
173
+ // Sculpt mode's W/E/R swing the turntable camera to a reference view.
174
+ sculpt: [
175
+ { key: "W", id: "front", name: ko("Front view", "정면 보기"), active: sculptView?.view === "front", pick: () => requestSculptView("front") },
176
+ { key: "E", id: "side", name: ko("Side view", "측면 보기"), active: sculptView?.view === "side", pick: () => requestSculptView("side") },
177
+ { key: "R", id: "back", name: ko("Back view", "뒷면 보기"), active: sculptView?.view === "back", pick: () => requestSculptView("back") },
178
+ ],
181
179
  }[workflowMode] ?? [];
182
180
 
183
181
  // Lens choices are the real primes that fit the editor's FOV range, turned
@@ -379,7 +377,7 @@ export default function ViewportToolbar() {
379
377
 
380
378
  <div className="vp-mode-toolbar" role="toolbar" data-testid="mode-toolbar" aria-label={ko("Mode and tools", "모드와 도구")}>
381
379
  <div className="vp-mode-keys" role="tablist" aria-label={ko("Workflow", "작업 모드")}>
382
- {MODES.map((mode) => {
380
+ {modesFor(previsMode).map((mode) => {
383
381
  const active = workflowMode === mode.id;
384
382
  const refused = mode.id === "pose" ? poseRefusal : null;
385
383
  return (
@@ -392,6 +390,7 @@ export default function ViewportToolbar() {
392
390
  className={"vp-mode-key" + (active ? " active" : "")}
393
391
  aria-selected={active}
394
392
  aria-disabled={refused ? true : undefined}
393
+ data-disabled-reason={refused || undefined}
395
394
  aria-label={mode.label}
396
395
  title={refused || `${mode.label} (${mode.key})`}
397
396
  onClick={() => selectWorkflowMode(mode.id)}
@@ -500,20 +499,13 @@ export default function ViewportToolbar() {
500
499
  onClick={() => menu.toggle("shading")}
501
500
  >
502
501
  <RailIcon name="shading" />
503
- <span className="vp-label">{autoColor ? ko("Auto Color", "자동 색") : ko("Clay Lit", "클레이 조명")}</span>
502
+ <span className="vp-label">{ko("Clay Lit", "클레이 조명")}</span>
504
503
  </button>
505
504
  {menu.open === "shading" && (
506
505
  <div className="vp-menu" role="menu" aria-label={ko("Shading", "셰이딩")}>
507
506
  <MenuItem checked={!autoColor} onClick={() => { setAutoColorOn(false); menu.close(); }}>
508
507
  {ko("Clay Lit", "클레이 조명")}
509
508
  </MenuItem>
510
- <MenuItem
511
- checked={autoColor}
512
- title={ko("Flat, distinct colour per object — captures include them while on", "오브젝트별 평면 구분 색 — 켜둔 동안 캡처에도 포함됩니다")}
513
- onClick={() => { setAutoColorOn(true); menu.close(); }}
514
- >
515
- {ko("Auto Color", "자동 색")}
516
- </MenuItem>
517
509
  </div>
518
510
  )}
519
511
  </div>
@@ -572,6 +564,19 @@ export default function ViewportToolbar() {
572
564
  <span className="view-menu-mark" aria-hidden="true">{autoColor ? "✓" : ""}</span>
573
565
  {ko("Auto Color", "자동 색")}
574
566
  </button>
567
+ <button
568
+ type="button"
569
+ role="menuitemcheckbox"
570
+ className={"view-menu-item vp-guide-cycle" + (guideMode === "off" ? "" : " active")}
571
+ aria-checked={guideMode !== "off"}
572
+ aria-pressed={guideMode !== "off"}
573
+ title={ko(GUIDE_LABELS[guideMode].en, GUIDE_LABELS[guideMode].ko) + ko(" · click to cycle", " · 클릭으로 전환")}
574
+ onClick={() => runStudioAction("view.setGuideMode", { mode: nextGuideMode(guideMode) })}
575
+ >
576
+ <span className="view-menu-mark" aria-hidden="true">{guideMode !== "off" ? "✓" : ""}</span>
577
+ {ko("Composition guides", "구도 가이드")}
578
+ <small>{ko(GUIDE_LABELS[guideMode].en, GUIDE_LABELS[guideMode].ko)}</small>
579
+ </button>
575
580
  <button
576
581
  type="button"
577
582
  role="menuitemcheckbox"
@@ -90,4 +90,243 @@
90
90
  .app[data-embed-mode] .v2-dock-resize {
91
91
  display: none;
92
92
  }
93
+
94
+ /* Board (storyboard projects): the stills as a strip of panel cards in the
95
+ Sequencer's place, the Shot Camera card still docked at the right end.
96
+ DESIGN.md §2-3: 12 px text, 4 px controls, 6 px cards, the amber
97
+ selection ring and the primary fill for the one add action. */
98
+ .app:not([data-embed-mode]) .v2-dock > .dock-board {
99
+ --board-card-w: 184px;
100
+ grid-column: 1;
101
+ grid-row: 1;
102
+ display: flex;
103
+ flex-direction: column;
104
+ min-width: 0;
105
+ min-height: 0;
106
+ margin-right: var(--shell-camera, 0px);
107
+ color: var(--text-1);
108
+ font: 400 12px var(--sans);
109
+ }
110
+
111
+ .app:not([data-embed-mode]) .dock-board-head {
112
+ flex: none;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: var(--sp-1);
116
+ height: 48px;
117
+ padding: 0 var(--sp-3) 0 calc(var(--sp-3) + var(--dock-tabs-w, 0px) + var(--sp-3));
118
+ }
119
+
120
+ .app:not([data-embed-mode]) .dock-board-count {
121
+ color: var(--label, var(--text-2));
122
+ font-size: 11px;
123
+ font-variant-numeric: tabular-nums;
124
+ }
125
+
126
+ .app:not([data-embed-mode]) .dock-board-add {
127
+ height: var(--h-control);
128
+ padding: 0 var(--sp-2);
129
+ border: 0;
130
+ border-radius: var(--r-1);
131
+ background: var(--primary-fill, var(--primary));
132
+ color: var(--on-select);
133
+ font: 600 12px var(--sans);
134
+ cursor: pointer;
135
+ }
136
+
137
+ .app:not([data-embed-mode]) .dock-board-head > .dock-board-add { margin-left: auto; }
138
+
139
+ .app:not([data-embed-mode]) .dock-board-add:focus-visible,
140
+ .app:not([data-embed-mode]) .dock-board-actions button:focus-visible,
141
+ .app:not([data-embed-mode]) .dock-board-card:focus-visible {
142
+ outline: 2px solid var(--select);
143
+ outline-offset: 2px;
144
+ }
145
+
146
+ .app:not([data-embed-mode]) .dock-board-empty {
147
+ flex: 1 1 auto;
148
+ display: flex;
149
+ flex-direction: column;
150
+ align-items: center;
151
+ justify-content: center;
152
+ gap: var(--sp-2);
153
+ padding: var(--sp-3);
154
+ color: var(--text-2);
155
+ text-align: center;
156
+ }
157
+
158
+ .app:not([data-embed-mode]) .dock-board-empty p { margin: 0; max-width: 46ch; }
159
+
160
+ .app:not([data-embed-mode]) .dock-board-strip {
161
+ flex: 1 1 auto;
162
+ display: flex;
163
+ align-items: flex-start;
164
+ gap: var(--sp-2);
165
+ min-height: 0;
166
+ margin: 0;
167
+ padding: 2px var(--sp-3) var(--sp-2);
168
+ overflow: auto hidden;
169
+ list-style: none;
170
+ scroll-padding-inline: var(--sp-3);
171
+ }
172
+
173
+ .app:not([data-embed-mode]) .dock-board-card {
174
+ position: relative;
175
+ flex: none;
176
+ display: flex;
177
+ flex-direction: column;
178
+ gap: 6px;
179
+ width: var(--board-card-w);
180
+ padding: 6px;
181
+ box-sizing: border-box;
182
+ border-radius: var(--r-2);
183
+ background: var(--card-fill, var(--surface-raised));
184
+ box-shadow: var(--card-ring, none);
185
+ cursor: grab;
186
+ }
187
+
188
+ .app:not([data-embed-mode]) .dock-board-card:hover { background: var(--tile-fill, var(--surface-active)); }
189
+ .app:not([data-embed-mode]) .dock-board-card[data-selected] { box-shadow: 0 0 0 1.5px var(--select); }
190
+ .app:not([data-embed-mode]) .dock-board-card[data-dragging] { opacity: .45; cursor: grabbing; }
191
+
192
+ /* The drop slot: a 2 px amber bar in the gap on the side the panel lands. */
193
+ .app:not([data-embed-mode]) .dock-board-card[data-drop]::after {
194
+ content: "";
195
+ position: absolute;
196
+ top: 0;
197
+ bottom: 0;
198
+ width: 2px;
199
+ border-radius: 1px;
200
+ background: var(--select);
201
+ }
202
+ .app:not([data-embed-mode]) .dock-board-card[data-drop="before"]::after { left: calc(var(--sp-2) / -2 - 1px); }
203
+ .app:not([data-embed-mode]) .dock-board-card[data-drop="after"]::after { right: calc(var(--sp-2) / -2 - 1px); }
204
+
205
+ /* Card actions ride above the picture and show on hover or focus, the
206
+ Sequencer's shot-card pattern (#606). */
207
+ .app:not([data-embed-mode]) .dock-board-actions {
208
+ position: absolute;
209
+ z-index: 2;
210
+ top: 10px;
211
+ left: 10px;
212
+ right: 10px;
213
+ display: none;
214
+ flex-wrap: wrap;
215
+ gap: 4px;
216
+ }
217
+
218
+ .app:not([data-embed-mode]) .dock-board-card:hover .dock-board-actions,
219
+ .app:not([data-embed-mode]) .dock-board-card:focus-within .dock-board-actions {
220
+ display: flex;
221
+ }
222
+
223
+ .app:not([data-embed-mode]) .dock-board-actions button {
224
+ height: 20px;
225
+ padding: 0 6px;
226
+ border: 0;
227
+ border-radius: var(--r-1);
228
+ background: var(--surface-bar);
229
+ box-shadow: 0 0 0 .5px var(--divider-strong);
230
+ color: var(--text-1);
231
+ font: 500 11px var(--sans);
232
+ cursor: pointer;
233
+ }
234
+
235
+ .app:not([data-embed-mode]) .dock-board-menu { display: flex; flex-basis: 100%; gap: 4px; }
236
+ .app:not([data-embed-mode]) .dock-board-actions button:hover:not(:disabled) { background: var(--surface-active); }
237
+ .app:not([data-embed-mode]) .dock-board-actions button.danger:hover { color: var(--danger); }
238
+ .app:not([data-embed-mode]) .dock-board-actions button:disabled { color: var(--text-3); cursor: default; }
239
+
240
+ .app:not([data-embed-mode]) .dock-board-thumb {
241
+ position: relative;
242
+ aspect-ratio: 16 / 9;
243
+ overflow: hidden;
244
+ border-radius: var(--r-1);
245
+ background: var(--frame-well, var(--field-deep));
246
+ }
247
+
248
+ .app:not([data-embed-mode]) .dock-board-image {
249
+ display: block;
250
+ width: 100%;
251
+ height: 100%;
252
+ object-fit: contain;
253
+ pointer-events: none;
254
+ }
255
+
256
+ /* The greybox stays readable under a stylized picture as a corner inset. */
257
+ .app:not([data-embed-mode]) .dock-board-inset {
258
+ position: absolute;
259
+ right: 4px;
260
+ bottom: 4px;
261
+ width: 32%;
262
+ border-radius: var(--r-1);
263
+ box-shadow: 0 0 0 1px var(--overlay);
264
+ pointer-events: none;
265
+ }
266
+
267
+ .app:not([data-embed-mode]) .dock-board-index {
268
+ position: absolute;
269
+ left: 4px;
270
+ bottom: 4px;
271
+ min-width: 18px;
272
+ height: 18px;
273
+ padding: 0 4px;
274
+ box-sizing: border-box;
275
+ border-radius: var(--r-1);
276
+ background: var(--overlay);
277
+ color: var(--text-1);
278
+ font: 600 11px/18px var(--sans);
279
+ font-variant-numeric: tabular-nums;
280
+ text-align: center;
281
+ }
282
+
283
+ .app:not([data-embed-mode]) .dock-board-card[data-selected] .dock-board-index {
284
+ background: var(--select);
285
+ color: var(--on-select);
286
+ }
287
+
288
+ .app:not([data-embed-mode]) .dock-board-caption {
289
+ box-sizing: border-box;
290
+ width: 100%;
291
+ min-height: 36px;
292
+ padding: 2px 6px;
293
+ border: 0;
294
+ border-radius: var(--r-1);
295
+ background: var(--field-fill, var(--field));
296
+ color: var(--text-1);
297
+ font: 400 12px/16px var(--sans);
298
+ resize: none;
299
+ cursor: text;
300
+ }
301
+
302
+ .app:not([data-embed-mode]) .dock-board-caption::placeholder { color: var(--text-3); }
303
+
304
+ .app:not([data-embed-mode]) .dock-board-caption:focus-visible,
305
+ .app:not([data-embed-mode]) .dock-board-hold input:focus-visible {
306
+ outline: 1.5px solid var(--select);
307
+ outline-offset: 0;
308
+ }
309
+
310
+ .app:not([data-embed-mode]) .dock-board-hold {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 6px;
314
+ height: var(--h-row);
315
+ color: var(--label, var(--text-2));
316
+ font-size: 11px;
317
+ cursor: default;
318
+ }
319
+
320
+ .app:not([data-embed-mode]) .dock-board-hold input {
321
+ width: 56px;
322
+ height: 20px;
323
+ padding: 0 6px;
324
+ box-sizing: border-box;
325
+ border: 0;
326
+ border-radius: var(--r-1);
327
+ background: var(--field-fill, var(--field));
328
+ color: var(--text-1);
329
+ font: 400 12px var(--sans);
330
+ font-variant-numeric: tabular-nums;
331
+ }
93
332
  }
@@ -87,6 +87,7 @@
87
87
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-outliner .v2-outliner-chip.cast { background: currentColor; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2.25a1.5 1.5 0 1 1 0 3 1.5 1.5 0 1 1 0-3z M5 7.25h6 M8 7.25v3.5 M6.25 13.75 8 10.75l1.75 3'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 2.25a1.5 1.5 0 1 1 0 3 1.5 1.5 0 1 1 0-3z M5 7.25h6 M8 7.25v3.5 M6.25 13.75 8 10.75l1.75 3'/%3E%3C/svg%3E"); }
88
88
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-outliner .v2-outliner-chip.cam { background: currentColor; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 5.75A1.5 1.5 0 0 1 3.5 4.25h6A1.5 1.5 0 0 1 11 5.75v4.5a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 2 10.25z M11 7l3-1.5v5L11 9'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 5.75A1.5 1.5 0 0 1 3.5 4.25h6A1.5 1.5 0 0 1 11 5.75v4.5a1.5 1.5 0 0 1-1.5 1.5h-6A1.5 1.5 0 0 1 2 10.25z M11 7l3-1.5v5L11 9'/%3E%3C/svg%3E"); }
89
89
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-outliner .v2-outliner-chip.light { background: currentColor; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 5.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 1 1 0-5z M8 1.75v1.5 M8 12.75v1.5 M1.75 8h1.5 M12.75 8h1.5 M3.6 3.6l1 1 M11.4 11.4l1 1 M3.6 12.4l1-1 M11.4 4.6l1-1'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 5.5a2.5 2.5 0 1 1 0 5 2.5 2.5 0 1 1 0-5z M8 1.75v1.5 M8 12.75v1.5 M1.75 8h1.5 M12.75 8h1.5 M3.6 3.6l1 1 M11.4 11.4l1 1 M3.6 12.4l1-1 M11.4 4.6l1-1'/%3E%3C/svg%3E"); }
90
+ .app[data-shell="glass"]:not([data-embed-mode]) .v2-outliner .v2-outliner-chip.empty { background: currentColor; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 8V2.25 M8 8l-4.75 3 M8 8l5 2.75'/%3E%3Ccircle cx='8' cy='8' r='1.1'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 8V2.25 M8 8l-4.75 3 M8 8l5 2.75'/%3E%3Ccircle cx='8' cy='8' r='1.1'/%3E%3C/svg%3E"); }
90
91
  /* The toggles draw the same chevron as the rest of the shell. */
91
92
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-outliner .hierarchy-toggle:not(.placeholder) {
92
93
  font-size: 0;
@@ -442,7 +443,7 @@
442
443
  font-family: var(--sans);
443
444
  }
444
445
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-sequencer .tl-head {
445
- height: 48px;
446
+ height: auto;
446
447
  min-height: 48px;
447
448
  gap: 12px;
448
449
  padding: 0 16px;
@@ -700,7 +701,7 @@
700
701
 
701
702
  /* The Sequencer head drops its secondary groups as the dock narrows. */
702
703
  .app[data-shell="glass"]:not([data-embed-mode]) .bottom-timeline { container-type: inline-size; }
703
- .app[data-shell="glass"]:not([data-embed-mode]) .v2-sequencer .tl-head { overflow: hidden; }
704
+ .app[data-shell="glass"]:not([data-embed-mode]) .v2-sequencer .tl-head { overflow: visible; }
704
705
  @container (max-width: 760px) {
705
706
  .app[data-shell="glass"]:not([data-embed-mode]) .v2-sequencer .tl-badge { display: none; }
706
707
  }