cozyclay 3.1.1 → 3.1.3

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 (195) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +1 -1
  3. package/bin/agent/agent-routes.mjs +4 -4
  4. package/bin/agent/agent-runner.mjs +160 -151
  5. package/bin/agent/agent-stream.mjs +86 -0
  6. package/bin/agent/anthropic-subscription-events.mjs +120 -0
  7. package/bin/agent/anthropic-subscription-prompt.mjs +116 -0
  8. package/bin/agent/anthropic-subscription-stream.mjs +251 -0
  9. package/bin/agent/anthropic-subscription.mjs +73 -0
  10. package/bin/agent/claude-code-executable.mjs +93 -0
  11. package/bin/agent/cliproxy-provider.mjs +25 -11
  12. package/bin/agent/main-agent.mjs +21 -7
  13. package/bin/agent/main-prompt.mjs +7 -6
  14. package/bin/agent/model-roles.mjs +112 -0
  15. package/bin/agent/openai-compatible-provider.mjs +95 -0
  16. package/bin/agent/pi-tools.mjs +19 -10
  17. package/bin/agent/provider-login.mjs +3 -1
  18. package/bin/agent/provider-routes.mjs +2 -0
  19. package/bin/agent/providers.mjs +35 -130
  20. package/bin/agent/script-tool.mjs +2 -1
  21. package/bin/agent/studio-prompt.mjs +1 -1
  22. package/bin/agent/studio-rebase.mjs +5 -4
  23. package/bin/agent/studio-task-routes.mjs +1 -1
  24. package/bin/agent/studio-tasks.mjs +116 -25
  25. package/bin/agent/studio-tools.mjs +3 -2
  26. package/bin/cozyclay.mjs +4 -2
  27. package/bin/live-endpoint.mjs +2 -0
  28. package/dist/app/index.html +6 -6
  29. package/dist/assets/TaskBoard-U1Yl-4F1.js +1 -0
  30. package/dist/assets/app-BdRlMf4J.css +1 -0
  31. package/dist/assets/app-DwETmoQZ.js +5144 -0
  32. package/dist/assets/compile-worker-oy_P9HHh.js +6 -0
  33. package/dist/assets/motion3-compile-DDG3wklD.js +1 -0
  34. package/dist/assets/scene-asset-sync-BEjjhNEo.css +1 -0
  35. package/dist/assets/scene-asset-sync-Bz59Ghdz.js +13 -0
  36. package/dist/assets/task-board-DFfNa0xg.js +15 -0
  37. package/dist/assets/workflow-BMJGd_gZ.js +179 -0
  38. package/dist/cozyclay-package.json +1 -1
  39. package/dist/index.html +1 -1
  40. package/dist/sitemap.xml +1 -1
  41. package/dist/workflow/index.html +4 -4
  42. package/mcp/README.md +1 -1
  43. package/mcp/live-hub.mjs +5 -3
  44. package/mcp/tool-handlers.mjs +5 -5
  45. package/package.json +6 -4
  46. package/src/App.jsx +65 -87
  47. package/src/agent-image-client.js +2 -2
  48. package/src/asset-pane.jsx +14 -14
  49. package/src/bus-task-scopes.js +59 -11
  50. package/src/bus-task-targets.js +35 -9
  51. package/src/bus-tasks.js +23 -10
  52. package/src/character-kind.js +13 -12
  53. package/src/command-bus.js +7 -4
  54. package/src/commands/ai.js +1 -1
  55. package/src/commands/export.js +1 -1
  56. package/src/commands/motion3.js +27 -7
  57. package/src/commands/project.js +2 -1
  58. package/src/commands/shot.js +1 -1
  59. package/src/commands/view.js +2 -2
  60. package/src/domains/cast.js +15 -15
  61. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  62. package/src/domains/motion.js +11 -11
  63. package/src/domains/objects.js +20 -30
  64. package/src/domains/scenes.js +14 -21
  65. package/src/domains/shots.js +3 -3
  66. package/src/export-workspace.js +244 -0
  67. package/src/first-success-guide.jsx +19 -3
  68. package/src/hierarchy-panel.jsx +6 -7
  69. package/src/keyframe-pack.js +5 -1
  70. package/src/live-control.js +14 -7
  71. package/src/locale-zh/app.js +124 -0
  72. package/src/locale-zh/domains.js +36 -0
  73. package/src/locale-zh/labels.js +148 -0
  74. package/src/locale-zh/panels.js +225 -0
  75. package/src/locale-zh/shell.js +230 -0
  76. package/src/locale-zh/timeline.js +145 -0
  77. package/src/locale-zh/workflow.js +104 -0
  78. package/src/locale-zh/workspace.js +190 -0
  79. package/src/locale-zh.js +21 -0
  80. package/src/locale.js +16 -6
  81. package/src/main.jsx +1 -1
  82. package/src/motion/beat-board-model.js +4 -4
  83. package/src/motion/beat-board.jsx +1 -1
  84. package/src/motion/ik-pick.js +24 -0
  85. package/src/motion/take-segments.jsx +5 -5
  86. package/src/motion/timeline.jsx +30 -40
  87. package/src/motion/waypoints.js +6 -6
  88. package/src/motion3/compile-job.js +15 -3
  89. package/src/motion3/compiler/channels.js +74 -24
  90. package/src/motion3/compiler/compile.js +2 -1
  91. package/src/motion3/compiler/idle.js +2 -0
  92. package/src/motion3/compiler/overlap.js +180 -0
  93. package/src/motion3/compiler/pose.js +6 -4
  94. package/src/motion3/compiler/validate-common.js +1 -0
  95. package/src/motion3/compiler/validate-parts.js +5 -1
  96. package/src/motion3/compiler/validate.js +30 -10
  97. package/src/motion3/correct.js +236 -0
  98. package/src/motion3/docs.js +8 -6
  99. package/src/motion3/face-lean.js +31 -0
  100. package/src/motion3/fix/index.js +5 -1
  101. package/src/motion3/fix/reach-body.js +172 -0
  102. package/src/motion3/hint-check.js +44 -0
  103. package/src/motion3/json-patch.js +120 -12
  104. package/src/motion3/lenient.js +46 -4
  105. package/src/motion3/object-keys.js +17 -5
  106. package/src/motion3/pipeline.js +46 -9
  107. package/src/motion3/qa/arm-checks.js +96 -0
  108. package/src/motion3/qa/asked.js +113 -0
  109. package/src/motion3/qa/goals.js +37 -8
  110. package/src/motion3/qa/hints-goal.js +6 -5
  111. package/src/motion3/qa/index.js +5 -2
  112. package/src/motion3/qa/key-checks.js +4 -3
  113. package/src/motion3/qa/motion-life.js +243 -0
  114. package/src/motion3/qa/spec-checks.js +15 -9
  115. package/src/motion3/qa/verdict.js +50 -0
  116. package/src/motion3/refs.js +38 -14
  117. package/src/motion3/repair-watch.js +132 -0
  118. package/src/motion3/repair.js +52 -19
  119. package/src/motion3/report.js +50 -9
  120. package/src/motion3/schema/topics.js +6 -4
  121. package/src/motion3/schema/types.js +1 -1
  122. package/src/motion3/skills/basic.js +15 -8
  123. package/src/motion3/skills/index.js +52 -18
  124. package/src/motion3/skills/info.js +1 -1
  125. package/src/motion3/skills/stance.js +6 -0
  126. package/src/motion3/skills/walk-fit.js +170 -0
  127. package/src/object-catalog.jsx +4 -5
  128. package/src/panels/CameraPanel.jsx +3 -3
  129. package/src/panels/HdrEnvironmentControls.jsx +2 -1
  130. package/src/panels/ObjectTransformPanel.jsx +19 -29
  131. package/src/panels/PoseImagePanel.jsx +11 -10
  132. package/src/panels/PosePanel.jsx +2 -2
  133. package/src/panels/ReferenceImageField.jsx +2 -2
  134. package/src/panels/RigControlPanel.jsx +2 -2
  135. package/src/panels/SculptPanel.jsx +11 -11
  136. package/src/panels/SubjectBox.jsx +3 -3
  137. package/src/panels/SubjectsPanel.jsx +1 -1
  138. package/src/posestudio.jsx +10 -8
  139. package/src/project-browser.css +7 -6
  140. package/src/project-browser.jsx +7 -6
  141. package/src/project-open-file.js +19 -0
  142. package/src/qa-label.js +1 -1
  143. package/src/range-pin-panel.jsx +14 -14
  144. package/src/resource-status.jsx +3 -3
  145. package/src/result-modal.jsx +13 -13
  146. package/src/settings-menu.jsx +4 -1
  147. package/src/shell/BoardSlot.jsx +5 -5
  148. package/src/shell/CompositionGuideButton.jsx +1 -1
  149. package/src/shell/ExportFolderPreference.jsx +103 -0
  150. package/src/shell/GhostsMenuGroup.jsx +1 -1
  151. package/src/shell/PreferencesDialog.jsx +3 -166
  152. package/src/shell/StatusBar.jsx +4 -4
  153. package/src/shell/ViewportToolbar.jsx +3 -3
  154. package/src/shell/add-camera-shot.js +2 -2
  155. package/src/shell/details-selection.js +1 -1
  156. package/src/shell/preference-controls.jsx +115 -0
  157. package/src/shell/preference-pages.jsx +72 -0
  158. package/src/shell/preferences.css +46 -0
  159. package/src/shot-guides.js +5 -5
  160. package/src/shot-prompt.js +29 -9
  161. package/src/stage-hdr.js +3 -3
  162. package/src/stage-labels-ko.js +2 -2
  163. package/src/stage-startup-defaults.js +3 -3
  164. package/src/studio-actions.js +4 -4
  165. package/src/studio-agent-motion.js +19 -9
  166. package/src/studio-agent-protocol.js +1 -1
  167. package/src/studio-app-binding.js +3 -2
  168. package/src/styles/tokens.css +1 -1
  169. package/src/styles.css +1 -1
  170. package/src/use-case-question.jsx +18 -18
  171. package/src/workflow/AgentPanel.jsx +6 -6
  172. package/src/workflow/AgentPlanCard.jsx +1 -1
  173. package/src/workflow/AgentProgressCard.jsx +4 -4
  174. package/src/workflow/AgentTurnSteps.jsx +1 -1
  175. package/src/workflow/CozySceneNode.jsx +1 -1
  176. package/src/workflow/LoginPanel.jsx +1 -1
  177. package/src/workflow/TaskBoard.jsx +1 -1
  178. package/src/workflow/TaskInline.jsx +1 -1
  179. package/src/workflow/WorkflowBuilder.jsx +2 -2
  180. package/src/workflow/agent-client.js +1 -1
  181. package/src/workflow/agent-turn.js +5 -5
  182. package/src/workflow/keyframe-pack-request.js +3 -10
  183. package/src/workflow/task-board.css +1 -1
  184. package/src/workflow/task-board.js +11 -9
  185. package/tools/loc-baseline.json +2 -3
  186. package/tools/run-tests.mjs +9 -9
  187. package/dist/assets/TaskBoard-ngdqiu7u.js +0 -1
  188. package/dist/assets/app-BlwOpest.css +0 -1
  189. package/dist/assets/app-DmR7jBkz.js +0 -5144
  190. package/dist/assets/compile-worker-D0pKioa0.js +0 -5
  191. package/dist/assets/motion3-compile-BB3_1-EB.js +0 -1
  192. package/dist/assets/scene-asset-sync-CBx7XRti.css +0 -1
  193. package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +0 -13
  194. package/dist/assets/task-board-ErQvfoaz.js +0 -14
  195. package/dist/assets/workflow-zQM-EKW1.js +0 -179
@@ -0,0 +1,145 @@
1
+ // Chinese UI copy for the animation timeline, camera lanes and motion tools.
2
+ export const ZH_TIMELINE = {
3
+ "frames": "帧",
4
+ "Agent draft beats": "智能体草稿节拍",
5
+ "The agent's newest draft, not installed yet": "智能体的最新草稿,尚未应用",
6
+ "Compiled take": "已编译的动作片段",
7
+ "Drag to trim · right-click restores the full take": "拖动裁剪 · 右键恢复完整片段",
8
+ "Trim take start": "裁剪片段起点",
9
+ "Retime segment by stretch": "拖动拉伸调整片段速度",
10
+ "Drag — wider is slower, narrower is faster": "拖动 — 拉长变慢,缩短变快",
11
+ "Trim take end": "裁剪片段终点",
12
+ "Cut the Full-Body clip at the playhead": "在播放头处切分全身片段",
13
+ "Cut": "切分",
14
+ "Selected Full-Body segment speed": "所选全身片段的速度",
15
+ "Selected Full-Body segment speed value": "所选全身片段的速度值",
16
+ "Body": "身体",
17
+ "Root path": "根路径",
18
+ "Camera Cuts": "摄影机剪辑",
19
+ "Crane height: click to add, click a point to select, drag vertically to change height": "摇臂高度:点击添加,点击点选中,上下拖动改变高度",
20
+ "average": "平均",
21
+ "Pin the instant at the playhead: the spot being walked then never moves again": "固定播放头这一刻:此时正经过的位置此后不再移动",
22
+ "Cut at playhead": "在播放头处切分",
23
+ "Back to constant speed — clears the curve and every cut": "恢复匀速 — 清除曲线和所有切分",
24
+ "Reset curve": "重置曲线",
25
+ "Drag the curve · double-click or the button cuts · Delete removes a cut": "拖动曲线 · 双击或按按钮切分 · Delete 删除切分",
26
+ "Cut: click to select, Delete removes it": "切分:点击选中,Delete 删除",
27
+ "PROP": "道具",
28
+ "Drawing…": "绘制中…",
29
+ "Redraw path": "重绘路径",
30
+ "Draw path": "绘制路径",
31
+ "Metres per second; 0 spreads the route across the whole take": "米/秒;为 0 时在整个片段内均匀走完",
32
+ "Speed": "速度",
33
+ "Travel speed": "移动速度",
34
+ "fills take": "铺满片段",
35
+ "Turn to face the direction of travel": "转向行进方向",
36
+ "Faces travel": "朝行进方向",
37
+ "Fixed facing": "固定朝向",
38
+ "Keep going in the last direction after the route ends": "路径结束后沿最后方向继续前进",
39
+ "Keep going": "继续前进",
40
+ "Loop": "循环",
41
+ "Delete this route; the object stands still again": "删除此路径;物体将重新静止",
42
+ "Delete path": "删除路径",
43
+ "Draw a route on the Top-View map to make this prop travel.": "在顶视图地图上绘制路径,让此道具移动。",
44
+ "Dots": "点",
45
+ "Add a dot where the prop is on this frame": "在当前帧道具所在位置添加一个点",
46
+ "No room for a dot here: the route is full, or the playhead is on a dot or an end": "此处无法添加点:路径已满,或播放头位于某个点或端点",
47
+ "Add dot at playhead": "在播放头处添加点",
48
+ "Ends are always level; select a dot between them to lean the prop there": "两端始终水平;选中中间的点可让道具在该处倾斜",
49
+ "Bank": "侧倾",
50
+ "Roll about the direction of travel; positive leans onto the right side": "绕行进方向滚转;正值向右侧倾",
51
+ "Pitch": "俯仰",
52
+ "Nose up (positive) or down; the prop holds this angle at the dot": "抬头(正值)或低头;道具在该点保持此角度",
53
+ "Eases back to level at the next dot · E shows rings on the dot": "在下一个点缓动回水平 · 按 E 在点上显示圆环",
54
+ "Ends stay level": "两端保持水平",
55
+ "Select a dot to lean the prop there": "选中一个点让道具在该处倾斜",
56
+ "CAMERA": "摄影机",
57
+ "Turn Waypoint off to edit or preview this camera.": "关闭路径点后才能编辑或预览此摄影机。",
58
+ "Turn Waypoint off": "关闭路径点",
59
+ "Stop": "停止",
60
+ "Preview": "预览",
61
+ "Redraw rail": "重绘轨道",
62
+ "Draw rail": "绘制轨道",
63
+ "Shot curve": "镜头曲线",
64
+ "Draw dolly speed in the Shot box": "在镜头栏中绘制推轨速度",
65
+ "Draw crane height in the Shot box": "在镜头栏中绘制摇臂高度",
66
+ "Height": "高度",
67
+ "Pin the instant at the playhead: the spot being passed then never moves again": "固定播放头这一刻:此时经过的位置此后不再移动",
68
+ "Read automatically from the camera position": "从摄影机位置自动读取",
69
+ "Distance": "距离",
70
+ "Cap dolly travel speed": "限制推轨的最高移动速度",
71
+ "Read automatically from the camera tilt": "从摄影机俯仰自动读取",
72
+ "Lens height of the selected crane point — click a purple dot in the scene to pick one, double-click the lifted curve to add one": "所选摇臂点的镜头高度 — 点击场景中的紫点选择,双击抬起的曲线可添加",
73
+ "Point height": "点高度",
74
+ "Crane points on this rail — click the Shot block's key strip to add one": "此轨道上的摇臂点 — 点击镜头栏的关键帧条添加",
75
+ " pts": " 点",
76
+ "Remove the selected interior crane point": "删除所选中间摇臂点",
77
+ "Remove point": "删除点",
78
+ "Advanced": "高级",
79
+ "Delete this Shot's rail geometry and return to Follow": "删除此镜头的轨道几何并返回跟随模式",
80
+ "Delete rail": "删除轨道",
81
+ "Keep the camera at the captured distance from the subject": "让摄影机与角色保持捕获时的距离",
82
+ "Follow On": "跟随开",
83
+ "Follow Off": "跟随关",
84
+ "Choose whether the dolly starts at the rail head or the nearest useful point": "选择推轨从轨道起点还是最近的有效点出发",
85
+ "Head start": "起点出发",
86
+ "Nearest": "最近点",
87
+ "Set how softly the rig catches up": "设置摄影机跟随的柔和程度",
88
+ "Damping": "阻尼",
89
+ "Aim ahead of subject travel": "朝角色行进方向预瞄",
90
+ "Look-ahead": "预瞄",
91
+ "Dolly on rail": "轨道推轨",
92
+ "Camera preview": "摄影机预览",
93
+ "Animation timeline": "动画时间线",
94
+ "Sequencer": "序列器",
95
+ "Playback transport": "播放控制",
96
+ "Previous frame": "上一帧",
97
+ "Previous frame (k)": "上一帧 (k)",
98
+ "Pause playback": "暂停播放",
99
+ "Play playback": "开始播放",
100
+ "Play / pause (Space)": "播放/暂停 (Space)",
101
+ "Next frame": "下一帧",
102
+ "Next frame (j)": "下一帧 (j)",
103
+ "Sequencer actions": "序列器操作",
104
+ "Split the selected Camera Cut at the playhead": "在播放头处切分所选摄影机剪辑",
105
+ "Add or select a shot first — the rail belongs to a shot": "请先添加或选择镜头 — 轨道属于镜头",
106
+ "Draw or redraw the selected camera rail": "绘制或重绘所选摄影机轨道",
107
+ "Add a Camera Cut track block": "添加摄影机剪辑轨道块",
108
+ "+ Track": "+ 轨道",
109
+ "Timeline view tools": "时间线视图工具",
110
+ "Timeline zoom": "时间线缩放",
111
+ "Two-finger up/down over FRAME ruler to zoom — click to reset to 1×": "在帧标尺上双指上下滚动缩放 — 点击重置为 1×",
112
+ "Root path mode": "根路径模式",
113
+ "Enable or disable root path constraints (P)": "开启或关闭根路径约束 (P)",
114
+ "Pose correction tools": "姿势修正工具",
115
+ "Load a rig to edit poses": "加载骨骼后才能编辑姿势",
116
+ "Foot snap": "脚部吸附",
117
+ "Foot snap — keep the feet planted while you move the body (hips); the knees bend instead of the feet sinking through the floor": "脚部吸附 — 移动身体(髋部)时双脚保持踩地;膝盖弯曲而不是脚陷进地面",
118
+ "Body contact": "身体接触",
119
+ "Body contact — keep hands, knees, feet, head, and hips above the floor": "身体接触 — 让手、膝、脚、头和髋部都不低于地面",
120
+ "Motion controls": "动作控制",
121
+ "Clear loaded motion": "清除已加载的动作",
122
+ "Clear the compiled take, then restore the blocking pose": "清除已编译的动作片段,然后恢复摆位姿势",
123
+ "Clear motion": "清除动作",
124
+ "Click the set floor in the Shot view to drop waypoints": "在镜头视图中点击布景地面放置路径点",
125
+ "Collapse timeline": "折叠时间线",
126
+ "Scrub timeline": "浏览时间线",
127
+ "Pins": "固定",
128
+ "IK keys": "IK 键",
129
+ "Rail": "轨道",
130
+ "Crane": "摇臂",
131
+ "Add a 2 second shot without changing existing shots": "添加一个 2 秒镜头,不改变现有镜头",
132
+ "+ Add shot": "+ 添加镜头",
133
+ "No shots yet — use + Add shot in the lane header to create one.": "还没有镜头 — 点击轨道标题栏的 + 添加镜头创建。",
134
+ "s": "秒",
135
+ "Split at the playhead": "在播放头处切分",
136
+ "Split": "切分",
137
+ "Duplicate shot": "复制镜头",
138
+ "Delete shot and leave free-camera time": "删除镜头并留出自由摄影机时间",
139
+ "Crane height — drag a point, click empty time to add one": "摇臂高度 — 拖动点,点击空白时间添加",
140
+ "Dolly speed — drag the line; cuts and reset live in the camera bar above": "推轨速度 — 拖动线条;切分和重置在上方摄影机栏",
141
+ "Click the crane graph to add a point; click a point and drag it to change height": "点击摇臂曲线添加点;点击点并拖动改变高度",
142
+ "Click at a frame to store the current camera framing": "在某一帧点击以保存当前摄影机取景",
143
+ "Click the set floor in the Shot view to add waypoints": "在镜头视图中点击布景地面添加路径点",
144
+ "Expand timeline": "展开时间线",
145
+ };
@@ -0,0 +1,104 @@
1
+ // Chinese UI copy for the agent panel, task board and turn status.
2
+ export const ZH_WORKFLOW = {
3
+ "Agent server is not running": "智能体服务器未运行",
4
+ "Checking…": "检查中…",
5
+ "helper": "助手",
6
+ "advisor": "顾问",
7
+ "vision": "视觉",
8
+ "blocker": "阻断",
9
+ "concern": "顾虑",
10
+ "note": "备注",
11
+ "done": "完成",
12
+ "partial": "部分完成",
13
+ "missing": "缺失",
14
+ "Supervisor review unavailable": "监督审查不可用",
15
+ "Supervisor": "监督",
16
+ "Less": "收起",
17
+ "Autofix is on: the blocker fixes would be applied as written": "自动修复已开启:阻断项修复将按原样应用",
18
+ "Apply fixes as next prompt": "将修复作为下一轮提示",
19
+ "Send as next prompt": "作为下一轮发送",
20
+ "Sign-in could not start.": "无法开始登录。",
21
+ "The agent server is not running": "智能体服务器未运行",
22
+ "Task mode": "任务模式",
23
+ "Tasks": "任务",
24
+ "in progress": "进行中",
25
+ "next": "下一步",
26
+ "not finished": "未完成",
27
+ "Plan": "计划",
28
+ "Close image": "关闭图片",
29
+ "Image the agent checked": "智能体查看过的图片",
30
+ "Installed": "已安装",
31
+ "Not installed yet": "尚未安装",
32
+ "Not installed": "未安装",
33
+ "Goals": "目标",
34
+ "met": "已达成",
35
+ "missed": "未达成",
36
+ "Largest issues": "最严重的问题",
37
+ "What the agent looked at": "智能体查看的内容",
38
+ "running": "运行中",
39
+ "failed": "失败",
40
+ "stopped": "已停止",
41
+ "The turn failed.": "本轮失败。",
42
+ "Compile waits for a free worker": "编译正在等待空闲工作进程",
43
+ "need a look": "需要查看",
44
+ "waiting": "等待中",
45
+ "No steps yet.": "暂无步骤。",
46
+ "Sent to this task.": "已发送到此任务。",
47
+ "Chat": "对话",
48
+ "Cancelled; its changes go back.": "已取消;改动已回滚。",
49
+ "Undone.": "已撤销。",
50
+ "Undo task": "撤销任务",
51
+ "Touches": "涉及",
52
+ "Tell this task…": "告诉此任务…",
53
+ "Tell this task": "告诉此任务",
54
+ "Block a two-shot": "摆一个双人镜头",
55
+ "Frame the selected character": "给所选角色取景",
56
+ "Light the set warmer": "把布光调暖",
57
+ "Read the scene": "读取场景",
58
+ "Selection and view": "选择与视图",
59
+ "Arrange objects": "摆放物体",
60
+ "Arrange characters": "摆放角色",
61
+ "Edit properties": "编辑属性",
62
+ "Frame the shot": "取景镜头",
63
+ "Generate motion": "生成动作",
64
+ "Verify the result": "验证结果",
65
+ "Undo an edit": "撤销编辑",
66
+ "Run an editor action": "执行编辑器操作",
67
+ "Direct the scene": "导演场景",
68
+ "Ask the agent to block, frame or animate…": "让智能体摆位、取景或做动画…",
69
+ "Supervisor review — please address:": "监督审查 — 请处理:",
70
+ "Supervisor review — apply these fixes exactly, in order, in ONE script call, then verify_result; do not re-derive them:": "监督审查 — 请按顺序在一次 script 调用中完全照此修复,然后 verify_result;不要重新推导:",
71
+ "Also check:": "同时检查:",
72
+ "Script": "脚本",
73
+ "cameras cross the line": "摄影机越轴",
74
+ "motion checked": "动作已检查",
75
+ "motion unverified": "动作未验证",
76
+ "looked at a contact sheet": "查看了缩略图总览",
77
+ "looked at the frame": "查看了画面",
78
+ "stale evidence": "过期的证据",
79
+ "not applied": "未应用",
80
+ "result unknown": "结果未知",
81
+ " · its edits were rolled back": " · 其编辑已回滚",
82
+ "started": "已开始",
83
+ "Working": "处理中",
84
+ "Stopped": "已停止",
85
+ "Done": "完成",
86
+ "Changed the scene": "修改了场景",
87
+ "Stopped by you. What already ran stays in the scene; Cmd+Z undoes this turn.": "你已停止。已执行的操作会留在场景中;按 Cmd+Z 可撤销这一轮。",
88
+ "Stopped by you while a step was running; its result is unknown.": "你在某一步运行时停止了;该步骤结果未知。",
89
+ "Stopped by you before anything changed in the scene.": "你在场景发生任何改动前停止了。",
90
+ "Queued": "排队中",
91
+ "Waiting for scope": "等待占用范围",
92
+ "Starting": "启动中",
93
+ "Running": "运行中",
94
+ "Paused: Studio lost": "已暂停:Studio 已断开",
95
+ "Being checked": "检查中",
96
+ "Starts when another task frees its part": "等其他任务释放该部分后开始",
97
+ "Working…": "处理中…",
98
+ "timeline": "时间线",
99
+ "stage": "舞台",
100
+ "scene": "场景",
101
+ "object": "物体",
102
+ "character": "角色",
103
+ "shot": "镜头",
104
+ };
@@ -0,0 +1,190 @@
1
+ // Chinese UI copy for the asset pane, project browser, range pins and result modal.
2
+ export const ZH_WORKSPACE = {
3
+ "Capsule figure": "胶囊人物",
4
+ "(unreadable image)": "(无法读取的图片)",
5
+ "Untitled model": "未命名模型",
6
+ "Untitled image": "未命名图片",
7
+ "Model": "模型",
8
+ "HDR environment": "HDR 环境",
9
+ "Matte": "遮罩",
10
+ "Image": "图像",
11
+ "Loading details…": "正在加载详情…",
12
+ "Unused by every scene. Delete it?": "所有场景都未使用。删除吗?",
13
+ "Delete": "删除",
14
+ "Cancel": "取消",
15
+ "Deleting…": "正在删除…",
16
+ "Manage storage": "管理存储",
17
+ "Review unused and in-use stored images. Deleted images can be restored until this page is reloaded.": "查看未使用和正在使用的存储图片。删除的图片可在刷新页面前恢复。",
18
+ "No stored image assets.": "没有存储的图片资源。",
19
+ "Unused": "未使用",
20
+ "No unused image assets. Every stored image is still used by a scene.": "没有未使用的图片资源。所有存储图片仍被场景使用。",
21
+ "In use": "使用中",
22
+ "No stored image assets are used by a scene.": "没有被场景使用的存储图片资源。",
23
+ "Unreadable": "无法读取",
24
+ "Basic Shapes": "基础形状",
25
+ "Characters": "角色",
26
+ "Props": "道具",
27
+ "Cameras": "摄影机",
28
+ "Motions": "动作",
29
+ "Poses": "姿势",
30
+ "Shape": "形状",
31
+ "Character": "角色",
32
+ "No imported props yet. Drop or paste a picture, or drop a .glb, .obj or .fbx into the studio.": "还没有导入的道具。拖放或粘贴图片,或将 .glb、.obj、.fbx 文件拖入工作室。",
33
+ "Camera": "摄影机",
34
+ "No shot cameras yet. Use + Add › Camera in the viewport.": "还没有镜头摄影机。使用视口中的 + 添加 › 摄影机。",
35
+ "Motion": "动作",
36
+ "No takes for this character yet. Ask the agent to compile one.": "此角色还没有动作片段。请让智能体编译一个。",
37
+ "Pose": "姿势",
38
+ "No saved poses yet. Save one from the pose studio.": "还没有保存的姿势。请在姿势工作室中保存。",
39
+ "Nothing matches this search.": "没有匹配此搜索的结果。",
40
+ "Note": "通知",
41
+ "Generate": "生成",
42
+ "Export": "导出",
43
+ "Info": "信息",
44
+ "No events yet this session. Notices, generation jobs and exports appear here.": "本次会话还没有事件。通知、生成任务和导出会显示在这里。",
45
+ "Content": "内容",
46
+ "Expand content": "展开内容",
47
+ "Collapse content": "折叠内容",
48
+ "Expand Content": "展开内容",
49
+ "Collapse Content — the Sequencer takes the full width": "折叠内容 — 序列器将占满宽度",
50
+ "Content and Log": "内容与日志",
51
+ "Log": "日志",
52
+ "Folder": "文件夹",
53
+ "Location": "位置",
54
+ "This session": "本次会话",
55
+ "Search objects": "搜索物体",
56
+ "Search": "搜索",
57
+ "Search the log": "搜索日志",
58
+ "Folders": "文件夹",
59
+ "Drag into the scene to place": "拖入场景以放置",
60
+ "New project": "新建项目",
61
+ "Start from an empty stage.": "从空舞台开始。",
62
+ "Storyboard": "故事板",
63
+ "Describe scenes in words and direct one panel at a time. Output: image references.": "用文字描述场景,逐个面板进行导演。产出:图像参考。",
64
+ "Animation": "动画",
65
+ "Block and shoot continuous motion. Output: clip references.": "摆位并拍摄连续动作。产出:片段参考。",
66
+ "Recently": "最近",
67
+ "Project name": "项目名称",
68
+ "Create": "创建",
69
+ "Enter a project name to create a project": "输入项目名称以创建项目",
70
+ "Browser project store": "浏览器项目存储",
71
+ "Recent": "最近",
72
+ "Learn": "了解",
73
+ "Choose project": "选择项目",
74
+ "Project navigation": "项目导航",
75
+ "Project mode": "项目模式",
76
+ "Block a shot in three moves": "三步完成镜头摆位",
77
+ "Choose a template, name the project, then place a character and frame the camera in the editor.": "选择模板、命名项目,然后在编辑器中放置角色并为摄影机取景。",
78
+ "Start a new project": "开始新项目",
79
+ "Recent projects": "最近项目",
80
+ "Refresh": "刷新",
81
+ "Open project file…": "打开项目文件…",
82
+ "Open project": "打开项目",
83
+ "A saved .cclayproject file": "已保存的 .cclayproject 文件",
84
+ "No recent projects in this browser yet. ": "此浏览器中还没有最近项目。",
85
+ "Open a project file…": "打开项目文件…",
86
+ "Project templates": "项目模板",
87
+ "Start from an empty stage": "从空舞台开始",
88
+ "Project preview": "项目预览",
89
+ "Choose the project storage folder": "选择项目存储文件夹",
90
+ "Projects are stored in this browser": "项目存储在此浏览器中",
91
+ "Frame rate": "帧率",
92
+ "Units": "单位",
93
+ "Folder access needs to be re-allowed.": "需要重新允许文件夹访问。",
94
+ "Using the browser project store.": "正在使用浏览器项目存储。",
95
+ "Re-allow": "重新允许",
96
+ "Create project": "创建项目",
97
+ "No recent projects yet.": "还没有最近项目。",
98
+ " shots": " 个镜头",
99
+ "Remove from list": "从列表中移除",
100
+ "Missing object": "缺少物体",
101
+ "Load a motion before creating a pin.": "创建固定前请先加载动作。",
102
+ "Choose a hand or foot.": "请选择手或脚。",
103
+ "In and Out must be whole frames.": "In 和 Out 必须是整数帧。",
104
+ "Out must be on or after In.": "Out 必须不早于 In。",
105
+ "Choose a scene object.": "请选择场景物体。",
106
+ "That scene object is no longer available.": "该场景物体已不可用。",
107
+ "Blend must be at least one frame.": "混合必须至少为一帧。",
108
+ "Hold at In": "固定在 In",
109
+ "Range pins": "区间固定",
110
+ "Hold a hand or foot across a frame range": "在帧区间内固定手或脚",
111
+ "New": "新建",
112
+ "Load a motion to enable range pins.": "加载动作后才能使用区间固定。",
113
+ "Part": "部位",
114
+ "Pin part": "固定部位",
115
+ "Range": "区间",
116
+ "In": "起点",
117
+ "Out": "终点",
118
+ "Set In to playhead": "将 In 设为播放头",
119
+ "Set Out to playhead": "将 Out 设为播放头",
120
+ "Keyboard: I sets In · O sets Out": "键盘:I 设置 In · O 设置 Out",
121
+ "Target": "目标",
122
+ "Pin target": "固定目标",
123
+ "Hold where it is at In": "固定在 In 时的位置",
124
+ "Capture the part's world position at the In frame.": "记录该部位在 In 帧的世界坐标。",
125
+ "Stick to object": "附着到物体",
126
+ "Follow a scene object's transform and travel path.": "跟随场景物体的变换和移动路径。",
127
+ "Add a scene object to use an object target.": "添加场景物体后才能使用物体目标。",
128
+ "Scene object": "场景物体",
129
+ "Choose an object": "选择物体",
130
+ "Following": "跟随对象",
131
+ "Body follows when out of reach": "够不到时身体跟随",
132
+ "Blend": "混合",
133
+ "Replace confirmed": "已确认替换",
134
+ "Replace existing keys": "替换现有键",
135
+ "Update pin": "更新固定",
136
+ "Apply pin": "应用固定",
137
+ "No pins yet. Choose a part and apply a range.": "还没有固定。选择部位并应用区间。",
138
+ "Missing object target": "缺少物体目标",
139
+ "The referenced object was deleted.": "引用的物体已被删除。",
140
+ "Invalid target": "无效目标",
141
+ "Delete pin": "删除固定",
142
+ "Workflow output": "工作流输出",
143
+ "In project file": "项目文件内",
144
+ "External URL": "外部 URL",
145
+ "Browser-only copy": "仅浏览器副本",
146
+ "Missing": "缺失",
147
+ "Project resources": "项目资源",
148
+ "Size": "大小",
149
+ "Missing resources": "缺失资源",
150
+ "The project file would be too large.": "项目文件会过大。",
151
+ "Unknown reason": "未知原因",
152
+ "The project was not saved": "项目未保存",
153
+ "OK": "确定",
154
+ "your AI video tool": "你的 AI 视频工具",
155
+ "your selected image model": "你选择的图像模型",
156
+ " as the start frame, and ": "作为起始帧,并将 ",
157
+ " as the end frame.": "作为结束帧。",
158
+ " as the reference frame.": "作为参考帧。",
159
+ " as the reference image.": "作为参考图像。",
160
+ "Your shot is ready": "你的镜头已就绪",
161
+ "Close the result": "关闭结果",
162
+ "Camera move start frame": "摄影机运动起始帧",
163
+ "Start": "开始",
164
+ "Camera move end frame": "摄影机运动结束帧",
165
+ "End": "结束",
166
+ "Finished scene frame": "完成场景帧",
167
+ "Camera move made from timeline keyframes": "由时间线关键帧生成的摄影机运动",
168
+ "Prompt": "提示词",
169
+ "copied": "已复制",
170
+ "Copied ✓": "已复制 ✓",
171
+ "Copy prompt": "复制提示词",
172
+ "Exported": "已导出",
173
+ "Export start and end frames": "导出起始和结束帧",
174
+ "Export frame": "导出帧",
175
+ "Handing off to your AI": "交付给 AI 的步骤",
176
+ "Copy the prompt": "复制提示词",
177
+ "Copied. Paste it into your AI service's prompt box.": "已复制。粘贴到 AI 服务的提示框中。",
178
+ "Press “Copy prompt” above, then paste it into your AI service.": "点击上方的“复制提示词”,然后粘贴到 AI 服务中。",
179
+ "Export the frame": "导出帧",
180
+ "Press “Export start and end frames” to save both PNGs.": "点击“导出起始和结束帧”保存两个 PNG。",
181
+ "Press “Export frame” to save the PNG.": "点击“导出帧”保存 PNG。",
182
+ "This result has no frame to export.": "此结果没有可导出的帧。",
183
+ "Attach the image to your AI": "将图像附加到 AI",
184
+ "Use your AI service's image attach button to upload the start and end frames together.": "使用 AI 服务的图片附件按钮,同时上传起始帧和结束帧。",
185
+ "Use your AI service's image attach button to upload blocking-frame.png.": "使用 AI 服务的图片附件按钮上传 blocking-frame.png。",
186
+ "The prompt describes the scene's content and mood; the frame shows the camera framing. Use both to reproduce this scene as closely as possible.": "提示词描述场景的内容和氛围;帧展示摄影机取景。两者结合才能尽量还原此场景。",
187
+ "Optional · Reference video": "可选 · 参考视频",
188
+ "A video recorded with the recording feature: ": "使用录制功能生成的 ",
189
+ " file. Use it separately from the reference frame in the prompt above.": "文件。请与上方提示词中的参考帧分开使用。",
190
+ };
@@ -0,0 +1,21 @@
1
+ // Simplified Chinese dictionary, keyed by the English default passed to ko().
2
+ // Split by area so no file crosses the 250-line ratchet; assembled here.
3
+ import { ZH_APP } from "./locale-zh/app.js";
4
+ import { ZH_DOMAINS } from "./locale-zh/domains.js";
5
+ import { ZH_LABELS } from "./locale-zh/labels.js";
6
+ import { ZH_PANELS } from "./locale-zh/panels.js";
7
+ import { ZH_SHELL } from "./locale-zh/shell.js";
8
+ import { ZH_TIMELINE } from "./locale-zh/timeline.js";
9
+ import { ZH_WORKFLOW } from "./locale-zh/workflow.js";
10
+ import { ZH_WORKSPACE } from "./locale-zh/workspace.js";
11
+
12
+ export const ZH = Object.freeze({
13
+ ...ZH_APP,
14
+ ...ZH_DOMAINS,
15
+ ...ZH_LABELS,
16
+ ...ZH_PANELS,
17
+ ...ZH_SHELL,
18
+ ...ZH_TIMELINE,
19
+ ...ZH_WORKFLOW,
20
+ ...ZH_WORKSPACE,
21
+ });
package/src/locale.js CHANGED
@@ -1,15 +1,22 @@
1
- // CozyClay UI locale. English is the default; Korean is opt-in.
1
+ // CozyClay UI locale. English is the default; Korean and Chinese are opt-in.
2
2
  //
3
3
  // An explicit choice saved in localStorage wins. Without one, the UI always
4
4
  // starts in English regardless of browser or operating-system language.
5
5
  // The locale is fixed for the lifetime of the page — every label goes through
6
6
  // ko() at render time, so switching saves the choice and reloads.
7
+ //
8
+ // Chinese lives in a central dictionary (locale-zh.js) keyed by the English
9
+ // default, so the ~1500 call sites stay unchanged upstream. A third argument
10
+ // overrides the dictionary where one English string needs two translations.
11
+ import { ZH } from "./locale-zh.js";
12
+
7
13
  const KEY = "cozyclay.locale";
14
+ const LOCALES = new Set(["en", "ko", "zh"]);
8
15
 
9
16
  function stored() {
10
17
  try {
11
18
  const value = localStorage.getItem(KEY);
12
- return value === "ko" || value === "en" ? value : null;
19
+ return LOCALES.has(value) ? value : null;
13
20
  } catch {
14
21
  return null;
15
22
  }
@@ -17,18 +24,21 @@ function stored() {
17
24
 
18
25
  export const LOCALE = stored() ?? "en";
19
26
  export const isKo = LOCALE === "ko";
27
+ export const isZh = LOCALE === "zh";
20
28
  // Whether the operator has ever picked a language. A Korean browser that has
21
29
  // not chosen yet still gets the English default, so the Settings trigger wears
22
30
  // "한국어" as its first-run cue until a choice is stored (#193).
23
31
  export const localeChosen = stored() !== null;
24
32
 
25
- /** Pick the label for the active locale: ko("Frame", "프레임"). */
26
- export function ko(en, koText) {
27
- return isKo ? koText : en;
33
+ /** Pick the label for the active locale: ko("Frame", "프레임", "帧"). */
34
+ export function ko(en, koText, zhText) {
35
+ if (isKo) return koText;
36
+ if (isZh) return zhText ?? ZH[en] ?? en;
37
+ return en;
28
38
  }
29
39
 
30
40
  export function setLocale(next) {
31
- if (next !== "ko" && next !== "en") return;
41
+ if (!LOCALES.has(next)) return;
32
42
  try {
33
43
  localStorage.setItem(KEY, next);
34
44
  } catch {
package/src/main.jsx CHANGED
@@ -16,7 +16,7 @@ import { preloadBodies } from "./motion3/body.js";
16
16
 
17
17
  registerPwa();
18
18
  void initAnalytics();
19
- document.documentElement.lang = LOCALE;
19
+ document.documentElement.lang = LOCALE === "zh" ? "zh-CN" : LOCALE;
20
20
  applyTheme(readTheme());
21
21
 
22
22
  async function boot() {
@@ -10,15 +10,15 @@ import { ko } from "../locale.js";
10
10
  const MAX_ROWS = 3;
11
11
 
12
12
  const LABELS = {
13
- walk: ["Walk", "걷기"], walkTo: ["Walk", "걷기"], keys: ["Keys", "키 포즈"], standIdle: ["Stand", "서 있기"],
14
- sitInSeat: ["Sit", "앉기"], exitCar: ["Exit car", "차에서 내리기"], reachGrip: ["Reach and grip", "뻗어 잡기"],
15
- smokeCigarette: ["Smoke", "담배"],
13
+ walk: ["Walk", "걷기", "行走"], walkTo: ["Walk", "걷기", "行走"], keys: ["Keys", "키 포즈", "关键姿势"], standIdle: ["Stand", "서 있기", "站立"],
14
+ sitInSeat: ["Sit", "앉기", "坐下"], exitCar: ["Exit car", "차에서 내리기", "下车"], reachGrip: ["Reach and grip", "뻗어 잡기", "伸手抓取"],
15
+ smokeCigarette: ["Smoke", "담배", "吸烟"],
16
16
  };
17
17
 
18
18
  /** A short, readable name for a beat (the timeline's skill or generator name). */
19
19
  export function beatLabel(beat) {
20
20
  const pair = LABELS[beat];
21
- return pair ? ko(pair[0], pair[1]) : String(beat ?? "");
21
+ return pair ? ko(pair[0], pair[1], pair[2]) : String(beat ?? "");
22
22
  }
23
23
 
24
24
  const overlaps = (a0, a1, b0, b1) => a0 <= b1 && b0 <= a1;
@@ -14,7 +14,7 @@ import "./beat-board.css";
14
14
  function stateChip(block) {
15
15
  const issues = block.issues ? `⚠ ${block.issues}` : "";
16
16
  if (!block.missed) return issues || "✓";
17
- const missed = ko(`goal ✗${block.missed > 1 ? ` ${block.missed}` : ""}`, `목표 ✗${block.missed > 1 ? ` ${block.missed}` : ""}`);
17
+ const missed = ko(`goal ✗${block.missed > 1 ? ` ${block.missed}` : ""}`, `목표 ✗${block.missed > 1 ? ` ${block.missed}` : ""}`, `目标 ✗${block.missed > 1 ? ` ${block.missed}` : ""}`);
18
18
  return issues ? `${missed} · ${issues}` : missed;
19
19
  }
20
20
 
@@ -0,0 +1,24 @@
1
+ /** Which IK control a viewport press grabs (posestudio IkHandles). */
2
+
3
+ /** The pick a press takes, from raycast hits sorted near to far. An exposed
4
+ * control beats one hidden behind the body, so a far-side joint never steals
5
+ * a press; a hidden one still answers when nothing exposed is under the
6
+ * pointer, since it is drawn. A visible part (sphere, arrow, ring) beats an
7
+ * arrow's invisible forgiving volume, so a ring is not lost to an arrow
8
+ * crossing it. Distance breaks ties. */
9
+ export function pickIkHit(hits, picks, handles) {
10
+ const pickOf = (hit) => picks.find((p) => p.mesh === hit.object);
11
+ const rank = (hit) => {
12
+ const pick = pickOf(hit);
13
+ if (!pick) return Infinity;
14
+ const hidden = handles[pick.track.id]?.userData.ikExposed === false;
15
+ return (hidden ? 2 : 0) + (/^hit-/.test(pick.part ?? "") ? 1 : 0);
16
+ };
17
+ let best = null;
18
+ let bestRank = Infinity;
19
+ for (const hit of hits) {
20
+ const value = rank(hit);
21
+ if (value < bestRank) [best, bestRank] = [hit, value];
22
+ }
23
+ return best ? pickOf(best) : null;
24
+ }
@@ -3,7 +3,7 @@
3
3
  // outer green grips to trim, right-click a segment to drop it. Each gesture previews here and
4
4
  // commits ONE timing command on release (commands/motion-timing.js), so each is one Undo.
5
5
  import { useRef, useState } from "react";
6
- import { isKo, ko } from "../locale.js";
6
+ import { ko } from "../locale.js";
7
7
  import { frameFromClientX } from "./timeline-coordinates.js";
8
8
  import { createTakeEdit, segmentSpeedForFrames, takeEditLayout, TRIM_MIN_FRAMES } from "../motion3/take-edit.js";
9
9
 
@@ -97,9 +97,9 @@ export function TakeSegmentClips({ motion, frame, frameCount, displayFrameCount,
97
97
  key={segment.id}
98
98
  className={"tl-motion-clip" + (preview?.kind === "trim" ? " trimming" : "") + (segment.previewSpeed !== undefined ? " retiming" : "") + (!single && selected?.id === segment.id ? " selected" : "")}
99
99
  style={{ "--tl-f-start": clipPct(Math.min(segment.timelineStart, displayFrameCount)), "--tl-f-end": clipPct(Math.min(segment.timelineEnd + 1, displayFrameCount)) }}
100
- title={segment.preview ? undefined : isKo
101
- ? `전신 구간 ${index + 1} — ${segment.speed}× · ${segment.timelineFrames}프레임. 재생 헤드에서 컷 · 주황 그립을 끌어 배속 조절 · 우클릭으로 구간 삭제`
102
- : `Full-Body segment ${index + 1} — ${segment.speed}× · ${segment.timelineFrames} frames. Cut at the playhead; drag the amber grip to retime; right-click to delete`}
100
+ title={segment.preview ? undefined : ko(`Full-Body segment ${index + 1} — ${segment.speed}× · ${segment.timelineFrames} frames. Cut at the playhead; drag the amber grip to retime; right-click to delete`,
101
+ `전신 구간 ${index + 1} — ${segment.speed}× · ${segment.timelineFrames}프레임. 재생 헤드에서 컷 · 주황 그립을 끌어 배속 조절 · 우클릭으로 구간 삭제`,
102
+ `全身片段 ${index + 1} — ${segment.speed}× · ${segment.timelineFrames} 帧。在播放头处切分;拖动橙色把手调整速度;右键删除`)}
103
103
  onContextMenu={segment.preview || single ? undefined : e => { e.preventDefault(); e.stopPropagation(); motion.remove(segment.id); }}
104
104
  >
105
105
  {index === 0 && grips && grip("start", ko("Trim take start", "테이크 시작점 자르기"), trimState("start"), trimReset)}
@@ -128,7 +128,7 @@ export function TakeSegmentTools({ motion, frame }) {
128
128
  </button>
129
129
  {selected && (
130
130
  <label className="tl-motion-speed-editor">
131
- <span>{ko(`Segment ${index + 1} speed`, `구간 ${index + 1} 배속`)}</span>
131
+ <span>{ko(`Segment ${index + 1} speed`, `구간 ${index + 1} 배속`, `片段 ${index + 1} 速度`)}</span>
132
132
  <input type="range" min="0.1" max="4" step="0.1" value={draft ?? selected.speed} aria-label={ko("Selected Full-Body segment speed", "선택한 전신 구간 배속")}
133
133
  onChange={e => setDraft(e.currentTarget.valueAsNumber)} onPointerUp={e => commit(e.currentTarget.valueAsNumber)} onKeyUp={e => commit(e.currentTarget.valueAsNumber)} />
134
134
  <input type="number" min="0.1" max="4" step="0.1" value={selected.speed} aria-label={ko("Selected Full-Body segment speed value", "선택한 전신 구간 배속 값")}