cozyclay 1.0.0 → 1.2.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 (94) hide show
  1. package/README.md +59 -24
  2. package/THIRD_PARTY_NOTICES.md +31 -0
  3. package/bin/cozyclay.mjs +105 -11
  4. package/dist/CNAME +1 -0
  5. package/dist/app/index.html +57 -0
  6. package/dist/assets/app-Cgpk2hwX.js +4803 -0
  7. package/dist/assets/app-DgZvaAE1.css +1 -0
  8. package/dist/favicon.ico +0 -0
  9. package/dist/icons/apple-touch-icon.png +0 -0
  10. package/dist/icons/icon-192.png +0 -0
  11. package/dist/icons/icon-32.png +0 -0
  12. package/dist/icons/icon-512.png +0 -0
  13. package/dist/icons/icon-maskable-512.png +0 -0
  14. package/dist/index.html +406 -5
  15. package/dist/manifest.webmanifest +42 -0
  16. package/dist/media/cozyclay-demo-poster.jpg +0 -0
  17. package/dist/media/cozyclay-logo.png +0 -0
  18. package/dist/robots.txt +4 -0
  19. package/dist/sitemap.xml +9 -0
  20. package/dist/sw.js +127 -0
  21. package/package.json +15 -3
  22. package/src/App.jsx +1393 -648
  23. package/src/ardy/client.js +5 -4
  24. package/src/ardy/mp4-duration.js +102 -0
  25. package/src/ardy/timeline-coordinates.js +11 -0
  26. package/src/ardy/timeline.jsx +528 -67
  27. package/src/ardy/waypoints.js +14 -9
  28. package/src/camera-block.js +60 -0
  29. package/src/camera-follow.js +49 -10
  30. package/src/camera-rail-schedule.js +176 -0
  31. package/src/controls.jsx +16 -2
  32. package/src/cuts.js +184 -0
  33. package/src/generation/client.js +56 -0
  34. package/src/generation/session.js +47 -0
  35. package/src/generation/shot-spec.js +40 -0
  36. package/src/generation/use-generation.js +55 -0
  37. package/src/hierarchy-model.js +9 -17
  38. package/src/hierarchy-panel.jsx +170 -23
  39. package/src/locale-toggle.jsx +18 -0
  40. package/src/locale.js +34 -0
  41. package/src/main.jsx +5 -0
  42. package/src/object-catalog.jsx +32 -3
  43. package/src/planview.jsx +174 -66
  44. package/src/pose-extract/browser.js +42 -0
  45. package/src/pose-extract/fit.js +194 -0
  46. package/src/pose-extract/index.js +53 -0
  47. package/src/pose-extract/keyframes.js +74 -0
  48. package/src/pose-extract/landmarks.js +162 -0
  49. package/src/poses.js +114 -0
  50. package/src/posestudio.jsx +75 -13
  51. package/src/pwa.js +42 -0
  52. package/src/result-modal.jsx +124 -0
  53. package/src/scenes.js +223 -0
  54. package/src/shot-authoring.js +204 -77
  55. package/src/shot.js +8 -4
  56. package/src/styles.css +839 -38
  57. package/src/ui.jsx +54 -5
  58. package/tools/ardy/BRIDGE.md +70 -4
  59. package/tools/ardy/README.md +55 -0
  60. package/tools/ardy/bridge.mjs +89 -389
  61. package/tools/ardy/cclay_constrained_generate.py +449 -0
  62. package/tools/ardy/cclay_motion_edit.py +74 -9
  63. package/tools/ardy/cclay_sequence_generate.py +68 -7
  64. package/tools/ardy/cclay_worker.py +304 -0
  65. package/tools/ardy/merge-text-encoder.py +160 -0
  66. package/tools/ardy/run-local.mjs +233 -0
  67. package/tools/ardy/runners/index.mjs +23 -0
  68. package/tools/ardy/runners/local.mjs +452 -0
  69. package/tools/ardy/runners/proc.mjs +125 -0
  70. package/tools/ardy/runners/remote.mjs +246 -0
  71. package/tools/ardy/setup-local.mjs +189 -0
  72. package/tools/ardy/setup-text-encoder-on-box.sh +93 -0
  73. package/tools/ardy/setup-text-encoder.py +296 -0
  74. package/tools/generation/README.md +29 -0
  75. package/tools/generation/bridge.mjs +149 -0
  76. package/tools/generation/job-store.mjs +51 -0
  77. package/tools/generation/providers/kling.mjs +76 -0
  78. package/tools/generation/providers/runway.mjs +101 -0
  79. package/tools/generation/providers/seedance.mjs +87 -0
  80. package/tools/generation/providers/shared.mjs +49 -0
  81. package/tools/generation/providers/veo.mjs +84 -0
  82. package/tools/metrics/archive-traffic.mjs +138 -0
  83. package/dist/assets/index-BW_S1YRy.js +0 -4395
  84. package/dist/assets/index-nRmmJgap.css +0 -1
  85. package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
  86. package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
  87. package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
  88. package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
  89. package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
  90. package/tools/qa-browser.mjs +0 -84
  91. package/tools/qa-crop3.mjs +0 -22
  92. package/tools/qa-playview.mjs +0 -49
  93. package/tools/qa-screenshot.mjs +0 -24
  94. package/tools/qa-visual.mjs +0 -51
package/README.md CHANGED
@@ -1,23 +1,55 @@
1
- # CozyClay
1
+ <p align="center">
2
+ <img src="docs/images/cozyclay-logo.png" alt="CozyClay" width="340">
3
+ </p>
2
4
 
3
- CozyClay is a browser-based 3D staging studio built with Three.js and React Three Fiber. 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.
5
+ <p align="center">
6
+ <em>Block a scene, pose the cast, cut the camera — in a browser tab.</em>
7
+ </p>
4
8
 
5
- **[Open the live demo](https://had0yun.github.io/CozyClay/)** — the studio itself, running in your browser. Nothing to install. It comes seeded with a pre-generated motion clip so you can scrub the timeline, drive the cameras and draw a dolly rail straight away; generating new motion needs a local ARDY machine, so that part is off.
9
+ <p align="center">
10
+ <a href="LICENSE"><img alt="License: GPL-3.0" src="https://img.shields.io/badge/license-GPL--3.0-blue"></a>
11
+ <a href="https://www.npmjs.com/package/cozyclay"><img alt="npm" src="https://img.shields.io/npm/v/cozyclay"></a>
12
+ <img alt="Node 22+" src="https://img.shields.io/badge/node-22%2B-brightgreen">
13
+ <a href="https://github.com/HaD0Yun/CozyClay/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/HaD0Yun/CozyClay?style=flat"></a>
14
+ </p>
6
15
 
7
- ![CozyClay browser-based 3D staging studio](docs/images/cozyclay-studio.png)
16
+ <p align="center">
17
+ <a href="https://cozyclay.org/">Demo reel</a> ·
18
+ <a href="#quick-start">Quick start</a> ·
19
+ <a href="#what-you-can-do">Features</a> ·
20
+ <a href="#controls">Controls</a> ·
21
+ <a href="https://github.com/HaD0Yun/CozyClay/issues">Issues</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ CozyClay is a browser-based 3D staging studio built with Three.js and React Three Fiber. Block a scene, pose characters, sequence motion prompts on a timeline, and preview generated motion — all in one local workspace.
27
+
28
+ ```bash
29
+ npx cozyclay
30
+ ```
31
+
32
+ That is the whole install. **[cozyclay.org](https://cozyclay.org/)** has the demo reel and a walkthrough of what the studio does; to use it, run it on your own machine. It ships seeded with a pre-generated motion clip, so you can scrub the timeline, drive the cameras and draw a dolly rail straight away — generating *new* motion needs a local ARDY machine, so that part stays off until you point it at one.
8
33
 
9
34
  ## Demo
10
35
 
11
- ![CozyClay motion, staging, and camera workflow](docs/images/cozyclay-demo.gif)
36
+ https://github.com/user-attachments/assets/1d0113e5-6922-443d-affc-1bdabc666247
12
37
 
13
- CozyClay can connect to [NVIDIA ARDY](https://github.com/nv-tlabs/ardy) for motion generation. ARDY is a separate third-party project owned and maintained by NVIDIA; it is not included in this repository, and CozyClay is not affiliated with or endorsed by NVIDIA.
38
+ ## What you can do
39
+
40
+ | | |
41
+ | --- | --- |
42
+ | **Stage a scene** | Create primitives and set pieces, then move, rotate and scale them with a W/E/R gizmo. Grid snapping is a preference, not a law — hold `Ctrl` mid-drag to invert it. A bird's-eye plan view drives 2D root waypoints for character paths. |
43
+ | **Fly the camera** | Right-drag flies (WASD walks, Q/E cranes), middle-drag pans, Alt+drag orbits the selection, click selects, `F` frames — the muscle memory you already have from a 3D editor. |
44
+ | **Undo anything** | Every scene mutation goes through one history store: a drag, a scrub, an inspector edit is exactly one undo entry. `Esc` cancels an in-flight drag and restores the pre-drag transform. |
45
+ | **Generate motion** | Pose characters and export poses, sequence multi-phase motion as Prompt Blocks on a resizable timeline, send them to ARDY, then play the result back with sparse IK correction where the generated motion needs fixing. |
14
46
 
15
47
  ## Requirements
16
48
 
17
49
  - Node.js 22 or newer
18
50
  - npm, or bun
19
51
  - A Chromium-based browser
20
- - Optional: an SSH-accessible NVIDIA machine running ARDY, for motion generation
52
+ - *Optional:* an SSH-accessible NVIDIA machine running ARDY, for motion generation
21
53
 
22
54
  ## Quick start
23
55
 
@@ -27,17 +59,15 @@ npx cozyclay
27
59
  bunx cozyclay
28
60
  ```
29
61
 
30
- That downloads the built studio and opens it at `http://127.0.0.1:5180`. There is
31
- nothing to compile and no dependency tree to install. Useful flags:
32
- `--port 5200`, `--no-open`, `--no-ardy`.
62
+ That downloads the built studio and opens it at `http://127.0.0.1:5180`. Nothing to compile, no dependency tree to install. Useful flags: `--port 5200`, `--no-open`, `--no-ardy`.
33
63
 
34
- Motion generation is off until you point it at a machine that can run it:
64
+ Motion generation stays off until you point it at a machine that can run it:
35
65
 
36
66
  ```bash
37
67
  CCLAY_ARDY_HOST=user@your-gpu-box npx cozyclay
38
68
  ```
39
69
 
40
- Everything else, staging through camera work and playback, runs without it.
70
+ Everything else staging through camera work and playback runs without it.
41
71
 
42
72
  ### From a clone
43
73
 
@@ -48,19 +78,20 @@ npm install
48
78
  npm run dev
49
79
  ```
50
80
 
51
- Open `http://127.0.0.1:5180`. `npm run dev` starts the studio together with its local ARDY bridge; `npm run dev:ui` starts the browser UI alone, without Block Generation.
81
+ Open `http://127.0.0.1:5180`. `npm run dev` starts the studio together with its local ARDY bridge; `npm run dev:ui` starts the browser UI alone, without Block Generation. The bridge listens on loopback only; the environment variables that point it at a remote ARDY machine are documented in [`tools/ardy/BRIDGE.md`](tools/ardy/BRIDGE.md).
52
82
 
53
- The bridge listens on loopback only. The environment variables that point it at a remote ARDY machine are documented in `tools/ardy/BRIDGE.md`.
83
+ <details>
84
+ <summary><b>Token-free ARDY text encoder</b> — skip the Hugging Face gate</summary>
54
85
 
55
- ## What you can do
86
+ ARDY's text encoder normally requires a Hugging Face account, gated-model approval, and an access token on the ARDY machine. CozyClay ships a token-free alternative — one command provisions the same encoder stack from public repositories, pinned by commit and SHA-256:
56
87
 
57
- **Stage a scene.** Create primitives and set pieces, then move, rotate, and scale them with a W/E/R gizmo. Grid snapping is a preference, not a law — hold `Ctrl` during a drag to invert it. A bird's-eye plan view drives 2D root waypoints for character paths.
58
-
59
- **Edit like Unity.** Right-drag flies the camera (WASD walks, Q/E cranes), middle-drag pans, Alt+drag orbits the selection, left click selects, `F` frames. The full rule set, its source in Unity's manual, and every deliberate divergence are recorded in `docs/unity-reference.md`.
88
+ ```bash
89
+ CCLAY_ARDY_HOST=user@your-gpu-box tools/ardy/setup-text-encoder-on-box.sh
90
+ ```
60
91
 
61
- **Undo anything.** Every scene mutation goes through a single history store: one interaction a drag, a scrub, an inspector edit is exactly one undo entry. `Esc` cancels an in-flight drag and restores the pre-drag transform.
92
+ See [`tools/ardy/README.md`](tools/ardy/README.md) for details. This workflow is built with Meta Llama 3; the encoder's base weights are licensed under the Meta Llama 3 Community License.
62
93
 
63
- **Generate motion.** Pose characters and export poses, sequence multi-phase motion as Prompt Blocks on a resizable timeline, send them to ARDY, then play the result back with sparse IK correction where the generated motion needs fixing.
94
+ </details>
64
95
 
65
96
  ## Controls
66
97
 
@@ -100,10 +131,14 @@ Ad-hoc browser QA, while a dev server is available:
100
131
  npm run qa:browser -- <qa-script>
101
132
  ```
102
133
 
103
- ## Repository hygiene
134
+ ## Contributing
135
+
136
+ Found something broken, or want a feature? [Open an issue](https://github.com/HaD0Yun/CozyClay/issues) — bug reports with a repro are the most useful thing you can send.
104
137
 
105
- Generated motion archives, QA output, build output, logs, and local runtime artifacts are not source files and must not be committed. In particular, keep `tools/ardy/out/`, `artifacts/`, `dist/`, `.gjc/`, and `.npz` files local.
138
+ **Repository hygiene.** Generated motion archives, QA output, build output, logs and local runtime artifacts are not source files and must not be committed. Keep `tools/ardy/out/`, `artifacts/`, `dist/`, `.gjc/` and `.npz` files local.
106
139
 
107
- ## License
140
+ ## License & credits
108
141
 
109
- GNU General Public License v3.0 or later — see `LICENSE`. Third-party projects retain their own licenses and copyright; see `THIRD_PARTY_NOTICES.md`.
142
+ GNU General Public License v3.0 or later — see [`LICENSE`](LICENSE). Third-party projects retain their own licenses and copyright; see [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).
143
+
144
+ CozyClay can connect to [NVIDIA ARDY](https://github.com/nv-tlabs/ardy) for motion generation. ARDY is a separate third-party project owned and maintained by NVIDIA; it is not included in this repository, and CozyClay is not affiliated with or endorsed by NVIDIA.
@@ -24,6 +24,37 @@ ARDY is not bundled with CozyClay. Users must obtain, install, and operate ARDY
24
24
 
25
25
  ARDY model checkpoints and other model assets may be governed by separate terms, including the NVIDIA Open Model License identified by the ARDY project. Users are responsible for reviewing and complying with those terms before downloading or using the models.
26
26
 
27
+ ## Meta Llama 3
28
+
29
+ ARDY's text encoder is based on Meta Llama 3 (`Meta-Llama-3-8B-Instruct`).
30
+ CozyClay does not bundle or redistribute the model weights; the optional
31
+ `tools/ardy/setup-text-encoder.py` script downloads them directly from a
32
+ public repository to the user's own machine for local use, together with the
33
+ model's LICENSE and USE_POLICY files.
34
+
35
+ Built with Meta Llama 3.
36
+
37
+ - Copyright © Meta Platforms, Inc. All Rights Reserved.
38
+ - License: Meta Llama 3 Community License
39
+ - License text: https://www.llama.com/llama3/license/
40
+ - Acceptable Use Policy: https://www.llama.com/llama3/use-policy/
41
+
42
+ Meta Llama 3 is licensed under the Meta Llama 3 Community License,
43
+ Copyright © Meta Platforms, Inc. All Rights Reserved.
44
+
45
+ ## LLM2Vec
46
+
47
+ ARDY's text encoder applies the LLM2Vec adapters from McGill NLP
48
+ (`LLM2Vec-Meta-Llama-3-8B-Instruct-mntp` and `-mntp-supervised`). Like the
49
+ base weights, they are downloaded by the setup script for local use, not
50
+ bundled.
51
+
52
+ - Copyright (c) 2024 McGill NLP
53
+ - License: MIT (the adapters are derived from Meta Llama 3; the Meta Llama 3
54
+ Community License applies to that underlying model)
55
+ - Source: https://github.com/McGill-NLP/llm2vec
56
+ - License text: https://github.com/McGill-NLP/llm2vec/blob/main/LICENSE
57
+
27
58
  ## CozyClay license scope
28
59
 
29
60
  The CozyClay source code in this repository is licensed under GPL-3.0-or-later. That license does not replace or relicense Three.js, ARDY, ARDY model checkpoints, or any other third-party component.
package/bin/cozyclay.mjs CHANGED
@@ -15,9 +15,11 @@
15
15
  * before it can serve a prebuilt app is a launcher that will break.
16
16
  */
17
17
  import { spawn } from "node:child_process";
18
- import { createReadStream, existsSync, statSync } from "node:fs";
18
+ import { createReadStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
19
19
  import { createServer, request as httpRequest } from "node:http";
20
+ import { homedir } from "node:os";
20
21
  import { extname, join, normalize, resolve } from "node:path";
22
+ import { createInterface } from "node:readline";
21
23
  import { fileURLToPath } from "node:url";
22
24
 
23
25
  const PKG_ROOT = resolve(fileURLToPath(new URL("..", import.meta.url)));
@@ -43,7 +45,7 @@ const TYPES = {
43
45
  };
44
46
 
45
47
  function parseArgs(argv) {
46
- const opts = { port: 5180, host: "127.0.0.1", ardy: true, open: true };
48
+ const opts = { port: 5180, host: "127.0.0.1", ardy: true, open: true, star: true };
47
49
  for (let i = 0; i < argv.length; i += 1) {
48
50
  const arg = argv[i];
49
51
  if (arg === "--port" || arg === "-p") opts.port = Number(argv[++i]);
@@ -52,6 +54,7 @@ function parseArgs(argv) {
52
54
  else if (arg.startsWith("--host=")) opts.host = arg.slice(7);
53
55
  else if (arg === "--no-ardy") opts.ardy = false;
54
56
  else if (arg === "--no-open") opts.open = false;
57
+ else if (arg === "--no-star") opts.star = false;
55
58
  else if (arg === "--help" || arg === "-h") opts.help = true;
56
59
  else if (arg === "--version" || arg === "-v") opts.version = true;
57
60
  else {
@@ -66,12 +69,20 @@ function parseArgs(argv) {
66
69
  return opts;
67
70
  }
68
71
 
72
+ const REPO_URL = "https://github.com/HaD0Yun/CozyClay";
73
+ const STATE_DIR = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "cozyclay");
74
+ const STATE_FILE = join(STATE_DIR, "state.json");
75
+ // Only worth asking someone who actually used the thing. A prompt three
76
+ // seconds in is a popup; a prompt after a real session is a question.
77
+ const STAR_AFTER_MS = Number(process.env.COZYCLAY_STAR_AFTER_MS ?? 60_000);
78
+
69
79
  const HELP = `cozyclay - browser-based 3D staging studio
70
80
 
71
81
  npx cozyclay start the studio and open it
72
82
  npx cozyclay --port 5200 serve on another port
73
83
  npx cozyclay --no-ardy skip the optional motion-generation sidecar
74
84
  npx cozyclay --no-open do not open a browser
85
+ npx cozyclay --no-star never ask about starring the repo
75
86
 
76
87
  Motion generation needs an SSH-reachable NVIDIA machine running ARDY; point
77
88
  the sidecar at it with CCLAY_ARDY_HOST. Everything else - staging, posing,
@@ -108,6 +119,66 @@ function proxyToBridge(req, res) {
108
119
  req.pipe(upstream);
109
120
  }
110
121
 
122
+ function readState() {
123
+ try {
124
+ return JSON.parse(readFileSync(STATE_FILE, "utf8"));
125
+ } catch {
126
+ return {};
127
+ }
128
+ }
129
+
130
+ function writeState(patch) {
131
+ try {
132
+ mkdirSync(STATE_DIR, { recursive: true });
133
+ writeFileSync(STATE_FILE, JSON.stringify({ ...readState(), ...patch }, null, "\t"));
134
+ } catch {
135
+ /* a read-only home is not a reason to fail a local dev server */
136
+ }
137
+ }
138
+
139
+ async function starCount() {
140
+ try {
141
+ const res = await fetch("https://api.github.com/repos/HaD0Yun/CozyClay", {
142
+ headers: { accept: "application/vnd.github+json" },
143
+ signal: AbortSignal.timeout(2500),
144
+ });
145
+ if (!res.ok) return null;
146
+ const body = await res.json();
147
+ return Number.isInteger(body?.stargazers_count) ? body.stargazers_count : null;
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ // Asked once, on the way out, and never again on this machine. A CLI that
154
+ // nags on every run is worse than one that never asks.
155
+ async function maybeAskForStar(startedAt, opts) {
156
+ if (!opts.star) return;
157
+ if (process.env.CI || process.env.COZYCLAY_NO_STAR) return;
158
+ if (!process.stdin.isTTY || !process.stdout.isTTY) return;
159
+ if (Date.now() - startedAt < STAR_AFTER_MS) return;
160
+ if (readState().starPromptedAt) return;
161
+
162
+ writeState({ starPromptedAt: new Date().toISOString() });
163
+ const stars = await starCount();
164
+ const tally = stars === null ? "" : ` (${stars} so far)`;
165
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
166
+ const answer = await new Promise((done) => {
167
+ const timer = setTimeout(() => done(""), 12_000);
168
+ rl.question(`\nWas CozyClay any good? A star helps people find it${tally}. Open GitHub? [Y/n] `, (a) => {
169
+ clearTimeout(timer);
170
+ done(a);
171
+ });
172
+ });
173
+ rl.close();
174
+ if (/^(y|yes|)$/i.test(answer.trim())) {
175
+ openBrowser(REPO_URL);
176
+ console.log("Thanks. Opening the repo.");
177
+ } else {
178
+ console.log(`Fair enough. ${REPO_URL} if you change your mind.`);
179
+ }
180
+ }
181
+
111
182
  function openBrowser(url) {
112
183
  const cmd =
113
184
  process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -128,8 +199,8 @@ if (opts.version) {
128
199
  console.log(pkg.version);
129
200
  process.exit(0);
130
201
  }
131
- if (!existsSync(join(DIST, "index.html"))) {
132
- console.error("cozyclay: this package is missing its build (dist/index.html).");
202
+ if (!existsSync(join(DIST, "app", "index.html"))) {
203
+ console.error("cozyclay: this package is missing its build (dist/app/index.html).");
133
204
  console.error("cozyclay: from a clone, run `npm install && npm run build` first.");
134
205
  process.exit(1);
135
206
  }
@@ -152,10 +223,21 @@ const server = createServer((req, res) => {
152
223
  proxyToBridge(req, res);
153
224
  return;
154
225
  }
155
- const rel = decodeURIComponent(url.pathname === "/" ? "/index.html" : url.pathname);
226
+ const rel = decodeURIComponent(url.pathname);
156
227
  // normalize + prefix check: a request must not escape dist/.
157
- const target = join(DIST, normalize(rel));
158
- if (!target.startsWith(DIST) || !existsSync(target) || statSync(target).isDirectory()) {
228
+ let target = join(DIST, normalize(rel));
229
+ if (!target.startsWith(DIST)) {
230
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
231
+ res.end("not found");
232
+ return;
233
+ }
234
+ // The build is multi-page: "/" is the landing and "/app/" is the studio, so a
235
+ // directory has to resolve to its index the way a static host would. Without
236
+ // this the studio 404s and the CLI only ever serves the landing page.
237
+ if (existsSync(target) && statSync(target).isDirectory()) {
238
+ target = join(target, "index.html");
239
+ }
240
+ if (!existsSync(target) || statSync(target).isDirectory()) {
159
241
  res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
160
242
  res.end("not found");
161
243
  return;
@@ -163,10 +245,19 @@ const server = createServer((req, res) => {
163
245
  serveFile(res, target);
164
246
  });
165
247
 
166
- function shutdown() {
248
+ const startedAt = Date.now();
249
+ let shuttingDown = false;
250
+ async function shutdown() {
251
+ if (shuttingDown) process.exit(0);
252
+ shuttingDown = true;
167
253
  if (bridge) bridge.kill("SIGTERM");
168
- server.close(() => process.exit(0));
169
- setTimeout(() => process.exit(0), 2000).unref();
254
+ server.close();
255
+ try {
256
+ await maybeAskForStar(startedAt, opts);
257
+ } catch {
258
+ /* never let the goodbye prompt hold the process hostage */
259
+ }
260
+ process.exit(0);
170
261
  }
171
262
  process.on("SIGINT", shutdown);
172
263
  process.on("SIGTERM", shutdown);
@@ -181,7 +272,10 @@ server.on("error", (err) => {
181
272
  });
182
273
 
183
274
  server.listen(opts.port, opts.host, () => {
184
- const url = `http://${opts.host}:${opts.port}/`;
275
+ // The package exists to open the studio, which the site serves from /app/.
276
+ // Landing on "/" would greet someone who just typed `npx cozyclay` with a
277
+ // marketing page.
278
+ const url = `http://${opts.host}:${opts.port}/app/`;
185
279
  console.log(`CozyClay is running at ${url}`);
186
280
  if (!opts.ardy) console.log("Motion generation: off (--no-ardy).");
187
281
  else if (bridge) console.log(`Motion generation: sidecar running against ${ardyHost}.`);
package/dist/CNAME ADDED
@@ -0,0 +1 @@
1
+ cozyclay.org
@@ -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 previz 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 previz 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 previz 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-Cgpk2hwX.js"></script>
39
+ <link rel="stylesheet" crossorigin href="/assets/app-DgZvaAE1.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>