cozyclay 2.0.2 → 2.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 (262) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +17 -2
  3. package/THIRD_PARTY_NOTICES.md +8 -0
  4. package/bin/agent/agent-routes.mjs +211 -36
  5. package/bin/agent/agent-runner.mjs +98 -4
  6. package/bin/agent/codex-client.mjs +2 -1
  7. package/bin/agent/fal-contract.mjs +0 -17
  8. package/bin/agent/h3-preservation.mjs +6 -8
  9. package/bin/agent/provider-keys.mjs +1 -1
  10. package/bin/agent/providers.mjs +124 -3
  11. package/bin/agent/script-tool.mjs +74 -0
  12. package/bin/agent/session-store.mjs +5 -5
  13. package/bin/agent/studio-prompt.mjs +2 -1
  14. package/bin/agent/studio-rebase.mjs +314 -0
  15. package/bin/agent/studio-tools.mjs +21 -14
  16. package/bin/agent/supervisor.mjs +311 -0
  17. package/bin/agent/video-adapters.mjs +0 -7
  18. package/bin/codex-auth.mjs +4 -4
  19. package/bin/live/cli.mjs +23 -8
  20. package/bin/live/client.mjs +1 -1
  21. package/bin/live/studio.mjs +5 -2
  22. package/bin/mcp-runtime.mjs +2 -2
  23. package/bin/telemetry-state.mjs +3 -3
  24. package/dist/app/index.html +6 -6
  25. package/dist/assets/analytics-C9pMpW7s.js +1 -0
  26. package/dist/assets/app-B_2XB9rw.css +1 -0
  27. package/dist/assets/app-DAuvKPP8.js +4920 -0
  28. package/dist/assets/{demo-ByeYp-dY.js → demo-CvKuBgy5.js} +1 -1
  29. package/dist/assets/{first-shot-handoff-DgT28ZiV.js → first-shot-handoff-msLI71g1.js} +1 -1
  30. package/dist/assets/{landing-BaXkpFip.js → landing-ChUvB_zL.js} +1 -1
  31. package/dist/assets/scene-asset-sync-DKkpjKBV.js +19 -0
  32. package/dist/assets/{shot-prompt-CbqWcWYi.css → scene-asset-sync-DjJ_cJ2R.css} +1 -1
  33. package/dist/assets/{ticket-BcmsnHn5.js → ticket-BPk2Ni95.js} +1 -1
  34. package/dist/assets/workflow-jJRBR94Z.js +179 -0
  35. package/dist/cozyclay-package.json +1 -1
  36. package/dist/index.html +328 -222
  37. package/dist/llms.txt +14 -0
  38. package/dist/sitemap.xml +1 -1
  39. package/dist/workflow/index.html +4 -4
  40. package/mcp/LIVE-PROTOCOL.md +23 -2
  41. package/mcp/README.md +37 -2
  42. package/mcp/ardy-prompts.mjs +1 -1
  43. package/mcp/image-file.mjs +132 -0
  44. package/mcp/live-hub.mjs +2 -2
  45. package/mcp/package.json +1 -0
  46. package/mcp/runtime/package-lock.json +7 -0
  47. package/mcp/runtime/package.json +1 -0
  48. package/mcp/server.mjs +0 -6
  49. package/mcp/tool-handlers.mjs +289 -87
  50. package/package.json +11 -2
  51. package/src/App.jsx +813 -278
  52. package/src/agent-image-client.js +94 -0
  53. package/src/analytics.js +47 -3
  54. package/src/app-stage.jsx +129 -2745
  55. package/src/ardy/auto-physics.js +6 -16
  56. package/src/ardy/collision-blockers.js +3 -7
  57. package/src/ardy/fix-collisions.js +5 -35
  58. package/src/ardy/ik-drag.js +86 -5
  59. package/src/ardy/ik-key-json.js +3 -1
  60. package/src/ardy/ik.js +15 -13
  61. package/src/ardy/motion-calibration.js +1 -1
  62. package/src/ardy/npz.js +0 -8
  63. package/src/ardy/physics-panel.jsx +3 -2
  64. package/src/ardy/physics-review.js +3 -2
  65. package/src/ardy/physics-support.js +1 -1
  66. package/src/ardy/physics-surface.js +1 -1
  67. package/src/ardy/platform-fit-panel.jsx +2 -1
  68. package/src/ardy/playback.js +1 -5
  69. package/src/ardy/range-pin.js +2 -2
  70. package/src/ardy/root-drop.js +0 -1
  71. package/src/ardy/timeline.css +184 -6
  72. package/src/ardy/timeline.jsx +121 -36
  73. package/src/ardy/to-cskel27.js +0 -2
  74. package/src/ardy/waypoints.js +0 -3
  75. package/src/ardy-bridge-contract.js +281 -0
  76. package/src/asset-pane.jsx +19 -9
  77. package/src/auto-color.js +1 -1
  78. package/src/burn-in.js +1 -1
  79. package/src/camera-block.js +1 -1
  80. package/src/camera-follow.js +75 -32
  81. package/src/camera-move.js +0 -6
  82. package/src/camera-tutorial.jsx +6 -7
  83. package/src/character-kind.js +79 -0
  84. package/src/command-bus.js +76 -17
  85. package/src/commands/cast.js +31 -7
  86. package/src/commands/elements/object.js +1 -1
  87. package/src/commands/elements/shot.js +1 -1
  88. package/src/commands/elements.js +1 -1
  89. package/src/commands/export.js +3 -3
  90. package/src/commands/motion.js +2 -2
  91. package/src/commands/objects.js +105 -10
  92. package/src/commands/project.js +15 -2
  93. package/src/commands/sculpt-images.js +53 -0
  94. package/src/commands/shot.js +88 -10
  95. package/src/commands/view.js +10 -4
  96. package/src/coplanar-depth.js +216 -0
  97. package/src/cuts.js +7 -6
  98. package/src/domains/cast.js +60 -43
  99. package/src/domains/motion/motion-intent-codec.js +50 -0
  100. package/src/domains/motion/motion-range-pin-rules.js +23 -0
  101. package/src/domains/motion/motion-video-capture.js +664 -0
  102. package/src/domains/motion.js +253 -742
  103. package/src/domains/objects.js +106 -5
  104. package/src/domains/scenes.js +79 -28
  105. package/src/domains/shots.js +33 -7
  106. package/src/error-boundary.jsx +1 -1
  107. package/src/execution-telemetry.js +5 -5
  108. package/src/facing-marks.js +14 -1
  109. package/src/first-success-guide.jsx +35 -4
  110. package/src/gizmo-claim.js +3 -1
  111. package/src/grid-floor.jsx +1 -4
  112. package/src/hierarchy-model.js +48 -35
  113. package/src/hierarchy-panel.css +1 -0
  114. package/src/hierarchy-panel.jsx +54 -102
  115. package/src/history.js +0 -2
  116. package/src/i2v-motion-client.js +1 -4
  117. package/src/ime.js +6 -0
  118. package/src/keyframe-pack.js +15 -6
  119. package/src/line-edit.js +4 -4
  120. package/src/live-control.js +6 -6
  121. package/src/matte-editor.js +2 -8
  122. package/src/matte.js +0 -19
  123. package/src/mesh-graph-clone.js +16 -2
  124. package/src/mesh-sniff.js +0 -1
  125. package/src/monotone-cubic.js +40 -0
  126. package/src/motion-store.js +4 -4
  127. package/src/motion-trail.js +5 -11
  128. package/src/multimodel-ingest.js +1 -3
  129. package/src/object-catalog.jsx +7 -5
  130. package/src/object-gizmo.jsx +45 -31
  131. package/src/object-path.js +310 -67
  132. package/src/object-travel.js +224 -0
  133. package/src/otio.js +8 -3
  134. package/src/panels/CharacterTransformPanel.jsx +24 -6
  135. package/src/panels/ObjectTransformPanel.jsx +4 -3
  136. package/src/panels/PoseImagePanel.jsx +205 -0
  137. package/src/panels/PromptBlocksPanel.jsx +3 -1
  138. package/src/panels/ProxyFigurePanel.jsx +56 -0
  139. package/src/panels/ReferenceImageField.jsx +1 -1
  140. package/src/panels/RigControlPanel.jsx +24 -3
  141. package/src/panels/RigPanel.jsx +2 -1
  142. package/src/panels/SculptPanel.jsx +405 -0
  143. package/src/panels/SubjectBox.jsx +1 -1
  144. package/src/panels/SubjectsPanel.jsx +0 -2
  145. package/src/panels/VideoCapturePanel.jsx +2 -1
  146. package/src/panels/pose-image.css +42 -0
  147. package/src/panels/pose.css +3 -0
  148. package/src/panels/sculpt.css +162 -0
  149. package/src/plan-diagram.js +184 -0
  150. package/src/planview.jsx +127 -80
  151. package/src/playground.js +3 -5
  152. package/src/pose-fade.js +77 -0
  153. package/src/pose-gizmo.css +23 -0
  154. package/src/pose-gizmo.js +96 -0
  155. package/src/posestudio.jsx +134 -83
  156. package/src/project-browser.css +53 -0
  157. package/src/project-browser.jsx +49 -4
  158. package/src/project.js +55 -24
  159. package/src/props.jsx +177 -36
  160. package/src/proxy-figure.jsx +69 -0
  161. package/src/range-pin-panel.jsx +0 -1
  162. package/src/receipt-findings.js +20 -0
  163. package/src/render-passes.js +0 -1
  164. package/src/resource-status.jsx +1 -1
  165. package/src/root-path.js +48 -0
  166. package/src/route-owner.js +52 -0
  167. package/src/scene-asset-cache.js +1 -2
  168. package/src/scene-assets.js +12 -4
  169. package/src/scene-fbx.js +1 -1
  170. package/src/scene-mesh-cache.js +0 -1
  171. package/src/scene-objects.js +319 -19
  172. package/src/scenes.js +58 -9
  173. package/src/sculpt-blueprint.js +65 -0
  174. package/src/sculpt-compare.js +384 -0
  175. package/src/sculpt-geometry.js +141 -0
  176. package/src/sculpt-mode.js +168 -0
  177. package/src/sculpt-recipe.js +385 -0
  178. package/src/sculpt-run.js +351 -0
  179. package/src/sculpt-stage.jsx +221 -0
  180. package/src/settings-menu.jsx +1 -1
  181. package/src/shell/BoardSlot.jsx +341 -0
  182. package/src/shell/BottomDock.jsx +60 -16
  183. package/src/shell/CompositionGuideButton.jsx +20 -0
  184. package/src/shell/DetailsSlot.jsx +26 -6
  185. package/src/shell/MenuBar.jsx +78 -6
  186. package/src/shell/OutlinerSlot.jsx +3 -1
  187. package/src/shell/ShotPreviewHeader.jsx +17 -0
  188. package/src/shell/TopBar.jsx +18 -3
  189. package/src/shell/ViewportToolbar.jsx +36 -31
  190. package/src/shell/dock.css +239 -0
  191. package/src/shell/glass-regions.css +3 -2
  192. package/src/shell/log-store.js +4 -4
  193. package/src/shell/mode.css +20 -0
  194. package/src/shell/studio-shell-context.js +4 -1
  195. package/src/shell/viewport.css +12 -0
  196. package/src/shot-authoring.js +69 -6
  197. package/src/stable-items.js +1 -1
  198. package/src/stage-attach-frames.js +169 -0
  199. package/src/stage-canvas-rigs.jsx +402 -0
  200. package/src/stage-capture.jsx +216 -0
  201. package/src/stage-characters.jsx +302 -0
  202. package/src/stage-editor-gizmos.jsx +1229 -0
  203. package/src/stage-labels-ko.js +204 -0
  204. package/src/stage-startup-defaults.js +182 -0
  205. package/src/starter-scenes.js +1 -1
  206. package/src/storyboard.js +24 -5
  207. package/src/studio-actions.js +42 -9
  208. package/src/studio-agent-commands.js +213 -23
  209. package/src/studio-agent-context.js +11 -9
  210. package/src/studio-agent-motion.js +2 -1
  211. package/src/studio-agent-protocol.js +60 -24
  212. package/src/studio-app-binding.js +56 -23
  213. package/src/studio-contact-sheet.js +3 -3
  214. package/src/studio-elements.js +11 -2
  215. package/src/studio-geometry-facts.js +114 -0
  216. package/src/styles.css +92 -9
  217. package/src/ui-scrub.js +4 -4
  218. package/src/ui.jsx +6 -4
  219. package/src/use-asset-shelf.js +60 -0
  220. package/src/use-camera-tutorial.js +50 -0
  221. package/src/use-embed-bridge.js +12 -0
  222. package/src/use-first-success-prompt.js +7 -0
  223. package/src/use-i2v-capture-modal.js +15 -0
  224. package/src/use-sculpt-mode.js +95 -0
  225. package/src/workflow/AgentPanel.jsx +106 -18
  226. package/src/workflow/WorkflowBuilder.jsx +1 -1
  227. package/src/workflow/agent-client.js +113 -45
  228. package/src/workflow/agent-panel.css +35 -0
  229. package/src/workflow/attachment-image.js +1 -1
  230. package/src/workflow/canvas-commands.js +3 -3
  231. package/src/workflow/cozy-scene-node.js +3 -3
  232. package/src/workflow/local-workflow.js +2 -2
  233. package/src/workflow/motion-input.js +3 -3
  234. package/src/workflow/node-schema.js +1 -1
  235. package/src/workflow/scene-asset-sync.js +2 -9
  236. package/src/workflow/shot-prompt-node.js +1 -1
  237. package/src/workflow/video-contract.js +1 -1
  238. package/src/workflow/workflow-resources.js +0 -8
  239. package/src/workflow-send.js +87 -0
  240. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  241. package/tools/check-eslint.mjs +57 -0
  242. package/tools/check-knip.mjs +60 -0
  243. package/tools/check-loc.mjs +54 -0
  244. package/tools/demo-worker/api-client.mjs +7 -13
  245. package/tools/demo-worker/generate.mjs +2 -4
  246. package/tools/eslint-baseline.json +23 -0
  247. package/tools/gt-render/browser.mjs +23 -0
  248. package/tools/headless-studio.mjs +85 -0
  249. package/tools/kimodo/effector-constraints.mjs +4 -4
  250. package/tools/kimodo/generate.mjs +1 -1
  251. package/tools/knip-baseline.json +331 -0
  252. package/tools/loc-baseline.json +126 -0
  253. package/tools/process-supervisor.mjs +1 -1
  254. package/tools/projflow/replay.mjs +4 -4
  255. package/tools/projflow/runner.mjs +3 -3
  256. package/tools/qa/studio-control-count.mjs +2 -0
  257. package/tools/run-tests.mjs +95 -7
  258. package/dist/assets/analytics-MVZOEW8O.js +0 -1
  259. package/dist/assets/app-BxBLC6GT.js +0 -4876
  260. package/dist/assets/app-CJdxvnrl.css +0 -1
  261. package/dist/assets/shot-prompt-hN0j_O-p.js +0 -17
  262. package/dist/assets/workflow-B5XZwVfe.js +0 -179
package/dist/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <meta name="description" content="Free, open source previs software that runs in the browser. Block a scene, pose characters, and author camera moves and cuts, then hand the same shots to an AI video model. No Blender, no Unity — one command." />
7
- <meta name="theme-color" content="#232323" />
7
+ <meta name="theme-color" content="#f5f5f7" />
8
8
  <meta name="apple-mobile-web-app-title" content="CozyClay" />
9
9
  <link rel="canonical" href="https://cozyclay.org/" />
10
10
  <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
@@ -59,13 +59,18 @@
59
59
 
60
60
  <style>
61
61
  :root {
62
- --bg: #1b1b1b;
63
- --panel: #232323;
64
- --line: #343434;
65
- --ink: #ece7de;
66
- --muted: #a9a29a;
67
- --clay: #f2eee6;
68
- --accent: #d9a066;
62
+ --bg: #f5f5f7;
63
+ --alt: #ececf0;
64
+ --panel: #ffffff;
65
+ --line: #d2d2d7;
66
+ --line-strong: #c7c7cc;
67
+ --ink: #1d1d1f;
68
+ --muted: #636366;
69
+ --accent: #0066cc;
70
+ --ok: #2f6b3a;
71
+ --shadow-card: 0 1px 2px rgba(0, 0, 0, .04), 0 8px 24px rgba(0, 0, 0, .06);
72
+ --shadow-window: 0 2px 6px rgba(0, 0, 0, .06), 0 30px 60px rgba(0, 0, 0, .18);
73
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
69
74
  }
70
75
  * { box-sizing: border-box; }
71
76
  html { scroll-behavior: smooth; }
@@ -73,237 +78,271 @@
73
78
  margin: 0;
74
79
  background: var(--bg);
75
80
  color: var(--ink);
76
- font: 16px/1.65 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
81
+ font: 17px/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
77
82
  -webkit-font-smoothing: antialiased;
78
83
  }
79
84
  a { color: var(--accent); }
80
- .wrap { max-width: 980px; margin: 0 auto; padding: 0 20px; }
85
+ button { font-family: inherit; }
86
+ :focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
87
+ .wrap { max-width: 1160px; margin: 0 auto; padding: 0 24px; }
88
+ .wrap.narrow { max-width: 820px; }
89
+
90
+ /* Type scale: 64 / 36 / 19 / 17 / 14. Nothing else. */
91
+ h1 { font-size: clamp(40px, 6vw, 64px); line-height: 1.05; letter-spacing: -.02em; font-weight: 700; margin: 0; max-width: 820px; }
92
+ h1 em { font-style: normal; color: var(--accent); }
93
+ h2 { font-size: clamp(28px, 3.4vw, 36px); line-height: 1.15; letter-spacing: -.01em; font-weight: 700; margin: 0 0 32px; }
94
+ h3 { font-size: 19px; font-weight: 600; margin: 0; }
95
+ .small, figcaption { font-size: 14px; color: var(--muted); }
96
+ code, pre, kbd { font-family: var(--mono); font-size: 14px; }
97
+
81
98
  header.site {
99
+ position: sticky; top: 0; z-index: 10;
100
+ background: rgba(245, 245, 247, .8);
101
+ backdrop-filter: saturate(180%) blur(20px); -webkit-backdrop-filter: saturate(180%) blur(20px);
82
102
  border-bottom: 1px solid var(--line);
83
- position: sticky; top: 0; background: rgba(27,27,27,.9);
84
- backdrop-filter: blur(8px); z-index: 10;
85
- }
86
- header.site .wrap {
87
- display: flex; align-items: center; gap: 16px;
88
- min-height: 60px; flex-wrap: wrap;
89
103
  }
90
- .brand { font-weight: 700; letter-spacing: -.01em; color: var(--clay); text-decoration: none; font-size: 18px; }
91
- header.site nav { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; }
92
- header.site nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
104
+ header.site .wrap { display: flex; align-items: center; justify-content: space-between; gap: 8px 16px; flex-wrap: wrap; padding-top: 10px; padding-bottom: 10px; }
105
+ .brand { display: flex; align-items: center; gap: 10px; min-height: 44px; font-weight: 600; color: var(--ink); text-decoration: none; }
106
+ .brand img { width: 32px; height: auto; border-radius: 8px; background: #fff; padding: 3px 2px; }
107
+ header.site nav { display: flex; align-items: center; gap: 4px 24px; flex-wrap: wrap; font-size: 14px; }
108
+ header.site nav a { display: flex; align-items: center; min-height: 44px; color: var(--muted); text-decoration: none; }
93
109
  header.site nav a:hover { color: var(--ink); }
110
+ header.site nav a.btn, header.site nav a.btn:hover { color: #fff; font-size: 14px; min-height: 44px; padding: 0 16px; }
94
111
 
95
- .hero { padding: 48px 0 40px; }
96
- h1 { font-size: clamp(30px, 5.2vw, 46px); line-height: 1.12; margin: 0 0 18px; letter-spacing: -.02em; color: var(--clay); }
97
- .lede { font-size: clamp(17px, 2.2vw, 20px); color: var(--muted); margin: 0 0 28px; max-width: 44em; }
98
- .cta { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
99
112
  .btn {
100
- display: inline-block; padding: 12px 20px; border-radius: 8px;
101
- text-decoration: none; font-weight: 600; font-size: 15px; border: 1px solid var(--line);
113
+ display: inline-flex; align-items: center; justify-content: center;
114
+ min-height: 48px; padding: 0 24px; border-radius: 8px; border: 1px solid transparent;
115
+ font-size: 17px; font-weight: 600; text-decoration: none; cursor: pointer;
116
+ transition: filter .15s, transform .15s;
102
117
  }
103
- .btn.primary { background: var(--clay); color: #1b1b1b; border-color: var(--clay); }
104
- .btn.ghost { color: var(--ink); }
105
- .note { color: var(--muted); font-size: 14px; margin: 0; }
106
-
107
- figure { margin: 40px 0; }
108
- figure img { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: 10px; }
109
- figcaption { color: var(--muted); font-size: 13px; margin-top: 10px; }
110
-
111
- /* The mark is blue on white and the eyes are knocked out of the fill, so
112
- it sits on a light plate rather than straight on the dark page. */
113
- .hero-head { display: flex; align-items: center; gap: 18px; margin: 0 0 16px; }
114
- .mark {
115
- width: 72px; height: auto; flex: none; border: 0;
116
- border-radius: 12px; background: var(--clay); padding: 6px 5px;
117
- }
118
- .hero-head h1 { margin: 0; }
119
-
120
- .reels {
121
- display: grid;
122
- grid-template-columns: 1.778fr 1.167fr;
123
- gap: 16px;
124
- align-items: start;
125
- margin: 0 0 30px;
118
+ .btn:hover { filter: brightness(1.08); }
119
+ .btn:active { transform: scale(.98); }
120
+ .btn.primary { background: var(--accent); color: #fff; }
121
+ .btn.ghost { background: var(--panel); color: var(--ink); border-color: var(--line-strong); }
122
+ .cta { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
123
+
124
+ .hero { padding: 80px 0 48px; }
125
+ .hero .wrap { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 24px; }
126
+ .lede { margin: 0; color: var(--muted); }
127
+ .cmd {
128
+ display: inline-flex; align-items: center; gap: 4px; padding: 0 4px 0 14px;
129
+ background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
126
130
  }
127
- .reel {
128
- margin: 0;
129
- border: 1px solid var(--line);
130
- border-radius: 12px;
131
- overflow: hidden;
132
- background: #000;
133
- box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
131
+ .cmd .copy { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; background: none; border: 0; color: var(--muted); cursor: pointer; }
132
+ .cmd .copy .ic-done { display: none; color: var(--ok); }
133
+ .cmd .copy[data-state="copied"] .ic-copy { display: none; }
134
+ .cmd .copy[data-state="copied"] .ic-done { display: block; }
135
+
136
+ .window { border: 1px solid var(--line-strong); border-radius: 12px; overflow: hidden; background: var(--panel); box-shadow: var(--shadow-window); }
137
+ .titlebar { display: flex; align-items: center; gap: 8px; height: 40px; padding: 0 14px; background: #e8e8ed; border-bottom: 1px solid var(--line); }
138
+ .titlebar i { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; }
139
+ .titlebar i:nth-child(2) { background: #febc2e; }
140
+ .titlebar i:nth-child(3) { background: #28c840; }
141
+ .titlebar span { flex: 1; text-align: center; margin-right: 52px; font-size: 14px; font-weight: 600; color: var(--muted); }
142
+
143
+ .proof { padding: 0 0 96px; }
144
+ .proof figure { margin: 0; }
145
+ .proof video { display: block; width: 100%; height: auto; background: #000; }
146
+ .proof figcaption { margin-top: 16px; text-align: center; }
147
+
148
+ section { padding: 88px 0; border-top: 1px solid var(--line); }
149
+ section.alt { background: var(--alt); }
150
+ section.hero, section.proof { border-top: 0; }
151
+
152
+ .cards { display: flex; flex-wrap: wrap; gap: 20px; }
153
+ .card {
154
+ flex: 1 1 240px; min-width: 0; padding: 24px;
155
+ background: var(--panel); border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--shadow-card);
134
156
  }
135
- .reel video { display: block; width: 100%; height: auto; }
136
- .reel-note { color: var(--muted); font-size: 13px; margin: 10px 0 0; }
137
- .reel-note .chip {
138
- display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px;
139
- border: 1px solid var(--line); color: var(--muted); text-decoration: none; font-size: 12px;
157
+ .card p { margin: 4px 0 0; color: var(--muted); }
158
+ .card .n { display: block; margin-bottom: 16px; font-size: clamp(28px, 3.4vw, 36px); font-weight: 700; line-height: 1; color: var(--accent); }
159
+ a.card { color: var(--ink); text-decoration: none; font-size: 19px; font-weight: 600; min-height: 96px; transition: transform .2s; }
160
+ a.card:hover { transform: translateY(-2px); }
161
+
162
+ /* Playground. The ids and data-state values belong to the tutorial script below. */
163
+ /* The steps sit in the window's title bar so the studio gets the full
164
+ content width and height; started, the demo scrolls up to fill the screen. */
165
+ .demo { scroll-margin-top: 76px; }
166
+ .demo .window { min-width: 0; box-shadow: 0 2px 6px rgba(0, 0, 0, .06), 0 24px 48px rgba(0, 0, 0, .14); }
167
+ .tutorial-steps { flex: 1; min-width: 0; display: flex; justify-content: center; gap: 4px; margin: 0 52px 0 0; padding: 0; list-style: none; overflow: hidden; }
168
+ .tutorial-steps[hidden] { display: none; }
169
+ .tutorial-steps li {
170
+ display: flex; align-items: center; gap: 6px; min-height: 28px; padding: 0 10px; border-radius: 999px; white-space: nowrap;
171
+ background: var(--panel); border: 1px solid var(--line); color: var(--ink); font-size: 14px;
172
+ opacity: .55; transition: opacity .2s, background .15s;
140
173
  }
141
- .reel-note .chip:hover { color: var(--ink); border-color: var(--muted); }
174
+ .playground-live .tutorial-steps li { opacity: 1; }
175
+ .tutorial-steps li[data-done="1"] { color: var(--ok); border-color: var(--ok); }
176
+ .tutorial-steps li[data-done="1"]::after { content: "✓"; font-weight: 700; }
177
+ .tutorial-steps li[data-current="1"] { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
142
178
 
143
179
  .playground {
144
180
  position: relative;
145
- height: clamp(480px, 70vh, 720px);
146
- border: 1px solid var(--line);
147
- border-radius: 12px;
181
+ /* Header 64 + margin 12 + title bar 40 + bottom bar 52 + 12. */
182
+ height: clamp(480px, calc(100vh - 180px), 900px);
148
183
  overflow: hidden;
149
- background: #000 center / cover no-repeat;
150
- box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
184
+ background: #1b1b1b center / cover no-repeat;
151
185
  }
152
186
  .playground iframe { display: block; width: 100%; height: 100%; border: 0; background: #1b1b1b; }
153
187
  .playground-cover {
154
188
  position: absolute; inset: 0; display: flex; flex-direction: column;
155
189
  align-items: center; justify-content: center; gap: 12px;
156
- background: rgba(0, 0, 0, .35); cursor: pointer; border: 0; color: var(--ink);
190
+ background: rgba(245, 245, 247, .55); cursor: pointer; border: 0; color: var(--ink);
157
191
  font: inherit; padding: 0; width: 100%;
158
192
  }
159
- .playground-cover:hover { background: rgba(0, 0, 0, .25); }
193
+ .playground-cover:hover { background: rgba(245, 245, 247, .4); }
160
194
  .playground-cover[hidden] { display: none; }
195
+ .playground-cover .btn { pointer-events: none; }
196
+ .playground-cover small { font-size: 14px; font-weight: 600; color: var(--ink); }
161
197
  .playground[data-state="loading"] iframe { opacity: 0; }
162
198
  .playground-loading {
163
199
  position: absolute; inset: 0; display: none; align-items: center; justify-content: center;
164
- color: var(--ink); font-size: 14px; background: rgba(0, 0, 0, .45); pointer-events: none;
200
+ color: #fff; font-size: 14px; background: rgba(0, 0, 0, .45); pointer-events: none;
165
201
  }
166
202
  .playground[data-state="loading"] .playground-loading { display: flex; }
167
- .playground-cover .btn { pointer-events: none; }
168
- .playground-cover small { color: var(--ink); font-size: 13px; opacity: .85; }
203
+ .playground-phone { display: none; }
169
204
  .playground-bar {
170
- display: none; gap: 14px; align-items: center; flex-wrap: wrap;
171
- margin: 10px 0 0; font-size: 13px; color: var(--muted);
205
+ display: none; gap: 8px 12px; align-items: center; flex-wrap: wrap;
206
+ padding: 10px 14px; border-top: 1px solid var(--line); background: #e8e8ed;
207
+ font-size: 14px; color: var(--muted);
172
208
  }
173
- .playground-bar a { color: var(--muted); }
174
209
  .playground[data-state="live"] + .playground-bar,
175
210
  .playground[data-state="loading"] + .playground-bar { display: flex; }
176
- .playground-bar button {
177
- background: none; border: 1px solid var(--line); color: var(--ink);
178
- border-radius: 6px; padding: 5px 10px; font: inherit; font-size: 13px; cursor: pointer;
211
+ .playground-bar a { color: var(--muted); }
212
+ .playground-bar button, button.copy {
213
+ min-height: 32px; padding: 0 12px; border-radius: 6px; cursor: pointer;
214
+ background: var(--panel); border: 1px solid var(--line-strong); color: var(--ink); font-size: 14px;
179
215
  }
180
216
  .playground-bar .spacer { flex: 1; }
181
- .playground-phone { display: none; }
217
+ button.copy[data-state="copied"] { border-color: var(--ok); color: var(--ok); }
182
218
 
183
- .tutorial-steps { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; padding: 0; list-style: none; }
184
- .tutorial-steps li {
185
- font-size: 13px; color: var(--muted); border: 1px solid var(--line); border-radius: 999px;
186
- padding: 4px 11px; background: var(--panel); opacity: .55; transition: opacity .2s;
187
- }
188
- .playground-live .tutorial-steps li { opacity: 1; }
189
- .tutorial-steps li[data-done="1"] { color: var(--ink); border-color: #4f7a4a; background: #24301f; }
190
- .tutorial-steps li[data-done="1"]::before { content: "✓ "; color: #8fd47f; }
191
- .tutorial-steps li[data-current="1"] { color: var(--clay); border-color: var(--accent); }
192
219
  .tutorial-card {
193
220
  position: absolute; left: 14px; top: 14px; z-index: 2; max-width: 46%;
194
221
  display: flex; gap: 10px; align-items: baseline;
195
222
  padding: 10px 14px; border-radius: 10px; pointer-events: none;
196
- background: rgba(20, 20, 20, .82); border: 1px solid var(--line); color: var(--clay);
197
- font-size: 15px; line-height: 1.45; backdrop-filter: blur(6px);
223
+ background: rgba(255, 255, 255, .92); border: 1px solid var(--line); color: var(--ink);
224
+ font-size: 17px; line-height: 1.45; backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
225
+ box-shadow: var(--shadow-card);
198
226
  }
199
227
  .tutorial-card[hidden] { display: none; }
200
- .tutorial-card-step { color: var(--accent); font-size: 12px; font-weight: 600; white-space: nowrap; }
201
- .tutorial-card kbd[data-done="1"] { border-color: #4f7a4a; color: #8fd47f; background: #24301f; }
228
+ .tutorial-card-step { color: var(--accent); font-size: 14px; font-weight: 600; white-space: nowrap; }
202
229
  .tutorial-card b { color: var(--accent); font-weight: 600; }
203
230
  .tutorial-card kbd, .tutorial-done kbd {
204
- font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace; padding: 3px 6px; border-radius: 5px;
205
- border: 1px solid var(--line); background: var(--panel); color: var(--ink);
231
+ padding: 2px 6px; border-radius: 5px;
232
+ border: 1px solid var(--line-strong); background: var(--bg); color: var(--ink);
206
233
  }
207
- .tutorial-done { flex-basis: 100%; margin: 0 0 4px; font-size: 14px; color: var(--clay); display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
208
- .tutorial-done a { color: var(--accent); }
209
- .related { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
210
- .related a { display: block; background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; text-decoration: none; color: var(--clay); font-weight: 600; }
211
- .related a small { display: block; color: var(--muted); margin-top: 4px; font-weight: 400; }
212
- .related a:hover { border-color: var(--muted); }
234
+ .tutorial-card kbd[data-done="1"] { border-color: var(--ok); color: var(--ok); background: #e6f2e7; }
235
+ .tutorial-done { flex-basis: 100%; margin: 0 0 4px; color: var(--ink); display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
213
236
  .tutorial-done[hidden] { display: none; }
214
- .tutorial-steps[hidden] { display: none; }
215
237
  .tutorial-done p { flex-basis: 100%; margin: 0; }
216
238
  #playground-download-status, #playground-download-error { flex-basis: 100%; margin: 0; }
217
- .playground-bar button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
218
- button.copy {
219
- background: var(--panel); border: 1px solid var(--line); color: var(--ink);
220
- border-radius: 6px; padding: 5px 10px; font: inherit; font-size: 13px; cursor: pointer;
221
- }
222
- button.copy[data-state="copied"] { border-color: #4f7a4a; color: #8fd47f; }
223
- pre.with-copy { position: relative; }
224
- pre.with-copy .copy { position: absolute; right: 10px; top: 10px; }
225
- @media (max-width: 720px), (hover: none) {
226
- .playground-cover .btn, .playground-cover small { display: none; }
227
- .playground-phone { display: block; color: var(--ink); font-size: 14px; padding: 0 24px; text-align: center; }
228
- .playground-cover { cursor: default; }
229
- }
230
-
231
- section { padding: 40px 0; border-top: 1px solid var(--line); }
232
- h2 { font-size: clamp(22px, 3.2vw, 28px); margin: 0 0 8px; color: var(--clay); letter-spacing: -.01em; }
233
- h3 { font-size: 17px; margin: 0 0 6px; color: var(--clay); }
234
- .sub { color: var(--muted); margin: 0 0 26px; max-width: 48em; }
239
+ .demo-note { margin: 24px 0 0; }
235
240
 
236
- .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
237
- @media (min-width: 900px) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
238
- @media (max-width: 520px) { .grid { grid-template-columns: 1fr; } }
239
- .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 18px; }
240
- .card p { margin: 0; color: var(--muted); font-size: 14px; }
241
-
242
- pre {
243
- background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
244
- padding: 16px 18px; overflow-x: auto; font-size: 14px; line-height: 1.7;
241
+ /* Quick start */
242
+ .start { display: flex; flex-wrap: wrap; gap: 32px 40px; }
243
+ .start-head { flex: 1 1 280px; }
244
+ .start-head h2 { margin-bottom: 8px; }
245
+ .start-head p { margin: 0 0 8px; color: var(--muted); }
246
+ .start-head a { display: inline-flex; align-items: center; min-height: 44px; font-size: 14px; text-decoration: none; }
247
+ .start-body { flex: 999 1 480px; min-width: 0; display: flex; flex-direction: column; gap: 16px; }
248
+ .seg { align-self: flex-start; display: flex; gap: 2px; padding: 2px; background: #dcdce1; border-radius: 10px; }
249
+ .seg button {
250
+ min-height: 44px; padding: 0 18px; border: 0; border-radius: 8px; background: transparent;
251
+ color: var(--ink); font-size: 14px; font-weight: 600; cursor: pointer; transition: background .15s;
252
+ }
253
+ .seg button[aria-pressed="true"] { background: var(--panel); box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
254
+ .code {
255
+ position: relative; min-height: 140px; padding: 16px 96px 16px 20px;
256
+ background: var(--panel); border: 1px solid var(--line); border-radius: 16px; box-shadow: var(--shadow-card);
245
257
  }
246
- code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
258
+ .code[hidden] { display: none; }
259
+ .code pre { margin: 0; overflow-x: auto; line-height: 1.7; }
260
+ .code .copy { position: absolute; right: 12px; top: 12px; min-height: 44px; padding: 0 14px; border: 0; background: none; color: var(--accent); font-weight: 600; }
247
261
 
248
- table { width: 100%; border-collapse: collapse; font-size: 15px; }
262
+ table { width: 100%; border-collapse: collapse; }
249
263
  th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
250
- th { color: var(--clay); font-weight: 600; white-space: nowrap; }
264
+ th { font-weight: 600; white-space: nowrap; }
251
265
  td { color: var(--muted); }
252
266
  .scroller { overflow-x: auto; }
253
267
 
254
- details { border-bottom: 1px solid var(--line); padding: 16px 0; }
255
- #controls { padding: 8px 0 0; border-top: 0; }
256
- #controls details { padding: 8px 0 16px; }
257
- #controls .scroller { margin-top: 12px; }
258
- details summary { cursor: pointer; font-weight: 600; color: var(--clay); font-size: 16px; }
259
- details p { color: var(--muted); margin: 12px 0 0; }
268
+ details { border-bottom: 1px solid var(--line); padding: 6px 0; }
269
+ details summary { display: flex; align-items: center; min-height: 44px; cursor: pointer; font-weight: 600; }
270
+ details p { color: var(--muted); margin: 0 0 12px; }
271
+ #controls { padding: 0 0 88px; border-top: 0; }
272
+ #controls .scroller { margin: 12px 0; }
273
+ #faq h2 { margin-bottom: 16px; }
274
+
275
+ .closing .wrap { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 24px; }
276
+ .closing h2 { margin: 0; }
260
277
 
261
- footer.site { border-top: 1px solid var(--line); padding: 32px 0 56px; color: var(--muted); font-size: 14px; }
262
- footer.site a { color: var(--muted); }
278
+ footer.site { border-top: 1px solid var(--line); padding: 20px 0 32px; color: var(--muted); font-size: 14px; }
279
+ footer.site .wrap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px 24px; }
280
+ footer.site p { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 20px; }
281
+ footer.site a { display: inline-flex; align-items: center; min-height: 44px; color: var(--muted); text-decoration: none; }
282
+ footer.site a:hover { color: var(--ink); }
263
283
 
284
+ @media (max-width: 720px), (hover: none) {
285
+ .playground-cover .btn, .playground-cover small { display: none; }
286
+ .playground-phone { display: block; font-size: 14px; padding: 0 24px; text-align: center; }
287
+ .playground-cover { cursor: default; }
288
+ /* No playground here, so no steps squeezed into the title bar. */
289
+ .tutorial-steps { visibility: hidden; }
290
+ }
264
291
  @media (max-width: 720px) {
265
- .reels { grid-template-columns: 1fr; }
292
+ .tutorial-card { max-width: 90%; }
293
+ section { padding: 64px 0; }
294
+ .hero { padding: 56px 0 40px; }
295
+ .proof { padding-bottom: 64px; }
296
+ .cta .btn { width: 100%; }
297
+ .code { padding-right: 20px; padding-top: 60px; }
266
298
  }
267
- @media (max-width: 620px) {
268
- .hero { padding: 40px 0 28px; }
269
- .hero-head { align-items: flex-start; }
270
- .tutorial-card { max-width: 90%; font-size: 14px; }
271
- .btn { width: 100%; text-align: center; }
272
- header.site nav { gap: 14px; width: 100%; margin-left: 0; padding-bottom: 10px; }
299
+ @media (prefers-reduced-motion: reduce) {
300
+ html { scroll-behavior: auto; }
301
+ .btn, a.card, .seg button, .tutorial-steps li { transition: none; }
273
302
  }
274
303
  </style>
275
- <script type="module" crossorigin src="/assets/landing-BaXkpFip.js"></script>
276
- <link rel="modulepreload" crossorigin href="/assets/analytics-MVZOEW8O.js">
277
- <link rel="modulepreload" crossorigin href="/assets/first-shot-handoff-DgT28ZiV.js">
304
+ <script type="module" crossorigin src="/assets/landing-ChUvB_zL.js"></script>
305
+ <link rel="modulepreload" crossorigin href="/assets/analytics-C9pMpW7s.js">
306
+ <link rel="modulepreload" crossorigin href="/assets/first-shot-handoff-msLI71g1.js">
278
307
  </head>
279
308
  <body>
280
309
  <header class="site">
281
310
  <div class="wrap">
282
- <a class="brand" href="/">CozyClay</a>
311
+ <a class="brand" href="/"><img src="/media/cozyclay-logo.png" width="880" height="714" alt="" />CozyClay</a>
283
312
  <nav>
284
- <a href="#try">Try it</a>
313
+ <a href="#how">How it works</a>
314
+ <a href="#try">Demo</a>
285
315
  <a href="#start">Quick start</a>
286
- <a href="/greybox-to-video/">Greybox to video</a>
287
- <a href="/previs-software/">Previs software</a>
288
316
  <a href="https://github.com/NomaDamas/CozyClay">GitHub</a>
317
+ <a class="btn primary" href="#try">Try it</a>
289
318
  </nav>
290
319
  </div>
291
320
  </header>
292
321
 
293
- <main class="wrap">
294
- <div class="hero">
295
- <div class="hero-head">
296
- <img class="mark" src="/media/cozyclay-logo.png" width="880" height="714" alt="CozyClay" />
297
- <h1>Block the shot before you pay to render it</h1>
322
+ <main>
323
+ <section class="hero">
324
+ <div class="wrap">
325
+ <h1>Block the shot before you <em>pay to render it</em></h1>
326
+ <p class="lede">Free, open source previs in your browser.</p>
327
+ <div class="cta">
328
+ <a class="btn primary" href="#try">Try it in your browser</a>
329
+ <a class="btn ghost" href="https://github.com/NomaDamas/CozyClay">View on GitHub</a>
330
+ </div>
331
+ <span class="cmd">
332
+ <code>npx cozyclay --via site</code>
333
+ <button type="button" class="copy" data-copy="npx cozyclay --via site" aria-label="Copy command">
334
+ <svg class="ic-copy" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15V6a2 2 0 0 1 2-2h9"></path></svg>
335
+ <svg class="ic-done" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12l5 5 9-10"></path></svg>
336
+ </button>
337
+ </span>
298
338
  </div>
299
- <p class="lede">
300
- Free, open source previs in your browser. Block the scene, pose the characters,
301
- set the camera, then hand that exact shot to an AI video model.
302
- </p>
339
+ </section>
303
340
 
304
- <div class="reels">
305
- <div>
306
- <div class="reel">
341
+ <section class="proof">
342
+ <div class="wrap">
343
+ <figure>
344
+ <div class="window">
345
+ <div class="titlebar" aria-hidden="true"><i></i><i></i><i></i><span>CozyClay</span></div>
307
346
  <video
308
347
  src="/media/cozyclay-demo.mp4"
309
348
  poster="/media/cozyclay-demo-poster.jpg"
@@ -311,38 +350,44 @@
311
350
  autoplay muted loop playsinline preload="metadata"
312
351
  ></video>
313
352
  </div>
314
- <p class="reel-note">Greybox left of the wipe, generated result right. Same camera, every cut.</p>
315
- </div>
316
- <div>
317
- <div class="reel">
318
- <video
319
- src="/media/cozyclay-seedance-vs-greybox.mp4"
320
- poster="/media/cozyclay-seedance-vs-greybox-poster.jpg"
321
- width="1104" height="946"
322
- autoplay muted loop playsinline preload="metadata"
323
- ></video>
324
- </div>
325
- <p class="reel-note">Top: blocked here. Bottom: Seedance 2.5, same camera. <a class="chip" href="https://x.com/Yun_HDY/status/2091577179914338583">the post ↗</a></p>
326
- </div>
353
+ <figcaption>Greybox left, generated right. Same camera, every cut.</figcaption>
354
+ </figure>
327
355
  </div>
356
+ </section>
328
357
 
329
- <div class="cta">
330
- <a class="btn primary" href="#try">Try it in your browser</a>
331
- <a class="btn ghost" href="https://github.com/NomaDamas/CozyClay">View on GitHub</a>
358
+ <section id="how">
359
+ <div class="wrap">
360
+ <h2>How it works</h2>
361
+ <div class="cards">
362
+ <div class="card">
363
+ <span class="n">1</span>
364
+ <h3>Block the scene</h3>
365
+ <p>Set pieces and characters.</p>
366
+ </div>
367
+ <div class="card">
368
+ <span class="n">2</span>
369
+ <h3>Set the camera</h3>
370
+ <p>Moves and cuts on a timeline.</p>
371
+ </div>
372
+ <div class="card">
373
+ <span class="n">3</span>
374
+ <h3>Hand off the shot</h3>
375
+ <p>Export the move, not a prompt.</p>
376
+ </div>
377
+ </div>
332
378
  </div>
333
- <p class="note">
334
- Or run it on your machine: <code>npx cozyclay --via site</code>. No Blender, no Unity. Open a saved <code>.cclayproject</code> from the first dialog.
335
- </p>
336
- </div>
379
+ </section>
337
380
 
338
- <section id="try">
381
+ <section id="try" class="alt">
382
+ <div class="wrap">
339
383
  <h2>Try it</h2>
340
- <p class="sub">The city block from the reels, live. Seven moves: fly the camera, cut a shot, draw its dolly, play it back.</p>
341
- <ol class="tutorial-steps" id="tutorial-steps"></ol>
384
+ <div class="demo">
385
+ <div class="window">
386
+ <div class="titlebar"><i aria-hidden="true"></i><i aria-hidden="true"></i><i aria-hidden="true"></i><ol class="tutorial-steps" id="tutorial-steps" aria-label="Tutorial steps"></ol></div>
342
387
  <div class="playground" id="playground" style="background-image: url('/media/playground-poster.jpg')">
343
388
  <button class="playground-cover" id="playground-start" type="button" aria-label="Start the playground">
344
389
  <span class="btn primary">Play with the scene</span>
345
- <small>Loads the studio in this box (about 2 MB)</small>
390
+ <small>About 2 MB</small>
346
391
  <span class="playground-phone">Needs a mouse and keyboard. Come back on a computer, or run <code>npx cozyclay --via site</code>.</span>
347
392
  </button>
348
393
  <div class="playground-loading">Loading the studio…</div>
@@ -365,44 +410,67 @@
365
410
  <a href="/app/" target="_blank" rel="noopener">Open studio ↗</a>
366
411
  <button type="button" id="playground-close">Close</button>
367
412
  </div>
413
+ </div>
414
+ </div>
415
+ <p class="small demo-note">Needs a mouse and keyboard.</p>
416
+ </div>
368
417
  </section>
369
418
 
370
419
  <section id="what">
371
- <h2>What you can do</h2>
372
- <div class="grid">
373
- <div class="card">
374
- <h3>Stage a scene</h3>
375
- <p>Primitives, set pieces, a W/E/R gizmo, and a plan view for character paths.</p>
376
- </div>
377
- <div class="card">
378
- <h3>Edit like Unity</h3>
379
- <p>Right-drag flies, WASD walks, Alt-drag orbits, F frames. Muscle memory carries over.</p>
380
- </div>
381
- <div class="card">
382
- <h3>Author the camera</h3>
383
- <p>Lens, dolly, crane, cuts on a timeline. Export the move, not a prompt.</p>
384
- </div>
385
- <div class="card">
386
- <h3>Generate motion</h3>
387
- <p>Pose characters, sequence Prompt Blocks, play generated motion back with IK fixes.</p>
420
+ <div class="wrap">
421
+ <h2>What you can do</h2>
422
+ <div class="cards">
423
+ <div class="card">
424
+ <h3>Stage a scene</h3>
425
+ <p>Primitives, set pieces, plan view.</p>
426
+ </div>
427
+ <div class="card">
428
+ <h3>Move through the set</h3>
429
+ <p>Fly, walk, orbit, frame.</p>
430
+ </div>
431
+ <div class="card">
432
+ <h3>Author the camera</h3>
433
+ <p>Lens, dolly, crane, cuts.</p>
434
+ </div>
435
+ <div class="card">
436
+ <h3>Generate motion</h3>
437
+ <p>Poses, Prompt Blocks, IK fixes.</p>
438
+ </div>
388
439
  </div>
389
440
  </div>
390
441
  </section>
391
442
 
392
- <section id="start">
393
- <h2>Quick start</h2>
394
- <p class="sub">Node.js 22.13+ and a Chromium browser.</p>
395
- <pre class="with-copy"><code>npx cozyclay --via site</code><button type="button" class="copy" data-copy="npx cozyclay --via site">Copy</button></pre>
396
- <p class="sub">Or from source:</p>
397
- <pre><code>git clone https://github.com/NomaDamas/CozyClay.git
443
+ <section id="start" class="alt">
444
+ <div class="wrap start">
445
+ <div class="start-head">
446
+ <h2>Quick start</h2>
447
+ <p>Node.js 22.13+ and a Chromium browser.</p>
448
+ <a href="#controls" id="controls-link">Full control list</a>
449
+ </div>
450
+ <div class="start-body">
451
+ <div class="seg" id="start-tabs">
452
+ <button type="button" aria-pressed="true" data-panel="start-npx">npx</button>
453
+ <button type="button" aria-pressed="false" data-panel="start-src">From source</button>
454
+ </div>
455
+ <div class="code" id="start-npx">
456
+ <pre><code>npx cozyclay --via site</code></pre>
457
+ <button type="button" class="copy" data-copy="npx cozyclay --via site">Copy</button>
458
+ </div>
459
+ <div class="code" id="start-src" hidden>
460
+ <pre><code>git clone https://github.com/NomaDamas/CozyClay.git
398
461
  cd CozyClay
399
462
  npm install
400
463
  npm run dev</code></pre>
464
+ <button type="button" class="copy" data-copy="git clone https://github.com/NomaDamas/CozyClay.git&#10;cd CozyClay&#10;npm install&#10;npm run dev">Copy</button>
465
+ </div>
466
+ </div>
467
+ </div>
401
468
  </section>
402
469
 
403
- <section id="controls">
470
+ <section id="controls" class="alt">
471
+ <div class="wrap">
404
472
  <details>
405
- <summary>Full control list (Unity conventions)</summary>
473
+ <summary>Full control list</summary>
406
474
  <div class="scroller">
407
475
  <table>
408
476
  <thead><tr><th>Input</th><th>Action</th></tr></thead>
@@ -421,21 +489,25 @@ npm run dev</code></pre>
421
489
  </table>
422
490
  </div>
423
491
  </details>
492
+ </div>
424
493
  </section>
425
494
 
426
495
  <section id="read">
427
- <h2>Read</h2>
428
- <div class="related">
429
- <a href="/greybox-to-video/">Greybox to AI video<small>Why a grey blockout is the input models keep</small></a>
430
- <a href="/seedance-camera-control/">Seedance 2.5 camera control<small>Reference clip beats camera prompts</small></a>
431
- <a href="/previs-software/">Previs software compared<small>Free, open source, browser</small></a>
432
- <a href="/ai-camera-control/">Camera control for AI video<small>Why prompts drift</small></a>
496
+ <div class="wrap">
497
+ <h2>Read</h2>
498
+ <div class="cards">
499
+ <a class="card" href="/greybox-to-video/">Greybox to AI video</a>
500
+ <a class="card" href="/seedance-camera-control/">Seedance 2.5 camera control</a>
501
+ <a class="card" href="/previs-software/">Previs software compared</a>
502
+ <a class="card" href="/ai-camera-control/">Camera control for AI video</a>
503
+ </div>
433
504
  </div>
434
505
  </section>
435
506
 
436
507
  <section id="faq">
508
+ <div class="wrap narrow">
437
509
  <h2>FAQ</h2>
438
- <details open>
510
+ <details>
439
511
  <summary>What is CozyClay?</summary>
440
512
  <p>Open source previs software in a browser: block a scene, pose characters, author camera moves and cuts, preview motion.</p>
441
513
  </details>
@@ -455,14 +527,48 @@ npm run dev</code></pre>
455
527
  <summary>Is it free? Does it phone home?</summary>
456
528
  <p>Free, AGPL-3.0, runs on your machine and works offline. Project content is never uploaded. The npm package sends anonymous usage metrics; <code>cclay telemetry off</code> disables them.</p>
457
529
  </details>
530
+ </div>
531
+ </section>
532
+
533
+ <section class="closing alt">
534
+ <div class="wrap">
535
+ <h2>Block your first shot</h2>
536
+ <div class="cta">
537
+ <a class="btn primary" href="#try">Try it in your browser</a>
538
+ <a class="btn ghost" href="https://github.com/NomaDamas/CozyClay">View on GitHub</a>
539
+ </div>
540
+ </div>
458
541
  </section>
459
542
  </main>
460
543
 
544
+ <script>
545
+ // Quick start: one code block at a time. The link to the control list opens it.
546
+ (() => {
547
+ const tabs = document.querySelectorAll("#start-tabs button");
548
+ tabs.forEach((tab) => tab.addEventListener("click", () => {
549
+ tabs.forEach((other) => {
550
+ const on = other === tab;
551
+ other.setAttribute("aria-pressed", String(on));
552
+ document.getElementById(other.dataset.panel).hidden = !on;
553
+ });
554
+ }));
555
+ document.getElementById("controls-link").addEventListener("click", () => {
556
+ document.querySelector("#controls details").open = true;
557
+ });
558
+ // A started playground takes the screen: steps, window and its bar in view.
559
+ document.getElementById("playground-start").addEventListener("click", () => {
560
+ if (!matchMedia("(min-width: 721px) and (hover: hover)").matches) return;
561
+ const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
562
+ document.querySelector("#try .demo").scrollIntoView({ block: "start", behavior: still ? "auto" : "smooth" });
563
+ });
564
+ })();
565
+ </script>
566
+
461
567
  <footer class="site">
462
568
  <div class="wrap">
463
569
  <p>
464
- <a href="https://github.com/NomaDamas/CozyClay">Source code (AGPL)</a> ·
465
- <a href="https://www.npmjs.com/package/cozyclay">npm</a> ·
570
+ <a href="https://github.com/NomaDamas/CozyClay">Source (AGPL)</a>
571
+ <a href="https://www.npmjs.com/package/cozyclay">npm</a>
466
572
  <a href="/privacy/">Privacy</a>
467
573
  </p>
468
574
  <p>