cozyclay 1.10.0 → 2.0.0

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 (272) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/bin/agent/agent-routes.mjs +74 -70
  3. package/bin/agent/agent-runner.mjs +10 -3
  4. package/bin/agent/motion-runtime.mjs +32 -260
  5. package/bin/agent/providers.mjs +46 -3
  6. package/bin/agent/session-store.mjs +41 -5
  7. package/bin/agent/studio-prompt.mjs +1 -1
  8. package/bin/agent/studio-tools.mjs +48 -11
  9. package/bin/cozyclay.mjs +26 -8
  10. package/bin/live/cli.mjs +22 -3
  11. package/bin/mcp-runtime.mjs +35 -20
  12. package/bin/telemetry-state.mjs +5 -2
  13. package/bin/update-check.mjs +15 -0
  14. package/dist/app/index.html +6 -6
  15. package/dist/assets/analytics-MVZOEW8O.js +1 -0
  16. package/dist/assets/app-C1sdVbg4.css +1 -0
  17. package/dist/assets/app-CA55tcI3.js +4876 -0
  18. package/dist/assets/{demo-B-eg1CT8.js → demo-ByeYp-dY.js} +1 -1
  19. package/dist/assets/{first-shot-handoff-CGgnvNu8.js → first-shot-handoff-DgT28ZiV.js} +1 -1
  20. package/dist/assets/{landing-IbRp3FnQ.js → landing-BaXkpFip.js} +1 -1
  21. package/dist/assets/shot-prompt-CbqWcWYi.css +1 -0
  22. package/dist/assets/shot-prompt-Yrt99wZx.js +17 -0
  23. package/dist/assets/{ticket-BgIzOBOf.js → ticket-BcmsnHn5.js} +1 -1
  24. package/dist/assets/workflow-DRdwoDBT.css +1 -0
  25. package/dist/assets/workflow-dNPGY9xz.js +179 -0
  26. package/dist/cozyclay-package.json +1 -1
  27. package/dist/fonts/IBM-Plex-Sans-OFL.txt +92 -0
  28. package/dist/fonts/JetBrains-Mono-OFL.txt +93 -0
  29. package/dist/fonts/README.md +13 -9
  30. package/dist/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
  31. package/dist/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
  32. package/dist/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
  33. package/dist/fonts/jetbrains-mono-400-latin.woff2 +0 -0
  34. package/dist/fonts/jetbrains-mono-500-latin.woff2 +0 -0
  35. package/dist/index.html +7 -7
  36. package/dist/privacy/index.html +13 -8
  37. package/dist/sitemap.xml +6 -6
  38. package/dist/workflow/index.html +5 -5
  39. package/mcp/live-hub.mjs +35 -93
  40. package/mcp/mesh-file.mjs +48 -0
  41. package/mcp/server.mjs +6 -77
  42. package/mcp/tool-handlers.mjs +219 -376
  43. package/package.json +2 -1
  44. package/src/App.jsx +1249 -9076
  45. package/src/analytics.js +393 -9
  46. package/src/app-context.js +211 -0
  47. package/src/app-stage.jsx +25 -24
  48. package/src/ardy/auto-fix-panel.css +108 -0
  49. package/src/ardy/fix-collisions.js +10 -66
  50. package/src/ardy/ik-drag.js +101 -0
  51. package/src/ardy/ik-key-json.js +43 -0
  52. package/src/ardy/ik.js +97 -67
  53. package/src/ardy/physics-panel.css +2 -0
  54. package/src/ardy/physics-panel.jsx +17 -5
  55. package/src/ardy/platform-fit-panel.css +64 -0
  56. package/src/ardy/platform-fit-panel.jsx +40 -0
  57. package/src/ardy/platform-fit.js +326 -0
  58. package/src/ardy/playback.js +166 -20
  59. package/src/ardy/range-pin.js +299 -0
  60. package/src/ardy/timeline-coordinates.js +26 -0
  61. package/src/ardy/timeline.css +1199 -0
  62. package/src/ardy/timeline.jsx +149 -41
  63. package/src/ardy/waypoints.js +2 -2
  64. package/src/asset-pane.css +462 -0
  65. package/src/asset-pane.jsx +461 -232
  66. package/src/command-bus.js +349 -0
  67. package/src/commands/ai.js +31 -0
  68. package/src/commands/cast.js +140 -0
  69. package/src/commands/elements/character.js +13 -0
  70. package/src/commands/elements/motion.js +14 -0
  71. package/src/commands/elements/object.js +22 -0
  72. package/src/commands/elements/scene.js +16 -0
  73. package/src/commands/elements/shot.js +9 -0
  74. package/src/commands/elements/stage.js +11 -0
  75. package/src/commands/elements.js +155 -0
  76. package/src/commands/export.js +20 -0
  77. package/src/commands/index.js +40 -0
  78. package/src/commands/motion.js +231 -0
  79. package/src/commands/objects.js +174 -0
  80. package/src/commands/project.js +53 -0
  81. package/src/commands/scene.js +81 -0
  82. package/src/commands/shared.js +19 -0
  83. package/src/commands/shot.js +103 -0
  84. package/src/commands/stage.js +17 -0
  85. package/src/commands/view.js +42 -0
  86. package/src/document-store.js +144 -0
  87. package/src/domains/cast.js +1007 -0
  88. package/src/domains/motion.js +3219 -0
  89. package/src/domains/objects.js +932 -0
  90. package/src/domains/scenes.js +817 -0
  91. package/src/domains/shots.js +404 -0
  92. package/src/domains/stage.js +93 -0
  93. package/src/dualview.jsx +113 -57
  94. package/src/facing-marks.js +3 -0
  95. package/src/first-success-guide.jsx +14 -14
  96. package/src/grid-view.js +18 -5
  97. package/src/hierarchy-panel.css +386 -0
  98. package/src/hierarchy-panel.jsx +88 -19
  99. package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
  100. package/src/i2v-motion-studio.jsx +180 -0
  101. package/src/ik-camera.js +3 -0
  102. package/src/main.jsx +6 -0
  103. package/src/motion/generation.js +105 -0
  104. package/src/motion-readiness-ui.jsx +4 -4
  105. package/src/motion-readiness.js +11 -0
  106. package/src/motion-trail.js +366 -9
  107. package/src/object-gizmo.jsx +7 -1
  108. package/src/panels/CameraPanel.jsx +60 -0
  109. package/src/panels/CharacterTransformPanel.jsx +48 -0
  110. package/src/panels/EnvironmentPanel.jsx +40 -0
  111. package/src/panels/Foldout.jsx +32 -0
  112. package/src/panels/LightPanel.jsx +24 -0
  113. package/src/panels/ObjectTransformPanel.jsx +554 -0
  114. package/src/panels/PosePanel.jsx +97 -0
  115. package/src/panels/ProjectPanel.jsx +33 -0
  116. package/src/panels/PromptBlocksPanel.jsx +442 -0
  117. package/src/panels/PropsPanel.jsx +70 -0
  118. package/src/panels/ReferenceImageField.jsx +91 -0
  119. package/src/panels/RigControlPanel.jsx +78 -0
  120. package/src/panels/RigPanel.jsx +37 -0
  121. package/src/panels/SubjectBox.jsx +47 -0
  122. package/src/panels/SubjectsPanel.jsx +39 -0
  123. package/src/panels/VideoCapturePanel.jsx +180 -0
  124. package/src/panels/details.css +1171 -0
  125. package/src/panels/motion.css +197 -0
  126. package/src/panels/pose.css +39 -0
  127. package/src/planview.jsx +60 -31
  128. package/src/posestudio.jsx +59 -4
  129. package/src/project-browser.css +1073 -0
  130. package/src/project-browser.jsx +284 -123
  131. package/src/range-pin-object-transform.js +19 -0
  132. package/src/range-pin-panel.css +518 -0
  133. package/src/range-pin-panel.jsx +344 -0
  134. package/src/result-modal.jsx +5 -5
  135. package/src/room.jsx +79 -51
  136. package/src/scenes.js +5 -0
  137. package/src/semantic-edit.js +2 -0
  138. package/src/settings-menu.jsx +39 -105
  139. package/src/shell/BottomDock.jsx +450 -0
  140. package/src/shell/DetailsSlot.jsx +513 -0
  141. package/src/shell/LibrarySlot.jsx +86 -0
  142. package/src/shell/MenuBar.jsx +601 -0
  143. package/src/shell/OutlinerSlot.jsx +50 -0
  144. package/src/shell/PreferencesDialog.jsx +538 -0
  145. package/src/shell/PreferencesSlot.jsx +40 -0
  146. package/src/shell/StatusBar.jsx +125 -0
  147. package/src/shell/StudioShell.jsx +92 -0
  148. package/src/shell/TopBar.jsx +129 -0
  149. package/src/shell/ViewportToolbar.jsx +656 -0
  150. package/src/shell/agent-glass.css +296 -0
  151. package/src/shell/dock.css +93 -0
  152. package/src/shell/glass-regions.css +985 -0
  153. package/src/shell/glass.css +687 -0
  154. package/src/shell/log-store.js +52 -0
  155. package/src/shell/mode.css +9 -0
  156. package/src/shell/preferences.css +651 -0
  157. package/src/shell/shell.css +282 -0
  158. package/src/shell/studio-shell-context.js +19 -0
  159. package/src/shell/topbar.css +435 -0
  160. package/src/shell/viewport.css +451 -0
  161. package/src/store/authored-intent.js +22 -0
  162. package/src/store/runtime-adapters.js +92 -0
  163. package/src/store/scene-stage.js +21 -0
  164. package/src/store/use-document-store.js +14 -0
  165. package/src/studio-actions.js +210 -0
  166. package/src/studio-agent-commands.js +38 -268
  167. package/src/studio-agent-context.js +38 -9
  168. package/src/studio-agent-motion.js +122 -378
  169. package/src/studio-agent-protocol.js +66 -30
  170. package/src/studio-app-binding.js +379 -0
  171. package/src/studio-contact-sheet.js +73 -0
  172. package/src/studio-elements.js +58 -51
  173. package/src/styles/themes.css +200 -0
  174. package/src/styles/tokens.css +69 -0
  175. package/src/styles.css +443 -1753
  176. package/src/theme.js +44 -0
  177. package/src/timeline-extent.js +16 -0
  178. package/src/trail-key-conflicts.js +39 -0
  179. package/src/trail-pick.js +59 -0
  180. package/src/ui.jsx +8 -8
  181. package/src/use-case-question.jsx +66 -0
  182. package/src/workflow/AgentPanel.jsx +82 -69
  183. package/src/workflow/agent-client.js +12 -2
  184. package/src/workflow/agent-panel.css +34 -32
  185. package/src/workflow/cozy-scene-node.css +2 -0
  186. package/src/workflow/workflow.css +2 -0
  187. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  188. package/tools/ardy/bridge.mjs +164 -146
  189. package/tools/ardy/visual-qa.mjs +5 -5
  190. package/tools/bench/EXP3.md +61 -0
  191. package/tools/bench/cclay_bench_extract_incam.py +125 -0
  192. package/tools/bench/cclay_bench_extract_obs.py +204 -0
  193. package/tools/bench/cclay_bench_runner.py +42 -0
  194. package/tools/bench/cube-contact.mjs +162 -0
  195. package/tools/bench/exp3.mjs +152 -0
  196. package/tools/bench/extract-bench-lib.mjs +216 -0
  197. package/tools/bench/extract-bench.mjs +302 -0
  198. package/tools/bench/fal-generate.mjs +88 -0
  199. package/tools/bench/fit/README.md +189 -0
  200. package/tools/bench/fit/camera.mjs +59 -0
  201. package/tools/bench/fit/contact.mjs +419 -0
  202. package/tools/bench/fit/footlock.mjs +234 -0
  203. package/tools/bench/fit/motion.mjs +85 -0
  204. package/tools/bench/fit/pin.mjs +27 -0
  205. package/tools/bench/fit/remote.mjs +105 -0
  206. package/tools/bench/fit-bench.mjs +126 -0
  207. package/tools/bench/fit-sanity.mjs +45 -0
  208. package/tools/bench/metrics.mjs +313 -0
  209. package/tools/bench/obs/depth.mjs +258 -0
  210. package/tools/bench/obs/extrinsics.mjs +175 -0
  211. package/tools/bench/obs/fit_mannequin_betas.py +340 -0
  212. package/tools/bench/obs/ground.mjs +294 -0
  213. package/tools/bench/obs/heading.mjs +151 -0
  214. package/tools/bench/obs/ladder.mjs +555 -0
  215. package/tools/bench/obs/mannequin-betas.json +124 -0
  216. package/tools/bench/obs/remote.mjs +83 -0
  217. package/tools/bench/obs/rest_joints.py +52 -0
  218. package/tools/bench/obs/ybot-targets.mjs +49 -0
  219. package/tools/bench/obs-bench.mjs +520 -0
  220. package/tools/bench/score.mjs +343 -0
  221. package/tools/bench/summarize.mjs +86 -0
  222. package/tools/dev/pages/privacy.html +13 -8
  223. package/tools/gt-render/browser.mjs +159 -0
  224. package/tools/gt-render/camera-math.mjs +276 -0
  225. package/tools/gt-render/page.mjs +267 -0
  226. package/tools/gt-render/render.mjs +514 -0
  227. package/tools/gt-render/scene-box.mjs +31 -0
  228. package/tools/gt-render/take-transform.mjs +87 -0
  229. package/tools/qa/css-rule-usage.mjs +283 -0
  230. package/tools/qa/studio-control-count.mjs +70 -32
  231. package/tools/run-tests.mjs +58 -3
  232. package/tools/track/DESIGN.md +513 -0
  233. package/tools/track/backfill-provenance.mjs +116 -0
  234. package/tools/track/budget.mjs +8 -0
  235. package/tools/track/check-rig.mjs +209 -0
  236. package/tools/track/diagnostics.schema.json +60 -0
  237. package/tools/track/export-rig.mjs +256 -0
  238. package/tools/track/fallback.mjs +7 -0
  239. package/tools/track/fk-parity-fixture.mjs +161 -0
  240. package/tools/track/gate.mjs +349 -0
  241. package/tools/track/masks.mjs +318 -0
  242. package/tools/track/metrics.mjs +201 -0
  243. package/tools/track/publish-obs.mjs +108 -0
  244. package/tools/track/py/check_env.py +61 -0
  245. package/tools/track/py/eval_lr.py +229 -0
  246. package/tools/track/py/lr_viterbi.py +294 -0
  247. package/tools/track/py/masks.py +405 -0
  248. package/tools/track/py/objective.py +382 -0
  249. package/tools/track/py/rig.py +508 -0
  250. package/tools/track/py/scene.py +326 -0
  251. package/tools/track/py/test_joint_indices.py +123 -0
  252. package/tools/track/py/test_lr_viterbi.py +195 -0
  253. package/tools/track/py/test_masks.py +229 -0
  254. package/tools/track/py/test_rig.py +288 -0
  255. package/tools/track/py/test_scene.py +354 -0
  256. package/tools/track/py/test_track.py +328 -0
  257. package/tools/track/py/track.py +411 -0
  258. package/tools/track/remote.mjs +140 -0
  259. package/tools/track/rig-dump.mjs +227 -0
  260. package/tools/track/run-box-tests.mjs +14 -0
  261. package/tools/track/run-box.mjs +168 -0
  262. package/tools/track/setup-box.sh +59 -0
  263. package/tools/track/study-2d.mjs +728 -0
  264. package/dist/assets/analytics-B1hnH66c.js +0 -1
  265. package/dist/assets/app-Cu1cexZR.css +0 -1
  266. package/dist/assets/app-Dxdh8kds.js +0 -4861
  267. package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
  268. package/dist/assets/shot-prompt-HVJRP4Gr.js +0 -17
  269. package/dist/assets/workflow-CJd-PDtV.js +0 -179
  270. package/dist/assets/workflow-DsudxKHF.css +0 -1
  271. package/src/fal-motion-studio.jsx +0 -180
  272. package/src/scene-history.js +0 -129
@@ -0,0 +1,386 @@
1
+ .v2-outliner {
2
+ min-height: 0;
3
+ max-height: none;
4
+ flex: 1 1 auto;
5
+ display: flex;
6
+ flex-direction: column;
7
+ background: var(--surface-panel, #151517);
8
+ border: 0;
9
+ color: var(--text-1, #e4e4e7);
10
+ font-family: var(--sans, 'IBM Plex Sans');
11
+ font-size: var(--outliner-ui-size, 12px);
12
+ line-height: 1.2;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .v2-outliner .hierarchy-heading {
17
+ height: var(--outliner-header-height, 36px);
18
+ min-height: var(--outliner-header-height, 36px);
19
+ padding: 0 var(--sp-2, 12px);
20
+ border-bottom: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
21
+ background: var(--surface-panel, #151517);
22
+ color: var(--text-1, #e4e4e7);
23
+ }
24
+
25
+ .v2-outliner .hierarchy-heading strong {
26
+ margin: 0;
27
+ color: var(--text-1, #e4e4e7);
28
+ font-size: var(--outliner-title-size, 12px);
29
+ font-weight: 500;
30
+ letter-spacing: 0;
31
+ }
32
+
33
+ .v2-outliner .hierarchy-kicker,
34
+ .v2-outliner .hierarchy-frame-status {
35
+ display: none;
36
+ }
37
+
38
+ .v2-outliner .v2-outliner-count {
39
+ color: var(--text-3, #636369);
40
+ font-family: var(--mono, 'JetBrains Mono');
41
+ font-size: var(--outliner-secondary-size, 11px);
42
+ font-weight: 400;
43
+ }
44
+
45
+ .v2-outliner .v2-outliner-search-wrap {
46
+ position: relative;
47
+ display: flex;
48
+ align-items: center;
49
+ height: var(--h-control, 28px);
50
+ min-height: var(--h-control, 28px);
51
+ margin: 0 var(--sp-2, 12px) var(--sp-1, 8px);
52
+ border-radius: var(--r-1, 4px);
53
+ background: var(--field, #0f0f11);
54
+ color: var(--text-3, #636369);
55
+ }
56
+
57
+ .v2-outliner .v2-outliner-search-icon {
58
+ position: absolute;
59
+ left: var(--sp-1, 8px);
60
+ color: var(--text-3, #636369);
61
+ font-size: var(--outliner-search-icon-size, 14px);
62
+ line-height: 1;
63
+ pointer-events: none;
64
+ }
65
+
66
+ .v2-outliner .v2-outliner-search {
67
+ width: 100%;
68
+ height: var(--h-control, 28px);
69
+ min-height: var(--h-control, 28px);
70
+ padding: 0 var(--sp-1, 8px) 0 var(--sp-3, 16px);
71
+ border: 0;
72
+ border-radius: var(--r-1, 4px);
73
+ outline: 0;
74
+ background: none;
75
+ color: var(--text-1, #e4e4e7);
76
+ font: inherit;
77
+ font-size: var(--outliner-secondary-size, 11px);
78
+ }
79
+
80
+ .v2-outliner .v2-outliner-search::placeholder {
81
+ color: var(--text-3, #636369);
82
+ opacity: 1;
83
+ }
84
+
85
+ .v2-outliner .v2-outliner-search:focus-visible {
86
+ outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
87
+ outline-offset: calc(var(--outliner-border-width, 1px) * -1);
88
+ }
89
+
90
+ .v2-outliner .hierarchy-tree {
91
+ flex: 1 1 auto;
92
+ min-height: 0;
93
+ padding: 0 var(--sp-1, 8px) var(--sp-1, 8px);
94
+ overflow: auto;
95
+ background: var(--surface-panel, #151517);
96
+ }
97
+
98
+ .v2-outliner .hierarchy-row-wrap {
99
+ position: relative;
100
+ height: var(--h-row, 24px);
101
+ min-height: var(--h-row, 24px);
102
+ margin-left: calc(var(--hierarchy-depth, 0) * var(--outliner-indent, 14px));
103
+ padding-right: 0;
104
+ border-radius: var(--r-1, 4px);
105
+ background: none;
106
+ }
107
+
108
+ .v2-outliner .hierarchy-row-wrap:hover {
109
+ background: var(--surface-raised, #1e1e21);
110
+ }
111
+
112
+ .v2-outliner .hierarchy-row-wrap.selected {
113
+ background: var(--select-tint, rgba(232,163,61,.14));
114
+ box-shadow: none;
115
+ }
116
+
117
+ .v2-outliner .hierarchy-row-wrap[data-own-hidden="true"] {
118
+ opacity: .52;
119
+ }
120
+
121
+ .v2-outliner .hierarchy-row-wrap:before {
122
+ display: none;
123
+ }
124
+
125
+ .v2-outliner .hierarchy-toggle,
126
+ .v2-outliner .hierarchy-fold-space {
127
+ flex: 0 0 var(--outliner-chevron-slot, 12px);
128
+ width: var(--outliner-chevron-slot, 12px);
129
+ height: var(--h-row, 24px);
130
+ padding: 0;
131
+ border: 0;
132
+ background: none;
133
+ }
134
+
135
+ .v2-outliner .hierarchy-toggle {
136
+ display: grid;
137
+ place-items: center;
138
+ color: var(--text-3, #636369);
139
+ font-size: var(--outliner-chevron-size, 8px);
140
+ font-weight: 500;
141
+ line-height: 1;
142
+ cursor: pointer;
143
+ }
144
+
145
+ .v2-outliner .hierarchy-toggle:hover,
146
+ .v2-outliner .hierarchy-toggle:focus-visible {
147
+ color: var(--text-1, #e4e4e7);
148
+ }
149
+
150
+ .v2-outliner .hierarchy-toggle.placeholder,
151
+ .v2-outliner .hierarchy-fold-space {
152
+ pointer-events: none;
153
+ }
154
+
155
+ .v2-outliner .hierarchy-row {
156
+ width: 100%;
157
+ min-width: 0;
158
+ height: var(--h-row, 24px);
159
+ padding: 0 var(--sp-1, 8px) 0 0;
160
+ border: 0;
161
+ background: none;
162
+ color: var(--text-row, #c9c9ce);
163
+ font: inherit;
164
+ font-size: var(--outliner-ui-size, 12px);
165
+ font-weight: 400;
166
+ text-align: left;
167
+ cursor: default;
168
+ }
169
+
170
+ .v2-outliner .hierarchy-row:not(.icon-only) {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: var(--sp-1, 8px);
174
+ }
175
+
176
+ .v2-outliner .hierarchy-row.icon-only {
177
+ flex: 0 0 auto;
178
+ display: inline-flex;
179
+ align-items: center;
180
+ gap: var(--sp-1, 8px);
181
+ width: auto;
182
+ }
183
+
184
+ .v2-outliner .v2-outliner-chip {
185
+ flex: 0 0 var(--outliner-chip-size, 6px);
186
+ width: var(--outliner-chip-size, 6px);
187
+ height: var(--outliner-chip-size, 6px);
188
+ border-radius: var(--outliner-chip-radius, 2px);
189
+ background: var(--chip-folder, #8a7a55);
190
+ }
191
+
192
+ .v2-outliner .v2-outliner-chip.folder { background: var(--chip-folder, #8a7a55); }
193
+ .v2-outliner .v2-outliner-chip.mesh { background: var(--chip-mesh, #7e8b99); }
194
+ .v2-outliner .v2-outliner-chip.cast { background: var(--chip-cast, #5fb584); }
195
+ .v2-outliner .v2-outliner-chip.cam { background: var(--camera, #a78bfa); }
196
+ .v2-outliner .v2-outliner-chip.light { background: var(--chip-light, #d9b84a); }
197
+
198
+ .v2-outliner .hierarchy-label {
199
+ flex: 1 1 auto;
200
+ min-width: 0;
201
+ overflow: hidden;
202
+ color: var(--text-row, #c9c9ce);
203
+ font-size: var(--outliner-ui-size, 12px);
204
+ font-weight: 400;
205
+ text-overflow: ellipsis;
206
+ white-space: nowrap;
207
+ }
208
+
209
+ .v2-outliner .hierarchy-row-wrap.selected .hierarchy-label {
210
+ color: var(--text-1, #e4e4e7);
211
+ font-weight: 500;
212
+ }
213
+
214
+ .v2-outliner .v2-outliner-type {
215
+ flex: 0 0 auto;
216
+ margin-left: auto;
217
+ color: var(--text-off, #4f4f55);
218
+ font-size: var(--outliner-type-size, 10.5px);
219
+ font-weight: 400;
220
+ white-space: nowrap;
221
+ }
222
+
223
+ .v2-outliner .hierarchy-status,
224
+ .v2-outliner .hierarchy-badge {
225
+ display: none;
226
+ }
227
+
228
+ .v2-outliner .hierarchy-eye {
229
+ flex: 0 0 var(--outliner-eye-closed-width, 0px);
230
+ width: var(--outliner-eye-closed-width, 0px);
231
+ height: var(--h-row, 24px);
232
+ padding: 0;
233
+ border: 0;
234
+ background: none;
235
+ color: var(--text-2, #9b9ba1);
236
+ opacity: 0;
237
+ overflow: hidden;
238
+ }
239
+
240
+ .v2-outliner .hierarchy-eye:focus-visible {
241
+ flex-basis: var(--outliner-eye-width, 24px);
242
+ width: var(--outliner-eye-width, 24px);
243
+ opacity: 1;
244
+ outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
245
+ }
246
+
247
+ .v2-outliner .hierarchy-row-wrap[data-drop="over"] {
248
+ border: var(--outliner-border-width, 1px) dashed var(--select, #e8a33d);
249
+ background: var(--select-tint, rgba(232,163,61,.14));
250
+ }
251
+
252
+ .v2-outliner .hierarchy-row-wrap[data-drop="target"] {
253
+ border: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
254
+ }
255
+
256
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill {
257
+ flex: 1 1 auto;
258
+ min-width: 0;
259
+ max-width: none;
260
+ height: var(--h-row, 24px);
261
+ margin: 0 var(--sp-1, 8px) 0 var(--sp-1, 8px);
262
+ }
263
+
264
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dropdown,
265
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger {
266
+ width: 100%;
267
+ min-width: 0;
268
+ }
269
+
270
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger {
271
+ height: var(--h-row, 24px);
272
+ gap: var(--outliner-trigger-gap, 6px);
273
+ padding: 0 var(--sp-1, 8px);
274
+ border: 0;
275
+ border-radius: var(--r-1, 4px);
276
+ background: var(--surface-active, #26262a);
277
+ color: var(--text-row, #c9c9ce);
278
+ font: inherit;
279
+ font-size: var(--outliner-ui-size, 12px);
280
+ text-align: left;
281
+ }
282
+
283
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .trigger:hover,
284
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dropdown.open .trigger {
285
+ background: var(--surface-raised, #1e1e21);
286
+ color: var(--text-1, #e4e4e7);
287
+ }
288
+
289
+ .v2-outliner .hierarchy-row-wrap .hierarchy-scene-pill .dd-caret {
290
+ flex: 0 0 var(--outliner-chevron-slot, 12px);
291
+ width: var(--outliner-chevron-slot, 12px);
292
+ height: var(--outliner-chevron-slot, 12px);
293
+ color: var(--text-3, #636369);
294
+ }
295
+
296
+ .v2-outliner .hierarchy-scene-hint {
297
+ flex: none;
298
+ margin: 0;
299
+ padding: 0 var(--sp-2, 12px) var(--sp-1, 8px);
300
+ color: var(--text-2, #9b9ba1);
301
+ font-size: var(--outliner-secondary-size, 11px);
302
+ }
303
+
304
+ .v2-outliner .v2-outliner-empty {
305
+ min-height: var(--h-row, 24px);
306
+ padding: var(--sp-1, 8px);
307
+ color: var(--text-3, #636369);
308
+ font-size: var(--outliner-secondary-size, 11px);
309
+ }
310
+
311
+ .v2-outliner .hierarchy-rename-input {
312
+ flex: 1 1 auto;
313
+ min-width: 0;
314
+ height: var(--h-control, 28px);
315
+ padding: 0 var(--sp-1, 8px);
316
+ border: 0;
317
+ border-radius: var(--r-1, 4px);
318
+ background: var(--field, #0f0f11);
319
+ color: var(--text-1, #e4e4e7);
320
+ font: inherit;
321
+ font-size: var(--outliner-ui-size, 12px);
322
+ }
323
+
324
+ .v2-outliner .hierarchy-rename-input:focus-visible {
325
+ outline: var(--outliner-border-width, 1px) solid var(--select, #e8a33d);
326
+ }
327
+
328
+ .v2-outliner .hierarchy-context-menu {
329
+ min-width: var(--outliner-menu-width, 220px);
330
+ padding: var(--outliner-menu-padding, 4px);
331
+ border: var(--outliner-border-width, 1px) solid var(--row-divider, #1d1d20);
332
+ border-radius: var(--r-2, 6px);
333
+ background: var(--surface-raised, #1e1e21);
334
+ box-shadow: 0 var(--sp-1, 8px) var(--sp-3, 16px) var(--overlay, rgba(12,12,13,.78));
335
+ }
336
+
337
+ .v2-outliner .hierarchy-context-item {
338
+ min-height: var(--h-control, 28px);
339
+ padding: 0 var(--sp-1, 8px);
340
+ border: 0;
341
+ border-radius: var(--r-1, 4px);
342
+ background: none;
343
+ color: var(--text-row, #c9c9ce);
344
+ font: inherit;
345
+ font-size: var(--outliner-secondary-size, 11px);
346
+ text-align: left;
347
+ }
348
+
349
+ .v2-outliner .hierarchy-context-item:hover,
350
+ .v2-outliner .hierarchy-context-item:focus-visible {
351
+ background: var(--surface-active, #26262a);
352
+ color: var(--text-1, #e4e4e7);
353
+ }
354
+
355
+ .v2-outliner .hierarchy-context-item.danger {
356
+ color: var(--danger, #e5484d);
357
+ }
358
+
359
+ .v2-outliner .add-object-item {
360
+ min-height: var(--h-control, 28px);
361
+ padding: 0 var(--sp-1, 8px);
362
+ border: 0;
363
+ border-radius: var(--r-1, 4px);
364
+ background: none;
365
+ color: var(--text-row, #c9c9ce);
366
+ font: inherit;
367
+ font-size: var(--outliner-secondary-size, 11px);
368
+ }
369
+
370
+ .v2-outliner .add-object-item:hover,
371
+ .v2-outliner .add-object-item:focus-visible {
372
+ background: var(--surface-active, #26262a);
373
+ color: var(--text-1, #e4e4e7);
374
+ }
375
+
376
+ .v2-outliner .add-object-heading {
377
+ padding: var(--sp-1, 8px) var(--sp-1, 8px) calc(var(--sp-1, 8px) / 2);
378
+ color: var(--text-3, #636369);
379
+ font-size: var(--outliner-type-size, 10.5px);
380
+ }
381
+
382
+ @media (max-width: 700px) {
383
+ .v2-outliner .v2-outliner-type {
384
+ display: none;
385
+ }
386
+ }
@@ -2,8 +2,9 @@ import { useEffect, useMemo, useRef, useState } from "react";
2
2
  import { ko, isKo } from "./locale.js";
3
3
  import { buildHierarchyNodes } from "./hierarchy-model.js";
4
4
  import { sceneObjectIdFromHierarchy } from "./scene-objects.js";
5
- import AddObjectMenu, { CatalogueEntries, displayObjectLabel } from "./object-catalog.jsx";
5
+ import { CatalogueEntries, displayObjectLabel } from "./object-catalog.jsx";
6
6
  import { Dropdown } from "./ui.jsx";
7
+ import "./hierarchy-panel.css";
7
8
 
8
9
  const HIERARCHY_LABELS_KO = {
9
10
  "SCENE 01": "장면 01",
@@ -35,6 +36,8 @@ const HIERARCHY_LABELS_KO = {
35
36
  Environment: "환경",
36
37
  Props: "소품",
37
38
  Light: "조명",
39
+ "Scene structure": "장면 구조",
40
+ "Scene hierarchy": "장면 계층",
38
41
  };
39
42
 
40
43
  const FALLBACK_SCENES = [{ id: "current-scene", name: "SCENE 01" }];
@@ -198,6 +201,57 @@ function displayHierarchyLabel(node) {
198
201
  return isKo ? (HIERARCHY_LABELS_KO[node.label] ?? node.label) : node.label;
199
202
  }
200
203
 
204
+ const OUTLINER_TYPES = {
205
+ folder: "Folder",
206
+ mesh: "Mesh",
207
+ cast: "Cast",
208
+ cam: "Cam",
209
+ light: "Light",
210
+ };
211
+
212
+ function outlinerTypeFor(node) {
213
+ const key = node.kind === "object"
214
+ ? "mesh"
215
+ : node.kind === "character" || node.kind === "bone"
216
+ ? "cast"
217
+ : node.kind === "camera"
218
+ ? "cam"
219
+ : node.kind === "light"
220
+ ? "light"
221
+ : "folder";
222
+ const label = key === "folder"
223
+ ? ko(OUTLINER_TYPES[key], "폴더")
224
+ : key === "mesh"
225
+ ? ko(OUTLINER_TYPES[key], "메시")
226
+ : key === "cast"
227
+ ? ko(OUTLINER_TYPES[key], "캐스트")
228
+ : key === "cam"
229
+ ? ko(OUTLINER_TYPES[key], "카메라")
230
+ : ko(OUTLINER_TYPES[key], "조명");
231
+ return { key, label };
232
+ }
233
+
234
+ function OutlinerChip({ type }) {
235
+ return <span className={`v2-outliner-chip ${type}`} aria-hidden="true" />;
236
+ }
237
+
238
+ function countHierarchyNodes(nodes) {
239
+ return nodes.reduce((count, node) => count + 1 + countHierarchyNodes(node.children ?? []), 0);
240
+ }
241
+
242
+ function filterHierarchyNodes(nodes, query, showB) {
243
+ const normalized = query.trim().toLocaleLowerCase();
244
+ const filterNode = (node) => {
245
+ if (node.optional === "showB" && !showB) return null;
246
+ if (!normalized) return node;
247
+ const label = displayHierarchyLabel(node).toLocaleLowerCase();
248
+ const children = (node.children ?? []).map(filterNode).filter(Boolean);
249
+ if (!label.includes(normalized) && children.length === 0) return null;
250
+ return children.length ? { ...node, children } : { ...node, children: undefined };
251
+ };
252
+ return nodes.map(filterNode).filter(Boolean);
253
+ }
254
+
201
255
  function indexParents(nodes, parent = null, parents = new Map()) {
202
256
  for (const node of nodes) {
203
257
  if (parent) parents.set(node.id, parent);
@@ -435,6 +489,7 @@ function TreeRow({
435
489
  // only exists when the caller asked for reparenting.
436
490
  const dropEvents = rowDrag || drop || null;
437
491
  const label = displayHierarchyLabel(node);
492
+ const outlinerType = outlinerTypeFor(node);
438
493
  const rowWrapRef = useRef(null);
439
494
  const inputRef = useRef(null);
440
495
  // A commit/cancel may race the blur that follows the input unmounting;
@@ -495,7 +550,7 @@ function TreeRow({
495
550
  // blur commits (docs/unity-reference.md §9.7). A div, not the
496
551
  // row button — an input inside a button is invalid HTML.
497
552
  <div className={"hierarchy-row" + (touched ? " agent-touched" : "")}>
498
- <HierarchyIcon kind={node.kind} />
553
+ <OutlinerChip type={outlinerType.key} />
499
554
  <input
500
555
  ref={inputRef}
501
556
  className="hierarchy-rename-input"
@@ -524,10 +579,11 @@ function TreeRow({
524
579
  onClick={() => onSelect(node.id)}
525
580
  onDoubleClick={onRenameStart ?? undefined}
526
581
  >
527
- <HierarchyIcon kind={node.kind} />
582
+ <OutlinerChip type={outlinerType.key} />
528
583
  {showLabel && <span className="hierarchy-label">{label}</span>}
529
584
  {status && <span className="hierarchy-status">{status}</span>}
530
585
  {badge !== null && badge !== undefined && badge !== 0 && <span className="hierarchy-badge">{badge}</span>}
586
+ <span className="v2-outliner-type">{outlinerType.label}</span>
531
587
  </button>
532
588
  )}
533
589
  {(node.kind === "object" || node.kind === "character") && onToggleHidden && (
@@ -586,6 +642,7 @@ export default function HierarchyPanel({
586
642
  onSceneDelete,
587
643
  }) {
588
644
  const [expanded, setExpanded] = useState(() => new Set(["shot", "characterA"]));
645
+ const [search, setSearch] = useState("");
589
646
  const [contextMenu, setContextMenu] = useState(null);
590
647
  // Row currently in in-place rename. The panel owns it: F2/Return and the
591
648
  // row context menu are the only ways in, so app state stays out of it.
@@ -621,6 +678,14 @@ export default function HierarchyPanel({
621
678
  }));
622
679
  }, [activeSceneName, sceneObjects, characters]);
623
680
  const parents = useMemo(() => indexParents(hierarchyNodes), [hierarchyNodes]);
681
+ const filteredHierarchyNodes = useMemo(
682
+ () => filterHierarchyNodes(hierarchyNodes, search, showB),
683
+ [hierarchyNodes, search, showB],
684
+ );
685
+ const outlinerCount = useMemo(
686
+ () => countHierarchyNodes(filteredHierarchyNodes),
687
+ [filteredHierarchyNodes],
688
+ );
624
689
 
625
690
  useEffect(() => {
626
691
  setExpanded((current) => {
@@ -795,9 +860,9 @@ export default function HierarchyPanel({
795
860
  nodes.flatMap((node) => {
796
861
  if (node.optional === "showB" && !showB) return [];
797
862
  const sceneRoot = node.id === SCENE_ROOT_ID;
798
- // The root carries no fold caret, so nothing can close it: the scene is
799
- // always open under its own name.
800
- const open = sceneRoot || expanded.has(node.id);
863
+ // Search opens only the filtered branches. The stored expansion set is
864
+ // left untouched so clearing the query restores the author's tree.
865
+ const open = sceneRoot || expanded.has(node.id) || Boolean(search.trim());
801
866
  const editing = editingId === node.id;
802
867
  return [
803
868
  <TreeRow
@@ -846,22 +911,26 @@ export default function HierarchyPanel({
846
911
  });
847
912
 
848
913
  return (
849
- <section className="hierarchy-pane" aria-label={ko("Scene hierarchy", "장면 계층")}>
914
+ <section className="hierarchy-pane v2-outliner" aria-label={ko("Outliner", "아웃라이너")}>
850
915
  <div className="hierarchy-heading">
851
- <div>
852
- <span className="hierarchy-kicker">{ko("Hierarchy", "계층")}</span>
853
- <strong>{ko("Scene structure", "장면 구조")}</strong>
854
- </div>
855
- <span className="hierarchy-frame-status">{motionFrames ? (isKo ? `${motionFrames}프레임` : `${motionFrames} frames`) : ko("Blocking", "블로킹")}</span>
916
+ <strong>{ko("Outliner", "아웃라이너")}</strong>
917
+ <span className="v2-outliner-count" data-testid="outliner-count">{outlinerCount}</span>
856
918
  </div>
857
- {onAddObject && (
858
- <div className="hierarchy-toolbar">
859
- <AddObjectMenu onAdd={onAddObject} />
860
- <span className="hierarchy-frame-status">{motionFrames ? (isKo ? `${motionFrames}프레임` : `${motionFrames} frames`) : ko("Blocking", "블로킹")}</span>
861
- </div>
862
- )}
919
+ <label className="v2-outliner-search-wrap">
920
+ <span className="v2-outliner-search-icon" aria-hidden="true">⌕</span>
921
+ <input
922
+ className="v2-outliner-search"
923
+ type="search"
924
+ value={search}
925
+ placeholder={ko("Search", "검색")}
926
+ aria-label={ko("Search Outliner", "아웃라이너 검색")}
927
+ onChange={(event) => setSearch(event.target.value)}
928
+ />
929
+ </label>
863
930
  <div className="hierarchy-tree" role="tree" ref={treeRef} onKeyDown={onTreeKeyDown} onContextMenu={openCreateMenu}>
864
- {renderNodes(hierarchyNodes)}
931
+ {filteredHierarchyNodes.length > 0 ? renderNodes(filteredHierarchyNodes) : (
932
+ <div className="v2-outliner-empty" role="status">{ko("No matches", "일치하는 항목 없음")}</div>
933
+ )}
865
934
  </div>
866
935
  {sceneHint && (
867
936
  <p className="hierarchy-scene-hint" role="status">{sceneHint}</p>
@@ -1,16 +1,16 @@
1
- export const FAL_MOTION_MODEL = "minimax/h3-max-turbo/image-to-video";
2
- export const FAL_MOTION_RESOLUTION = "480P";
3
- export const FAL_MOTION_MIN_DURATION = 5;
4
- export const FAL_MOTION_MAX_DURATION = 15;
5
- // The durations the Fal card offers. 15 s (362 frames) is the measured upper
1
+ export const I2V_MOTION_MODEL = "minimax/h3-max-turbo/image-to-video";
2
+ export const I2V_MOTION_RESOLUTION = "480P";
3
+ export const I2V_MOTION_MIN_DURATION = 5;
4
+ export const I2V_MOTION_MAX_DURATION = 15;
5
+ // The durations the AI motion card offers. 15 s (362 frames) is the measured upper
6
6
  // end the H3 endpoint accepts and the extractor handles (#380 sit-to-bench).
7
- export const FAL_MOTION_DURATIONS = Object.freeze([5, 10, 15]);
7
+ export const I2V_MOTION_DURATIONS = Object.freeze([5, 10, 15]);
8
8
  // H3's 480P output is 832x480. The reference still is captured at exactly
9
9
  // twice that canvas, and the Studio switches its viewport to the matching
10
10
  // "fal 480P" ratio when A is captured, so the user composes the shot on the
11
11
  // canvas the clip will have.
12
- export const FAL_MOTION_STILL_OUTPUT = Object.freeze({ width: 1664, height: 960 });
13
- export const FAL_MOTION_SHOT_ASPECT = "fal 480P";
12
+ export const I2V_MOTION_STILL_OUTPUT = Object.freeze({ width: 1664, height: 960 });
13
+ export const I2V_MOTION_SHOT_ASPECT = "fal 480P";
14
14
 
15
15
  export function motionApiOrigin(location = globalThis.location) {
16
16
  const configured = globalThis.__COZYCLAY_MOTION_API__;
@@ -51,24 +51,24 @@ async function request(path, body, fetchImpl = fetch) {
51
51
  return payload;
52
52
  }
53
53
 
54
- export function submitFalMotion(input, fetchImpl = fetch) {
54
+ export function submitMotionJob(input, fetchImpl = fetch) {
55
55
  const kind = input?.kind === "interpolate" ? "interpolate" : "act";
56
56
  const body = {
57
57
  ...(kind === "interpolate" ? { stillA: input.stillA, stillB: input.stillB } : { still: input.still }),
58
58
  prompt: input.prompt,
59
- duration: Number(input.duration ?? FAL_MOTION_MIN_DURATION),
59
+ duration: Number(input.duration ?? I2V_MOTION_MIN_DURATION),
60
60
  };
61
61
  return request(`/v1/motion/${kind}`, body, fetchImpl);
62
62
  }
63
63
 
64
- export function getFalMotionJob(id, fetchImpl = fetch) {
64
+ export function getMotionJob(id, fetchImpl = fetch) {
65
65
  return request(`/v1/motion/jobs/${encodeURIComponent(id)}`, undefined, fetchImpl);
66
66
  }
67
67
 
68
- export async function waitForFalMotionJob(id, { intervalMs = 500, timeoutMs = 120_000, fetchImpl = fetch, onUpdate } = {}) {
68
+ export async function waitForMotionJob(id, { intervalMs = 500, timeoutMs = 120_000, fetchImpl = fetch, onUpdate } = {}) {
69
69
  const deadline = Date.now() + timeoutMs;
70
70
  while (Date.now() < deadline) {
71
- const payload = await getFalMotionJob(id, fetchImpl);
71
+ const payload = await getMotionJob(id, fetchImpl);
72
72
  const job = payload?.job ?? null;
73
73
  onUpdate?.(job, payload);
74
74
  if (job?.status === "done" || job?.status === "failed") return payload;