cozyclay 1.9.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 (286) hide show
  1. package/CHANGELOG.md +171 -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-PROTOCOL.md +1 -1
  40. package/mcp/live-hub.mjs +35 -93
  41. package/mcp/mesh-file.mjs +48 -0
  42. package/mcp/server.mjs +6 -77
  43. package/mcp/tool-handlers.mjs +224 -377
  44. package/package.json +2 -1
  45. package/src/App.jsx +1273 -9075
  46. package/src/analytics.js +393 -9
  47. package/src/app-context.js +211 -0
  48. package/src/app-stage.jsx +25 -24
  49. package/src/ardy/auto-fix-panel.css +108 -0
  50. package/src/ardy/collision-blockers.js +9 -3
  51. package/src/ardy/fix-collisions.js +10 -66
  52. package/src/ardy/ground.js +4 -4
  53. package/src/ardy/ik-drag.js +101 -0
  54. package/src/ardy/ik-key-json.js +43 -0
  55. package/src/ardy/ik.js +122 -76
  56. package/src/ardy/physics-panel.css +2 -0
  57. package/src/ardy/physics-panel.jsx +17 -5
  58. package/src/ardy/platform-fit-panel.css +64 -0
  59. package/src/ardy/platform-fit-panel.jsx +40 -0
  60. package/src/ardy/platform-fit.js +326 -0
  61. package/src/ardy/playback.js +166 -20
  62. package/src/ardy/range-pin.js +299 -0
  63. package/src/ardy/timeline-coordinates.js +26 -0
  64. package/src/ardy/timeline.css +1199 -0
  65. package/src/ardy/timeline.jsx +149 -41
  66. package/src/ardy/waypoints.js +2 -2
  67. package/src/asset-pane.css +462 -0
  68. package/src/asset-pane.jsx +461 -232
  69. package/src/command-bus.js +349 -0
  70. package/src/commands/ai.js +31 -0
  71. package/src/commands/cast.js +140 -0
  72. package/src/commands/elements/character.js +13 -0
  73. package/src/commands/elements/motion.js +14 -0
  74. package/src/commands/elements/object.js +22 -0
  75. package/src/commands/elements/scene.js +16 -0
  76. package/src/commands/elements/shot.js +9 -0
  77. package/src/commands/elements/stage.js +11 -0
  78. package/src/commands/elements.js +155 -0
  79. package/src/commands/export.js +20 -0
  80. package/src/commands/index.js +40 -0
  81. package/src/commands/motion.js +231 -0
  82. package/src/commands/objects.js +174 -0
  83. package/src/commands/project.js +53 -0
  84. package/src/commands/scene.js +81 -0
  85. package/src/commands/shared.js +19 -0
  86. package/src/commands/shot.js +103 -0
  87. package/src/commands/stage.js +17 -0
  88. package/src/commands/view.js +42 -0
  89. package/src/document-store.js +144 -0
  90. package/src/domains/cast.js +1007 -0
  91. package/src/domains/motion.js +3219 -0
  92. package/src/domains/objects.js +932 -0
  93. package/src/domains/scenes.js +817 -0
  94. package/src/domains/shots.js +404 -0
  95. package/src/domains/stage.js +93 -0
  96. package/src/dualview.jsx +113 -57
  97. package/src/facing-marks.js +3 -0
  98. package/src/first-success-guide.jsx +14 -14
  99. package/src/grid-view.js +18 -5
  100. package/src/hierarchy-model.js +24 -16
  101. package/src/hierarchy-panel.css +386 -0
  102. package/src/hierarchy-panel.jsx +150 -21
  103. package/src/{fal-motion-client.js → i2v-motion-client.js} +13 -13
  104. package/src/i2v-motion-studio.jsx +180 -0
  105. package/src/ik-camera.js +3 -0
  106. package/src/main.jsx +6 -0
  107. package/src/motion/generation.js +105 -0
  108. package/src/motion-readiness-ui.jsx +4 -4
  109. package/src/motion-readiness.js +11 -0
  110. package/src/motion-trail.js +366 -9
  111. package/src/object-gizmo.jsx +7 -1
  112. package/src/otio.js +2 -1
  113. package/src/panels/CameraPanel.jsx +60 -0
  114. package/src/panels/CharacterTransformPanel.jsx +48 -0
  115. package/src/panels/EnvironmentPanel.jsx +40 -0
  116. package/src/panels/Foldout.jsx +32 -0
  117. package/src/panels/LightPanel.jsx +24 -0
  118. package/src/panels/ObjectTransformPanel.jsx +554 -0
  119. package/src/panels/PosePanel.jsx +97 -0
  120. package/src/panels/ProjectPanel.jsx +33 -0
  121. package/src/panels/PromptBlocksPanel.jsx +442 -0
  122. package/src/panels/PropsPanel.jsx +70 -0
  123. package/src/panels/ReferenceImageField.jsx +91 -0
  124. package/src/panels/RigControlPanel.jsx +78 -0
  125. package/src/panels/RigPanel.jsx +37 -0
  126. package/src/panels/SubjectBox.jsx +47 -0
  127. package/src/panels/SubjectsPanel.jsx +39 -0
  128. package/src/panels/VideoCapturePanel.jsx +180 -0
  129. package/src/panels/details.css +1171 -0
  130. package/src/panels/motion.css +197 -0
  131. package/src/panels/pose.css +39 -0
  132. package/src/planview.jsx +60 -31
  133. package/src/posestudio.jsx +59 -4
  134. package/src/project-browser.css +1073 -0
  135. package/src/project-browser.jsx +284 -123
  136. package/src/range-pin-object-transform.js +19 -0
  137. package/src/range-pin-panel.css +518 -0
  138. package/src/range-pin-panel.jsx +344 -0
  139. package/src/result-modal.jsx +5 -5
  140. package/src/room.jsx +79 -51
  141. package/src/scene-objects.js +37 -1
  142. package/src/scenes.js +5 -0
  143. package/src/semantic-edit.js +2 -0
  144. package/src/settings-menu.jsx +39 -105
  145. package/src/shell/BottomDock.jsx +450 -0
  146. package/src/shell/DetailsSlot.jsx +513 -0
  147. package/src/shell/LibrarySlot.jsx +86 -0
  148. package/src/shell/MenuBar.jsx +601 -0
  149. package/src/shell/OutlinerSlot.jsx +50 -0
  150. package/src/shell/PreferencesDialog.jsx +538 -0
  151. package/src/shell/PreferencesSlot.jsx +40 -0
  152. package/src/shell/StatusBar.jsx +125 -0
  153. package/src/shell/StudioShell.jsx +92 -0
  154. package/src/shell/TopBar.jsx +129 -0
  155. package/src/shell/ViewportToolbar.jsx +656 -0
  156. package/src/shell/agent-glass.css +296 -0
  157. package/src/shell/dock.css +93 -0
  158. package/src/shell/glass-regions.css +985 -0
  159. package/src/shell/glass.css +687 -0
  160. package/src/shell/log-store.js +52 -0
  161. package/src/shell/mode.css +9 -0
  162. package/src/shell/preferences.css +651 -0
  163. package/src/shell/shell.css +282 -0
  164. package/src/shell/studio-shell-context.js +19 -0
  165. package/src/shell/topbar.css +435 -0
  166. package/src/shell/viewport.css +451 -0
  167. package/src/store/authored-intent.js +22 -0
  168. package/src/store/runtime-adapters.js +92 -0
  169. package/src/store/scene-stage.js +21 -0
  170. package/src/store/use-document-store.js +14 -0
  171. package/src/studio-actions.js +210 -0
  172. package/src/studio-agent-commands.js +41 -271
  173. package/src/studio-agent-context.js +39 -10
  174. package/src/studio-agent-motion.js +122 -373
  175. package/src/studio-agent-protocol.js +67 -31
  176. package/src/studio-app-binding.js +379 -0
  177. package/src/studio-contact-sheet.js +73 -0
  178. package/src/studio-elements.js +58 -51
  179. package/src/styles/themes.css +200 -0
  180. package/src/styles/tokens.css +69 -0
  181. package/src/styles.css +460 -1742
  182. package/src/theme.js +44 -0
  183. package/src/timeline-extent.js +16 -0
  184. package/src/trail-key-conflicts.js +39 -0
  185. package/src/trail-pick.js +59 -0
  186. package/src/ui.jsx +8 -8
  187. package/src/use-case-question.jsx +66 -0
  188. package/src/workflow/AgentPanel.jsx +82 -69
  189. package/src/workflow/agent-client.js +12 -2
  190. package/src/workflow/agent-panel.css +34 -32
  191. package/src/workflow/cozy-scene-node.css +2 -0
  192. package/src/workflow/workflow.css +2 -0
  193. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  194. package/tools/ardy/bridge.mjs +164 -146
  195. package/tools/ardy/visual-qa.mjs +5 -5
  196. package/tools/bench/EXP3.md +61 -0
  197. package/tools/bench/cclay_bench_extract_incam.py +125 -0
  198. package/tools/bench/cclay_bench_extract_obs.py +204 -0
  199. package/tools/bench/cclay_bench_runner.py +42 -0
  200. package/tools/bench/cube-contact.mjs +162 -0
  201. package/tools/bench/exp3.mjs +152 -0
  202. package/tools/bench/extract-bench-lib.mjs +216 -0
  203. package/tools/bench/extract-bench.mjs +302 -0
  204. package/tools/bench/fal-generate.mjs +88 -0
  205. package/tools/bench/fit/README.md +189 -0
  206. package/tools/bench/fit/camera.mjs +59 -0
  207. package/tools/bench/fit/contact.mjs +419 -0
  208. package/tools/bench/fit/footlock.mjs +234 -0
  209. package/tools/bench/fit/motion.mjs +85 -0
  210. package/tools/bench/fit/pin.mjs +27 -0
  211. package/tools/bench/fit/remote.mjs +105 -0
  212. package/tools/bench/fit-bench.mjs +126 -0
  213. package/tools/bench/fit-sanity.mjs +45 -0
  214. package/tools/bench/metrics.mjs +313 -0
  215. package/tools/bench/obs/depth.mjs +258 -0
  216. package/tools/bench/obs/extrinsics.mjs +175 -0
  217. package/tools/bench/obs/fit_mannequin_betas.py +340 -0
  218. package/tools/bench/obs/ground.mjs +294 -0
  219. package/tools/bench/obs/heading.mjs +151 -0
  220. package/tools/bench/obs/ladder.mjs +555 -0
  221. package/tools/bench/obs/mannequin-betas.json +124 -0
  222. package/tools/bench/obs/remote.mjs +83 -0
  223. package/tools/bench/obs/rest_joints.py +52 -0
  224. package/tools/bench/obs/ybot-targets.mjs +49 -0
  225. package/tools/bench/obs-bench.mjs +520 -0
  226. package/tools/bench/score.mjs +343 -0
  227. package/tools/bench/summarize.mjs +86 -0
  228. package/tools/dev/pages/privacy.html +13 -8
  229. package/tools/gt-render/browser.mjs +159 -0
  230. package/tools/gt-render/camera-math.mjs +276 -0
  231. package/tools/gt-render/page.mjs +267 -0
  232. package/tools/gt-render/render.mjs +514 -0
  233. package/tools/gt-render/scene-box.mjs +31 -0
  234. package/tools/gt-render/take-transform.mjs +87 -0
  235. package/tools/morphgs/assets/gen_truth.py +39 -0
  236. package/tools/morphgs/assets/mesh_ori_rig.txt +27 -0
  237. package/tools/morphgs/assets/playback-check.mjs +30 -0
  238. package/tools/morphgs/demo-gate.sh +26 -0
  239. package/tools/morphgs/fbx2morphgs.mjs +68 -0
  240. package/tools/morphgs/morphgs-to-cskel27.mjs +105 -0
  241. package/tools/morphgs/patches/preprocess_src-none-mode.patch +76 -0
  242. package/tools/morphgs/setup-on-cluster.sh +126 -0
  243. package/tools/qa/css-rule-usage.mjs +283 -0
  244. package/tools/qa/studio-control-count.mjs +70 -32
  245. package/tools/run-tests.mjs +134 -12
  246. package/tools/track/DESIGN.md +513 -0
  247. package/tools/track/backfill-provenance.mjs +116 -0
  248. package/tools/track/budget.mjs +8 -0
  249. package/tools/track/check-rig.mjs +209 -0
  250. package/tools/track/diagnostics.schema.json +60 -0
  251. package/tools/track/export-rig.mjs +256 -0
  252. package/tools/track/fallback.mjs +7 -0
  253. package/tools/track/fk-parity-fixture.mjs +161 -0
  254. package/tools/track/gate.mjs +349 -0
  255. package/tools/track/masks.mjs +318 -0
  256. package/tools/track/metrics.mjs +201 -0
  257. package/tools/track/publish-obs.mjs +108 -0
  258. package/tools/track/py/check_env.py +61 -0
  259. package/tools/track/py/eval_lr.py +229 -0
  260. package/tools/track/py/lr_viterbi.py +294 -0
  261. package/tools/track/py/masks.py +405 -0
  262. package/tools/track/py/objective.py +382 -0
  263. package/tools/track/py/rig.py +508 -0
  264. package/tools/track/py/scene.py +326 -0
  265. package/tools/track/py/test_joint_indices.py +123 -0
  266. package/tools/track/py/test_lr_viterbi.py +195 -0
  267. package/tools/track/py/test_masks.py +229 -0
  268. package/tools/track/py/test_rig.py +288 -0
  269. package/tools/track/py/test_scene.py +354 -0
  270. package/tools/track/py/test_track.py +328 -0
  271. package/tools/track/py/track.py +411 -0
  272. package/tools/track/remote.mjs +140 -0
  273. package/tools/track/rig-dump.mjs +227 -0
  274. package/tools/track/run-box-tests.mjs +14 -0
  275. package/tools/track/run-box.mjs +168 -0
  276. package/tools/track/setup-box.sh +59 -0
  277. package/tools/track/study-2d.mjs +728 -0
  278. package/dist/assets/analytics-B1hnH66c.js +0 -1
  279. package/dist/assets/app-BWusbqgO.js +0 -4861
  280. package/dist/assets/app-qKDo4PBX.css +0 -1
  281. package/dist/assets/shot-prompt-CHUtw6af.js +0 -17
  282. package/dist/assets/shot-prompt-C_g2BHVc.css +0 -1
  283. package/dist/assets/workflow-CVwzMhz2.js +0 -179
  284. package/dist/assets/workflow-DsudxKHF.css +0 -1
  285. package/src/fal-motion-studio.jsx +0 -180
  286. package/src/scene-history.js +0 -129
@@ -0,0 +1,197 @@
1
+ /* v2 Motion Details (G6): prompt blocks, line editing, Motion › Advanced and
2
+ the Takes section. The generate actions live in the top bar (R1). Rooted like details.css so these rules beat
3
+ the legacy inspector material without editing src/styles.css. */
4
+
5
+ /* The selected block's range, a read-only value box in the Field grid. */
6
+ .v2-details-section.v2-details-section .motion-block-range {
7
+ box-sizing: border-box;
8
+ display: flex;
9
+ align-items: center;
10
+ height: var(--h-row, 24px);
11
+ padding: 0 var(--sp-1, 8px);
12
+ border-radius: var(--r-1, 4px);
13
+ background: var(--field, #0f0f11);
14
+ color: var(--text-1, #e4e4e7);
15
+ font-family: var(--mono, 'JetBrains Mono');
16
+ font-size: var(--details-mono-size, 11px);
17
+ font-variant-numeric: tabular-nums;
18
+ }
19
+
20
+ /* A full-width button that is a Field's control sits on the label's row. */
21
+ /* biome-ignore lint/style/noDescendingSpecificity: the repeated region root is intentional to beat legacy inspector rules */
22
+ .v2-details-section.v2-details-section .field > .btn.full {
23
+ margin-top: 0;
24
+ }
25
+
26
+ /* Readiness sits inline under the Prompt Blocks controls in the same quiet hint voice. */
27
+ .v2-details-section.v2-details-section .motion-readiness {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: space-between;
31
+ gap: var(--sp-1, 8px);
32
+ min-height: var(--h-row, 24px);
33
+ margin: var(--details-gap, 4px) 0 0;
34
+ padding: 0;
35
+ border: 0;
36
+ background: transparent;
37
+ color: var(--text-2, #9b9ba1);
38
+ font-size: var(--details-secondary-size, 11px);
39
+ }
40
+
41
+ /* biome-ignore lint/style/noDescendingSpecificity: the repeated region root is intentional to beat legacy inspector rules */
42
+ .v2-details-section.v2-details-section .motion-readiness .btn {
43
+ flex: none;
44
+ width: auto;
45
+ height: var(--h-row, 24px);
46
+ min-height: var(--h-row, 24px);
47
+ line-height: var(--h-row, 24px);
48
+ }
49
+
50
+ .v2-details-section.v2-details-section .ardy-status {
51
+ margin: 0;
52
+ color: var(--text-2, #9b9ba1);
53
+ font-size: var(--details-secondary-size, 11px);
54
+ }
55
+
56
+ /* Motion › Advanced: a collapsed subsection with the same arrow grammar as a
57
+ section head, one step indented by living inside the section body. */
58
+ .v2-details-section.v2-details-section .motion-advanced {
59
+ margin-top: var(--sp-1, 8px);
60
+ border-top: var(--details-border-width, 1px) solid var(--row-divider, #1d1d20);
61
+ }
62
+
63
+ .v2-details-section.v2-details-section .motion-advanced-head {
64
+ box-sizing: border-box;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--sp-1, 8px);
68
+ width: 100%;
69
+ height: var(--h-row, 24px);
70
+ margin: 0;
71
+ padding: 0;
72
+ border: 0;
73
+ background: transparent;
74
+ color: var(--text-3, #636369);
75
+ font-family: var(--sans, 'IBM Plex Sans');
76
+ font-size: var(--details-secondary-size, 11px);
77
+ font-weight: 500;
78
+ text-align: left;
79
+ cursor: pointer;
80
+ }
81
+
82
+ .v2-details-section.v2-details-section .motion-advanced-head:hover,
83
+ .v2-details-section.v2-details-section .motion-advanced-head:focus-visible {
84
+ color: var(--text-1, #e4e4e7);
85
+ outline: none;
86
+ }
87
+
88
+ .v2-details-section.v2-details-section .motion-advanced-head:focus-visible {
89
+ box-shadow: inset 0 0 0 var(--details-focus-width, 1px) var(--select, #e8a33d);
90
+ }
91
+
92
+ .v2-details-section.v2-details-section .motion-advanced-body {
93
+ display: flex;
94
+ flex-direction: column;
95
+ gap: var(--details-gap, 4px);
96
+ }
97
+
98
+ .v2-details-section.v2-details-section .motion-preserve-scale {
99
+ display: flex;
100
+ justify-content: space-between;
101
+ gap: var(--sp-1, 8px);
102
+ padding-left: calc(var(--details-label-width, 100px) + var(--sp-1, 8px));
103
+ font-size: var(--details-meta-size, 10px);
104
+ }
105
+
106
+ /* Takes: one 24 px row per version. The current take wears the amber
107
+ selection tint, the same one the Outliner uses for a selected row. */
108
+ .v2-details-section.v2-details-section .motion-takes {
109
+ display: flex;
110
+ flex-direction: column;
111
+ gap: var(--details-gap, 4px);
112
+ }
113
+
114
+ .v2-details-section.v2-details-section .motion-take-list {
115
+ display: grid;
116
+ gap: var(--details-group-gap, 2px);
117
+ }
118
+
119
+ .v2-details-section.v2-details-section .motion-take-row {
120
+ box-sizing: border-box;
121
+ display: grid;
122
+ grid-template-columns: var(--motion-take-index-width, 28px) minmax(0, 1fr) auto;
123
+ align-items: center;
124
+ column-gap: var(--sp-1, 8px);
125
+ width: 100%;
126
+ height: var(--h-row, 24px);
127
+ min-height: var(--h-row, 24px);
128
+ margin: 0;
129
+ padding: 0 var(--sp-1, 8px);
130
+ border: 0;
131
+ border-radius: var(--r-1, 4px);
132
+ background: transparent;
133
+ color: var(--text-row, #c9c9ce);
134
+ font-family: var(--sans, 'IBM Plex Sans');
135
+ font-size: var(--details-secondary-size, 11px);
136
+ line-height: var(--h-row, 24px);
137
+ text-align: left;
138
+ cursor: pointer;
139
+ }
140
+
141
+ .v2-details-section.v2-details-section .motion-take-row:hover,
142
+ .v2-details-section.v2-details-section .motion-take-row:focus-visible {
143
+ background: var(--surface-raised, #1e1e21);
144
+ color: var(--text-1, #e4e4e7);
145
+ outline: none;
146
+ }
147
+
148
+ .v2-details-section.v2-details-section .motion-take-row:focus-visible {
149
+ box-shadow: inset 0 0 0 var(--details-focus-width, 1px) var(--select, #e8a33d);
150
+ }
151
+
152
+ .v2-details-section.v2-details-section .motion-take-row.current {
153
+ background: var(--select-tint, rgba(232,163,61,.14));
154
+ color: var(--text-selected, #fff);
155
+ }
156
+
157
+ .v2-details-section.v2-details-section .motion-take-index {
158
+ color: var(--text-2, #9b9ba1);
159
+ font-family: var(--mono, 'JetBrains Mono');
160
+ font-size: var(--details-mono-size, 11px);
161
+ font-variant-numeric: tabular-nums;
162
+ }
163
+
164
+ .v2-details-section.v2-details-section .motion-take-row.current .motion-take-index {
165
+ color: var(--select, #e8a33d);
166
+ }
167
+
168
+ .v2-details-section.v2-details-section .motion-take-label {
169
+ min-width: 0;
170
+ overflow: hidden;
171
+ text-overflow: ellipsis;
172
+ white-space: nowrap;
173
+ }
174
+
175
+ .v2-details-section.v2-details-section .motion-take-time {
176
+ color: var(--text-3, #636369);
177
+ font-family: var(--mono, 'JetBrains Mono');
178
+ font-size: var(--details-meta-size, 10px);
179
+ font-variant-numeric: tabular-nums;
180
+ }
181
+
182
+ .v2-details-section.v2-details-section .replay-notice {
183
+ color: var(--select, #e8a33d);
184
+ }
185
+
186
+ /* The recipe summary is the section's footer line. */
187
+ .v2-details-section.v2-details-section .motion-takes-footer {
188
+ box-sizing: border-box;
189
+ margin: 0;
190
+ padding: var(--details-gap, 4px) 0 0;
191
+ border-top: var(--details-border-width, 1px) solid var(--row-divider, #1d1d20);
192
+ color: var(--text-3, #636369);
193
+ font-family: var(--mono, 'JetBrains Mono');
194
+ font-size: var(--details-meta-size, 10px);
195
+ font-variant-numeric: tabular-nums;
196
+ line-height: var(--details-hint-line-height, 1.4);
197
+ }
@@ -0,0 +1,39 @@
1
+ @layer regions {
2
+ .pose-details {
3
+ display: grid;
4
+ gap: 8px;
5
+ padding: 8px 12px 12px;
6
+ color: var(--text-1, #e4e4e7);
7
+ }
8
+ .pose-details .pose-section {
9
+ display: grid;
10
+ gap: 4px;
11
+ padding: 8px;
12
+ background: var(--surface-raised, #1e1e21);
13
+ border: 1px solid var(--row-divider, #2a2a2e);
14
+ border-radius: var(--r-1, 4px);
15
+ }
16
+ .pose-details .pose-section > h4 {
17
+ margin: 0;
18
+ color: var(--text-2, #9b9ba1);
19
+ font-size: 11px;
20
+ font-weight: 500;
21
+ }
22
+ .pose-details .pose-row { min-height: 24px; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; font-size: 12px; }
23
+ .pose-details .pose-row + .pose-row { border-top: 1px solid var(--row-divider, #2a2a2e); }
24
+ .pose-details .pose-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; }
25
+ .pose-details .pose-preview {
26
+ display: grid;
27
+ gap: 4px;
28
+ padding: 8px;
29
+ border: 1px solid color-mix(in srgb, var(--select, #e8a33d) 65%, transparent);
30
+ border-radius: var(--r-1, 4px);
31
+ }
32
+ .pose-details .pose-preview-title { color: var(--select, #e8a33d); font-weight: 600; }
33
+ .pose-details .pose-preview-summary { color: var(--text-2, #9b9ba1); font-size: 11px; }
34
+ .pose-details .pose-results { display: grid; gap: 2px; margin: 0; padding: 0; list-style: none; }
35
+ .pose-details .pose-results li { min-height: 24px; display: flex; align-items: center; color: var(--text-2, #9b9ba1); font-size: 11px; }
36
+ .pose-details .pose-results li > .btn { width: 100%; justify-content: flex-start; text-align: left; padding: 0 4px; color: inherit; }
37
+ .pose-details .pose-results li[data-status="tooHigh"], .pose-details .pose-results li[data-status="wall"] { color: var(--danger, #e5484d); }
38
+ .pose-details .pose-keyhint { color: var(--text-3, #636369); font-size: 10px; }
39
+ }
package/src/planview.jsx CHANGED
@@ -18,8 +18,14 @@ const HANDLE_GRAB = 0.5;
18
18
  const CAMERA_COLOR = "#007f9e";
19
19
  const SUBJECT_ONE_COLOR = "#2457d6";
20
20
  const SUBJECT_TWO_COLOR = "#d63b55";
21
- const OBJECT_COLOR = "#c14f2c";
21
+ // Props are the plan's backdrop: a pale wash, a hairline edge and a muted
22
+ // name, so the camera, the cast and the rail are what reads first.
23
+ const OBJECT_COLOR = "#f3f0ea";
24
+ const PROP_EDGE_COLOR = "#a19b91";
25
+ const PROP_LABEL_COLOR = "#8a847b";
22
26
  const SELECTED_COLOR = "#b77900";
27
+ const CAST_RING_INNER = 0.62;
28
+ const CAST_RING_OUTER = 0.8;
23
29
 
24
30
  /** yaw that makes the character's local +Z face the given direction */
25
31
  const yawToward = (dx, dz) => Math.atan2(dx, dz);
@@ -149,19 +155,19 @@ function Puck({ color, dragging, turning, showBody = true, handleDist = HANDLE_D
149
155
  * Short by design: full names ran into each other the moment two actors stood
150
156
  * closer than the width of the word, which in a two-shot is always.
151
157
  */
152
- function PlanLabel({ text, color, offset = -0.72 }) {
158
+ function PlanLabel({ text, color, offset = -0.72, muted = false }) {
153
159
  return (
154
160
  <Text
155
161
  position={[0, 0.05, offset]}
156
162
  rotation={[-Math.PI / 2, 0, 0]}
157
- fontSize={0.32}
163
+ fontSize={muted ? 0.26 : 0.32}
158
164
  color={color}
159
165
  anchorX="center"
160
166
  anchorY="middle"
161
- outlineWidth={0.045}
167
+ outlineWidth={muted ? 0 : 0.045}
162
168
  outlineColor="#0e0d10"
163
169
  outlineOpacity={0.92}
164
- renderOrder={12}
170
+ renderOrder={muted ? 8 : 12}
165
171
  depthOffset={-1}
166
172
  >
167
173
  {text}
@@ -169,36 +175,58 @@ function PlanLabel({ text, color, offset = -0.72 }) {
169
175
  );
170
176
  }
171
177
 
178
+ /** A ring around a character's real mesh in its role colour: the cast reads
179
+ * first on a busy plan without the mesh itself being covered. */
180
+ function CastRing({ color }) {
181
+ return (
182
+ <mesh position={[0, 0.05, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={11}>
183
+ <ringGeometry args={[CAST_RING_INNER, CAST_RING_OUTER, 40]} />
184
+ <meshBasicMaterial color={color} transparent opacity={0.9} depthWrite={false} depthTest={false} />
185
+ </mesh>
186
+ );
187
+ }
188
+
172
189
  function SceneObjectFootprint({ object, selected, dragging, turning }) {
173
- const { width, depth } = objectSize(object);
190
+ const { width, height, depth } = objectSize(object);
174
191
  const objectLabel = displayObjectLabel(object.name).slice(0, 8);
175
192
  const handleDist = depth / 2 + 0.65;
176
193
  const rotation = (object.rot * Math.PI) / 180;
194
+ // An unselected prop is washed where it stands: a pale cap just above its
195
+ // top, depth-tested, so its own colours fade while a character beside or on
196
+ // it still shows through. Selection keeps the loud amber footprint.
197
+ const capY = (object.y ?? 0) + height + 0.02;
198
+ const edge = useMemo(() => {
199
+ const x = width / 2;
200
+ const z = depth / 2;
201
+ return [[-x, capY, -z], [x, capY, -z], [x, capY, z], [-x, capY, z], [-x, capY, -z]];
202
+ }, [width, depth, capY]);
177
203
  return (
178
204
  <group position={[object.x, 0, object.z]} rotation={[0, rotation, 0]}>
179
- <mesh position={[0, 0.032, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={10}>
180
- <planeGeometry args={[width, depth]} />
181
- {/* Auto-color mode stamps `autoColor` on displayed objects; the board
182
- agrees with the viewport so "the teal box" means the same thing in
183
- both panes. Selection still wins. */}
184
- <meshBasicMaterial
185
- color={selected ? SELECTED_COLOR : object.autoColor ?? OBJECT_COLOR}
186
- transparent
187
- opacity={selected ? 0.58 : 0.34}
188
- depthWrite={false}
189
- depthTest={false}
190
- />
191
- </mesh>
192
- <mesh position={[0, 0.04, 0]} renderOrder={11}>
193
- <boxGeometry args={[width, 0.02, depth]} />
194
- <meshBasicMaterial color={selected ? SELECTED_COLOR : object.autoColor ?? OBJECT_COLOR} wireframe depthTest={false} />
195
- </mesh>
205
+ {selected ? (
206
+ <>
207
+ <mesh position={[0, 0.032, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={10}>
208
+ <planeGeometry args={[width, depth]} />
209
+ <meshBasicMaterial color={SELECTED_COLOR} transparent opacity={0.58} depthWrite={false} depthTest={false} />
210
+ </mesh>
211
+ <mesh position={[0, 0.04, 0]} renderOrder={11}>
212
+ <boxGeometry args={[width, 0.02, depth]} />
213
+ <meshBasicMaterial color={SELECTED_COLOR} wireframe depthTest={false} />
214
+ </mesh>
215
+ </>
216
+ ) : (
217
+ <>
218
+ {/* Auto-color mode stamps `autoColor` on displayed objects; the board
219
+ agrees with the viewport so "the teal box" means the same thing in
220
+ both panes. */}
221
+ <mesh position={[0, capY, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={6}>
222
+ <planeGeometry args={[width, depth]} />
223
+ <meshBasicMaterial color={object.autoColor ?? OBJECT_COLOR} transparent opacity={0.72} depthWrite={false} />
224
+ </mesh>
225
+ <Line points={edge} color={PROP_EDGE_COLOR} lineWidth={1} transparent opacity={0.7} depthWrite={false} renderOrder={7} />
226
+ </>
227
+ )}
196
228
  <group position={[0, 0, depth / 2 + 0.45]} rotation={[0, -rotation, 0]}>
197
- <PlanLabel
198
- text={objectLabel}
199
- color={selected ? SELECTED_COLOR : OBJECT_COLOR}
200
- offset={0}
201
- />
229
+ <PlanLabel text={objectLabel} color={selected ? SELECTED_COLOR : PROP_LABEL_COLOR} offset={0} muted={!selected} />
202
230
  </group>
203
231
  {selected && <Puck color={SELECTED_COLOR} showBody={false} handleDist={handleDist} dragging={dragging} turning={turning} />}
204
232
  </group>
@@ -253,8 +281,8 @@ function SubjectMovementGuide({ track }) {
253
281
  </mesh>
254
282
  )}
255
283
  {[
256
- { point: guide.start, label: ko("ARDY START", "ARDY 시작") },
257
- { point: guide.end, label: ko("ARDY END", "ARDY 끝") },
284
+ { point: guide.start, label: ko("PATH START", "경로 시작") },
285
+ { point: guide.end, label: ko("PATH END", "경로 끝") },
258
286
  ].map(({ point, label }) => (
259
287
  <group key={label} position={[point.x, 0, point.z]}>
260
288
  <mesh position={[0, 0.055, 0]} rotation={[-Math.PI / 2, 0, 0]} renderOrder={11}>
@@ -874,7 +902,8 @@ export function PlanBoard({ hostRef, planCamRef, shotCamRef, look, fovDeg, chara
874
902
  const color = listIndex === 0 ? SUBJECT_ONE_COLOR : SUBJECT_TWO_COLOR;
875
903
  return (
876
904
  <group key={entry.id} position={[entry.x, 0, entry.z]}>
877
- <PlanLabel text={ko(`S${listIndex + 1}`, `인물 ${listIndex + 1}`)} color={color} />
905
+ <PlanLabel text={ko(`S${listIndex + 1}`, `인물 ${listIndex + 1}`)} color={color} offset={-(CAST_RING_OUTER + 0.3)} />
906
+ <CastRing color={color} />
878
907
  <group rotation={[0, (entry.rot * Math.PI) / 180, 0]}>
879
908
  {/* The real character mesh already renders in Top-View. Keep only
880
909
  its facing stem/handle instead of covering it with a hex puck. */}
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
  import { useFrame, useThree } from "@react-three/fiber";
3
+ import { Line } from "@react-three/drei";
3
4
  import * as THREE from "three";
4
5
  import { POSE_BONES, normalizeBoneName, primeBindPose } from "./poses.js";
5
6
  import { poseThumbnail, warmThumbnailModels } from "./pose-thumbs.js";
@@ -282,7 +283,7 @@ const FK_ROTATE_SPEED = 5;
282
283
  * Every drag move calls `onSolve(kind, trackId, targetWorld)`; the drag end
283
284
  * calls `onDragEnd(trackId)` so the caller can key it.
284
285
  */
285
- export function IkHandles({ chains, fkJoints, ikState, enabled, focus, onFocus, onSolve, onDragEnd }) {
286
+ export function IkHandles({ chains, fkJoints, ikState, enabled, focus, onFocus, onSolve, onDragEnd, selectionOnly = false, onPartPick }) {
286
287
  const { camera, gl, raycaster, scene } = useThree();
287
288
  const handleRefs = useRef({}); // id -> mesh (all sphere handles)
288
289
  const handleMetaRef = useRef(new Map()); // id -> { track, radius }
@@ -304,6 +305,8 @@ export function IkHandles({ chains, fkJoints, ikState, enabled, focus, onFocus,
304
305
  const solveRef = useRef(onSolve);
305
306
  const endRef = useRef(onDragEnd);
306
307
  const focusRef = useRef(onFocus);
308
+ const partPickRef = useRef(onPartPick);
309
+ partPickRef.current = onPartPick;
307
310
  // The pointerdown handler filters picks through the CURRENT focus: while
308
311
  // a joint is focused, only that joint (sphere + its gizmo arrows) is
309
312
  // interactive — unfocused handles are inert, so they can never shadow a
@@ -887,6 +890,12 @@ const CLICK_PX = 4;
887
890
  if (kind === "body") dragRef.current.startLocalPos = fkJoints.get(track.id).bone.position.clone();
888
891
  }
889
892
  focusRef.current?.(track.id);
893
+ if (selectionOnly) {
894
+ partPickRef.current?.(track.id);
895
+ dragRef.current = null;
896
+ gl.domElement.style.cursor = "";
897
+ return;
898
+ }
890
899
  gl.domElement.style.cursor = "grabbing";
891
900
  window.addEventListener("pointermove", handlersRef.current.onMove);
892
901
  window.addEventListener("pointerup", handlersRef.current.onUp);
@@ -911,8 +920,9 @@ const CLICK_PX = 4;
911
920
  handleRefs.current[p.track.id]?.visible &&
912
921
  handleRefs.current[p.track.id]?.userData.ikExposed !== false
913
922
  );
914
- if (focused) picks = picks.filter((p) => p.track.id === focused);
915
- else picks = picks.filter((p) => !p.axisDir); // spheres only
923
+ if (focused && !selectionOnly) picks = picks.filter((p) => p.track.id === focused);
924
+ else picks = picks.filter((p) => !p.axisDir); // spheres only
925
+ if (selectionOnly) picks = picks.filter((p) => !p.axisDir);
916
926
  if (!picks.length) {
917
927
  if (focused) beginDrag("empty", { id: "empty" }, null, null, downXY);
918
928
  return;
@@ -1101,6 +1111,51 @@ const CLICK_PX = 4;
1101
1111
  );
1102
1112
  }
1103
1113
 
1114
+ /** A poser-only target marker for the active range pin. The target is supplied
1115
+ * in world space; the line is rebuilt every render frame so object travel and
1116
+ * the effector's keyed pose stay visually connected while the playhead moves. */
1117
+ export function RangePinMarker({ chains, track, target = null, enabled = false }) {
1118
+ const markerRef = useRef(null);
1119
+ const lineRef = useRef(null);
1120
+ const effector = useRef(new THREE.Vector3()).current;
1121
+ const targetVector = useRef(new THREE.Vector3()).current;
1122
+ const markerColor = track?.endsWith("Hand") ? "#ff8a3d" : "#4dd2ff";
1123
+
1124
+ useFrame(() => {
1125
+ if (!enabled || !track || !target) return;
1126
+ const chain = chains?.get(track);
1127
+ if (!chain || !markerRef.current || !lineRef.current) return;
1128
+ targetVector.fromArray(target);
1129
+ markerRef.current.position.copy(targetVector);
1130
+ chain.bones[2].getWorldPosition(effector);
1131
+ lineRef.current.geometry.setPositions([...effector.toArray(), ...targetVector.toArray()]);
1132
+ });
1133
+
1134
+ return (
1135
+ <group visible={enabled} renderOrder={1001}>
1136
+ <mesh ref={(mesh) => {
1137
+ markerRef.current = mesh;
1138
+ if (mesh) mesh.layers.set(POSER_LAYER);
1139
+ }}>
1140
+ <sphereGeometry args={[0.035, 12, 8]} />
1141
+ <meshBasicMaterial color={markerColor} transparent opacity={0.95} depthTest={false} depthWrite={false} />
1142
+ </mesh>
1143
+ <Line
1144
+ ref={(line) => {
1145
+ lineRef.current = line;
1146
+ if (line) line.layers.set(POSER_LAYER);
1147
+ }}
1148
+ points={[[0, 0, 0], [0, 0, 0]]}
1149
+ color={markerColor}
1150
+ lineWidth={1.2}
1151
+ transparent
1152
+ opacity={0.72}
1153
+ depthTest={false}
1154
+ />
1155
+ </group>
1156
+ );
1157
+ }
1158
+
1104
1159
  /**
1105
1160
  * Pose Studio overlay panel. `poses` is the merged list (built-ins + custom)
1106
1161
  * the caller owns; every pose has `{ id, label, bones }` and custom poses add
@@ -1195,7 +1250,7 @@ export function PoseStudioPanel({ subject, model, poses, selectedId, onSelect, o
1195
1250
  {motionActive && (
1196
1251
  <p className="studio-hint" data-pose-motion-warning role="status">
1197
1252
  {ikCorrection
1198
- ? ko("IK mode is on — applying keys the pose as a full-body correction at the current frame; the motion stays.", "IK 모드가 켜져 있어요 — 적용하면 현재 프레임에 전신 보정 키로 들어가고, 모션은 그대로 유지됩니다.")
1253
+ ? ko("Pose mode is on — applying keys the pose as a full-body correction at the current frame; the motion stays.", "포즈 모드가 켜져 있어요 — 적용하면 현재 프레임에 전신 보정 키로 들어가고, 모션은 그대로 유지됩니다.")
1199
1254
  : ko("A sample motion is moving the character — applying a pose clears it and returns to the blocking pose.", "현재 샘플 모션이 캐릭터를 움직이고 있어요. 포즈를 눈앞에 적용하려면 샘플 모션을 지우고 블로킹 포즈로 전환합니다.")}
1200
1255
  </p>
1201
1256
  )}