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.
- package/CHANGELOG.md +93 -0
- package/INSTALL_FOR_AGENTS.md +146 -0
- package/README.md +74 -192
- package/THIRD_PARTY_NOTICES.md +0 -21
- package/bin/agent/agent-routes.mjs +29 -62
- package/bin/agent/agent-runner.mjs +5 -3
- package/bin/agent/main-agent.mjs +70 -0
- package/bin/agent/main-prompt.mjs +17 -0
- package/bin/agent/plan-tool.mjs +114 -0
- package/bin/agent/script-tool.mjs +2 -0
- package/bin/agent/sculpt-sketch.mjs +190 -0
- package/bin/agent/session-store.mjs +4 -3
- package/bin/agent/sketch-code.mjs +151 -0
- package/bin/agent/sketch-sandbox.mjs +102 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-task-routes.mjs +185 -0
- package/bin/agent/studio-tasks.mjs +156 -0
- package/bin/agent/studio-tools.mjs +3 -2
- package/bin/agent/studio-turn-transaction.mjs +76 -0
- package/bin/agent/turn-frames.mjs +4 -0
- package/bin/cozyclay.mjs +5 -7
- package/dist/app/index.html +7 -6
- package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
- package/dist/assets/app-BlwOpest.css +1 -0
- package/dist/assets/app-DmR7jBkz.js +5144 -0
- package/dist/assets/compile-worker-D0pKioa0.js +5 -0
- package/dist/assets/landing-y3EqDgSZ.js +1 -0
- package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/assets/motion3-compile-BB3_1-EB.js +1 -0
- package/dist/assets/scene-asset-sync-CBx7XRti.css +1 -0
- package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
- package/dist/assets/task-board-ErQvfoaz.js +14 -0
- package/dist/assets/workflow-zQM-EKW1.js +179 -0
- package/dist/assets/x-bot-tpose-body-BusyR3eg.json +1 -0
- package/dist/assets/y-bot-tpose-body-Dy1VKChZ.json +1 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/greybox-to-video/index.html +2 -3
- package/dist/index.html +7 -57
- package/dist/llms.txt +2 -1
- package/dist/previs-software/index.html +2 -2
- package/dist/privacy/index.html +3 -7
- package/dist/seedance-camera-control/index.html +2 -2
- package/dist/sitemap.xml +5 -5
- package/dist/sw.js +5 -1
- package/dist/thumbs/blank-stage-dark.webp +0 -0
- package/dist/thumbs/blank-stage-light.webp +0 -0
- package/dist/workflow/index.html +5 -4
- package/mcp/mesh-file.mjs +3 -1
- package/mcp/server.mjs +3 -0
- package/mcp/tool-handlers.mjs +2 -2
- package/package.json +5 -3
- package/src/App.jsx +51 -282
- package/src/analytics.js +3 -148
- package/src/app-stage.jsx +1 -1
- package/src/asset-pane.jsx +5 -10
- package/src/auto-flat.js +10 -0
- package/src/bus-tasks.js +11 -5
- package/src/command-bus.js +1 -1
- package/src/commands/elements.js +4 -3
- package/src/commands/motion3-compile.js +51 -27
- package/src/commands/motion3.js +3 -3
- package/src/commands/objects.js +1 -1
- package/src/commands/project.js +1 -1
- package/src/commands/shot.js +16 -4
- package/src/controls.jsx +0 -11
- package/src/cuts.js +11 -0
- package/src/domains/cast.js +3 -2
- package/src/domains/motion.js +3 -10
- package/src/domains/playback.js +32 -0
- package/src/domains/scenes.js +3 -22
- package/src/domains/shots.js +5 -5
- package/src/dualview.jsx +11 -15
- package/src/hierarchy-panel.css +18 -4
- package/src/hierarchy-panel.jsx +12 -11
- package/src/kits/held.js +3 -2
- package/src/light-blockers.js +2 -2
- package/src/main.jsx +5 -0
- package/src/material-look.js +189 -0
- package/src/material-presets.js +78 -0
- package/src/material-textures.js +157 -0
- package/src/motion/ground.js +5 -14
- package/src/motion/playback-frame-store.js +79 -0
- package/src/motion/playback-probe.js +16 -0
- package/src/motion/playback-subject.js +7 -0
- package/src/motion/timeline.css +43 -5
- package/src/motion/timeline.jsx +4 -3
- package/src/motion3/body.js +37 -5
- package/src/motion3/carried.js +11 -3
- package/src/motion3/compile-job.js +1 -1
- package/src/motion3/compiler/compile.js +7 -1
- package/src/motion3/compiler/finger-close.js +137 -0
- package/src/motion3/compiler/limbs.js +18 -3
- package/src/motion3/compiler/pose.js +6 -0
- package/src/motion3/compiler/validate-objects.js +17 -2
- package/src/motion3/compiler/validate-parts.js +2 -1
- package/src/motion3/compiler/vocab.js +4 -4
- package/src/motion3/describe.js +2 -2
- package/src/motion3/fix/index.js +5 -3
- package/src/motion3/flight.js +38 -0
- package/src/motion3/free-motion.js +10 -7
- package/src/motion3/load.js +125 -0
- package/src/motion3/mass.js +23 -0
- package/src/motion3/object-keys.js +11 -6
- package/src/motion3/object-pose.js +11 -3
- package/src/motion3/pipeline.js +3 -0
- package/src/motion3/qa/explain.js +3 -1
- package/src/motion3/qa/grasp-checks.js +123 -0
- package/src/motion3/qa/hints-grasp.js +71 -0
- package/src/motion3/qa/index.js +3 -1
- package/src/motion3/qa/object-checks.js +7 -2
- package/src/motion3/qa/patch.js +2 -1
- package/src/motion3/qa/spec-checks.js +10 -1
- package/src/motion3/schema/fields.js +3 -3
- package/src/motion3/schema/topics.js +9 -8
- package/src/motion3/schema/types.js +4 -1
- package/src/motion3/seat.js +84 -0
- package/src/motion3/settle.js +11 -6
- package/src/motion3/shapes.js +70 -0
- package/src/motion3/skills/catch.js +71 -0
- package/src/motion3/skills/index.js +10 -3
- package/src/motion3/skills/info.js +13 -0
- package/src/motion3/skills/throw.js +72 -0
- package/src/object-catalog.jsx +0 -4
- package/src/object-gizmo.jsx +2 -1
- package/src/object-look.js +9 -5
- package/src/object-path.js +1 -1
- package/src/panels/ProjectPanel.jsx +42 -21
- package/src/panels/details.css +15 -4
- package/src/popover.js +37 -0
- package/src/posestudio.jsx +15 -10
- package/src/primitive-look.js +38 -0
- package/src/project-browser-art.jsx +19 -0
- package/src/project-browser.css +74 -121
- package/src/project-browser.jsx +36 -60
- package/src/props.jsx +24 -35
- package/src/render-passes.js +7 -1
- package/src/retired-kinds.js +37 -0
- package/src/scene-objects.js +5 -9
- package/src/scenes.js +4 -5
- package/src/sculpt-colours.js +65 -0
- package/src/sculpt-compare.js +13 -50
- package/src/sculpt-csg.js +112 -0
- package/src/sculpt-curve.js +123 -0
- package/src/sculpt-cutters.js +43 -0
- package/src/sculpt-detail.js +207 -0
- package/src/sculpt-geometry-loft-convex.js +100 -0
- package/src/sculpt-geometry-shapes.js +250 -0
- package/src/sculpt-geometry.js +33 -15
- package/src/sculpt-loft-rings.js +225 -0
- package/src/sculpt-noise.js +65 -0
- package/src/sculpt-part-fields.js +27 -0
- package/src/sculpt-recipe-read.js +65 -0
- package/src/sculpt-recipe.js +33 -75
- package/src/sculpt-run.js +1 -1
- package/src/sculpt-scatter-items.js +183 -0
- package/src/sculpt-scatter-shape.js +57 -0
- package/src/sculpt-scatter.js +124 -0
- package/src/sculpt-shapes-curved.js +211 -0
- package/src/sculpt-shapes-loft-convex.js +218 -0
- package/src/sculpt-shapes.js +138 -0
- package/src/sculpt-sketch-state.js +76 -0
- package/src/sculpt-sketch.jsx +124 -0
- package/src/sculpt-surface-fields.js +65 -0
- package/src/settings-menu.jsx +1 -20
- package/src/shell/BottomDock.jsx +9 -9
- package/src/shell/MenuBar.jsx +1 -9
- package/src/shell/PreferencesDialog.jsx +3 -4
- package/src/shell/ViewportToolbar.jsx +3 -3
- package/src/shell/add-camera-shot.js +11 -0
- package/src/shell/glass-regions.css +3 -2
- package/src/shell/glass.css +0 -5
- package/src/shell/lens-scrub-math.js +51 -0
- package/src/shell/lens-scrub.css +73 -0
- package/src/shell/lens-scrub.jsx +98 -0
- package/src/shell/mode.css +6 -5
- package/src/shell/preferences.css +1 -32
- package/src/shell/shell.css +18 -1
- package/src/shell/studio-shell-context.js +5 -0
- package/src/shot-frame.js +26 -0
- package/src/stage-canvas-rigs.jsx +7 -8
- package/src/stage-characters.jsx +26 -0
- package/src/stage-gizmo.js +43 -0
- package/src/stage-labels-ko.js +0 -6
- package/src/stage-startup-defaults.js +3 -9
- package/src/starter-scenes.js +5 -11
- package/src/studio-agent-commands.js +2 -2
- package/src/studio-agent-protocol.js +2 -7
- package/src/studio-app-binding.js +1 -1
- package/src/studio-elements.js +6 -2
- package/src/styles/themes.css +13 -0
- package/src/styles.css +6 -674
- package/src/text/text-object.jsx +1 -1
- package/src/ui.jsx +8 -16
- package/src/use-embed-bridge.js +3 -6
- package/src/use-render-activity.js +3 -1
- package/src/view-ghosts.jsx +5 -3
- package/src/viewport-visible-rect.js +37 -0
- package/src/workflow/AgentPanel.jsx +79 -46
- package/src/workflow/AgentPlanCard.jsx +41 -0
- package/src/workflow/AgentProgressCard.jsx +1 -1
- package/src/workflow/TaskBoard.jsx +102 -0
- package/src/workflow/TaskInline.jsx +114 -0
- package/src/workflow/agent-client.js +25 -36
- package/src/workflow/agent-panel.css +14 -1
- package/src/workflow/agent-plan.css +93 -0
- package/src/workflow/agent-plan.js +59 -0
- package/src/workflow/attachment-image.js +10 -3
- package/src/workflow/attachment-video.js +91 -0
- package/src/workflow/task-board.css +98 -0
- package/src/workflow/task-board.js +169 -0
- package/tools/check-eslint.mjs +2 -1
- package/tools/check-knip.mjs +2 -1
- package/tools/dev/pages/greybox-to-video.html +2 -3
- package/tools/dev/pages/previs-software.html +2 -2
- package/tools/dev/pages/privacy.html +3 -7
- package/tools/dev/pages/seedance-camera-control.html +2 -2
- package/tools/dev-full.mjs +11 -2
- package/tools/eslint-baseline.json +6 -6
- package/tools/knip-baseline.json +0 -11
- package/tools/loc-baseline.json +30 -31
- package/tools/metrics-snapshot.mjs +43 -5
- package/tools/render-start-thumbs.mjs +175 -0
- package/tools/run-tests.mjs +10 -17
- package/tools/sitemap.mjs +1 -1
- package/dist/assets/analytics-BibyE5ar.js +0 -6
- package/dist/assets/app-DhRJ-PBz.js +0 -5088
- package/dist/assets/app-f-fkV5uP.css +0 -1
- package/dist/assets/compile-worker-DqiA4AaN.js +0 -5
- package/dist/assets/first-shot-handoff-Qx7SKl38.js +0 -1
- package/dist/assets/landing-DYg87MDY.js +0 -1
- package/dist/assets/scene-asset-sync-DBYQBR0T.js +0 -20
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
- package/dist/assets/workflow-BSA22Ofr.js +0 -179
- package/dist/scenes/city-block.cclayproject +0 -1
- package/src/burn-in.js +0 -91
- package/src/camera-tutorial.jsx +0 -520
- package/src/facing-marks.js +0 -88
- package/src/first-shot-handoff.js +0 -44
- package/src/panels/motion.css +0 -197
- package/src/set-pieces.jsx +0 -183
- package/src/tutorial-analytics.js +0 -86
- package/src/tutorial-walk.js +0 -17
- package/src/use-camera-tutorial.js +0 -50
- package/src/use-semantic-state.js +0 -12
- package/tools/metrics/archive-traffic.mjs +0 -138
- package/tools/perf/ik-camera-drag.mjs +0 -138
package/src/facing-marks.js
DELETED
|
@@ -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
|
-
}
|
package/src/panels/motion.css
DELETED
|
@@ -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
|
-
}
|
package/src/set-pieces.jsx
DELETED
|
@@ -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
|
-
}
|
package/src/tutorial-walk.js
DELETED
|
@@ -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
|
-
}
|