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
@@ -0,0 +1,406 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <meta name="description" content="Camera control for AI video: why prompting an angle keeps drifting, what Seedance, Veo and Kling actually accept, and how blocking the shot in 3D first gives you the same camera on every take." />
7
+ <meta name="theme-color" content="#232323" />
8
+ <link rel="canonical" href="https://cozyclay.org/ai-camera-control/" />
9
+ <link rel="icon" href="/favicon.ico" sizes="any" />
10
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32.png" />
11
+ <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
12
+ <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
13
+
14
+ <meta property="og:type" content="article" />
15
+ <meta property="og:site_name" content="CozyClay" />
16
+ <meta property="og:title" content="Camera control for AI video: block the shot instead of describing it" />
17
+ <meta property="og:description" content="Why camera prompts drift between takes, and how blocking the angle, lens and dolly in 3D first gets you the same shot every time." />
18
+ <meta property="og:url" content="https://cozyclay.org/ai-camera-control/" />
19
+ <meta property="og:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
20
+ <meta property="og:image:width" content="1920" />
21
+ <meta property="og:image:height" content="1080" />
22
+ <meta name="twitter:card" content="summary_large_image" />
23
+ <meta name="twitter:title" content="Camera control for AI video: block the shot instead of describing it" />
24
+ <meta name="twitter:description" content="Why camera prompts drift between takes, and how blocking the angle, lens and dolly in 3D first gets you the same shot every time." />
25
+ <meta name="twitter:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
26
+
27
+ <title>Camera control for AI video — block the shot, don't describe it</title>
28
+
29
+
30
+ <script type="application/ld+json">
31
+ {
32
+ "@context": "https://schema.org",
33
+ "@type": "Article",
34
+ "headline": "Camera control for AI video: block the shot instead of describing it",
35
+ "description": "Why camera prompts drift between takes, and how blocking the angle, lens and dolly in 3D first gets you the same shot every time.",
36
+ "image": "https://cozyclay.org/media/cozyclay-demo-poster.jpg",
37
+ "mainEntityOfPage": "https://cozyclay.org/ai-camera-control/",
38
+ "author": {
39
+ "@type": "Organization",
40
+ "name": "CozyClay",
41
+ "url": "https://cozyclay.org/"
42
+ },
43
+ "publisher": {
44
+ "@type": "Organization",
45
+ "name": "CozyClay",
46
+ "logo": {
47
+ "@type": "ImageObject",
48
+ "url": "https://cozyclay.org/media/cozyclay-logo.png"
49
+ }
50
+ }
51
+ }
52
+ </script>
53
+ <script type="application/ld+json">
54
+ {
55
+ "@context": "https://schema.org",
56
+ "@type": "FAQPage",
57
+ "mainEntity": [
58
+ {
59
+ "@type": "Question",
60
+ "name": "Why does my camera angle change between takes?",
61
+ "acceptedAnswer": {
62
+ "@type": "Answer",
63
+ "text": "Because the prompt never fixed it. Words like low angle or close up carry no height, distance or focal length, so the model fills those in and fills them in differently on each run. Pinning composition needs something with numbers behind it - a starting frame, or a camera that exists in a scene."
64
+ }
65
+ },
66
+ {
67
+ "@type": "Question",
68
+ "name": "Do camera movement prompts work at all?",
69
+ "acceptedAnswer": {
70
+ "@type": "Answer",
71
+ "text": "They work as a nudge. Slow dolly in reliably gets you a push rather than a pan. What it will not do is get you the same push twice, or a push that matches the one in the shot you are cutting to."
72
+ }
73
+ },
74
+ {
75
+ "@type": "Question",
76
+ "name": "What is the highest-leverage thing I can control in AI video?",
77
+ "acceptedAnswer": {
78
+ "@type": "Answer",
79
+ "text": "The first frame. Composition, lens compression and eyeline are all decided the moment that image exists, and the generation inherits them instead of guessing. This is what blocking a shot in 3D gives you."
80
+ }
81
+ },
82
+ {
83
+ "@type": "Question",
84
+ "name": "Do I need Blender or Unreal to block a shot?",
85
+ "acceptedAnswer": {
86
+ "@type": "Answer",
87
+ "text": "No. Both will do it, and both ask you to learn a full DCC to move one camera. CozyClay does the blocking part in a browser tab with Unity-style controls."
88
+ }
89
+ }
90
+ ]
91
+ }
92
+ </script>
93
+
94
+ <script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
95
+ <style>
96
+ :root {
97
+ --bg: #1b1b1b;
98
+ --panel: #232323;
99
+ --line: #343434;
100
+ --ink: #ece7de;
101
+ --muted: #a9a29a;
102
+ --clay: #f2eee6;
103
+ --accent: #d9a066;
104
+ }
105
+ * { box-sizing: border-box; }
106
+ html { scroll-behavior: smooth; }
107
+ body {
108
+ margin: 0;
109
+ background: var(--bg);
110
+ color: var(--ink);
111
+ font: 16px/1.65 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
112
+ -webkit-font-smoothing: antialiased;
113
+ }
114
+ a { color: var(--accent); }
115
+ .wrap { max-width: 980px; margin: 0 auto; padding: 0 20px; }
116
+ header.site {
117
+ border-bottom: 1px solid var(--line);
118
+ position: sticky; top: 0; background: rgba(27,27,27,.9);
119
+ backdrop-filter: blur(8px); z-index: 10;
120
+ }
121
+ header.site .wrap {
122
+ display: flex; align-items: center; gap: 16px;
123
+ min-height: 60px; flex-wrap: wrap;
124
+ }
125
+ .brand { font-weight: 700; letter-spacing: -.01em; color: var(--clay); text-decoration: none; font-size: 18px; }
126
+ header.site nav { margin-left: auto; display: flex; gap: 18px; flex-wrap: wrap; }
127
+ header.site nav a { color: var(--muted); text-decoration: none; font-size: 14px; }
128
+ header.site nav a:hover { color: var(--ink); }
129
+
130
+ .hero { padding: 64px 0 40px; }
131
+ h1 { font-size: clamp(30px, 5.2vw, 46px); line-height: 1.12; margin: 0 0 18px; letter-spacing: -.02em; color: var(--clay); }
132
+ .lede { font-size: clamp(17px, 2.2vw, 20px); color: var(--muted); margin: 0 0 28px; max-width: 44em; }
133
+ .cta { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
134
+ .btn {
135
+ display: inline-block; padding: 12px 20px; border-radius: 8px;
136
+ text-decoration: none; font-weight: 600; font-size: 15px; border: 1px solid var(--line);
137
+ }
138
+ .btn.primary { background: var(--clay); color: #1b1b1b; border-color: var(--clay); }
139
+ .btn.ghost { color: var(--ink); }
140
+ .note { color: var(--muted); font-size: 14px; margin: 0; }
141
+
142
+ figure { margin: 40px 0; }
143
+ figure img { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: 10px; }
144
+ figcaption { color: var(--muted); font-size: 13px; margin-top: 10px; }
145
+
146
+ /* The mark is blue on white and the eyes are knocked out of the fill, so
147
+ it sits on a light plate rather than straight on the dark page. */
148
+ .mark { margin: 0 0 26px; max-width: 190px; }
149
+ .mark img {
150
+ border: 0;
151
+ border-radius: 14px;
152
+ background: var(--clay);
153
+ padding: 14px 12px;
154
+ }
155
+
156
+ .reel {
157
+ margin: 0 0 30px;
158
+ border: 1px solid var(--line);
159
+ border-radius: 12px;
160
+ overflow: hidden;
161
+ background: #000;
162
+ box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
163
+ }
164
+ .reel video { display: block; width: 100%; height: auto; }
165
+ .reel-note { color: var(--muted); font-size: 13px; margin: 12px 0 0; }
166
+
167
+ section { padding: 40px 0; border-top: 1px solid var(--line); }
168
+ h2 { font-size: clamp(22px, 3.2vw, 28px); margin: 0 0 8px; color: var(--clay); letter-spacing: -.01em; }
169
+ h3 { font-size: 17px; margin: 0 0 6px; color: var(--clay); }
170
+ .sub { color: var(--muted); margin: 0 0 26px; max-width: 48em; }
171
+
172
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; }
173
+ .card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 20px; }
174
+ .card p { margin: 0; color: var(--muted); font-size: 15px; }
175
+
176
+ pre {
177
+ background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
178
+ padding: 16px 18px; overflow-x: auto; font-size: 14px; line-height: 1.7;
179
+ }
180
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
181
+
182
+ table { width: 100%; border-collapse: collapse; font-size: 15px; }
183
+ th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--line); }
184
+ th { color: var(--clay); font-weight: 600; white-space: nowrap; }
185
+ td { color: var(--muted); }
186
+ .scroller { overflow-x: auto; }
187
+
188
+ details { border-bottom: 1px solid var(--line); padding: 16px 0; }
189
+ details summary { cursor: pointer; font-weight: 600; color: var(--clay); font-size: 16px; }
190
+ details p { color: var(--muted); margin: 12px 0 0; }
191
+
192
+ footer.site { border-top: 1px solid var(--line); padding: 32px 0 56px; color: var(--muted); font-size: 14px; }
193
+ footer.site a { color: var(--muted); }
194
+
195
+ @media (max-width: 620px) {
196
+ .hero { padding: 40px 0 28px; }
197
+ .btn { width: 100%; text-align: center; }
198
+ header.site nav { gap: 14px; width: 100%; margin-left: 0; padding-bottom: 10px; }
199
+ }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <header class="site">
204
+ <div class="wrap">
205
+ <a class="brand" href="/">CozyClay</a>
206
+ <nav>
207
+ <a href="/#what">What it does</a>
208
+ <a href="/#start">Quick start</a>
209
+ <a href="/ai-camera-control/">AI camera control</a>
210
+ <a href="https://github.com/HaD0Yun/CozyClay">GitHub</a>
211
+ </nav>
212
+ </div>
213
+ </header>
214
+
215
+ <main class="wrap">
216
+ <div class="hero">
217
+ <h1>Camera control for AI video</h1>
218
+ <p class="lede">
219
+ Video models will happily give you a low angle. They will not give you
220
+ <em>the same</em> low angle twice. Here is why the prompt keeps drifting, and what
221
+ to do about it.
222
+ </p>
223
+ </div>
224
+
225
+ <section>
226
+ <h2>The part nobody warns you about</h2>
227
+ <p class="sub">
228
+ A prompt describes a camera in words, and words have no units. "Low angle, close on
229
+ her face, slow push in" is four decisions with no numbers attached: how low, how
230
+ close, what focal length, how far the push travels. The model picks values you never
231
+ specified, and it picks different ones on the next run.
232
+ </p>
233
+ <p class="sub">
234
+ That is fine for a single hero shot. It falls apart the moment you need two shots to
235
+ belong to the same scene. Cut between two takes that each invented their own lens and
236
+ eyeline and the geometry does not agree — the room changes shape across the cut, and
237
+ the audience feels it even when they cannot name it.
238
+ </p>
239
+ <p class="sub">
240
+ The usual response is to write a longer prompt. More adjectives, a reference image,
241
+ a list of shot-type vocabulary. It narrows the spread. It does not close it, because
242
+ the thing being specified was never language in the first place.
243
+ </p>
244
+ </section>
245
+
246
+ <section>
247
+ <h2>What the models actually take</h2>
248
+ <p class="sub">
249
+ The current generation of video models accepts camera intent through some mix of
250
+ three channels, and it is worth knowing which one you are using:
251
+ </p>
252
+ <div class="grid">
253
+ <div class="card">
254
+ <h3>Words</h3>
255
+ <p>
256
+ Shot-type vocabulary in the prompt — wide, medium, close, dolly, crane, 35mm.
257
+ Universally supported, and the loosest. The model interprets; you do not specify.
258
+ </p>
259
+ </div>
260
+ <div class="card">
261
+ <h3>A first frame</h3>
262
+ <p>
263
+ An image the generation starts from. This pins composition hard, because framing,
264
+ lens compression and eyeline are already baked into the pixels. It is the highest
265
+ leverage input most people underuse.
266
+ </p>
267
+ </div>
268
+ <div class="card">
269
+ <h3>A path or reference clip</h3>
270
+ <p>
271
+ Some models take an explicit camera path, or a video whose motion is transferred
272
+ onto a new scene. Strongest control, narrowest support, and it needs the move to
273
+ exist somewhere before you can hand it over.
274
+ </p>
275
+ </div>
276
+ </div>
277
+ </section>
278
+
279
+ <section>
280
+ <h2>Block the shot, then generate</h2>
281
+ <p class="sub">
282
+ Film production solved this before any of these models existed, and the answer was
283
+ previs: build the scene rough in 3D, put a camera in it, and settle the shot while
284
+ it is still cheap to change. The blocking is the specification. Everything downstream
285
+ inherits it.
286
+ </p>
287
+ <p class="sub">
288
+ The same move works here. Put the geometry somewhere real first — a boxed-out set, a
289
+ character on a path, a camera with an actual position and an actual focal length —
290
+ and the questions the prompt was leaving open are already answered. You are no longer
291
+ asking for a low angle. You have one, and you can hand over the frame it produces.
292
+ </p>
293
+ <p class="sub">
294
+ Three things change once the camera lives in a scene instead of a sentence:
295
+ </p>
296
+ <div class="grid">
297
+ <div class="card">
298
+ <h3>Shots agree with each other</h3>
299
+ <p>
300
+ Two angles on one blocked set share a floor plan, so a cut between them holds.
301
+ Two prompts do not share anything.
302
+ </p>
303
+ </div>
304
+ <div class="card">
305
+ <h3>The move is reproducible</h3>
306
+ <p>
307
+ A dolly you drew is the same dolly tomorrow. Regenerate, change the subject, keep
308
+ the camera. Nothing about that survives a reroll of a text prompt.
309
+ </p>
310
+ </div>
311
+ <div class="card">
312
+ <h3>You fail cheaply</h3>
313
+ <p>
314
+ Finding out the angle was wrong costs a drag in a viewport, not a paid generation
315
+ and the wait attached to it.
316
+ </p>
317
+ </div>
318
+ </div>
319
+ </section>
320
+
321
+ <section>
322
+ <h2>Doing it in the browser</h2>
323
+ <p class="sub">
324
+ CozyClay is the previs half of that loop, built to be opened rather than installed.
325
+ Block a set out of primitives, drop a character and draw the path they walk, place
326
+ the camera and set its lens, cut between shots on a timeline, and scrub until the
327
+ timing is right. Then take those frames and those camera moves to whichever model you
328
+ are generating with.
329
+ </p>
330
+
331
+ <div class="reel">
332
+ <video
333
+ src="/media/cozyclay-demo.mp4"
334
+ poster="/media/cozyclay-demo-poster.jpg"
335
+ width="1920" height="1080"
336
+ muted loop playsinline controls preload="none"
337
+ ></video>
338
+ </div>
339
+ <p class="reel-note">
340
+ Left of the wipe is the generated result. Right of it is the blocking that produced
341
+ the camera.
342
+ </p>
343
+
344
+ <div class="cta">
345
+ <a class="btn primary" href="/">What CozyClay does</a>
346
+ <a class="btn ghost" href="https://github.com/HaD0Yun/CozyClay">View on GitHub</a>
347
+ </div>
348
+ <p class="note">
349
+ Free and open source. Runs locally with <code>npx cozyclay</code>.
350
+ </p>
351
+ </section>
352
+
353
+ <section id="faq">
354
+ <h2>FAQ</h2>
355
+ <details open>
356
+ <summary>Why does my camera angle change between takes?</summary>
357
+ <p>
358
+ Because the prompt never fixed it. Words like "low angle" or "close up" carry no
359
+ height, distance or focal length, so the model fills those in and fills them in
360
+ differently on each run. Pinning composition needs something with numbers behind it
361
+ — a starting frame, or a camera that exists in a scene.
362
+ </p>
363
+ </details>
364
+ <details>
365
+ <summary>Do camera movement prompts work at all?</summary>
366
+ <p>
367
+ They work as a nudge. "Slow dolly in" reliably gets you a push rather than a pan.
368
+ What it will not do is get you the same push twice, or a push that matches the one
369
+ in the shot you are cutting to. Use the vocabulary, but do not expect it to carry
370
+ continuity.
371
+ </p>
372
+ </details>
373
+ <details>
374
+ <summary>What is the highest-leverage thing I can control?</summary>
375
+ <p>
376
+ The first frame. Composition, lens compression and eyeline are all decided the
377
+ moment that image exists, and the generation inherits them instead of guessing.
378
+ This is exactly what blocking a shot in 3D gives you.
379
+ </p>
380
+ </details>
381
+ <details>
382
+ <summary>Do I need Blender or Unreal to block a shot?</summary>
383
+ <p>
384
+ No. Both will do it, and both ask you to learn a full DCC to move one camera.
385
+ CozyClay does the blocking part in a browser tab with Unity-style controls —
386
+ see <a href="/">what it does</a>.
387
+ </p>
388
+ </details>
389
+ </section>
390
+ </main>
391
+
392
+ <footer class="site">
393
+ <div class="wrap">
394
+ <p>
395
+ <a href="/">CozyClay</a> ·
396
+ <a href="https://github.com/HaD0Yun/CozyClay">GitHub</a> ·
397
+ <a href="https://www.npmjs.com/package/cozyclay">npm</a>
398
+ </p>
399
+ <p>
400
+ CozyClay is not affiliated with or endorsed by NVIDIA, Unity Technologies, ByteDance,
401
+ Google or Kuaishou. Product names are used only to describe interoperability.
402
+ </p>
403
+ </div>
404
+ </footer>
405
+ </body>
406
+ </html>
@@ -0,0 +1,57 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <meta name="description" content="CozyClay studio: block a 3D scene, pose characters, author camera moves, and preview generated motion in your browser." />
7
+ <meta name="theme-color" content="#232323" />
8
+ <meta name="apple-mobile-web-app-capable" content="yes" />
9
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
10
+ <meta name="apple-mobile-web-app-title" content="CozyClay" />
11
+ <!-- Unlinked on purpose: the studio is no longer promoted as a hosted demo.
12
+ It stays reachable so existing PWA installs keep working, but it should
13
+ not collect search traffic. -->
14
+ <meta name="robots" content="noindex, follow" />
15
+ <link rel="canonical" href="https://cozyclay.org/app/" />
16
+ <link rel="manifest" href="/manifest.webmanifest" />
17
+ <link rel="icon" href="/favicon.ico" sizes="any" />
18
+ <link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32.png" />
19
+ <link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png" />
20
+ <link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
21
+
22
+ <meta property="og:type" content="website" />
23
+ <meta property="og:site_name" content="CozyClay" />
24
+ <meta property="og:title" content="CozyClay Studio — 3D staging and previs in the browser" />
25
+ <meta property="og:description" content="Block a scene, pose characters, author camera moves, and preview generated motion. No install, no Blender, no Unity." />
26
+ <meta property="og:url" content="https://cozyclay.org/app/" />
27
+ <meta property="og:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
28
+ <meta property="og:image:width" content="1920" />
29
+ <meta property="og:image:height" content="1080" />
30
+ <meta name="twitter:card" content="summary_large_image" />
31
+ <meta name="twitter:title" content="CozyClay Studio — 3D staging and previs in the browser" />
32
+ <meta name="twitter:description" content="Block a scene, pose characters, author camera moves, and preview generated motion. No install, no Blender, no Unity." />
33
+ <meta name="twitter:image" content="https://cozyclay.org/media/cozyclay-demo-poster.jpg" />
34
+
35
+ <title>CozyClay Studio — 3D staging and previs in your browser</title>
36
+
37
+ <script defer src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "6a4384d1375d4ffc8a30a6346a8ad1a0"}'></script>
38
+ <script type="module" crossorigin src="/assets/app-B3U5aut1.js"></script>
39
+ <link rel="stylesheet" crossorigin href="/assets/app-BrRF0wso.css">
40
+ </head>
41
+ <body>
42
+ <div id="root">
43
+ <noscript>
44
+ <h1>CozyClay Studio</h1>
45
+ <p>
46
+ CozyClay is a browser-based 3D staging studio. Block a scene, pose characters,
47
+ author camera moves and cuts, sequence motion prompts on a timeline, and preview
48
+ generated motion — in one local workspace that handles like the Unity Editor.
49
+ </p>
50
+ <p>
51
+ The studio needs JavaScript and WebGL. Enable JavaScript to load it, or read
52
+ <a href="/">what CozyClay does</a> first.
53
+ </p>
54
+ </noscript>
55
+ </div>
56
+ </body>
57
+ </html>