cozyclay 3.2.0 → 3.2.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 (185) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/INSTALL_FOR_AGENTS.md +4 -4
  3. package/README.md +4 -2
  4. package/bin/agent/agent-failure-codes.mjs +34 -8
  5. package/bin/agent/agent-routes.mjs +6 -6
  6. package/bin/agent/agent-runner.mjs +16 -13
  7. package/bin/agent/agent-stream.mjs +26 -3
  8. package/bin/agent/main-prompt.mjs +1 -1
  9. package/bin/agent/studio-prompt.mjs +2 -2
  10. package/bin/agent/supervisor-followup.mjs +34 -0
  11. package/bin/agent/supervisor.mjs +6 -6
  12. package/bin/cozyclay.mjs +23 -60
  13. package/bin/star-prompt.mjs +15 -14
  14. package/bin/studio-routes.mjs +6 -3
  15. package/bin/telemetry-state.mjs +5 -7
  16. package/bin/update-check.mjs +61 -7
  17. package/bin/update-install.mjs +16 -3
  18. package/bin/update-restart.mjs +95 -0
  19. package/bin/update-routes.mjs +25 -3
  20. package/dist/app/index.html +13 -7
  21. package/dist/assets/TaskBoard-tgO0MNKL.js +1 -0
  22. package/dist/assets/analytics-CaTUuLPM.js +1 -0
  23. package/dist/assets/app-CnDkB5WO.css +1 -0
  24. package/dist/assets/app-DJt06Oyb.js +5144 -0
  25. package/dist/assets/{compile-worker-DRWRa9pr.js → compile-worker-D1pl6G7x.js} +5 -5
  26. package/dist/assets/landing-2Fy7DU41.js +1 -0
  27. package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
  28. package/dist/assets/motion3-compile-BxaM0Xwl.js +1 -0
  29. package/dist/assets/scene-asset-sync-CLGNbEBq.js +13 -0
  30. package/dist/assets/task-board-UVGTZLRF.js +15 -0
  31. package/dist/assets/workflow-CSiBlX1s.js +179 -0
  32. package/dist/cozyclay-package.json +1 -1
  33. package/dist/index.html +4 -1
  34. package/dist/privacy/index.html +9 -6
  35. package/dist/sitemap.xml +2 -2
  36. package/dist/workflow/index.html +5 -4
  37. package/mcp/README.md +1 -1
  38. package/mcp/live-hub.mjs +4 -4
  39. package/mcp/tool-handlers.mjs +14 -16
  40. package/package.json +2 -1
  41. package/src/App.jsx +37 -70
  42. package/src/analytics-controls.js +25 -3
  43. package/src/analytics-strict.js +16 -4
  44. package/src/analytics.js +32 -26
  45. package/src/app-version.js +13 -0
  46. package/src/asset-drop.js +39 -0
  47. package/src/asset-pane.css +34 -0
  48. package/src/asset-pane.jsx +4 -35
  49. package/src/bus-tasks.js +1 -1
  50. package/src/character-kind.js +8 -6
  51. package/src/click-friction.js +5 -4
  52. package/src/command-bus-telemetry.js +8 -7
  53. package/src/command-bus.js +2 -16
  54. package/src/commands/cast.js +2 -1
  55. package/src/commands/motion3.js +3 -1
  56. package/src/commands/objects.js +2 -37
  57. package/src/commands/project.js +3 -3
  58. package/src/commands/sculpt-compare.js +53 -0
  59. package/src/content-tile.jsx +36 -0
  60. package/src/domains/cast.js +7 -6
  61. package/src/domains/motion.js +6 -5
  62. package/src/domains/scenes.js +41 -31
  63. package/src/execution-telemetry.js +7 -3
  64. package/src/export-menu-telemetry.js +26 -0
  65. package/src/finger-handles.jsx +128 -0
  66. package/src/friction-hint.js +82 -0
  67. package/src/live-control.js +2 -1
  68. package/src/locale-zh/panels.js +20 -0
  69. package/src/locale-zh/shell.js +4 -0
  70. package/src/locale-zh/workspace.js +6 -0
  71. package/src/motion/find-bone.js +13 -0
  72. package/src/motion/fingers.js +170 -0
  73. package/src/motion/fingertip-pick.js +21 -0
  74. package/src/motion/ik-exposure.js +48 -0
  75. package/src/motion/ik.js +4 -11
  76. package/src/motion3/compiler/head-lead.js +4 -2
  77. package/src/motion3/hybrid-hint.js +19 -0
  78. package/src/motion3/load.js +19 -12
  79. package/src/motion3/qa/hints-contact.js +12 -0
  80. package/src/motion3/qa/patch.js +4 -1
  81. package/src/motion3/qa/verdict.js +7 -2
  82. package/src/motion3/repair.js +28 -8
  83. package/src/motion3/skills/catch.js +2 -1
  84. package/src/panels/FingerPanel.jsx +38 -0
  85. package/src/panels/RigControlPanel.jsx +9 -1
  86. package/src/panels/SculptPanel.jsx +12 -12
  87. package/src/panels/SculptPartTree.jsx +46 -0
  88. package/src/panels/SubjectsPanel.jsx +1 -1
  89. package/src/panels/sculpt.css +11 -0
  90. package/src/pose-fade.js +7 -3
  91. package/src/posestudio.jsx +14 -27
  92. package/src/project-browser.css +6 -0
  93. package/src/project-browser.jsx +9 -18
  94. package/src/project-start-choices.jsx +45 -0
  95. package/src/project.js +16 -12
  96. package/src/proxy-figure-shape.js +11 -0
  97. package/src/proxy-figure.jsx +25 -18
  98. package/src/scene-assets.js +17 -13
  99. package/src/scenes.js +22 -11
  100. package/src/sculpt-blueprint.js +56 -0
  101. package/src/sculpt-mode.js +5 -0
  102. package/src/sculpt-part-edit.js +115 -0
  103. package/src/sculpt-part-gizmo.jsx +159 -0
  104. package/src/sculpt-part-outline.jsx +72 -0
  105. package/src/sculpt-pick.js +53 -0
  106. package/src/sculpt-run-text.js +51 -0
  107. package/src/sculpt-run.js +10 -46
  108. package/src/sculpt-shelf-file.js +92 -0
  109. package/src/sculpt-shelf-tiles.jsx +69 -0
  110. package/src/sculpt-shelf.js +108 -0
  111. package/src/sculpt-stage.jsx +39 -2
  112. package/src/sculpt-thumb.js +72 -0
  113. package/src/sculpt-views.js +75 -0
  114. package/src/semantic-edit.js +0 -87
  115. package/src/settings-menu.jsx +2 -2
  116. package/src/shell/DetailsSlot.jsx +5 -6
  117. package/src/shell/LibrarySlot.jsx +8 -10
  118. package/src/shell/MenuBar.jsx +6 -6
  119. package/src/shell/PreferencesDialog.jsx +2 -3
  120. package/src/shell/ViewportToolbar.jsx +15 -18
  121. package/src/shell/add-character-entries.js +13 -0
  122. package/src/shell/pose-controls.js +32 -0
  123. package/src/shell/preference-pages.jsx +2 -11
  124. package/src/shell/storyboard-caption.js +21 -0
  125. package/src/shell/studio-shell-context.js +1 -0
  126. package/src/shortcut-friction.js +2 -1
  127. package/src/site/landing-analytics.js +28 -0
  128. package/src/studio-actions.js +3 -2
  129. package/src/studio-agent-protocol.js +4 -4
  130. package/src/studio-app-binding.js +4 -3
  131. package/src/update/UpdateChip.jsx +21 -2
  132. package/src/update/UpdateDialog.jsx +26 -6
  133. package/src/update/update-client.js +123 -17
  134. package/src/update/update-telemetry.js +15 -3
  135. package/src/use-sculpt-mode.js +62 -8
  136. package/src/workflow/agent-failure-codes.js +28 -9
  137. package/src/workflow/agent-turn-telemetry.js +1 -1
  138. package/src/workflow/task-board.js +2 -2
  139. package/tools/dev/page-shell.py +1 -1
  140. package/tools/dev/pages/greybox-to-video.html +21 -2
  141. package/tools/dev/pages/previs-software.html +3 -3
  142. package/tools/dev/pages/privacy.html +9 -6
  143. package/tools/dev/pages/seedance-camera-control.html +2 -2
  144. package/tools/loc-baseline.json +10 -10
  145. package/tools/run-tests.mjs +45 -69
  146. package/tools/select-tests.mjs +119 -0
  147. package/tools/test-durations.json +455 -0
  148. package/tools/test-graph.mjs +181 -0
  149. package/tools/test-shards.mjs +78 -0
  150. package/tools/tutorial/README.md +74 -0
  151. package/tools/tutorial/assemble.mjs +43 -0
  152. package/tools/tutorial/captions.mjs +43 -0
  153. package/tools/tutorial/cdp.mjs +55 -0
  154. package/tools/tutorial/cursor.mjs +159 -0
  155. package/tools/tutorial/episode.mjs +173 -0
  156. package/tools/tutorial/episodes/beginner-guide/00-install.mjs +42 -0
  157. package/tools/tutorial/episodes/beginner-guide/01-start.mjs +50 -0
  158. package/tools/tutorial/episodes/beginner-guide/02-layout.mjs +67 -0
  159. package/tools/tutorial/episodes/beginner-guide/03-stage.mjs +58 -0
  160. package/tools/tutorial/episodes/beginner-guide/04-motion.mjs +98 -0
  161. package/tools/tutorial/episodes/beginner-guide/05-camera.mjs +47 -0
  162. package/tools/tutorial/episodes/beginner-guide/06-export.mjs +59 -0
  163. package/tools/tutorial/episodes/beginner-guide/steps.mjs +32 -0
  164. package/tools/tutorial/episodes/beginner-guide.mjs +17 -0
  165. package/tools/tutorial/episodes/cast.mjs +12 -0
  166. package/tools/tutorial/episodes/first-video.mjs +121 -0
  167. package/tools/tutorial/episodes/stub-tour.mjs +97 -0
  168. package/tools/tutorial/launch.mjs +63 -0
  169. package/tools/tutorial/mux.mjs +61 -0
  170. package/tools/tutorial/overlay.mjs +142 -0
  171. package/tools/tutorial/record.mjs +156 -0
  172. package/tools/tutorial/scenes/terminal.html +63 -0
  173. package/tools/tutorial/steps.mjs +111 -0
  174. package/tools/tutorial/terminal.mjs +43 -0
  175. package/tools/tutorial/voice.mjs +49 -0
  176. package/tools/update-test-durations.mjs +42 -0
  177. package/tools/verify-package-tarball.mjs +18 -12
  178. package/dist/assets/TaskBoard-BUjjuvm5.js +0 -1
  179. package/dist/assets/app-Dige7q3X.css +0 -1
  180. package/dist/assets/app-DxoI941o.js +0 -5144
  181. package/dist/assets/motion3-compile-B4SX4TGx.js +0 -1
  182. package/dist/assets/rolldown-runtime-hePW80VL.js +0 -1
  183. package/dist/assets/scene-asset-sync-LeI3eSnO.js +0 -13
  184. package/dist/assets/task-board-5PKn7ZaA.js +0 -15
  185. package/dist/assets/workflow-Dg9jCKOq.js +0 -179
@@ -0,0 +1,42 @@
1
+ // Chapter 0 · Install: the terminal scene with the real first-run output.
2
+ import { chapterCard, spot, t } from "./steps.mjs";
3
+
4
+ export default {
5
+ id: "install",
6
+ title: t("0 · 설치", "0 · Install"),
7
+ steps: [
8
+ { id: "opening", card: "opening", hold: 3000 },
9
+ chapterCard("0.0", t("0 · 설치", "0 · Install"), t("명령 한 줄", "One command")),
10
+ {
11
+ id: "0.1",
12
+ narration: t("Node.js 22.19 이상과 Chrome 같은 Chromium 브라우저가 필요합니다. 설치는 명령 한 줄입니다.", "You need Node.js 22.19 or newer and a Chromium browser such as Chrome. The install is one command."),
13
+ terminal: {},
14
+ hold: 0,
15
+ },
16
+ {
17
+ id: "0.2",
18
+ narration: t("터미널에 npx cozyclay를 입력하고 Enter를 누르세요. 첫 실행은 패키지를 내려받느라 잠시 걸립니다.", "Type npx cozyclay and press Enter. The first run downloads the package, so it takes a moment."),
19
+ terminal: { type: "npx cozyclay" },
20
+ hold: 600,
21
+ },
22
+ {
23
+ id: "0.3",
24
+ narration: t("어디서 알게 됐는지 묻는 질문이 한 번 나옵니다. 답하거나 skip을 입력하세요.", "It asks once where you heard about CozyClay. Answer, or type skip."),
25
+ terminal: { print: ["How did you hear about CozyClay? [x/hn/reddit/github/site/friend/other/skip] "], type: "skip" },
26
+ hold: 600,
27
+ },
28
+ {
29
+ id: "0.4",
30
+ narration: t("익명 사용 통계가 켜져 있다는 안내입니다. cclay telemetry off로 언제든 끌 수 있습니다.", "Anonymous usage metrics are on. Turn them off any time with cclay telemetry off."),
31
+ terminal: { print: ["Anonymous usage metrics are on (no prompts, project content, filenames, or account information).", "Disable anytime: cclay telemetry off"] },
32
+ hold: 600,
33
+ },
34
+ {
35
+ id: "0.5",
36
+ narration: t("스튜디오가 http://127.0.0.1:5180/app/ 에서 실행되고 브라우저가 자동으로 열립니다. Safari와 Firefox는 지원하지 않습니다.", "The studio runs at http://127.0.0.1:5180/app/ and your browser opens it. Safari and Firefox are not supported."),
37
+ terminal: { print: ["CozyClay is running at http://127.0.0.1:5180/app/", "Use a Chromium-based browser — Safari and Firefox are not supported."] },
38
+ hold: 300,
39
+ },
40
+ spot("0.5b", { css: "#screen .line", text: "CozyClay is running at" }),
41
+ ],
42
+ };
@@ -0,0 +1,50 @@
1
+ // Chapter 1 · Start screen: project kind and settings, then Create.
2
+ import { castFirstCharacter } from "../cast.mjs";
3
+ import { chapterCard, clear, spot, t } from "./steps.mjs";
4
+
5
+ export default {
6
+ id: "start",
7
+ title: t("1 · 시작 화면", "1 · Start screen"),
8
+ steps: [
9
+ { id: "1.0", studio: true },
10
+ chapterCard("1.0b", t("1 · 시작 화면", "1 · Start screen"), t("프로젝트 종류와 설정", "Project kind and settings")),
11
+ {
12
+ id: "1.1",
13
+ narration: t("브라우저에 시작 화면이 열립니다. 왼쪽은 최근 프로젝트·새 프로젝트·배우기, 가운데가 새 프로젝트 설정입니다.", "The start screen opens. Left: Recent, New Project, Learn. Center: the new project settings."),
14
+ wait: ".v2-start-screen",
15
+ },
16
+ spot("1.2", "[data-testid=\"start-previs-mode\"]", t("스토리보드 / 애니메이션", "Storyboard / Animation"), t("스토리보드는 이미지 패널을 한 장씩, 애니메이션은 연속 모션과 카메라를", "Storyboard directs one image panel at a time, Animation blocks continuous motion"), {
17
+ caption: t("프로젝트 종류입니다. 이 가이드는 기본값인 애니메이션으로 갑니다.", "The project kind. This guide uses the default, Animation."),
18
+ }),
19
+ spot("1.3", "[data-testid=\"start-new-project-card\"]", t("새 프로젝트", "New project"), t("빈 무대에서 시작", "Start from an empty stage"), {
20
+ caption: t("새 프로젝트는 빈 무대에서 시작합니다. 캐릭터 한 명, 카메라, 조명이 들어 있습니다.", "New project starts from an empty stage with one character, a camera and a light."),
21
+ }),
22
+ spot("1.4", "[data-testid=\"start-open-project-card\"]", t("프로젝트 열기", "Open project"), t("저장한 .cclayproject 파일", "A saved .cclayproject file"), {
23
+ hold: 3000,
24
+ caption: t("저장해 둔 .cclayproject 파일은 여기서 엽니다.", "Open project loads a saved .cclayproject file."),
25
+ }),
26
+ spot("1.5", "[data-testid=\"start-project-name\"]", t("이름", "Name"), t("비우면 생성 버튼이 꺼집니다", "Empty name disables Create"), {
27
+ caption: t("프로젝트 이름을 적습니다.", "Name the project."),
28
+ }),
29
+ { id: "1.5b", type: { into: "[data-testid=\"start-project-name\"]", text: "my_first_video" } },
30
+ spot("1.6", "button.v2-start-location", t("위치", "Location"), t("기본은 브라우저 프로젝트 저장소", "Default: Browser project store"), {
31
+ hold: 3000,
32
+ caption: t("저장 위치입니다. 기본은 이 브라우저 안의 저장소이고, 폴더를 고를 수도 있습니다.", "Where the project is stored. The default is this browser; you can pick a folder instead."),
33
+ }),
34
+ spot("1.7", t("select[aria-label=\"프레임 레이트\"]", "select[aria-label=\"Frame rate\"]"), t("프레임 레이트", "Frame rate"), t("24 fps 또는 30 fps", "24 fps or 30 fps"), {
35
+ hold: 3000,
36
+ caption: t("프레임 레이트는 24 또는 30입니다. 영화 느낌은 24를 씁니다.", "Frame rate is 24 or 30. Film-style motion uses 24."),
37
+ }),
38
+ spot("1.8", t("select[aria-label=\"단위\"]", "select[aria-label=\"Units\"]"), t("단위", "Units"), t("미터 또는 피트", "Meters or Feet"), {
39
+ hold: 3000,
40
+ caption: t("단위는 미터가 기본입니다.", "Units default to meters."),
41
+ }),
42
+ spot("1.9", "[data-testid=\"start-create\"]", t("생성", "Create"), t("프로젝트를 만들고 에디터를 엽니다", "Creates the project and opens the editor"), {
43
+ caption: t("생성을 누르면 에디터가 열립니다.", "Press Create and the editor opens."),
44
+ }),
45
+ clear("1.9b"),
46
+ { id: "1.9c", click: "[data-testid=\"start-create\"]", until: "[data-testid=\"sequencer\"]" },
47
+ { id: "1.9d", hold: 1500 },
48
+ ...castFirstCharacter("1.10", t("새 프로젝트는 빈 무대입니다. 장면 보기의 \"추가\"에서 Y Bot을 골라 캐릭터를 놓으세요.", "A new project is an empty stage. Open Add in the Scene view and pick Y Bot to place a character.")),
49
+ ],
50
+ };
@@ -0,0 +1,67 @@
1
+ // Chapter 2 · Layout tour: five regions, five modes, the shortcut list.
2
+ import { chapterCard, clear, spot, t } from "./steps.mjs";
3
+
4
+ // The WebGL canvas and its containers fill the whole window behind the
5
+ // panels, so the Scene view is the gap the panels leave around it.
6
+ const SCENE_VIEW = {
7
+ js: `(() => {
8
+ const box = (selector) => document.querySelector(selector).getBoundingClientRect();
9
+ const left = box(".studio-left-column"), right = box(".studio-right-column"), top = box("header.topbar"), dock = box(".studio-dock-slot");
10
+ return { x: left.right + 6, y: top.bottom + 6, width: right.left - left.right - 12, height: dock.top - top.bottom - 12 };
11
+ })()`,
12
+ };
13
+
14
+ export default {
15
+ id: "layout",
16
+ title: t("2 · 화면 둘러보기", "2 · Layout tour"),
17
+ steps: [
18
+ chapterCard("2.0", t("2 · 화면 둘러보기", "2 · Layout tour"), t("다섯 영역과 다섯 모드", "Five regions, five modes")),
19
+ spot("2.1", ".v2-first-success-guide", t("첫 60초", "Your first 60 seconds"), t("네 단계 체크리스트", "A four-step checklist"), {
20
+ caption: t("처음 열면 네 단계 체크리스트가 뜹니다. 이 가이드가 같은 내용을 더 자세히 다루므로 닫겠습니다.", "A four-step checklist appears on first open. This guide covers the same ground in more detail, so we close it."),
21
+ }),
22
+ clear("2.2a"),
23
+ { id: "2.2", click: ".v2-first-success-guide-close" },
24
+ spot("2.3", ".topbar-project.studio-project-head", t("프로젝트", "Project"), t("이름, 저장 상태, 프로젝트 종류", "Name, save status, project kind"), {
25
+ caption: t("왼쪽 위는 프로젝트 이름과 저장 상태입니다. 편집하면 \"저장되지 않은 변경사항\"으로 바뀝니다.", "Top left: the project name and save status. It reads \"Unsaved changes\" after any edit."),
26
+ }),
27
+ spot("2.4", "nav.menubar", t("메뉴", "Menus"), t("파일·편집·창·도움말", "File, Edit, Window, Help"), {
28
+ caption: t("파일은 새로 만들기·열기·저장·내보내기, 편집은 되돌리기·다시 실행, 창은 에이전트·워크플로, 도움말은 단축키와 정보입니다.", "File: New, Open, Save, Export. Edit: Undo, Redo. Window: Agent, Workflow. Help: Keyboard shortcuts, About."),
29
+ }),
30
+ spot("2.5", "[data-testid=\"mode-toolbar\"]", t("모드", "Modes"), t("배치 1 · 포즈 2 · 카메라 3 · 모션 4 · 조각 5", "Stage 1, Pose 2, Camera 3, Motion 4, Sculpt 5"), {
31
+ caption: t("다섯 모드는 숫자 키 1~5로 바꿉니다. 배치에서 놓고, 포즈로 고치고, 카메라로 찍고, 모션으로 다듬고, 조각으로 만듭니다.", "Five modes, keys 1 to 5. Stage places, Pose fixes, Camera shoots, Motion trims, Sculpt builds."),
32
+ }),
33
+ spot("2.6", "section.hierarchy-pane.v2-outliner", t("아웃라이너", "Outliner"), t("장면 트리: 카메라, 조명, 캐릭터, 환경, 소품", "The scene tree: camera, light, character, environment, props"), {
34
+ caption: t("왼쪽 아웃라이너는 장면에 있는 모든 것의 목록입니다. 줄을 누르면 선택됩니다.", "The Outliner on the left lists everything in the scene. Click a row to select it."),
35
+ }),
36
+ spot("2.7", SCENE_VIEW, t("장면 보기", "Scene view"), t("우클릭 드래그로 둘러보기, 휠로 확대", "Right-drag to look around, wheel to zoom"), {
37
+ caption: t("가운데가 3D 장면 보기입니다. 우클릭 드래그로 둘러보고 휠로 확대합니다.", "The center is the 3D Scene view. Right-drag to look around, wheel to zoom."),
38
+ }),
39
+ spot("2.8", ".vp-overlay-left", t("도구", "Tools"), t("추가 +, 이동 W, 회전 E, 크기 R, 스냅 5cm·5°", "Add +, Move W, Rotate E, Scale R, snap 5cm·5°"), {
40
+ caption: t("왼쪽 세로 도구 열입니다. 오브젝트 추가와 이동·회전·크기 기즈모, 격자 스냅이 있습니다.", "The tool column: add objects, the move, rotate and scale gizmos, and grid snapping."),
41
+ }),
42
+ spot("2.9", ".vp-overlay-right", t("보기", "View"), t("원근/샷 카메라, 셰이딩, 표시 토글, 비행 속도", "Views, shading, display toggles, fly speed"), {
43
+ caption: t("오른쪽 알약은 시점, 셰이딩, 격자나 가이드 표시, 카메라 비행 속도입니다.", "The pills on the right: viewpoint, shading, display toggles and fly speed."),
44
+ }),
45
+ spot("2.10", ".dock-tabs", t("애니메이션 / 에셋", "Animation / Assets"), t("타임라인 또는 콘텐츠 선반", "The timeline or the content shelf"), {
46
+ caption: t("아래 도크는 애니메이션 탭이면 타임라인, 에셋 탭이면 도형과 카드가 있는 선반입니다.", "The bottom dock shows the timeline on the Animation tab and the content shelf on Assets."),
47
+ }),
48
+ spot("2.11", "[data-testid=\"sequencer\"]", t("타임라인", "Timeline"), t("재생 컨트롤, 레인, 샷", "Transport, lanes, shots"), {
49
+ caption: t("타임라인에는 재생 컨트롤과 레인이 있습니다. 바디 레인에 모션이, 카메라 컷 레인에 샷이 놓입니다.", "The timeline holds the transport and the lanes. Motion goes on Body, shots go on Camera Cuts."),
50
+ }),
51
+ spot("2.12", ".vp-pane.vp-shot-preview", t("샷 카메라", "Shot Camera"), t("샷 카메라가 보는 화면과 화면비", "What the shot camera sees, and the aspect ratio"), {
52
+ caption: t("오른쪽 아래는 샷 카메라 미리보기입니다. 최종 영상은 이 화면으로 나갑니다.", "Bottom right is the Shot Camera preview. The final video is what this camera sees."),
53
+ }),
54
+ spot("2.13", "[data-testid=\"studio-agent-bar\"]", t("에이전트", "Agent"), t("Cmd/Ctrl+B로 열기", "Open with Cmd/Ctrl+B"), {
55
+ caption: t("에이전트는 말로 요청을 받아 장면을 바꿉니다. Cmd/Ctrl+B로 엽니다. 4장에서 씁니다.", "The Agent takes requests in plain words and edits the scene. Cmd/Ctrl+B opens it. Chapter 4 uses it."),
56
+ }),
57
+ clear("2.14a"),
58
+ {
59
+ id: "2.14",
60
+ narration: t("단축키 목록은 도움말 메뉴에 있습니다. 모드 1~4, 도구 W E R, 재생 Space, 저장 ⌘S, 키프레임 팩 ⌘E, 에이전트 ⌘B.", "The shortcut list is under Help. Modes 1 to 4, tools W E R, play Space, save ⌘S, keyframe pack ⌘E, agent ⌘B."),
61
+ menu: { open: "[data-testid=\"menu-help\"]", then: { css: ".menubar-menu .menubar-item", text: t("단축키", "Keyboard shortcuts") } },
62
+ },
63
+ spot("2.14b", ".menubar-dialog[data-dialog=\"shortcuts\"]", t("단축키", "Keyboard shortcuts"), t("도움말 메뉴에서 언제든", "From the Help menu, any time"), { hold: 5000 }),
64
+ { id: "2.14c", key: "Escape" },
65
+ clear("2.14d"),
66
+ ],
67
+ };
@@ -0,0 +1,58 @@
1
+ // Chapter 3 · Stage: the Outliner rows, select, move, add, undo, save.
2
+ import { chapterCard, clear, spot, spots, t } from "./steps.mjs";
3
+
4
+ const row = (id) => `[data-node-id="${id}"] > button.hierarchy-row`;
5
+
6
+ export default {
7
+ id: "stage",
8
+ title: t("3 · 무대", "3 · Stage"),
9
+ steps: [
10
+ chapterCard("3.0", t("3 · 무대", "3 · Stage"), t("선택, 이동, 추가, 되돌리기, 저장", "Select, move, add, undo, save")),
11
+ spot("3.1", row("shot"), t("장면 01", "SCENE 01"), t("장면의 루트", "The scene root"), { hold: 3000, caption: t("아웃라이너의 맨 위는 장면입니다.", "The top row is the scene.") }),
12
+ spot("3.2", row("camera"), t("카메라", "Camera"), t("샷 카메라", "The shot camera"), { hold: 3000, caption: t("카메라는 샷을 찍는 카메라입니다.", "Camera is the shot camera.") }),
13
+ spot("3.3", row("light"), t("조명", "Light"), t("키 라이트", "The key light"), { hold: 3000, caption: t("조명은 키 라이트입니다.", "Light is the key light.") }),
14
+ spot("3.4", row("characterA"), t("캐릭터 1", "Character 1"), t("출연자. 리그 폴더에 몸 부위가 있음", "The cast; its Rig folder holds the body parts"), {
15
+ caption: t("캐릭터 1은 출연자입니다. 아래 리그 폴더에 몸 부위 그룹이 있습니다.", "Character 1 is the cast. Its Rig folder holds the body part groups."),
16
+ }),
17
+ ...spots("3.5", [row("environment"), row("props")], t("환경 / 소품", "Environment / Props"), t("세트와 추가한 오브젝트의 폴더", "Folders for the set and for added objects"), {
18
+ caption: t("환경은 세트, 소품은 추가한 오브젝트가 들어가는 폴더입니다. 새 프로젝트에서는 비어 있습니다.", "Environment is the set, Props holds objects you add. Both are empty in a new project."),
19
+ }),
20
+ clear("3.6a"),
21
+ {
22
+ id: "3.6",
23
+ narration: t("캐릭터를 누르면 오른쪽 디테일에 속성이 나오고 장면에 기즈모가 뜹니다. 변환 섹션을 펼치면 정확한 값을 적을 수 있습니다.", "Click the character: Details on the right shows its properties and a gizmo appears. Open Transform for exact values."),
24
+ click: row("characterA"),
25
+ until: "[data-testid=\"details-selection\"]",
26
+ },
27
+ spot("3.6b", "section.inspector-pane", t("디테일", "Details"), t("선택한 항목의 속성: 변환, 리그, 포즈", "Properties of the selection: Transform, Rig, Pose")),
28
+ spot("3.7", "[data-fid=\"tool_move\"]", t("이동 (W)", "Move (W)"), t("바닥 핸들을 끌어 옮기기", "Drag the floor handle"), {
29
+ caption: t("이동 도구가 기본입니다. 바닥의 네모 핸들을 끌면 평면 위에서 움직이고, 화살표는 한 축으로만 움직입니다.", "Move is the default tool. Drag the square floor handle to slide on the ground, or an arrow for one axis."),
30
+ }),
31
+ clear("3.7b"),
32
+ { id: "3.7c", drag: { from: { js: "window.__characterGizmoHandles?.().find((handle) => handle.axis === 'plane:xz')" }, to: { dx: -120, dy: 45 } } },
33
+ { id: "3.8", narration: t("E는 회전입니다. 고리를 끌어 돌립니다.", "E is Rotate. Drag a ring to turn it."), key: "E" },
34
+ spot("3.8b", "[data-fid=\"tool_rotate\"]", t("회전 (E)", "Rotate (E)"), t("회전 기즈모", "Rotate gizmo"), { hold: 3000 }),
35
+ { id: "3.9", narration: t("R은 크기입니다. W로 이동으로 돌아옵니다.", "R is Scale. W returns to Move."), key: "R" },
36
+ spot("3.9b", "[data-fid=\"tool_scale\"]", t("크기 (R)", "Scale (R)"), t("크기 기즈모", "Scale gizmo"), { hold: 3000 }),
37
+ { id: "3.9c", key: "W" },
38
+ spot("3.10", "[data-testid=\"snap-toggle\"]", t("스냅 5cm · 5°", "Snap 5cm · 5°"), t("격자 스냅. 드래그 중 Ctrl로 반전", "Grid snapping; hold Ctrl while dragging to invert"), {
39
+ hold: 3000,
40
+ caption: t("격자 스냅이 켜져 있어 5cm, 5° 단위로 움직입니다. 끄거나 드래그 중 Ctrl을 누르면 자유롭게 움직입니다.", "Snapping is on, so moves step by 5 cm and 5°. Turn it off, or hold Ctrl while dragging, for free movement."),
41
+ }),
42
+ clear("3.11a"),
43
+ { id: "3.11", narration: t("+로 큐브 같은 기본 도형, 조명, 두 번째 캐릭터나 카메라를 추가합니다.", "+ adds primitives such as a cube, lights, and a second character or camera."), click: "[data-testid=\"viewport-add\"]" },
44
+ spot("3.11b", ".add-object-menu", t("추가", "Add"), t("기본 도형, 조명, 효과, 장면(캐릭터·카메라)", "Primitives, Lights, Effects, Scene (Character, Camera)")),
45
+ { id: "3.12", narration: t("큐브가 소품 폴더에 생기고 선택됩니다. 같은 기즈모로 옮깁니다.", "The cube lands in Props and is selected. Move it with the same gizmo."), click: ".add-object-item:has(.add-object-swatch.cube)" },
46
+ spot("3.12b", row("props"), t("소품", "Props"), t("추가한 오브젝트가 들어감", "Added objects land here")),
47
+ { id: "3.13", narration: t("모든 편집은 ⌘Z로 되돌립니다. 에이전트가 바꾼 것도 한 단계씩 되돌아갑니다.", "Every edit undoes with ⌘Z, including each change the agent makes."), key: "Mod+Z" },
48
+ spot("3.13b", "[data-testid=\"menu-edit\"]", t("편집 › 되돌리기", "Edit › Undo"), t("⌘Z / ⇧⌘Z", "⌘Z, ⇧⌘Z"), { hold: 3000 }),
49
+ {
50
+ id: "3.14",
51
+ narration: t("⌘S로 저장합니다. 브라우저 저장소에 남고, 시작 화면의 최근 목록에 나옵니다.", "⌘S saves. It stays in the browser store and shows up under Recent on the start screen."),
52
+ key: "Mod+S",
53
+ until: "[data-testid=\"project-save-status\"]:not(.status-dirty)",
54
+ },
55
+ spot("3.14b", "[data-testid=\"project-save-status\"]", t("저장됨", "Saved"), t("⌘S", "⌘S")),
56
+ clear("3.14c"),
57
+ ],
58
+ };
@@ -0,0 +1,98 @@
1
+ // Chapter 4 · Motion: the agent's sign-in card, a walk take, playback and the lanes.
2
+ import { skeletonFor } from "../../../../src/motion3/rigs/index.js";
3
+ import { chapterCard, clear, spot, spots, t } from "./steps.mjs";
4
+
5
+ // The walk from first-video.mjs, started where the character stands.
6
+ const skeleton = skeletonFor("y-bot-tpose");
7
+ const rest = (bone) => skeleton.restWorldPos[skeleton.index[bone]];
8
+ const ankle = rest("LeftFoot")[1];
9
+ const WALK = {
10
+ schema: "cozyclay.motion-spec.v1",
11
+ fps: 24,
12
+ frames: 240,
13
+ keys: [{ f: 0, set: { root: [0, rest("Hips")[1] - 0.01, 0], yaw: 0, foot_l: [0.09, ankle, -0.03], foot_r: [-0.09, ankle, 0.03], fyaw_l: 0, fyaw_r: 0, armw_r: 0 } }],
14
+ generators: [{ type: "walk", f0: 6, steps: 8, heading: [0, 0] }],
15
+ };
16
+ const COMPILE_WALK = `(() => {
17
+ const character = window.__cozyclay.characters[0], { x, z } = character;
18
+ const spec = ${JSON.stringify(WALK)};
19
+ const set = spec.keys[0].set;
20
+ for (const name of ["root", "foot_l", "foot_r"]) set[name] = [set[name][0] + x, set[name][1], set[name][2] + z];
21
+ return window.__cozyclay.runStudioAction("motion.compile", { characterId: character.id, spec });
22
+ })()`;
23
+ const BODY_LANE = ".tl-track:has(.tl-track-chip.body)";
24
+ // Root path's data-lane-label is translated in the Korean UI ("루트 경로"); its class is not.
25
+ const point = (expression) => ({ js: `(() => { const box = ${expression}.getBoundingClientRect(); return { x: box.left + box.width / 2, y: box.top + box.height / 2 }; })()` });
26
+
27
+ export default {
28
+ id: "motion",
29
+ title: t("4 · 모션", "4 · Motion"),
30
+ steps: [
31
+ chapterCard("4.0", t("4 · 모션", "4 · Motion"), t("에이전트에게 요청하고, 재생하고, 확인하기", "Ask the agent, play, check")),
32
+ {
33
+ id: "4.1",
34
+ narration: t("에이전트 패널입니다. Sign in with ChatGPT를 누르거나 다른 제공자의 키로 로그인합니다. 토큰은 이 페이지에 저장되지 않습니다.", "The Agent panel. Press Sign in with ChatGPT, or log in with another provider's key. No token is stored in the page."),
35
+ key: "Mod+B",
36
+ until: "[data-agent-card=signed-out]",
37
+ },
38
+ spot("4.1b", "[data-agent-card=signed-out]", t("모델 연결", "Connect a model"), t("ChatGPT 로그인 또는 다른 제공자", "Sign in with ChatGPT, or another provider")),
39
+ spot("4.2", "button.agent-primary-button.agent-signin", t("Sign in with ChatGPT", "Sign in with ChatGPT"), t("브라우저에서 로그인", "Signs in through your browser"), {
40
+ hold: 3000,
41
+ caption: t("로그인하면 아래 입력창에 \"캐릭터가 앞으로 걷는다\"처럼 적기만 하면 됩니다.", "Once signed in, you just type a request like \"the character walks forward\"."),
42
+ }),
43
+ clear("4.3a"),
44
+ {
45
+ id: "4.3",
46
+ narration: t("에이전트는 모션 스펙을 쓰고, 브라우저 안의 컴파일러가 모든 프레임을 계산합니다. 이 영상에서는 완성된 걷기 테이크를 바로 불러왔습니다.", "The agent writes a motion spec and a compiler in the browser bakes every frame. This video loads a finished walk take directly."),
47
+ key: "Mod+B",
48
+ },
49
+ { id: "4.3b", eval: COMPILE_WALK, until: `${BODY_LANE} .tl-motion-clip` },
50
+ spot("4.4", BODY_LANE, t("바디", "Body"), t("모션 테이크가 놓이는 레인", "The lane where the motion take lands"), {
51
+ caption: t("테이크가 바디 레인에 클립으로 올라왔습니다.", "The take landed on the Body lane as a clip."),
52
+ }),
53
+ spot("4.5", "[data-testid=\"beat-board\"]", t("구간", "Beats"), t("각 구간은 프레임 범위와 QA 체크", "Each beat is a frame range with a QA tick"), {
54
+ caption: t("클립 위의 구간마다 체크가 붙습니다. 충돌, 발 미끄러짐, 손 닿기를 검사한 결과입니다.", "Each beat on the clip carries a tick: the QA checks for collisions, foot slide and hand contact."),
55
+ }),
56
+ spot("4.6", "[data-fid=\"tl_play\"]", t("재생 (Space)", "Play (Space)"), t("재생·정지", "Play and pause"), {
57
+ caption: t("Space로 재생하고 다시 Space로 멈춥니다.", "Space plays, Space again pauses."),
58
+ }),
59
+ { id: "4.6b", key: "Space", until: ".tl-btn.play.on" },
60
+ { id: "4.6c", hold: 1500 },
61
+ { id: "4.6d", key: "Space" },
62
+ spot("4.7", "[data-testid=\"sequencer-frame-readout\"]", t("프레임", "Frame"), t("현재 / 전체 · fps", "Current / total · fps"), {
63
+ hold: 3000,
64
+ caption: t("현재 프레임, 전체 프레임, 프레임 레이트입니다.", "Current frame, total frames and frame rate."),
65
+ }),
66
+ spot("4.8", ".tl-ruler-lane", t("타임라인 탐색", "Scrub timeline"), t("눈금자를 끌어 재생 헤드 이동", "Drag the ruler to move the playhead"), {
67
+ caption: t("눈금자를 끌면 재생 헤드가 따라옵니다. 프레임 0으로 되감아 둡니다.", "Drag the ruler to move the playhead. We rewind to frame 0."),
68
+ }),
69
+ {
70
+ id: "4.8b",
71
+ drag: { from: point("document.querySelector('.tl-ruler-lane .tl-frame-box')"), to: { js: "(() => { const box = document.querySelector('.tl-ruler-lane').getBoundingClientRect(); return { x: box.left + 1, y: box.top + box.height / 2 }; })()" } },
72
+ until: { css: "[data-testid=\"sequencer-frame-readout\"]", text: "0000 /" },
73
+ },
74
+ spot("4.9", ".tl-transport", t("한 프레임 (J / K)", "One frame (J / K)"), t("뒤로 K, 앞으로 J", "Back K, forward J"), {
75
+ hold: 3000,
76
+ caption: t("J와 K로 한 프레임씩 움직입니다.", "J and K step one frame."),
77
+ }),
78
+ ...spots("4.10", ["[data-lane-label=\"Pins\"]", "[data-lane-label=\"IK keys\"]", ".tl-track-chip.root"], t("핀 / IK 키 / 루트 경로", "Pins / IK keys / Root path"), t("팔다리 고정 / 손으로 고친 포즈 키 / 바닥 경로", "Limb holds / hand-fixed pose keys / the floor path"), {
79
+ caption: t("핀은 팔다리를 한 지점에 붙잡고, IK 키는 포즈 모드에서 고친 자세를, 루트 경로는 캐릭터가 걸을 바닥 경로를 둡니다.", "Pins hold a limb on a spot, IK keys store poses fixed in Pose mode, Root path is the floor path the character walks."),
80
+ }),
81
+ spot("4.11", ".tl-btn.clear", t("모션 지우기", "Clear motion"), t("테이크를 지우고 블로킹 포즈로", "Clears the take and restores the blocking pose"), {
82
+ hold: 3000,
83
+ caption: t("모션 지우기는 테이크를 지우고 원래 포즈로 돌아갑니다.", "Clear motion removes the take and restores the pose."),
84
+ }),
85
+ spot("4.12", ".tl-btn.zoom", t("타임라인 확대", "Timeline zoom"), t("재생 속도가 아님. 클릭하면 1×", "Not playback speed; click resets to 1×"), {
86
+ hold: 3000,
87
+ caption: t("1.00×는 눈금자 확대 비율입니다. 재생 속도가 아닙니다.", "1.00× is the ruler zoom, not the playback speed."),
88
+ }),
89
+ clear("4.13a"),
90
+ { id: "4.13", narration: t("포즈 모드는 모션 위에서 IK로 자세를 고칩니다. 발 스냅은 발을 바닥에 붙이고, 바닥 접촉은 몸이 바닥을 뚫지 않게 합니다.", "Pose mode fixes a pose on top of the motion with IK. Foot snap keeps the feet planted, Body contact keeps the body above the floor."), key: "2" },
91
+ spot("4.13b", ".vp-overlay-left", t("포즈 모드 (2)", "Pose mode (2)"), t("포즈 수정 W, 고정 R, 발 스냅, 바닥 접촉", "Pose fix W, Pin R, Foot snap, Body contact")),
92
+ clear("4.14a"),
93
+ { id: "4.14", narration: t("모션 모드는 테이크를 자르고, 끝을 트림하고, 구간 배속을 바꿉니다.", "Motion mode cuts the take, trims its ends and retimes segments."), key: "4" },
94
+ spot("4.14b", [".tl-transport", "[data-testid=\"sequencer-cut\"]"], t("모션 모드 (4)", "Motion mode (4)"), t("자르기, 트림, 배속", "Cut, trim, retime")),
95
+ clear("4.15a"),
96
+ { id: "4.15", narration: t("배치 모드로 돌아갑니다.", "Back to Stage mode."), key: "1" },
97
+ ],
98
+ };
@@ -0,0 +1,47 @@
1
+ // Chapter 5 · Camera and shots: Camera mode, lens and aspect, one shot.
2
+ import { chapterCard, clear, spot, spots, t } from "./steps.mjs";
3
+
4
+ export default {
5
+ id: "camera",
6
+ title: t("5 · 카메라와 샷", "5 · Camera and shots"),
7
+ steps: [
8
+ chapterCard("5.0", t("5 · 카메라와 샷", "5 · Camera and shots"), t("샷 하나 만들기", "Make one shot")),
9
+ { id: "5.1", narration: t("카메라 모드에서는 샷 카메라를 움직이고 샷을 편집합니다.", "Camera mode moves the shot camera and edits shots."), key: "3" },
10
+ spot("5.1b", "[data-fid=\"mode_camera\"]", t("카메라 모드 (3)", "Camera mode (3)"), t("카메라 조준과 샷 편집", "Aim the camera, edit shots")),
11
+ spot("5.2", ".vp-overlay-left", t("카메라 이동 W / 회전 E", "Move camera W / Rotate camera E"), t("같은 키, 카메라에 적용", "Same keys, applied to the camera"), {
12
+ hold: 3000,
13
+ caption: t("W와 E가 이제 카메라를 움직이고 돌립니다.", "W and E now move and aim the camera."),
14
+ }),
15
+ ...spots("5.3", ["[data-testid=\"lens-value\"]", "[data-testid=\"ratio-value\"]"], t("렌즈 / 화면비", "Lens / Aspect"), t("24mm 단렌즈 또는 화각, 16:9 등", "A prime lens or field of view, 16:9 and others"), {
16
+ caption: t("렌즈는 24mm 같은 단렌즈나 화각으로 고르고, 화면비는 16:9가 기본입니다.", "Pick a prime lens such as 24mm or a field of view; the aspect ratio defaults to 16:9."),
17
+ }),
18
+ spot("5.4", [".tl-track-chip.camera", "button.tl-track-add.cut"], t("카메라 컷", "Camera Cuts"), t("샷 블록이 놓이는 레인", "The lane of shot blocks"), {
19
+ caption: t("샷은 카메라 컷 레인에 블록으로 놓입니다. 아직 없으니 +로 만듭니다.", "Shots sit on the Camera Cuts lane as blocks. None yet, so we add one with +."),
20
+ }),
21
+ clear("5.5a"),
22
+ { id: "5.5", narration: t("재생 헤드 자리에 40프레임짜리 샷 1이 생깁니다. 24fps에서 1.7초입니다.", "Shot 1 appears at the playhead, 40 frames long: 1.7 s at 24 fps."), click: "button.tl-track-add.cut", until: ".tl-shot-block" },
23
+ spot("5.5b", ".tl-shot-block", t("Shot 1", "Shot 1"), t("재생 헤드에서 시작, 40프레임", "Starts at the playhead, 40 frames")),
24
+ spot("5.6", ".tl-shot-edge.end", t("끝 길이 조절", "Resize end"), t("가장자리를 끌어 길이 변경", "Drag an edge to change the length"), {
25
+ hold: 3000,
26
+ caption: t("블록 가장자리를 끌어 길이를 바꿉니다. 블록 위 버튼으로 분할·복제·삭제합니다.", "Drag an edge to change the length. The buttons above the block split, duplicate or delete it."),
27
+ }),
28
+ spot("5.7", [".vp-pane.vp-shot-preview", ".dock-shot-ratios"], t("샷 카메라", "Shot Camera"), t("샷 1이 보는 화면", "What Shot 1 sees"), {
29
+ caption: t("샷 카메라 미리보기가 샷 1의 화면을 보여 줍니다. 아래 버튼으로 16:9, 2.39, 4:3, 1:1, 9:16을 고릅니다.", "The Shot Camera preview shows Shot 1. The buttons below pick 16:9, 2.39, 4:3, 1:1 or 9:16."),
30
+ }),
31
+ spot("5.8", "button.dock-shot-look", t("샷 시점", "Look through"), t("샷 카메라로 보기, Esc로 복귀", "See through the shot camera, Esc returns"), {
32
+ caption: t("샷 시점을 누르면 장면 보기가 샷 카메라가 됩니다. Esc로 돌아옵니다.", "Look through turns the Scene view into the shot camera. Esc returns."),
33
+ }),
34
+ clear("5.8b"),
35
+ { id: "5.8c", click: "button.dock-shot-look" },
36
+ { id: "5.8d", hold: 3000 },
37
+ { id: "5.8e", key: "Escape" },
38
+ spot("5.9", "[data-fid=\"camera_key_add\"]", t("카메라 키", "Camera key"), t("프레임을 눌러 현재 프레이밍 저장", "Click a frame to store the framing"), {
39
+ hold: 3000,
40
+ caption: t("카메라를 움직인 뒤 이 띠의 프레임을 누르면 그 프레이밍이 키로 저장됩니다. 키가 둘 이상이면 카메라가 그 사이를 움직입니다.", "After you move the camera, click a frame on this strip to key the framing. Two or more keys make the camera move between them."),
41
+ }),
42
+ ...spots("5.10", ["[data-lane-label=\"Rail\"]", "[data-lane-label=\"Crane\"]"], t("레일 / 크레인", "Rail / Crane"), t("돌리 속도 곡선 / 크레인 높이 곡선", "Dolly speed curve / crane height curve"), {
43
+ caption: t("레일은 그려 둔 레일을 따라가는 돌리 속도, 크레인은 높이 곡선입니다. 샷이 생긴 뒤에 씁니다.", "Rail is the dolly speed along a drawn rail, Crane is the height curve. You use them once a shot exists."),
44
+ }),
45
+ clear("5.10c"),
46
+ ],
47
+ };
@@ -0,0 +1,59 @@
1
+ // Chapter 6 · Export: the seven exports, then the keyframe pack itself.
2
+ import { chapterCard, clear, spot, spots, t } from "./steps.mjs";
3
+
4
+ const item = (id) => `[data-testid="${id}"]`;
5
+
6
+ export default {
7
+ id: "export",
8
+ title: t("6 · 내보내기", "6 · Export"),
9
+ steps: [
10
+ chapterCard("6.0", t("6 · 내보내기", "6 · Export"), t("키프레임 팩과 영상 모델", "The keyframe pack and the video model")),
11
+ {
12
+ id: "6.1",
13
+ narration: t("내보내기는 파일 메뉴에 있습니다. 일곱 가지를 차례로 봅니다.", "Export lives under File. Seven kinds, in order."),
14
+ menu: { open: item("menu-file"), then: item("topbar-export") },
15
+ },
16
+ spot("6.1b", item("topbar-export"), t("파일 › 내보내기", "File › Export"), t("일곱 가지", "Seven exports")),
17
+ spot("6.2", item("export-keyframe-pack"), t("키프레임 팩 (zip) ⌘E", "Keyframe pack (zip) ⌘E"), t("첫·끝 프레임, 클립, 카메라, 프롬프트", "First and last frames, clip, camera and prompt"), {
18
+ caption: t("키프레임 팩은 영상 모델에 줄 참조 묶음입니다. Shift를 누른 채 고르면 모든 샷을 내보냅니다.", "The keyframe pack is the reference bundle for a video model. Hold Shift to export every shot."),
19
+ }),
20
+ ...spots("6.3", [item("export-video"), item("export-video-shot")], t("영상 (mp4)", "Video (mp4)"), t("타임라인 전체 또는 이 샷만", "Whole timeline or this shot only"), {
21
+ caption: t("영상은 타임라인 전체를 한 mp4로, 또는 샷 하나만 렌더합니다. 에디터 UI 없이 카메라와 모션만 나옵니다.", "Video renders the whole timeline to one mp4, or one shot. Camera and motion only, no editor chrome."),
22
+ }),
23
+ ...spots("6.4", [item("export-render-passes"), item("export-depth-video")], t("뎁스 + 노멀", "Depth + normal"), t("영상 모델 컨디셔닝용", "Conditioning plates for a video model"), {
24
+ caption: t("뎁스와 노멀 패스는 현재 프레이밍의 컨디셔닝 이미지이고, 뎁스 mp4는 샷 전체의 뎁스 영상입니다.", "Depth and normal passes are conditioning plates of the framing; Depth mp4 is the depth pass of the whole shot."),
25
+ }),
26
+ ...spots("6.5", [item("export-storyboard"), item("export-otio")], t("스토리보드 / OTIO", "Storyboard / OTIO"), t("샷별 콘택트 시트 / 컷 목록", "A contact sheet per shot / the cut list"), {
27
+ caption: t("스토리보드는 모든 샷과 프롬프트를 한 장에 모은 PNG, OTIO는 편집 프로그램용 컷 목록입니다.", "Storyboard is one PNG of every shot with its prompt; OTIO is the cut list for an editing app."),
28
+ }),
29
+ clear("6.6a"),
30
+ {
31
+ id: "6.6",
32
+ narration: t("키프레임 팩이 다운로드 폴더에 내려갑니다. zip 하나에 여섯 파일이 들어 있습니다.", "The keyframe pack lands in your downloads. One zip, six files."),
33
+ click: item("export-keyframe-pack"),
34
+ until: ".toast",
35
+ },
36
+ // The toast unmounts after 2.2 s (src/ui.jsx), so the ring shows at once and only as long.
37
+ spot("6.6b", ".toast", t("내보내기 완료", "Export completed"), t("zip 하나에 6개 파일", "One zip, six files"), { hold: 2000, glide: false }),
38
+ clear("6.6c"),
39
+ {
40
+ id: "6.7",
41
+ narration: t("first.png와 last.png는 시작·끝 모습, clip.mp4는 따라야 할 움직임, camera.json은 렌즈와 화면비, prompt.txt는 샷 설명, README.txt는 사용법입니다.", "first.png and last.png are the start and end looks, clip.mp4 the motion to follow, camera.json the lens and aspect, prompt.txt the shot description, README.txt the how-to."),
42
+ hold: 7000,
43
+ },
44
+ {
45
+ id: "6.8",
46
+ narration: t("이 묶음을 Seedance 같은 영상 모델에 참조로 넣으면 모델이 추측 대신 여러분의 블로킹을 따라갑니다.", "Feed this bundle to a video model such as Seedance as references, and it follows your blocking instead of guessing."),
47
+ hold: 6000,
48
+ },
49
+ {
50
+ id: "closing",
51
+ card: {
52
+ title: t("끝", "That's the loop"),
53
+ subtitle: t("배치 → 모션 → 카메라 → 내보내기 · 문서: github.com/NomaDamas/CozyClay", "Stage → Motion → Camera → Export · Docs: github.com/NomaDamas/CozyClay"),
54
+ chapters: true,
55
+ },
56
+ hold: 4000,
57
+ },
58
+ ],
59
+ };
@@ -0,0 +1,32 @@
1
+ // Step builders for the beginner-guide chapters, so each chapter file reads
2
+ // like its storyboard table: a caption (CAP), a spotlight (SPOT), a card.
3
+
4
+ /** { ko, en } */
5
+ export const t = (ko, en) => ({ ko, en });
6
+
7
+ /** A spotlight with its callout; `caption` makes it the start of a captioned cue. */
8
+ export const spot = (id, target, label, note, { hold = 4000, caption, glide } = {}) => ({
9
+ id,
10
+ ...(caption ? { narration: caption } : {}),
11
+ spotlight: target,
12
+ ...(label ? { label } : {}),
13
+ ...(note ? { note } : {}),
14
+ hold,
15
+ ...(glide === false ? { glide } : {}),
16
+ });
17
+
18
+ /** A chapter title card (2.5 s). */
19
+ export const chapterCard = (id, title, subtitle) => ({ id, card: { title, subtitle }, hold: 2500 });
20
+
21
+ /** Spotlights in a row that share one storyboard row: the label and note split on " / " when both split evenly. */
22
+ export function spots(idPrefix, targets, label, note, { hold = 3000, caption } = {}) {
23
+ const parts = (pair) => ({ ko: pair.ko.split(" / "), en: pair.en.split(" / ") });
24
+ const labels = parts(label), notes = parts(note);
25
+ const even = [labels, notes].every((pair) => pair.ko.length === targets.length && pair.en.length === targets.length);
26
+ return targets.map((target, index) => spot(`${idPrefix}${"abcdefgh"[index]}`, target,
27
+ even ? t(labels.ko[index], labels.en[index]) : label,
28
+ even ? t(notes.ko[index], notes.en[index]) : note,
29
+ { hold, caption: index === 0 ? caption : undefined }));
30
+ }
31
+
32
+ export const clear = (id) => ({ id, spotlight: null });
@@ -0,0 +1,17 @@
1
+ // Episode "beginner-guide": install to the first keyframe pack, pointing at
2
+ // each feature before using it. One file per chapter under beginner-guide/,
3
+ // transcribed from the storyboard.
4
+ import install from "./beginner-guide/00-install.mjs";
5
+ import start from "./beginner-guide/01-start.mjs";
6
+ import layout from "./beginner-guide/02-layout.mjs";
7
+ import stage from "./beginner-guide/03-stage.mjs";
8
+ import motion from "./beginner-guide/04-motion.mjs";
9
+ import camera from "./beginner-guide/05-camera.mjs";
10
+ import exportChapter from "./beginner-guide/06-export.mjs";
11
+
12
+ export default {
13
+ title: { ko: "CozyClay 초심자 가이드", en: "CozyClay beginner guide" },
14
+ subtitle: { ko: "설치부터 첫 키프레임 팩 내보내기까지 · 7개 챕터", en: "From install to your first keyframe pack · 7 chapters" },
15
+ firstChapter: 0,
16
+ chapters: [install, start, layout, stage, motion, camera, exportChapter],
17
+ };
@@ -0,0 +1,12 @@
1
+ // A new project opens on an empty stage (#986), so an episode adds its first
2
+ // character from the Scene view's Add menu before it clicks one. The Outliner
3
+ // gives the first character the legacy row id characterA (hierarchy-model.js),
4
+ // whatever its character id; evals reach it as characters[0].
5
+ export const FIRST_CHARACTER_ROW = "characterA";
6
+ export const ADD_CHARACTER = ".add-object-menu [data-add=character]";
7
+
8
+ /** Add > Y Bot, then a hold while its rig loads. */
9
+ export const castFirstCharacter = (id, narration) => [
10
+ { id, ...(narration ? { narration } : {}), menu: { open: "[data-testid=viewport-add]", then: ADD_CHARACTER }, until: `[data-node-id="${FIRST_CHARACTER_ROW}"]` },
11
+ { id: `${id}-rig`, hold: 1500 },
12
+ ];