cozyclay 1.1.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.
- package/README.md +59 -24
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/bin/cozyclay.mjs +20 -6
- package/dist/CNAME +1 -0
- package/dist/app/index.html +57 -0
- package/dist/assets/app-Cgpk2hwX.js +4803 -0
- package/dist/assets/app-DgZvaAE1.css +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/icons/apple-touch-icon.png +0 -0
- package/dist/icons/icon-192.png +0 -0
- package/dist/icons/icon-32.png +0 -0
- package/dist/icons/icon-512.png +0 -0
- package/dist/icons/icon-maskable-512.png +0 -0
- package/dist/index.html +406 -5
- package/dist/manifest.webmanifest +42 -0
- package/dist/media/cozyclay-demo-poster.jpg +0 -0
- package/dist/media/cozyclay-logo.png +0 -0
- package/dist/robots.txt +4 -0
- package/dist/sitemap.xml +9 -0
- package/dist/sw.js +127 -0
- package/package.json +15 -3
- package/src/App.jsx +1393 -648
- package/src/ardy/client.js +5 -4
- package/src/ardy/mp4-duration.js +102 -0
- package/src/ardy/timeline-coordinates.js +11 -0
- package/src/ardy/timeline.jsx +528 -67
- package/src/ardy/waypoints.js +14 -9
- package/src/camera-block.js +60 -0
- package/src/camera-follow.js +49 -10
- package/src/camera-rail-schedule.js +176 -0
- package/src/controls.jsx +16 -2
- package/src/cuts.js +184 -0
- package/src/generation/client.js +56 -0
- package/src/generation/session.js +47 -0
- package/src/generation/shot-spec.js +40 -0
- package/src/generation/use-generation.js +55 -0
- package/src/hierarchy-model.js +9 -17
- package/src/hierarchy-panel.jsx +170 -23
- package/src/locale-toggle.jsx +18 -0
- package/src/locale.js +34 -0
- package/src/main.jsx +5 -0
- package/src/object-catalog.jsx +32 -3
- package/src/planview.jsx +174 -66
- package/src/pose-extract/browser.js +42 -0
- package/src/pose-extract/fit.js +194 -0
- package/src/pose-extract/index.js +53 -0
- package/src/pose-extract/keyframes.js +74 -0
- package/src/pose-extract/landmarks.js +162 -0
- package/src/poses.js +114 -0
- package/src/posestudio.jsx +75 -13
- package/src/pwa.js +42 -0
- package/src/result-modal.jsx +124 -0
- package/src/scenes.js +223 -0
- package/src/shot-authoring.js +204 -77
- package/src/shot.js +8 -4
- package/src/styles.css +839 -38
- package/src/ui.jsx +54 -5
- package/tools/ardy/BRIDGE.md +70 -4
- package/tools/ardy/README.md +55 -0
- package/tools/ardy/bridge.mjs +89 -389
- package/tools/ardy/cclay_constrained_generate.py +449 -0
- package/tools/ardy/cclay_motion_edit.py +74 -9
- package/tools/ardy/cclay_sequence_generate.py +68 -7
- package/tools/ardy/cclay_worker.py +304 -0
- package/tools/ardy/merge-text-encoder.py +160 -0
- package/tools/ardy/run-local.mjs +233 -0
- package/tools/ardy/runners/index.mjs +23 -0
- package/tools/ardy/runners/local.mjs +452 -0
- package/tools/ardy/runners/proc.mjs +125 -0
- package/tools/ardy/runners/remote.mjs +246 -0
- package/tools/ardy/setup-local.mjs +189 -0
- package/tools/ardy/setup-text-encoder-on-box.sh +93 -0
- package/tools/ardy/setup-text-encoder.py +296 -0
- package/tools/generation/README.md +29 -0
- package/tools/generation/bridge.mjs +149 -0
- package/tools/generation/job-store.mjs +51 -0
- package/tools/generation/providers/kling.mjs +76 -0
- package/tools/generation/providers/runway.mjs +101 -0
- package/tools/generation/providers/seedance.mjs +87 -0
- package/tools/generation/providers/shared.mjs +49 -0
- package/tools/generation/providers/veo.mjs +84 -0
- package/tools/metrics/archive-traffic.mjs +138 -0
- package/dist/assets/index-BW_S1YRy.js +0 -4395
- package/dist/assets/index-nRmmJgap.css +0 -1
- package/tools/ardy/__pycache__/cclay_sequence_generate.cpython-313.pyc +0 -0
- package/tools/ardy/out/gen-1786443326924-c6019e-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443609325-0053db-generated.npz +0 -0
- package/tools/ardy/out/gen-1786443835628-749ed1-generated.npz +0 -0
- package/tools/ardy/out/gen-1786462605247-03cb19-generated.npz +0 -0
- package/tools/qa-browser.mjs +0 -84
- package/tools/qa-crop3.mjs +0 -22
- package/tools/qa-playview.mjs +0 -49
- package/tools/qa-screenshot.mjs +0 -24
- package/tools/qa-visual.mjs +0 -51
package/README.md
CHANGED
|
@@ -1,23 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/images/cozyclay-logo.png" alt="CozyClay" width="340">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
+
https://github.com/user-attachments/assets/1d0113e5-6922-443d-affc-1bdabc666247
|
|
12
37
|
|
|
13
|
-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
-
|
|
83
|
+
<details>
|
|
84
|
+
<summary><b>Token-free ARDY text encoder</b> — skip the Hugging Face gate</summary>
|
|
54
85
|
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
```bash
|
|
89
|
+
CCLAY_ARDY_HOST=user@your-gpu-box tools/ardy/setup-text-encoder-on-box.sh
|
|
90
|
+
```
|
|
60
91
|
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
|
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
|
|
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.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -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
|
@@ -199,8 +199,8 @@ if (opts.version) {
|
|
|
199
199
|
console.log(pkg.version);
|
|
200
200
|
process.exit(0);
|
|
201
201
|
}
|
|
202
|
-
if (!existsSync(join(DIST, "index.html"))) {
|
|
203
|
-
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).");
|
|
204
204
|
console.error("cozyclay: from a clone, run `npm install && npm run build` first.");
|
|
205
205
|
process.exit(1);
|
|
206
206
|
}
|
|
@@ -223,10 +223,21 @@ const server = createServer((req, res) => {
|
|
|
223
223
|
proxyToBridge(req, res);
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
|
-
const rel = decodeURIComponent(url.pathname
|
|
226
|
+
const rel = decodeURIComponent(url.pathname);
|
|
227
227
|
// normalize + prefix check: a request must not escape dist/.
|
|
228
|
-
|
|
229
|
-
if (!target.startsWith(DIST)
|
|
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()) {
|
|
230
241
|
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
231
242
|
res.end("not found");
|
|
232
243
|
return;
|
|
@@ -261,7 +272,10 @@ server.on("error", (err) => {
|
|
|
261
272
|
});
|
|
262
273
|
|
|
263
274
|
server.listen(opts.port, opts.host, () => {
|
|
264
|
-
|
|
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/`;
|
|
265
279
|
console.log(`CozyClay is running at ${url}`);
|
|
266
280
|
if (!opts.ardy) console.log("Motion generation: off (--no-ardy).");
|
|
267
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>
|