cozyclay 3.0.3 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/INSTALL_FOR_AGENTS.md +146 -0
  3. package/README.md +74 -192
  4. package/THIRD_PARTY_NOTICES.md +0 -21
  5. package/bin/agent/agent-routes.mjs +29 -62
  6. package/bin/agent/agent-runner.mjs +5 -3
  7. package/bin/agent/main-agent.mjs +70 -0
  8. package/bin/agent/main-prompt.mjs +17 -0
  9. package/bin/agent/plan-tool.mjs +114 -0
  10. package/bin/agent/script-tool.mjs +2 -0
  11. package/bin/agent/sculpt-sketch.mjs +190 -0
  12. package/bin/agent/session-store.mjs +4 -3
  13. package/bin/agent/sketch-code.mjs +151 -0
  14. package/bin/agent/sketch-sandbox.mjs +102 -0
  15. package/bin/agent/studio-prompt.mjs +1 -1
  16. package/bin/agent/studio-task-routes.mjs +185 -0
  17. package/bin/agent/studio-tasks.mjs +156 -0
  18. package/bin/agent/studio-tools.mjs +3 -2
  19. package/bin/agent/studio-turn-transaction.mjs +76 -0
  20. package/bin/agent/turn-frames.mjs +4 -0
  21. package/bin/cozyclay.mjs +5 -7
  22. package/dist/app/index.html +7 -6
  23. package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
  24. package/dist/assets/app-BlwOpest.css +1 -0
  25. package/dist/assets/app-DmR7jBkz.js +5144 -0
  26. package/dist/assets/compile-worker-D0pKioa0.js +5 -0
  27. package/dist/assets/landing-y3EqDgSZ.js +1 -0
  28. package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
  29. package/dist/assets/motion3-compile-BB3_1-EB.js +1 -0
  30. package/dist/assets/scene-asset-sync-CBx7XRti.css +1 -0
  31. package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
  32. package/dist/assets/task-board-ErQvfoaz.js +14 -0
  33. package/dist/assets/workflow-zQM-EKW1.js +179 -0
  34. package/dist/assets/x-bot-tpose-body-BusyR3eg.json +1 -0
  35. package/dist/assets/y-bot-tpose-body-Dy1VKChZ.json +1 -0
  36. package/dist/cozyclay-package.json +1 -1
  37. package/dist/greybox-to-video/index.html +2 -3
  38. package/dist/index.html +7 -57
  39. package/dist/llms.txt +2 -1
  40. package/dist/previs-software/index.html +2 -2
  41. package/dist/privacy/index.html +3 -7
  42. package/dist/seedance-camera-control/index.html +2 -2
  43. package/dist/sitemap.xml +5 -5
  44. package/dist/sw.js +5 -1
  45. package/dist/thumbs/blank-stage-dark.webp +0 -0
  46. package/dist/thumbs/blank-stage-light.webp +0 -0
  47. package/dist/workflow/index.html +5 -4
  48. package/mcp/mesh-file.mjs +3 -1
  49. package/mcp/server.mjs +3 -0
  50. package/mcp/tool-handlers.mjs +2 -2
  51. package/package.json +5 -3
  52. package/src/App.jsx +51 -282
  53. package/src/analytics.js +3 -148
  54. package/src/app-stage.jsx +1 -1
  55. package/src/asset-pane.jsx +5 -10
  56. package/src/auto-flat.js +10 -0
  57. package/src/bus-tasks.js +11 -5
  58. package/src/command-bus.js +1 -1
  59. package/src/commands/elements.js +4 -3
  60. package/src/commands/motion3-compile.js +51 -27
  61. package/src/commands/motion3.js +3 -3
  62. package/src/commands/objects.js +1 -1
  63. package/src/commands/project.js +1 -1
  64. package/src/commands/shot.js +16 -4
  65. package/src/controls.jsx +0 -11
  66. package/src/cuts.js +11 -0
  67. package/src/domains/cast.js +3 -2
  68. package/src/domains/motion.js +3 -10
  69. package/src/domains/playback.js +32 -0
  70. package/src/domains/scenes.js +3 -22
  71. package/src/domains/shots.js +5 -5
  72. package/src/dualview.jsx +11 -15
  73. package/src/hierarchy-panel.css +18 -4
  74. package/src/hierarchy-panel.jsx +12 -11
  75. package/src/kits/held.js +3 -2
  76. package/src/light-blockers.js +2 -2
  77. package/src/main.jsx +5 -0
  78. package/src/material-look.js +189 -0
  79. package/src/material-presets.js +78 -0
  80. package/src/material-textures.js +157 -0
  81. package/src/motion/ground.js +5 -14
  82. package/src/motion/playback-frame-store.js +79 -0
  83. package/src/motion/playback-probe.js +16 -0
  84. package/src/motion/playback-subject.js +7 -0
  85. package/src/motion/timeline.css +43 -5
  86. package/src/motion/timeline.jsx +4 -3
  87. package/src/motion3/body.js +37 -5
  88. package/src/motion3/carried.js +11 -3
  89. package/src/motion3/compile-job.js +1 -1
  90. package/src/motion3/compiler/compile.js +7 -1
  91. package/src/motion3/compiler/finger-close.js +137 -0
  92. package/src/motion3/compiler/limbs.js +18 -3
  93. package/src/motion3/compiler/pose.js +6 -0
  94. package/src/motion3/compiler/validate-objects.js +17 -2
  95. package/src/motion3/compiler/validate-parts.js +2 -1
  96. package/src/motion3/compiler/vocab.js +4 -4
  97. package/src/motion3/describe.js +2 -2
  98. package/src/motion3/fix/index.js +5 -3
  99. package/src/motion3/flight.js +38 -0
  100. package/src/motion3/free-motion.js +10 -7
  101. package/src/motion3/load.js +125 -0
  102. package/src/motion3/mass.js +23 -0
  103. package/src/motion3/object-keys.js +11 -6
  104. package/src/motion3/object-pose.js +11 -3
  105. package/src/motion3/pipeline.js +3 -0
  106. package/src/motion3/qa/explain.js +3 -1
  107. package/src/motion3/qa/grasp-checks.js +123 -0
  108. package/src/motion3/qa/hints-grasp.js +71 -0
  109. package/src/motion3/qa/index.js +3 -1
  110. package/src/motion3/qa/object-checks.js +7 -2
  111. package/src/motion3/qa/patch.js +2 -1
  112. package/src/motion3/qa/spec-checks.js +10 -1
  113. package/src/motion3/schema/fields.js +3 -3
  114. package/src/motion3/schema/topics.js +9 -8
  115. package/src/motion3/schema/types.js +4 -1
  116. package/src/motion3/seat.js +84 -0
  117. package/src/motion3/settle.js +11 -6
  118. package/src/motion3/shapes.js +70 -0
  119. package/src/motion3/skills/catch.js +71 -0
  120. package/src/motion3/skills/index.js +10 -3
  121. package/src/motion3/skills/info.js +13 -0
  122. package/src/motion3/skills/throw.js +72 -0
  123. package/src/object-catalog.jsx +0 -4
  124. package/src/object-gizmo.jsx +2 -1
  125. package/src/object-look.js +9 -5
  126. package/src/object-path.js +1 -1
  127. package/src/panels/ProjectPanel.jsx +42 -21
  128. package/src/panels/details.css +15 -4
  129. package/src/popover.js +37 -0
  130. package/src/posestudio.jsx +15 -10
  131. package/src/primitive-look.js +38 -0
  132. package/src/project-browser-art.jsx +19 -0
  133. package/src/project-browser.css +74 -121
  134. package/src/project-browser.jsx +36 -60
  135. package/src/props.jsx +24 -35
  136. package/src/render-passes.js +7 -1
  137. package/src/retired-kinds.js +37 -0
  138. package/src/scene-objects.js +5 -9
  139. package/src/scenes.js +4 -5
  140. package/src/sculpt-colours.js +65 -0
  141. package/src/sculpt-compare.js +13 -50
  142. package/src/sculpt-csg.js +112 -0
  143. package/src/sculpt-curve.js +123 -0
  144. package/src/sculpt-cutters.js +43 -0
  145. package/src/sculpt-detail.js +207 -0
  146. package/src/sculpt-geometry-loft-convex.js +100 -0
  147. package/src/sculpt-geometry-shapes.js +250 -0
  148. package/src/sculpt-geometry.js +33 -15
  149. package/src/sculpt-loft-rings.js +225 -0
  150. package/src/sculpt-noise.js +65 -0
  151. package/src/sculpt-part-fields.js +27 -0
  152. package/src/sculpt-recipe-read.js +65 -0
  153. package/src/sculpt-recipe.js +33 -75
  154. package/src/sculpt-run.js +1 -1
  155. package/src/sculpt-scatter-items.js +183 -0
  156. package/src/sculpt-scatter-shape.js +57 -0
  157. package/src/sculpt-scatter.js +124 -0
  158. package/src/sculpt-shapes-curved.js +211 -0
  159. package/src/sculpt-shapes-loft-convex.js +218 -0
  160. package/src/sculpt-shapes.js +138 -0
  161. package/src/sculpt-sketch-state.js +76 -0
  162. package/src/sculpt-sketch.jsx +124 -0
  163. package/src/sculpt-surface-fields.js +65 -0
  164. package/src/settings-menu.jsx +1 -20
  165. package/src/shell/BottomDock.jsx +9 -9
  166. package/src/shell/MenuBar.jsx +1 -9
  167. package/src/shell/PreferencesDialog.jsx +3 -4
  168. package/src/shell/ViewportToolbar.jsx +3 -3
  169. package/src/shell/add-camera-shot.js +11 -0
  170. package/src/shell/glass-regions.css +3 -2
  171. package/src/shell/glass.css +0 -5
  172. package/src/shell/lens-scrub-math.js +51 -0
  173. package/src/shell/lens-scrub.css +73 -0
  174. package/src/shell/lens-scrub.jsx +98 -0
  175. package/src/shell/mode.css +6 -5
  176. package/src/shell/preferences.css +1 -32
  177. package/src/shell/shell.css +18 -1
  178. package/src/shell/studio-shell-context.js +5 -0
  179. package/src/shot-frame.js +26 -0
  180. package/src/stage-canvas-rigs.jsx +7 -8
  181. package/src/stage-characters.jsx +26 -0
  182. package/src/stage-gizmo.js +43 -0
  183. package/src/stage-labels-ko.js +0 -6
  184. package/src/stage-startup-defaults.js +3 -9
  185. package/src/starter-scenes.js +5 -11
  186. package/src/studio-agent-commands.js +2 -2
  187. package/src/studio-agent-protocol.js +2 -7
  188. package/src/studio-app-binding.js +1 -1
  189. package/src/studio-elements.js +6 -2
  190. package/src/styles/themes.css +13 -0
  191. package/src/styles.css +6 -674
  192. package/src/text/text-object.jsx +1 -1
  193. package/src/ui.jsx +8 -16
  194. package/src/use-embed-bridge.js +3 -6
  195. package/src/use-render-activity.js +3 -1
  196. package/src/view-ghosts.jsx +5 -3
  197. package/src/viewport-visible-rect.js +37 -0
  198. package/src/workflow/AgentPanel.jsx +79 -46
  199. package/src/workflow/AgentPlanCard.jsx +41 -0
  200. package/src/workflow/AgentProgressCard.jsx +1 -1
  201. package/src/workflow/TaskBoard.jsx +102 -0
  202. package/src/workflow/TaskInline.jsx +114 -0
  203. package/src/workflow/agent-client.js +25 -36
  204. package/src/workflow/agent-panel.css +14 -1
  205. package/src/workflow/agent-plan.css +93 -0
  206. package/src/workflow/agent-plan.js +59 -0
  207. package/src/workflow/attachment-image.js +10 -3
  208. package/src/workflow/attachment-video.js +91 -0
  209. package/src/workflow/task-board.css +98 -0
  210. package/src/workflow/task-board.js +169 -0
  211. package/tools/check-eslint.mjs +2 -1
  212. package/tools/check-knip.mjs +2 -1
  213. package/tools/dev/pages/greybox-to-video.html +2 -3
  214. package/tools/dev/pages/previs-software.html +2 -2
  215. package/tools/dev/pages/privacy.html +3 -7
  216. package/tools/dev/pages/seedance-camera-control.html +2 -2
  217. package/tools/dev-full.mjs +11 -2
  218. package/tools/eslint-baseline.json +6 -6
  219. package/tools/knip-baseline.json +0 -11
  220. package/tools/loc-baseline.json +30 -31
  221. package/tools/metrics-snapshot.mjs +43 -5
  222. package/tools/render-start-thumbs.mjs +175 -0
  223. package/tools/run-tests.mjs +10 -17
  224. package/tools/sitemap.mjs +1 -1
  225. package/dist/assets/analytics-BibyE5ar.js +0 -6
  226. package/dist/assets/app-DhRJ-PBz.js +0 -5088
  227. package/dist/assets/app-f-fkV5uP.css +0 -1
  228. package/dist/assets/compile-worker-DqiA4AaN.js +0 -5
  229. package/dist/assets/first-shot-handoff-Qx7SKl38.js +0 -1
  230. package/dist/assets/landing-DYg87MDY.js +0 -1
  231. package/dist/assets/scene-asset-sync-DBYQBR0T.js +0 -20
  232. package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
  233. package/dist/assets/workflow-BSA22Ofr.js +0 -179
  234. package/dist/scenes/city-block.cclayproject +0 -1
  235. package/src/burn-in.js +0 -91
  236. package/src/camera-tutorial.jsx +0 -520
  237. package/src/facing-marks.js +0 -88
  238. package/src/first-shot-handoff.js +0 -44
  239. package/src/panels/motion.css +0 -197
  240. package/src/set-pieces.jsx +0 -183
  241. package/src/tutorial-analytics.js +0 -86
  242. package/src/tutorial-walk.js +0 -17
  243. package/src/use-camera-tutorial.js +0 -50
  244. package/src/use-semantic-state.js +0 -12
  245. package/tools/metrics/archive-traffic.mjs +0 -138
  246. package/tools/perf/ik-camera-drag.mjs +0 -138
@@ -1,88 +0,0 @@
1
- import * as THREE from "three";
2
-
3
- /**
4
- * Give the head a front.
5
- *
6
- * Both shipped rigs are smooth helmets with no facial geometry and no texture
7
- * of any kind — the FBX materials carry a flat colour and nothing else — and
8
- * the app then replaces every material with one clay tone. The result reads as
9
- * an ovoid with no direction, which matters most in an exported blocking
10
- * frame: the prompt claims a three-quarter front view and the picture has to
11
- * back it up.
12
- *
13
- * Sizes are in the head bone's own units. The rig is authored in Mixamo
14
- * centimetres and the whole clone is scaled by 0.01 afterwards, so a child of
15
- * the bone is written in centimetres too. Measured on both rigs (#407): the
16
- * skull's face surface is 15 units forward of the bone (z −13…+15) and the
17
- * crown is 20 up, so marks sit ON the face at z ≈ 14–15.
18
- *
19
- * Two eyes, not one band (#380). The eye band read to the video model as a
20
- * "dark horizontal eye slit" (its own expansion of the reference said so) and
21
- * it rendered exactly that: ViTPose then found the L/R eyes 5.6 px apart and
22
- * swapped them on 74 % of frames, which is the estimator's only heading cue
23
- * besides the shoulders. Two eyes 6 units apart are ~11 px at the fal
24
- * reference distance where the head is ~35 px — enough for the estimator to
25
- * key which side is which.
26
- *
27
- * Fixed near-black, NOT the body tint: pale tints washed the old marks into
28
- * the grey skull (#407). The extractor's neutral head mask swallows dark
29
- * pixels inside the skull silhouette (verified: 0 dark pixels escape), so
30
- * these cannot split segmentation. The COCO nose keypoint is the one the
31
- * estimator keys facing on; the wedge centres the face below the eyes.
32
- */
33
- const EYE_SEPARATION = 6;
34
-
35
- export function facingMarkSpecs() {
36
- const eye = (x) => ({
37
- role: "eye",
38
- geometry: () => new THREE.SphereGeometry(1.7, 12, 10),
39
- position: [x, 8, 14.4],
40
- rotation: null,
41
- });
42
- return [
43
- eye(-EYE_SEPARATION / 2),
44
- eye(EYE_SEPARATION / 2),
45
- {
46
- role: "nose",
47
- geometry: () => new THREE.ConeGeometry(2.4, 4.6, 4),
48
- position: [0, 4.5, 14.6],
49
- rotation: [Math.PI / 2, Math.PI / 4, 0],
50
- },
51
- ];
52
- }
53
-
54
- /** Hang the marks off the clone's head bone; posing, playback and pose
55
- * extraction are untouched — nothing here is skinned or animated. */
56
- export function addFacingMarks(clone) {
57
- let head = null;
58
- clone.traverse((node) => {
59
- if (!head && node.isBone && /head$/i.test(node.name)) head = node;
60
- });
61
- if (!head) return;
62
- const material = new THREE.MeshStandardMaterial({ color: "#1C1C1C", roughness: 0.7, metalness: 0 });
63
- for (const spec of facingMarkSpecs()) {
64
- const mesh = new THREE.Mesh(spec.geometry(), material);
65
- mesh.position.set(...spec.position);
66
- if (spec.rotation) mesh.rotation.set(...spec.rotation);
67
- mesh.castShadow = true;
68
- mesh.frustumCulled = false;
69
- // The marks are for the exported frame (the model's heading cue); the
70
- // studio's editor panes hide them per draw (dualview.jsx) by this tag.
71
- mesh.userData.facingMark = true;
72
- // The head bone's local +Z is the face direction on both rigs.
73
- head.add(mesh);
74
- }
75
- }
76
-
77
- export function proxyFacingMark(material = null) {
78
- const mesh = new THREE.Mesh(
79
- new THREE.ConeGeometry(0.055, 0.16, 4),
80
- material ?? new THREE.MeshStandardMaterial({ color: "#1C1C1C", roughness: 0.7, metalness: 0 }),
81
- );
82
- mesh.position.set(0, -0.015, 0.115);
83
- mesh.rotation.set(Math.PI / 2, Math.PI / 4, 0);
84
- mesh.castShadow = true;
85
- mesh.frustumCulled = false;
86
- mesh.userData.facingMark = true;
87
- return mesh;
88
- }
@@ -1,44 +0,0 @@
1
- // UI preference only. No progress, project content, IDs or analytics events.
2
- const TERMINAL_KEY = "cozyclay.camera-tutorial-terminal.v1";
3
-
4
- function readTerminal(storage) {
5
- try {
6
- return JSON.parse((storage ?? globalThis.localStorage)?.getItem(TERMINAL_KEY) || "null");
7
- } catch {
8
- return null;
9
- }
10
- }
11
-
12
- export function cameraTutorialSuppressed(storage) {
13
- const terminal = readTerminal(storage);
14
- return terminal?.dismissed === true || terminal?.completed === true || terminal?.export_started === true;
15
- }
16
-
17
- export function rememberCameraTutorialTerminal(reason, storage) {
18
- try {
19
- (storage ?? globalThis.localStorage)?.setItem(TERMINAL_KEY, JSON.stringify({ [reason]: true }));
20
- } catch {
21
- // Storage is optional; an attempt also owns its in-memory dismissal.
22
- }
23
- }
24
-
25
- export function createFirstShotHandoff(storage) {
26
- let closed = cameraTutorialSuppressed(storage);
27
- return {
28
- canShow(done) {
29
- return !closed && ["shot", "rail", "play"].every((kind) => done.has(kind));
30
- },
31
- complete() {
32
- // Future attempts stay quiet; this completed attempt keeps its action.
33
- rememberCameraTutorialTerminal("completed", storage);
34
- },
35
- dismiss() {
36
- closed = true;
37
- rememberCameraTutorialTerminal("dismissed", storage);
38
- },
39
- exportStarted() {
40
- closed = true;
41
- rememberCameraTutorialTerminal("export_started", storage);
42
- },
43
- };
44
- }
@@ -1,197 +0,0 @@
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
- }
@@ -1,183 +0,0 @@
1
- /**
2
- * The hand-built set pieces: a car, a small plane and a chair, modelled from
3
- * primitives in the same clay style as the room and the character tint.
4
- * Dimensions are metres against real sizes so the 1.8 m figure keeps honest
5
- * scale. Each takes `autoColor` (the viewport's auto-colour mode) and then
6
- * tints its primary surfaces flat; glass, tyres and rims keep their own.
7
- */
8
-
9
- // Blender's solid-view response for auto-colored surfaces. Workbench shades
10
- // with a neutral studio rig, so the derived hue reaches the eye unmultiplied;
11
- // under our warm key light a plain standard material turns every hue olive.
12
- // Full roughness kills the specular sheen and a self-light of the same hue
13
- // lifts the shadow side the way workbench's studio lighting does.
14
- export function autoFlat(hex) {
15
- return { color: hex, roughness: 1, metalness: 0, emissive: hex, emissiveIntensity: 0.4 };
16
- }
17
-
18
- const CLAY_CAR = "#d98770";
19
- const CLAY_CAR_TOP = "#e49a84";
20
- const CLAY_TIRE = "#41484c";
21
- const CLAY_RIM = "#7c8588";
22
- const CLAY_GLASS = "#55697a";
23
- const CLAY_PLANE = "#7896a4";
24
- const CLAY_PLANE_TRIM = "#e1a849";
25
- const CLAY_CHAIR = "#b9855d";
26
- const CLAY_CHAIR_LIGHT = "#cf9d72";
27
-
28
- function Wheel({ position }) {
29
- return (
30
- <group position={position}>
31
- <mesh rotation={[0, 0, Math.PI / 2]}>
32
- <cylinderGeometry args={[0.33, 0.33, 0.24, 20]} />
33
- <meshStandardMaterial color={CLAY_TIRE} roughness={0.95} />
34
- </mesh>
35
- <mesh rotation={[0, 0, Math.PI / 2]}>
36
- <cylinderGeometry args={[0.17, 0.17, 0.26, 16]} />
37
- <meshStandardMaterial color={CLAY_RIM} roughness={0.5} metalness={0.35} />
38
- </mesh>
39
- </group>
40
- );
41
- }
42
- /**
43
- * A generic 5-door-ish sedan silhouette, ~4.5 m long. Origin at the centre
44
- * of the footprint, +Z forward.
45
- */
46
- export function Car({ position = [0, 0, 0], rotY = 0, color = CLAY_CAR, topColor = CLAY_CAR_TOP, autoColor = undefined }) {
47
- const bodyMaterial = autoColor
48
- ? { ...autoFlat(autoColor) }
49
- : { color, roughness: 0.55, metalness: 0.25 };
50
- const topMaterial = autoColor
51
- ? { ...autoFlat(autoColor) }
52
- : { color: topColor, roughness: 0.5, metalness: 0.2 };
53
- return (
54
- <group position={position} rotation={[0, rotY, 0]}>
55
- <mesh position={[0, 0.62, 0]}>
56
- <boxGeometry args={[1.78, 0.62, 4.45]} />
57
- <meshStandardMaterial {...bodyMaterial} />
58
- </mesh>
59
- <mesh position={[0, 1.12, -0.15]}>
60
- <boxGeometry args={[1.58, 0.5, 2.2]} />
61
- <meshStandardMaterial {...topMaterial} />
62
- </mesh>
63
- <mesh position={[0, 1.14, -0.15]}>
64
- <boxGeometry args={[1.62, 0.3, 1.9]} />
65
- <meshStandardMaterial color={CLAY_GLASS} roughness={0.15} metalness={0.6} />
66
- </mesh>
67
- <mesh position={[0, 1.05, 0.95]} rotation={[0.5, 0, 0]}>
68
- <boxGeometry args={[1.56, 0.42, 0.08]} />
69
- <meshStandardMaterial color={CLAY_GLASS} roughness={0.15} metalness={0.6} />
70
- </mesh>
71
- <mesh position={[0, 1.05, -1.25]} rotation={[-0.55, 0, 0]}>
72
- <boxGeometry args={[1.56, 0.42, 0.08]} />
73
- <meshStandardMaterial color={CLAY_GLASS} roughness={0.15} metalness={0.6} />
74
- </mesh>
75
- <Wheel position={[0.82, 0.33, 1.45]} />
76
- <Wheel position={[-0.82, 0.33, 1.45]} />
77
- <Wheel position={[0.82, 0.33, -1.45]} />
78
- <Wheel position={[-0.82, 0.33, -1.45]} />
79
- </group>
80
- );
81
- }
82
-
83
- /** Compact single-engine propeller plane, ~3.4 m wingspan and +Z forward. */
84
- export function SmallPlane({ position = [0, 0, 0], rotY = 0, autoColor = undefined }) {
85
- // Auto-color mode tints the PRIMARY surfaces only (fuselage, wings, trim);
86
- // glass, tires and rims keep their materials so the silhouette still reads.
87
- // Each mesh keeps its own hand-tuned roughness when the mode is off.
88
- const mat = (color, roughness, metalness) =>
89
- autoColor ? autoFlat(autoColor) : { color, roughness, metalness };
90
- return (
91
- <group position={position} rotation={[0, rotY, 0]}>
92
- <mesh position={[0, 0.76, 0]} rotation={[Math.PI / 2, 0, 0]}>
93
- <cylinderGeometry args={[0.23, 0.31, 2.75, 16]} />
94
- <meshStandardMaterial {...mat(CLAY_PLANE, 0.58, 0.18)} />
95
- </mesh>
96
- <mesh position={[0, 0.76, 1.55]} rotation={[Math.PI / 2, 0, 0]}>
97
- <coneGeometry args={[0.23, 0.55, 16]} />
98
- <meshStandardMaterial {...mat(CLAY_PLANE_TRIM, 0.52, 0.2)} />
99
- </mesh>
100
-
101
- <mesh position={[0, 0.73, 0.15]}>
102
- <boxGeometry args={[3.4, 0.09, 0.58]} />
103
- <meshStandardMaterial {...mat(CLAY_PLANE, 0.62, 0.14)} />
104
- </mesh>
105
- <mesh position={[0, 0.84, -1.18]}>
106
- <boxGeometry args={[1.45, 0.07, 0.38]} />
107
- <meshStandardMaterial {...mat(CLAY_PLANE_TRIM, 0.62, 0.12)} />
108
- </mesh>
109
- <mesh position={[0, 1.08, -1.2]} rotation={[0.22, 0, 0]}>
110
- <boxGeometry args={[0.08, 0.62, 0.48]} />
111
- <meshStandardMaterial {...mat(CLAY_PLANE, 0.62, 0.12)} />
112
- </mesh>
113
-
114
- <mesh position={[0, 1.02, 0.42]} scale={[0.62, 0.46, 0.9]}>
115
- <sphereGeometry args={[0.42, 16, 10, 0, Math.PI * 2, 0, Math.PI / 2]} />
116
- <meshStandardMaterial color={CLAY_GLASS} roughness={0.16} metalness={0.55} />
117
- </mesh>
118
-
119
- <group position={[0, 0.76, 1.86]}>
120
- <mesh rotation={[Math.PI / 2, 0, 0]}>
121
- <cylinderGeometry args={[0.11, 0.11, 0.2, 12]} />
122
- <meshStandardMaterial color={CLAY_RIM} roughness={0.45} metalness={0.4} />
123
- </mesh>
124
- <mesh position={[0, 0, 0.12]} rotation={[0, 0, 0.28]}>
125
- <boxGeometry args={[1.25, 0.08, 0.06]} />
126
- <meshStandardMaterial color={CLAY_TIRE} roughness={0.8} />
127
- </mesh>
128
- </group>
129
-
130
- {[-0.55, 0.55].map((x) => (
131
- <group key={x} position={[x, 0.2, 0.15]}>
132
- <mesh position={[0, 0.24, 0]} rotation={[0, 0, x < 0 ? -0.35 : 0.35]}>
133
- <boxGeometry args={[0.045, 0.55, 0.045]} />
134
- <meshStandardMaterial color={CLAY_RIM} roughness={0.65} metalness={0.3} />
135
- </mesh>
136
- <mesh rotation={[0, 0, Math.PI / 2]}>
137
- <cylinderGeometry args={[0.16, 0.16, 0.09, 14]} />
138
- <meshStandardMaterial color={CLAY_TIRE} roughness={0.95} />
139
- </mesh>
140
- </group>
141
- ))}
142
- </group>
143
- );
144
- }
145
-
146
- export function Chair({ position = [0, 0, 0], rotY = 0, autoColor = undefined }) {
147
- // Auto-color mode tints seat/back and frame together; undefined keeps the
148
- // hand-picked clay pair exactly as it ships.
149
- const seat = autoColor ? autoFlat(autoColor) : { color: CLAY_CHAIR_LIGHT, roughness: 0.86 };
150
- const frame = autoColor ? autoFlat(autoColor) : { color: CLAY_CHAIR, roughness: 0.9 };
151
- const legPositions = [
152
- [-0.24, 0.23, -0.22],
153
- [0.24, 0.23, -0.22],
154
- [-0.24, 0.23, 0.22],
155
- [0.24, 0.23, 0.22],
156
- ];
157
- return (
158
- <group position={position} rotation={[0, rotY, 0]} scale={0.9}>
159
- <mesh position={[0, 0.49, 0]} castShadow receiveShadow>
160
- <boxGeometry args={[0.6, 0.12, 0.58]} />
161
- <meshStandardMaterial {...seat} />
162
- </mesh>
163
- {legPositions.map((leg, index) => (
164
- <mesh key={index} position={leg} castShadow receiveShadow>
165
- <boxGeometry args={[0.09, 0.46, 0.09]} />
166
- <meshStandardMaterial {...frame} />
167
- </mesh>
168
- ))}
169
- <mesh position={[-0.24, 0.92, -0.245]} castShadow receiveShadow>
170
- <boxGeometry args={[0.09, 0.86, 0.09]} />
171
- <meshStandardMaterial {...frame} />
172
- </mesh>
173
- <mesh position={[0.24, 0.92, -0.245]} castShadow receiveShadow>
174
- <boxGeometry args={[0.09, 0.86, 0.09]} />
175
- <meshStandardMaterial {...frame} />
176
- </mesh>
177
- <mesh position={[0, 1.08, -0.245]} castShadow receiveShadow>
178
- <boxGeometry args={[0.52, 0.38, 0.1]} />
179
- <meshStandardMaterial {...seat} />
180
- </mesh>
181
- </group>
182
- );
183
- }
@@ -1,86 +0,0 @@
1
- import { bucketMs, getAnalyticsOptOut, initAnalytics, track } from "./analytics.js";
2
-
3
- const KINDS = new Set(["fly", "walk", "dolly", "orbit", "shot", "rail", "play"]);
4
- const SOURCES = new Set(["query", "settings", "landing"]);
5
-
6
- /** One explicit opening, never persisted or identified on the wire. The UI's
7
- * done/current state is authoritative; this observer cannot advance a step.
8
- * Reuse the object on rerender/BFCache resume, create another on explicit start
9
- * or reload, and call dismiss only for an explicit close (not effect cleanup).
10
- *
11
- * Buckets use occurrence time even while initAnalytics is pending. The existing
12
- * two-argument track API uses capture timestamps, so a delayed SDK flush cannot
13
- * preserve exact started->play timestamp differences. No raw time is sent.
14
- */
15
- export function createTutorialAnalytics({ surface, startSource }, {
16
- now = () => performance.now(),
17
- initialize = initAnalytics,
18
- capture = track,
19
- optedOut = getAnalyticsOptOut,
20
- } = {}) {
21
- const metadata = { surface: surface === "playground" ? "playground" : "studio", tutorial_version: 1 };
22
- const startedAt = now();
23
- const entered = new Map();
24
- const completed = new Set();
25
- let currentStep = "fly";
26
- let terminal = false;
27
- let initialized = false;
28
- let muted = false;
29
- let pending = [];
30
- const canCapture = () => {
31
- // Do not replay an opted-out portion of an attempt after later opt-in,
32
- // or emit orphan completions for a suppressed started/entered marker.
33
- if (optedOut()) muted = true;
34
- if (muted) pending = [];
35
- return !muted;
36
- };
37
- const emit = (event, props = {}) => {
38
- if (!canCapture()) return;
39
- const payload = { ...metadata, ...props };
40
- if (initialized) capture(event, payload);
41
- else pending.push([event, payload]);
42
- };
43
- const enter = (kind, at) => {
44
- if (!KINDS.has(kind) || entered.has(kind)) return;
45
- entered.set(kind, at);
46
- emit("tutorial:step_entered", { step_kind: kind });
47
- };
48
- emit("tutorial:started", { start_source: SOURCES.has(startSource) ? startSource : "settings" });
49
- enter("fly", startedAt);
50
- const ready = Promise.resolve(initialize()).then(() => {
51
- initialized = true;
52
- if (!canCapture()) return;
53
- const events = pending;
54
- pending = [];
55
- for (const [event, props] of events) {
56
- if (!canCapture()) break;
57
- capture(event, props);
58
- }
59
- });
60
- return {
61
- ready,
62
- observe(done, currentKind) {
63
- if (terminal) return;
64
- const at = now();
65
- currentStep = KINDS.has(currentKind) ? currentKind : null;
66
- for (const kind of done) {
67
- if (!KINDS.has(kind) || completed.has(kind)) continue;
68
- // Existing tutorials accept out-of-order gestures. Give those an
69
- // entry at observation time, without moving their visible hint.
70
- enter(kind, at);
71
- completed.add(kind);
72
- emit("tutorial:step_completed", { step_kind: kind, elapsed_bucket: bucketMs(at - entered.get(kind)) });
73
- }
74
- enter(currentStep, at);
75
- if (completed.size === KINDS.size) {
76
- terminal = true;
77
- emit("tutorial:completed", { elapsed_bucket: bucketMs(at - startedAt) });
78
- }
79
- },
80
- dismiss() {
81
- if (terminal) return;
82
- terminal = true;
83
- emit("tutorial:dismissed", { step_kind: currentStep });
84
- },
85
- };
86
- }
@@ -1,17 +0,0 @@
1
- // The camera tutorial's set dressing: a short compiled walk (take v3) for the starter scene's
2
- // character, so the Shot / Rail / Play steps have somebody moving through the set. Built from
3
- // the character's own bind skeleton and placement; null for a body motion3 cannot drive.
4
- import { skeletonFor } from "./motion3/rigs/index.js";
5
- import { add, apply, ry } from "./motion3/compiler/math.js";
6
-
7
- const TUTORIAL_WALK_LABEL = "A person walks forward and stops.";
8
-
9
- export function tutorialWalkSpec(character) {
10
- const sk = skeletonFor(character?.model);
11
- if (!sk) return null;
12
- const I = sk.index, P = sk.restWorldPos, ank = P[I.LeftFoot][1], yaw = character.rot ?? 0;
13
- const at = (offset, y) => add([character.x ?? 0, character.y ?? 0, character.z ?? 0], apply(ry(yaw), [offset[0], y, offset[1]]));
14
- return { schema: "cozyclay.motion-spec.v1", fps: 24, frames: 216, label: TUTORIAL_WALK_LABEL,
15
- keys: [{ f: 0, set: { root: at([0, 0], P[I.Hips][1] - 0.01), yaw, foot_l: at([0.09, -0.03], ank), foot_r: at([-0.09, 0.03], ank), fyaw_l: yaw, fyaw_r: yaw } }],
16
- generators: [{ type: "walk", f0: 6, steps: 8, heading: [yaw, yaw] }] };
17
- }
@@ -1,50 +0,0 @@
1
- import { useRef, useState } from "react";
2
- import { cameraTutorialSuppressed } from "./first-shot-handoff.js";
3
- import { loadProjectSession } from "./project.js";
4
- import { SCENES_STORAGE_KEY } from "./scenes.js";
5
-
6
- export function useCameraTutorial({ embedMode, playgroundMode }) {
7
- // The camera tutorial (#206): the landing page's seven steps, run against
8
- // the real studio instead of the playground iframe. It opens from
9
- // /app/?tutorial=camera or from Settings ▾, and it is not offered inside an
10
- // embed.
11
-
12
- // Both doors go through startCameraTutorial (#209), which puts the studio in
13
- // the state the landing page teaches these steps in — the city-block starter
14
- // scene with the walk take on its character — so Shot / Rail / Play always
15
- // have something to frame. That function is declared with the project
16
- // actions further down; the listeners here reach it through a ref so they
17
- // always call the current render's closure (the confirm reads projectDirty).
18
- const [cameraTutorialEntry] = useState(() => {
19
- const params = new URLSearchParams(globalThis.location?.search || "");
20
- const query = params.get("tutorial") === "camera";
21
- let firstRun = false;
22
- try {
23
- firstRun = !query && !params.get("scene") && !cameraTutorialSuppressed()
24
- && !loadProjectSession()?.name && !globalThis.localStorage?.getItem(SCENES_STORAGE_KEY);
25
- } catch { firstRun = false; }
26
- return !embedMode && !playgroundMode && !cameraTutorialSuppressed() && (query ? "query" : firstRun ? "first-run" : null);
27
- });
28
- const cameraTutorialQuery = Boolean(cameraTutorialEntry);
29
- const [cameraTutorial, setCameraTutorial] = useState(false);
30
- const cameraTutorialAnalytics = useRef(null);
31
- const [cameraTutorialAttempt, setCameraTutorialAttempt] = useState(0);
32
- const [cameraTutorialHandoff, setCameraTutorialHandoff] = useState(null);
33
- const cameraTutorialCompletedRef = useRef(false);
34
- const exportMenuTriggerRef = useRef(null);
35
- const exportShotIdRef = useRef(null);
36
- // The step the tutorial is on, published on the .app root so styles.css can
37
- // spotlight the one control that step needs (#211).
38
- const [cameraTutorialStep, setCameraTutorialStep] = useState(null);
39
- const startCameraTutorialRef = useRef(null);
40
- const cameraTutorialStarted = useRef(false);
41
- // A tutorial restart never reloads the starter over the user's edits.
42
- const tutorialStarterRef = useRef(false);
43
- const tutorialLoadingRef = useRef(false);
44
- const tutorialProjectEpochRef = useRef(0);
45
- const tutorialSeedEpochRef = useRef(null);
46
- // Armed once the starter is applied, consumed by the seed effect next to the
47
- // hosted-demo seed as soon as the new character's rig exists.
48
- const [tutorialSeedPending, setTutorialSeedPending] = useState(false);
49
- return { cameraTutorialEntry, cameraTutorialQuery, cameraTutorial, setCameraTutorial, cameraTutorialAnalytics, cameraTutorialAttempt, setCameraTutorialAttempt, cameraTutorialHandoff, setCameraTutorialHandoff, cameraTutorialCompletedRef, exportMenuTriggerRef, exportShotIdRef, cameraTutorialStep, setCameraTutorialStep, startCameraTutorialRef, cameraTutorialStarted, tutorialStarterRef, tutorialLoadingRef, tutorialProjectEpochRef, tutorialSeedEpochRef, tutorialSeedPending, setTutorialSeedPending };
50
- }
@@ -1,12 +0,0 @@
1
- import { useRef, useState } from "react";
2
- import { createSemanticState } from "./semantic-edit.js";
3
-
4
- /** Explicit passive and authored setters for the same committed state owner. */
5
- export function useSemanticState(initial, observe, domain) {
6
- const [value, publish] = useState(initial);
7
- const owner = useRef(null);
8
- const observer = useRef(observe);
9
- observer.current = observe;
10
- if (!owner.current) owner.current = createSemanticState(value, publish, (...args) => observer.current(...args), domain);
11
- return [value, owner.current.set, owner.current.edit];
12
- }