cozyclay 1.1.0 → 1.3.0

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 (138) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +91 -25
  3. package/THIRD_PARTY_NOTICES.md +68 -1
  4. package/bin/cozyclay.mjs +71 -8
  5. package/dist/CNAME +1 -0
  6. package/dist/ai-camera-control/index.html +406 -0
  7. package/dist/app/index.html +57 -0
  8. package/dist/assets/app-B3U5aut1.js +4811 -0
  9. package/dist/assets/app-BrRF0wso.css +1 -0
  10. package/dist/assets/vision_bundle-jFkh-fIS.js +41 -0
  11. package/dist/favicon.ico +0 -0
  12. package/dist/fonts/InstrumentSerif-OFL.txt +93 -0
  13. package/dist/fonts/Inter-OFL.txt +92 -0
  14. package/dist/fonts/README.md +15 -0
  15. package/dist/icons/apple-touch-icon.png +0 -0
  16. package/dist/icons/icon-192.png +0 -0
  17. package/dist/icons/icon-32.png +0 -0
  18. package/dist/icons/icon-512.png +0 -0
  19. package/dist/icons/icon-maskable-512.png +0 -0
  20. package/dist/index.html +450 -5
  21. package/dist/manifest.webmanifest +42 -0
  22. package/dist/media/cozyclay-demo-poster.jpg +0 -0
  23. package/dist/media/cozyclay-logo.png +0 -0
  24. package/dist/robots.txt +4 -0
  25. package/dist/sitemap.xml +15 -0
  26. package/dist/sw.js +127 -0
  27. package/mcp/LIVE-PROTOCOL.md +63 -0
  28. package/mcp/README.md +142 -0
  29. package/mcp/ardy-prompts.mjs +170 -0
  30. package/mcp/live-hub.mjs +105 -0
  31. package/mcp/package.json +24 -0
  32. package/mcp/server.mjs +1394 -0
  33. package/package.json +122 -78
  34. package/src/App.jsx +4235 -1047
  35. package/src/ardy/client.js +5 -4
  36. package/src/ardy/cskel27.js +7 -2
  37. package/src/ardy/ik.js +25 -15
  38. package/src/ardy/mp4-duration.js +102 -0
  39. package/src/ardy/npz.js +64 -3
  40. package/src/ardy/playback.js +31 -1
  41. package/src/ardy/prompt-clips.js +7 -2
  42. package/src/ardy/retime.js +211 -0
  43. package/src/ardy/timeline-coordinates.js +24 -0
  44. package/src/ardy/timeline.jsx +650 -70
  45. package/src/ardy/to-cskel27.js +34 -12
  46. package/src/ardy/trim.js +33 -0
  47. package/src/ardy/waypoints.js +14 -9
  48. package/src/asset-pane.jsx +36 -0
  49. package/src/camera-block.js +60 -0
  50. package/src/camera-follow.js +49 -10
  51. package/src/camera-rail-schedule.js +176 -0
  52. package/src/controls.jsx +16 -2
  53. package/src/cuts.js +184 -0
  54. package/src/dualview.jsx +14 -8
  55. package/src/generation/client.js +56 -0
  56. package/src/generation/session.js +47 -0
  57. package/src/generation/shot-spec.js +40 -0
  58. package/src/generation/use-generation.js +55 -0
  59. package/src/hierarchy-model.js +104 -30
  60. package/src/hierarchy-panel.jsx +306 -26
  61. package/src/live-control.js +122 -0
  62. package/src/locale-toggle.jsx +18 -0
  63. package/src/locale.js +34 -0
  64. package/src/main.jsx +5 -0
  65. package/src/matte-editor.js +543 -0
  66. package/src/matte.js +503 -0
  67. package/src/multimodel-ingest.js +344 -0
  68. package/src/object-catalog.jsx +32 -3
  69. package/src/object-gizmo.jsx +43 -15
  70. package/src/planview.jsx +208 -89
  71. package/src/pose-extract/browser.js +42 -0
  72. package/src/pose-extract/detector.js +75 -0
  73. package/src/pose-extract/fit.js +194 -0
  74. package/src/pose-extract/index.js +56 -0
  75. package/src/pose-extract/keyframes.js +74 -0
  76. package/src/pose-extract/landmarks.js +162 -0
  77. package/src/pose-extract/take.js +87 -0
  78. package/src/pose-extract/video-frames.js +91 -0
  79. package/src/pose-thumbs.js +152 -0
  80. package/src/poses.js +114 -0
  81. package/src/posestudio.jsx +434 -22
  82. package/src/project-browser.jsx +135 -0
  83. package/src/project.js +289 -0
  84. package/src/props.jsx +69 -3
  85. package/src/pwa.js +42 -0
  86. package/src/result-modal.jsx +124 -0
  87. package/src/room.jsx +14 -35
  88. package/src/scene-asset-cache.js +125 -0
  89. package/src/scene-assets.js +288 -0
  90. package/src/scene-objects.js +245 -7
  91. package/src/scenes.js +404 -0
  92. package/src/shot-authoring.js +246 -77
  93. package/src/shot.js +8 -4
  94. package/src/styles.css +2151 -183
  95. package/src/ui.jsx +54 -5
  96. package/tools/ardy/BRIDGE.md +73 -6
  97. package/tools/ardy/README.md +64 -5
  98. package/tools/ardy/bridge.mjs +146 -390
  99. package/tools/ardy/bvh-cskel27.mjs +1209 -0
  100. package/tools/ardy/cclay_constrained_generate.py +561 -0
  101. package/tools/ardy/cclay_motion_edit.py +74 -9
  102. package/tools/ardy/cclay_sequence_generate.py +117 -7
  103. package/tools/ardy/cclay_worker.py +304 -0
  104. package/tools/ardy/extract.mjs +367 -0
  105. package/tools/ardy/footage.mjs +462 -0
  106. package/tools/ardy/merge-text-encoder.py +160 -0
  107. package/tools/ardy/npz.mjs +74 -9
  108. package/tools/ardy/run-local.mjs +233 -0
  109. package/tools/ardy/run-on-box.sh +25 -0
  110. package/tools/ardy/run-sequence-on-box.sh +15 -0
  111. package/tools/ardy/runners/index.mjs +23 -0
  112. package/tools/ardy/runners/local.mjs +452 -0
  113. package/tools/ardy/runners/proc.mjs +125 -0
  114. package/tools/ardy/runners/remote.mjs +252 -0
  115. package/tools/ardy/setup-local.mjs +189 -0
  116. package/tools/ardy/setup-text-encoder-on-box.sh +93 -0
  117. package/tools/ardy/setup-text-encoder.py +296 -0
  118. package/tools/generation/README.md +29 -0
  119. package/tools/generation/bridge.mjs +149 -0
  120. package/tools/generation/job-store.mjs +51 -0
  121. package/tools/generation/providers/kling.mjs +76 -0
  122. package/tools/generation/providers/runway.mjs +101 -0
  123. package/tools/generation/providers/seedance.mjs +87 -0
  124. package/tools/generation/providers/shared.mjs +49 -0
  125. package/tools/generation/providers/veo.mjs +84 -0
  126. package/tools/metrics/archive-traffic.mjs +138 -0
  127. package/dist/assets/index-BW_S1YRy.js +0 -4395
  128. package/dist/assets/index-nRmmJgap.css +0 -1
  129. package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
  130. package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
  131. package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
  132. package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
  133. package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
  134. package/tools/qa-browser.mjs +0 -84
  135. package/tools/qa-crop3.mjs +0 -22
  136. package/tools/qa-playview.mjs +0 -49
  137. package/tools/qa-screenshot.mjs +0 -24
  138. package/tools/qa-visual.mjs +0 -51
package/dist/index.html CHANGED
@@ -2,12 +2,457 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Cozy Clay</title>
7
- <script type="module" crossorigin src="./assets/index-BW_S1YRy.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-nRmmJgap.css">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
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, no install." />
7
+ <meta name="theme-color" content="#232323" />
8
+ <meta name="apple-mobile-web-app-title" content="CozyClay" />
9
+ <link rel="canonical" href="https://cozyclay.org/" />
10
+ <link rel="icon" href="/favicon.ico" sizes="any" />
11
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32.png" />
12
+ <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
13
+ <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
14
+
15
+ <meta property="og:type" content="website" />
16
+ <meta property="og:site_name" content="CozyClay" />
17
+ <meta property="og:title" content="CozyClay — open source 3D previs software, in your browser" />
18
+ <meta property="og:description" content="Block a scene, pose characters, author camera moves and cuts, and preview generated motion. Open source, runs in your browser." />
19
+ <meta property="og:url" content="https://cozyclay.org/" />
20
+ <meta property="og:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
21
+ <meta property="og:image:width" content="1920" />
22
+ <meta property="og:image:height" content="1080" />
23
+ <meta property="og:image:alt" content="A CozyClay shot mid-wipe: the greybox previs on one half, the generated result on the other." />
24
+ <meta name="twitter:card" content="summary_large_image" />
25
+ <meta name="twitter:title" content="CozyClay — open source 3D previs software, in your browser" />
26
+ <meta name="twitter:description" content="Block a scene, pose characters, author camera moves and cuts, and preview generated motion. Open source, runs in your browser." />
27
+ <meta name="twitter:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
28
+
29
+ <title>CozyClay — open source 3D previs software, in your browser</title>
30
+
31
+ <meta property="og:video" content="https://cozyclay.org/media/cozyclay-demo.mp4" />
32
+ <meta property="og:video:type" content="video/mp4" />
33
+ <meta property="og:video:width" content="1920" />
34
+ <meta property="og:video:height" content="1080" />
35
+
36
+ <!-- Cloudflare Web Analytics. Cookie-less, so no consent banner. Installed by
37
+ hand because cozyclay.org resolves straight to GitHub Pages (DNS-only),
38
+ so Cloudflare never proxies the response and cannot inject the beacon. -->
39
+ <script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
40
+
41
+ <script type="application/ld+json">
42
+ {
43
+ "@context": "https://schema.org",
44
+ "@type": "SoftwareApplication",
45
+ "name": "CozyClay",
46
+ "url": "https://cozyclay.org/",
47
+ "applicationCategory": "DesignApplication",
48
+ "applicationSubCategory": "3D previsualization",
49
+ "operatingSystem": "Any (Chromium-based browser)",
50
+ "description": "CozyClay is an open-source, browser-based 3D staging studio. Block a scene, pose characters, sequence motion prompts on a timeline, and preview generated motion in one local workspace that handles like the Unity Editor.",
51
+ "image": "https://cozyclay.org/media/cozyclay-demo-poster.jpg",
52
+ "screenshot": "https://cozyclay.org/media/cozyclay-demo-poster.jpg",
53
+ "logo": "https://cozyclay.org/media/cozyclay-logo.png",
54
+ "license": "https://github.com/HaD0Yun/CozyClay/blob/main/LICENSE",
55
+ "codeRepository": "https://github.com/HaD0Yun/CozyClay",
56
+ "programmingLanguage": ["JavaScript", "TypeScript"],
57
+ "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }
58
+ }
59
+ </script>
60
+ <script type="application/ld+json">
61
+ {
62
+ "@context": "https://schema.org",
63
+ "@type": "FAQPage",
64
+ "mainEntity": [
65
+ {
66
+ "@type": "Question",
67
+ "name": "What is CozyClay?",
68
+ "acceptedAnswer": {
69
+ "@type": "Answer",
70
+ "text": "CozyClay is open source previs software that runs in a browser. You block a scene with primitives and set pieces, pose characters, draw root paths, author camera moves and cuts on a timeline, and preview generated motion."
71
+ }
72
+ },
73
+ {
74
+ "@type": "Question",
75
+ "name": "What is previs, and is it spelled previz?",
76
+ "acceptedAnswer": {
77
+ "@type": "Answer",
78
+ "text": "Previs is short for previsualization: the rough 3D pass where a shot gets blocked — camera position, lens, character movement, cut points — before anyone commits to final assets or render budget. It is written both previs and previz; the industry mostly writes previs, and both mean the same thing."
79
+ }
80
+ },
81
+ {
82
+ "@type": "Question",
83
+ "name": "Is there free previs software?",
84
+ "acceptedAnswer": {
85
+ "@type": "Answer",
86
+ "text": "CozyClay is free and open source under GPL-3.0, and it runs in the browser, so there is no licence and no install between you and a blocked shot. Run it locally with npx cozyclay and nothing leaves your machine."
87
+ }
88
+ },
89
+ {
90
+ "@type": "Question",
91
+ "name": "Do I need Blender or Unity to use CozyClay?",
92
+ "acceptedAnswer": {
93
+ "@type": "Answer",
94
+ "text": "No. CozyClay runs entirely in a Chromium-based browser using Three.js and React Three Fiber. The camera and gizmo controls deliberately follow Unity Editor conventions, so the handling is familiar, but no desktop DCC install is required."
95
+ }
96
+ },
97
+ {
98
+ "@type": "Question",
99
+ "name": "How does motion generation work?",
100
+ "acceptedAnswer": {
101
+ "@type": "Answer",
102
+ "text": "You sequence multi-phase motion as Prompt Blocks on the timeline and send them to NVIDIA ARDY through a local bridge, then play the result back with sparse IK correction. ARDY is a separate third-party project maintained by NVIDIA; it is not bundled with CozyClay. Staging, root paths, cameras and playback all work without it, and the build ships a pre-generated clip so the timeline has real motion on it from the first run."
103
+ }
104
+ },
105
+ {
106
+ "@type": "Question",
107
+ "name": "Is CozyClay free and open source?",
108
+ "acceptedAnswer": {
109
+ "@type": "Answer",
110
+ "text": "Yes. CozyClay is free and open source on GitHub, and it can be run locally with a single command: npx cozyclay."
111
+ }
112
+ },
113
+ {
114
+ "@type": "Question",
115
+ "name": "Can I run CozyClay offline?",
116
+ "acceptedAnswer": {
117
+ "@type": "Answer",
118
+ "text": "Yes. CozyClay runs entirely on your own machine with npx cozyclay or from a clone. Nothing is uploaded, and the studio keeps working without a network connection once it is running."
119
+ }
120
+ }
121
+ ]
122
+ }
123
+ </script>
124
+
125
+ <style>
126
+ :root {
127
+ --bg: #1b1b1b;
128
+ --panel: #232323;
129
+ --line: #343434;
130
+ --ink: #ece7de;
131
+ --muted: #a9a29a;
132
+ --clay: #f2eee6;
133
+ --accent: #d9a066;
134
+ }
135
+ * { box-sizing: border-box; }
136
+ html { scroll-behavior: smooth; }
137
+ body {
138
+ margin: 0;
139
+ background: var(--bg);
140
+ color: var(--ink);
141
+ font: 16px/1.65 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
142
+ -webkit-font-smoothing: antialiased;
143
+ }
144
+ a { color: var(--accent); }
145
+ .wrap { max-width: 980px; margin: 0 auto; padding: 0 20px; }
146
+ header.site {
147
+ border-bottom: 1px solid var(--line);
148
+ position: sticky; top: 0; background: rgba(27,27,27,.9);
149
+ backdrop-filter: blur(8px); z-index: 10;
150
+ }
151
+ header.site .wrap {
152
+ display: flex; align-items: center; gap: 16px;
153
+ min-height: 60px; flex-wrap: wrap;
154
+ }
155
+ .brand { font-weight: 700; letter-spacing: -.01em; color: var(--clay); text-decoration: none; font-size: 18px; }
156
+ header.site nav { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; }
157
+ header.site nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
158
+ header.site nav a:hover { color: var(--ink); }
159
+
160
+ .hero { padding: 64px 0 40px; }
161
+ h1 { font-size: clamp(30px, 5.2vw, 46px); line-height: 1.12; margin: 0 0 18px; letter-spacing: -.02em; color: var(--clay); }
162
+ .lede { font-size: clamp(17px, 2.2vw, 20px); color: var(--muted); margin: 0 0 28px; max-width: 44em; }
163
+ .cta { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
164
+ .btn {
165
+ display: inline-block; padding: 12px 20px; border-radius: 8px;
166
+ text-decoration: none; font-weight: 600; font-size: 15px; border: 1px solid var(--line);
167
+ }
168
+ .btn.primary { background: var(--clay); color: #1b1b1b; border-color: var(--clay); }
169
+ .btn.ghost { color: var(--ink); }
170
+ .note { color: var(--muted); font-size: 14px; margin: 0; }
171
+
172
+ figure { margin: 40px 0; }
173
+ figure img { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: 10px; }
174
+ figcaption { color: var(--muted); font-size: 13px; margin-top: 10px; }
175
+
176
+ /* The mark is blue on white and the eyes are knocked out of the fill, so
177
+ it sits on a light plate rather than straight on the dark page. */
178
+ .mark { margin: 0 0 26px; max-width: 190px; }
179
+ .mark img {
180
+ border: 0;
181
+ border-radius: 14px;
182
+ background: var(--clay);
183
+ padding: 14px 12px;
184
+ }
185
+
186
+ .reel {
187
+ margin: 0 0 30px;
188
+ border: 1px solid var(--line);
189
+ border-radius: 12px;
190
+ overflow: hidden;
191
+ background: #000;
192
+ box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
193
+ }
194
+ .reel video { display: block; width: 100%; height: auto; }
195
+ .reel-note { color: var(--muted); font-size: 13px; margin: 12px 0 0; }
196
+
197
+ section { padding: 40px 0; border-top: 1px solid var(--line); }
198
+ h2 { font-size: clamp(22px, 3.2vw, 28px); margin: 0 0 8px; color: var(--clay); letter-spacing: -.01em; }
199
+ h3 { font-size: 17px; margin: 0 0 6px; color: var(--clay); }
200
+ .sub { color: var(--muted); margin: 0 0 26px; max-width: 48em; }
201
+
202
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; }
203
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 20px; }
204
+ .card p { margin: 0; color: var(--muted); font-size: 15px; }
205
+
206
+ pre {
207
+ background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
208
+ padding: 16px 18px; overflow-x: auto; font-size: 14px; line-height: 1.7;
209
+ }
210
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
211
+
212
+ table { width: 100%; border-collapse: collapse; font-size: 15px; }
213
+ th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
214
+ th { color: var(--clay); font-weight: 600; white-space: nowrap; }
215
+ td { color: var(--muted); }
216
+ .scroller { overflow-x: auto; }
217
+
218
+ details { border-bottom: 1px solid var(--line); padding: 16px 0; }
219
+ details summary { cursor: pointer; font-weight: 600; color: var(--clay); font-size: 16px; }
220
+ details p { color: var(--muted); margin: 12px 0 0; }
221
+
222
+ footer.site { border-top: 1px solid var(--line); padding: 32px 0 56px; color: var(--muted); font-size: 14px; }
223
+ footer.site a { color: var(--muted); }
224
+
225
+ @media (max-width: 620px) {
226
+ .hero { padding: 40px 0 28px; }
227
+ .btn { width: 100%; text-align: center; }
228
+ header.site nav { gap: 14px; width: 100%; margin-left: 0; padding-bottom: 10px; }
229
+ }
230
+ </style>
9
231
  </head>
10
232
  <body>
11
- <div id="root"></div>
233
+ <header class="site">
234
+ <div class="wrap">
235
+ <a class="brand" href="/">CozyClay</a>
236
+ <nav>
237
+ <a href="#what">What it does</a>
238
+ <a href="#start">Quick start</a>
239
+ <a href="#controls">Controls</a>
240
+ <a href="/ai-camera-control/">AI camera control</a>
241
+ <a href="https://github.com/HaD0Yun/CozyClay">GitHub</a>
242
+ </nav>
243
+ </div>
244
+ </header>
245
+
246
+ <main class="wrap">
247
+ <div class="hero">
248
+ <figure class="mark">
249
+ <img src="/media/cozyclay-logo.png" width="880" height="714" alt="CozyClay" />
250
+ </figure>
251
+
252
+ <h1>Block the shot before you pay to render it</h1>
253
+ <p class="lede">
254
+ CozyClay is free, open source previs software that runs in your browser. Block a
255
+ scene, pose characters, and author camera moves and cuts — then hand the same
256
+ shots to an AI video model. No Blender, no Unity, no install.
257
+ </p>
258
+
259
+ <div class="reel">
260
+ <video
261
+ src="/media/cozyclay-demo.mp4"
262
+ poster="/media/cozyclay-demo-poster.jpg"
263
+ width="1920" height="1080"
264
+ autoplay muted loop playsinline controls preload="metadata"
265
+ ></video>
266
+ </div>
267
+ <p class="reel-note">
268
+ Thirty seconds: greybox previs on the left of the wipe, the generated result on the
269
+ right. Every cut came out of the same camera setup you see being built — more on
270
+ <a href="/ai-camera-control/">camera control for AI video</a>.
271
+ </p>
272
+
273
+ <div class="cta">
274
+ <a class="btn primary" href="https://github.com/HaD0Yun/CozyClay">View on GitHub</a>
275
+ <a class="btn ghost" href="#start">Run it locally</a>
276
+ </div>
277
+ <p class="note">
278
+ Free and open source. Built with Three.js and React Three Fiber.
279
+ <a href="https://github.com/HaD0Yun/CozyClay/issues">Report anything broken</a>.
280
+ </p>
281
+ </div>
282
+
283
+ <section id="what">
284
+ <h2>What you can do</h2>
285
+ <p class="sub">
286
+ Previs is the part of animation where you decide what happens before anyone commits
287
+ to final assets. CozyClay keeps that whole loop in one page.
288
+ </p>
289
+ <div class="grid">
290
+ <div class="card">
291
+ <h3>Stage a scene</h3>
292
+ <p>
293
+ Create primitives and set pieces, then move, rotate, and scale them with a
294
+ W/E/R gizmo. Grid snapping is a preference, not a law — hold Ctrl during a drag
295
+ to invert it. A bird's-eye plan view drives 2D root waypoints for character paths.
296
+ </p>
297
+ </div>
298
+ <div class="card">
299
+ <h3>Edit like Unity</h3>
300
+ <p>
301
+ Right-drag flies the camera, WASD walks, Q/E cranes, middle-drag pans,
302
+ Alt+drag orbits the selection, F frames it. The rule set follows Unity's manual
303
+ on purpose, so muscle memory carries over.
304
+ </p>
305
+ </div>
306
+ <div class="card">
307
+ <h3>Undo anything</h3>
308
+ <p>
309
+ Every scene mutation goes through a single history store: one interaction —
310
+ a drag, a scrub, an inspector edit — is exactly one undo entry. Esc cancels
311
+ an in-flight drag and restores the pre-drag transform.
312
+ </p>
313
+ </div>
314
+ <div class="card">
315
+ <h3>Generate motion</h3>
316
+ <p>
317
+ Pose characters, sequence multi-phase motion as Prompt Blocks on a resizable
318
+ timeline, send them to NVIDIA ARDY, then play the result back with sparse IK
319
+ correction where the generated motion needs fixing.
320
+ </p>
321
+ </div>
322
+ </div>
323
+ </section>
324
+
325
+ <section id="start">
326
+ <h2>Quick start</h2>
327
+ <p class="sub">
328
+ CozyClay runs on your machine. One command is enough — there is nothing to compile
329
+ and no dependency tree to install.
330
+ </p>
331
+ <pre><code>npx cozyclay</code></pre>
332
+ <p class="sub">Or clone and run the source:</p>
333
+ <pre><code>git clone https://github.com/HaD0Yun/CozyClay.git
334
+ cd CozyClay
335
+ npm install
336
+ npm run dev</code></pre>
337
+ <p class="note">
338
+ Requires Node.js 22 or newer and a Chromium-based browser.
339
+ <code>npm run dev</code> starts the studio together with its local ARDY bridge;
340
+ <code>npm run dev:ui</code> starts the browser UI alone, without motion generation.
341
+ </p>
342
+ </section>
343
+
344
+ <section id="controls">
345
+ <h2>Controls</h2>
346
+ <p class="sub">Unity Editor conventions, with the divergences documented in the repo.</p>
347
+ <div class="scroller">
348
+ <table>
349
+ <thead><tr><th>Input</th><th>Action</th></tr></thead>
350
+ <tbody>
351
+ <tr><td>Right-drag</td><td>Look around (fly)</td></tr>
352
+ <tr><td>RMB + WASD</td><td>Walk while flying</td></tr>
353
+ <tr><td>RMB + Q/E</td><td>Crane down / up</td></tr>
354
+ <tr><td>Middle-drag</td><td>Pan</td></tr>
355
+ <tr><td>Alt + drag</td><td>Orbit the selection</td></tr>
356
+ <tr><td>Scroll</td><td>Dolly</td></tr>
357
+ <tr><td>Click</td><td>Select; empty space clears</td></tr>
358
+ <tr><td>W / E / R</td><td>Move / rotate / scale tool</td></tr>
359
+ <tr><td>Ctrl (during drag)</td><td>Invert grid snapping</td></tr>
360
+ <tr><td>Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z</td><td>Undo / redo</td></tr>
361
+ <tr><td>Esc</td><td>Cancel the in-flight drag</td></tr>
362
+ <tr><td>End</td><td>Drop the selection to the surface</td></tr>
363
+ <tr><td>Ctrl/Cmd+D</td><td>Duplicate the selection</td></tr>
364
+ <tr><td>F</td><td>Frame the selection</td></tr>
365
+ </tbody>
366
+ </table>
367
+ </div>
368
+ </section>
369
+
370
+ <section id="faq">
371
+ <h2>FAQ</h2>
372
+ <details open>
373
+ <summary>What is CozyClay?</summary>
374
+ <p>
375
+ CozyClay is open source previs software that runs in a browser. You block a scene
376
+ with primitives and set pieces, pose characters, draw root paths, author camera
377
+ moves and cuts on a timeline, and preview generated motion.
378
+ </p>
379
+ </details>
380
+ <details>
381
+ <summary>What is previs, and is it spelled previz?</summary>
382
+ <p>
383
+ Previs is short for previsualization: the rough 3D pass where a shot gets blocked —
384
+ camera position, lens, character movement, cut points — before anyone commits to
385
+ final assets or render budget. It is written both <strong>previs</strong> and
386
+ <strong>previz</strong>; the industry mostly writes previs, and both mean the same
387
+ thing.
388
+ </p>
389
+ </details>
390
+ <details>
391
+ <summary>Is there free previs software?</summary>
392
+ <p>
393
+ CozyClay is free and open source under GPL-3.0, and it runs in the browser, so there
394
+ is no licence and no install between you and a blocked shot. Run it locally with
395
+ <code>npx cozyclay</code> and nothing leaves your machine.
396
+ </p>
397
+ </details>
398
+ <details>
399
+ <summary>Can I use it for camera control in AI video?</summary>
400
+ <p>
401
+ That is the reason it exists. Block the shot in 3D — angle, lens, dolly, the frame
402
+ the cut lands on — then take those camera moves to a video model instead of
403
+ describing them in a prompt and hoping. See
404
+ <a href="/ai-camera-control/">camera control for AI video</a>.
405
+ </p>
406
+ </details>
407
+ <details>
408
+ <summary>Do I need Blender or Unity?</summary>
409
+ <p>
410
+ No. CozyClay runs entirely in a Chromium-based browser using Three.js and React
411
+ Three Fiber. The camera and gizmo controls deliberately follow Unity Editor
412
+ conventions, so the handling is familiar, but no desktop DCC install is required.
413
+ </p>
414
+ </details>
415
+ <details>
416
+ <summary>How does motion generation work?</summary>
417
+ <p>
418
+ You sequence multi-phase motion as Prompt Blocks on the timeline and send them to
419
+ <a href="https://github.com/nv-tlabs/ardy">NVIDIA ARDY</a> through a local bridge,
420
+ then play the result back with sparse IK correction. ARDY is a separate third-party
421
+ project owned and maintained by NVIDIA; it is not bundled here, and CozyClay is not
422
+ affiliated with or endorsed by NVIDIA. Staging, root paths, cameras and playback all
423
+ work without it, and the build ships a pre-generated clip so the timeline has real
424
+ motion on it from the first run.
425
+ </p>
426
+ </details>
427
+ <details>
428
+ <summary>Is it free and open source?</summary>
429
+ <p>
430
+ Yes. The source lives on
431
+ <a href="https://github.com/HaD0Yun/CozyClay">GitHub</a> and the studio can be run
432
+ locally with <code>npx cozyclay</code>.
433
+ </p>
434
+ </details>
435
+ <details>
436
+ <summary>Can I run it offline?</summary>
437
+ <p>
438
+ Yes. CozyClay runs entirely on your own machine with <code>npx cozyclay</code> or from
439
+ a clone. Nothing is uploaded, and the studio keeps working without a network
440
+ connection once it is running.
441
+ </p>
442
+ </details>
443
+ </section>
444
+ </main>
445
+
446
+ <footer class="site">
447
+ <div class="wrap">
448
+ <p>
449
+ <a href="https://github.com/HaD0Yun/CozyClay">GitHub</a> ·
450
+ <a href="https://www.npmjs.com/package/cozyclay">npm</a>
451
+ </p>
452
+ <p>
453
+ CozyClay is not affiliated with or endorsed by NVIDIA or Unity Technologies.
454
+ </p>
455
+ </div>
456
+ </footer>
12
457
  </body>
13
458
  </html>
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "/app/",
3
+ "name": "Cozy Clay",
4
+ "short_name": "Cozy Clay",
5
+ "description": "Browser-based 3D staging studio: block a scene, pose characters, and author camera moves.",
6
+ "lang": "en",
7
+ "start_url": "/app/",
8
+ "scope": "/app/",
9
+ "display": "standalone",
10
+ "display_override": [
11
+ "standalone",
12
+ "minimal-ui"
13
+ ],
14
+ "orientation": "landscape-primary",
15
+ "background_color": "#1e1e1e",
16
+ "theme_color": "#232323",
17
+ "categories": [
18
+ "graphics-design",
19
+ "photo",
20
+ "video"
21
+ ],
22
+ "icons": [
23
+ {
24
+ "src": "./icons/icon-192.png",
25
+ "sizes": "192x192",
26
+ "type": "image/png",
27
+ "purpose": "any"
28
+ },
29
+ {
30
+ "src": "./icons/icon-512.png",
31
+ "sizes": "512x512",
32
+ "type": "image/png",
33
+ "purpose": "any"
34
+ },
35
+ {
36
+ "src": "./icons/icon-maskable-512.png",
37
+ "sizes": "512x512",
38
+ "type": "image/png",
39
+ "purpose": "maskable"
40
+ }
41
+ ]
42
+ }
Binary file
@@ -0,0 +1,4 @@
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ Sitemap: https://cozyclay.org/sitemap.xml
@@ -0,0 +1,15 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://cozyclay.org/</loc>
5
+ <lastmod>2026-08-15</lastmod>
6
+ <changefreq>weekly</changefreq>
7
+ <priority>1.0</priority>
8
+ </url>
9
+ <url>
10
+ <loc>https://cozyclay.org/ai-camera-control/</loc>
11
+ <lastmod>2026-08-15</lastmod>
12
+ <changefreq>monthly</changefreq>
13
+ <priority>0.8</priority>
14
+ </url>
15
+ </urlset>
package/dist/sw.js ADDED
@@ -0,0 +1,127 @@
1
+ const CACHE_PREFIX = "cozyclay-pwa-";
2
+ // Bump on any change to what CORE_ASSETS resolves to. The icons are cached
3
+ // here, so a client on an older cache keeps serving the previous favicon from
4
+ // disk no matter what the server sends.
5
+ // v3: app shell moved from "/" to "/app/".
6
+ // v4: icons redrawn from the CozyClay mark.
7
+ // v5: tab icons cut to a circle so they stop reading as a sticker.
8
+ const CACHE_NAME = `${CACHE_PREFIX}v5`;
9
+ // The installable app is the studio, not the landing page.
10
+ const APP_SHELL = "/app/";
11
+ const CORE_ASSETS = [
12
+ APP_SHELL,
13
+ "/app/index.html",
14
+ "./manifest.webmanifest",
15
+ "./icons/icon-32.png",
16
+ "./icons/icon-192.png",
17
+ "./icons/icon-512.png",
18
+ "./icons/icon-maskable-512.png",
19
+ "./icons/apple-touch-icon.png",
20
+ "./favicon.ico",
21
+ "./fonts/inter-latin.woff2",
22
+ "./demo/walk-then-stop.npz",
23
+ "./models/x-bot-tpose.fbx",
24
+ "./models/y-bot-tpose.fbx",
25
+ ];
26
+
27
+ async function cacheResponse(cache, request, response) {
28
+ if (!response.ok || response.type === "opaque") {
29
+ throw new Error(`Required offline asset failed: ${request}`);
30
+ }
31
+ await cache.put(request, response.clone());
32
+ return response;
33
+ }
34
+
35
+ async function cacheBuiltAssets(cache) {
36
+ const response = await fetch(APP_SHELL, { cache: "reload" });
37
+ if (!response.ok) throw new Error("Required offline app shell failed");
38
+ await cache.put(APP_SHELL, response.clone());
39
+ const html = await response.text();
40
+ const assetUrls = [...html.matchAll(/(?:src|href)="([^"]+)"/g)]
41
+ .map((match) => new URL(match[1], self.location.href))
42
+ .filter((url) => url.origin === self.location.origin)
43
+ .map((url) => url.href);
44
+ await Promise.all(assetUrls.map(async (url) => {
45
+ const asset = await fetch(url, { cache: "reload" });
46
+ await cacheResponse(cache, url, asset);
47
+ }));
48
+ }
49
+
50
+ self.addEventListener("install", (event) => {
51
+ event.waitUntil((async () => {
52
+ const cache = await caches.open(CACHE_NAME);
53
+ await Promise.all(CORE_ASSETS.map(async (url) => {
54
+ const response = await fetch(url, { cache: "reload" });
55
+ await cacheResponse(cache, url, response);
56
+ }));
57
+ await cacheBuiltAssets(cache);
58
+ })());
59
+ });
60
+
61
+ self.addEventListener("activate", (event) => {
62
+ event.waitUntil((async () => {
63
+ const names = await caches.keys();
64
+ await Promise.all(names
65
+ .filter((name) => name.startsWith(CACHE_PREFIX) && name !== CACHE_NAME)
66
+ .map((name) => caches.delete(name)));
67
+ await self.clients.claim();
68
+ })());
69
+ });
70
+
71
+ self.addEventListener("message", (event) => {
72
+ if (event.data?.type === "SKIP_WAITING") self.skipWaiting();
73
+ });
74
+
75
+ self.addEventListener("fetch", (event) => {
76
+ const { request } = event;
77
+ if (request.method !== "GET") return;
78
+ const url = new URL(request.url);
79
+ if (url.origin !== self.location.origin || url.pathname.includes("/ardy/")) return;
80
+ if (request.headers.has("range")) {
81
+ event.respondWith((async () => {
82
+ try {
83
+ return await fetch(request);
84
+ } catch {
85
+ const cached = await caches.match(url.href);
86
+ if (!cached) return new Response(null, { status: 504 });
87
+ const bytes = await cached.arrayBuffer();
88
+ const range = request.headers.get("range")?.match(/bytes=(\d+)-(\d*)/);
89
+ if (!range) return cached;
90
+ const start = Number(range[1]);
91
+ const end = range[2] ? Number(range[2]) : bytes.byteLength - 1;
92
+ const chunk = bytes.slice(start, end + 1);
93
+ return new Response(chunk, {
94
+ status: 206,
95
+ headers: {
96
+ "accept-ranges": "bytes",
97
+ "content-length": String(chunk.byteLength),
98
+ "content-range": `bytes ${start}-${end}/${bytes.byteLength}`,
99
+ "content-type": cached.headers.get("content-type") || "application/octet-stream",
100
+ },
101
+ });
102
+ }
103
+ })());
104
+ return;
105
+ }
106
+
107
+ if (request.mode === "navigate") {
108
+ event.respondWith((async () => {
109
+ const cache = await caches.open(CACHE_NAME);
110
+ try {
111
+ const response = await fetch(request);
112
+ return cacheResponse(cache, request, response);
113
+ } catch {
114
+ return (await cache.match(request)) || (await cache.match(APP_SHELL)) || (await cache.match("/app/index.html"));
115
+ }
116
+ })());
117
+ return;
118
+ }
119
+
120
+ event.respondWith((async () => {
121
+ const cached = await caches.match(request);
122
+ if (cached) return cached;
123
+ const cache = await caches.open(CACHE_NAME);
124
+ const response = await fetch(request);
125
+ return cacheResponse(cache, request, response);
126
+ })());
127
+ });