@vosjs/cli 0.46.1 → 0.48.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 (39) hide show
  1. package/README.md +15 -5
  2. package/dist/{chunk-UE66DC2K.js → chunk-ANKECV7F.js} +658 -60
  3. package/dist/chunk-ANKECV7F.js.map +1 -0
  4. package/dist/{chunk-ID4OUS5L.js → chunk-AUPGJJTL.js} +18 -1
  5. package/dist/chunk-AUPGJJTL.js.map +1 -0
  6. package/dist/{chunk-EFI3WUQB.js → chunk-P3X5KVA6.js} +15 -2
  7. package/dist/{chunk-EFI3WUQB.js.map → chunk-P3X5KVA6.js.map} +1 -1
  8. package/dist/cli.js +4 -4
  9. package/dist/index.js +3 -3
  10. package/dist/manifest-A4R367EM.js +8 -0
  11. package/dist/{run-2OZS25ER.js → run-CYHH56KA.js} +3 -3
  12. package/dist/{shotList-JC4A3FBT.js → shotList-7F2FE3RU.js} +2 -2
  13. package/package.json +9 -7
  14. package/schema/actions.schema.json +2 -2
  15. package/skills/VERSION +1 -0
  16. package/skills/launch-kit/SKILL.md +356 -0
  17. package/skills/launch-kit/references/channel-specs.md +56 -0
  18. package/skills/product-video/SKILL.md +263 -0
  19. package/skills/product-video/references/destinations.md +71 -0
  20. package/skills/product-video/references/sessions.md +226 -0
  21. package/skills/product-video/references/taste.md +115 -0
  22. package/skills/product-video/references/troubleshooting.md +116 -0
  23. package/skills/vos-authoring/SKILL.md +191 -0
  24. package/skills/vos-authoring/references/examples.md +239 -0
  25. package/skills/vos-authoring/references/schema-reference.md +468 -0
  26. package/skills/vos-create/SKILL.md +258 -0
  27. package/skills/vos-cut/SKILL.md +241 -0
  28. package/skills/vos-footage/SKILL.md +98 -0
  29. package/skills/vos-migrate/SKILL.md +108 -0
  30. package/skills/vos-remix/SKILL.md +136 -0
  31. package/skills/vos-remix/references/3d-recipe.md +37 -0
  32. package/skills/vos-remix/references/params-knobs.md +80 -0
  33. package/skills/vos-remix/references/remix-contract.md +81 -0
  34. package/dist/chunk-ID4OUS5L.js.map +0 -1
  35. package/dist/chunk-UE66DC2K.js.map +0 -1
  36. package/dist/manifest-UCS6OVWZ.js +0 -8
  37. /package/dist/{manifest-UCS6OVWZ.js.map → manifest-A4R367EM.js.map} +0 -0
  38. /package/dist/{run-2OZS25ER.js.map → run-CYHH56KA.js.map} +0 -0
  39. /package/dist/{shotList-JC4A3FBT.js.map → shotList-7F2FE3RU.js.map} +0 -0
@@ -0,0 +1,115 @@
1
+ # Taste — the product-video quality bar
2
+
3
+ The rubric for judging a take before its render ships. Judge stills
4
+ **multimodally**: look at the images, don't just check numbers.
5
+
6
+ ## The quality loop (run it, don't skip it)
7
+
8
+ 1. `vos record --actions actions.json --out take --strict --json` —
9
+ exit 2 means the flow is broken; fix selectors, never ship around a skip.
10
+ 2. `vos validate take` — doc lints must pass (warnings are judgment calls).
11
+ 3. `vos frames take --at-zooms --times 0,25%,50%,75%,100%` — look at every
12
+ still against the rubric below. The zoom apexes are where quality lives;
13
+ judge those hardest.
14
+ 4. Iterate doc.json → spot-check with `vos render take x.webm --range a..b --draft`
15
+ (seconds, not a full re-render) → re-frame the changed region.
16
+ 5. Full render only when the stills pass. Verify the final with one more
17
+ `frames --at-zooms` against the SAME rubric before declaring done.
18
+
19
+ ## Story shape
20
+
21
+ 1. **One idea per clip.** A take demonstrates one flow or one feature —
22
+ navigation to it is overhead, not story. Open as close to the money shot
23
+ as possible; a cold viewer should see the point inside 3 seconds.
24
+ 2. **≤ 30s for marketing clips**; 8–15s is the sweet spot for a page hero.
25
+ Launch videos may run longer but every scene must still earn its seconds.
26
+ 3. **End settled.** Close on a resolved state (result visible, motion parked),
27
+ never mid-action — the loop point should read as intentional.
28
+
29
+ ## Flow authoring (actions.json)
30
+
31
+ 4. **Route the cursor away from hover-traps.** Nav links that live next to
32
+ hover-triggered menus (mega-panels, frosted backdrop-blur scrims) will
33
+ open them as the cursor passes — the rest of the take then plays behind a
34
+ blur. Unless the menu IS the subject, path the cursor below the nav
35
+ (`move` to page content first), or target an equivalent link outside the
36
+ nav.
37
+ 5. **Pacing is the zoom plan.** Hovers of 700–900ms on the things that matter
38
+ become the zooms; open with `wait ≥700ms`, give navigations 1200–2000ms,
39
+ end with a settle wait. A rushed flow reads as a rushed video AND plans
40
+ no zooms.
41
+ 6. **Real interactions only.** Click things that respond, type real strings
42
+ (no "asdf"), scroll to content — the witnessed-capture claim dies the
43
+ moment footage looks staged.
44
+
45
+ ## Zoom & camera
46
+
47
+ 7. **Levels 1.4–2.8 read well**; above 3× is for genuine detail (a number, a
48
+ toggle), and must be brief. Below 1.3× reads as drift, not intent.
49
+ 8. **One zoom per beat.** Zooms that chain across unrelated UI read as a lost
50
+ camera. If the planner over-suggested, delete spans — an un-zoomed second
51
+ is better than an unmotivated one.
52
+ 9. **Focus on the subject, not the cursor resting spot.** `cx/cy` are
53
+ normalized [0..1]; check the apex still shows the thing the beat is about,
54
+ with breathing room (the subject inside the middle ~60% of the crop).
55
+ 10. **Camera style is a sentence, not a spice rack.** Default `glide` for
56
+ marketing calm; `snappy` only for dense click-through demos; never mix
57
+ intents within one short clip.
58
+
59
+ ## Frame & destination
60
+
61
+ 11. **Footage honesty.** Never export above the recording's capture width —
62
+ `validate` warns; fix the viewport at record time instead of upscaling.
63
+ 12. **Background is ambience, not a subject.** A `frame.backgroundMedia`
64
+ loop should sit _behind_ the work, never compete with it: prefer a calm
65
+ ambient loop, and set `dim` (≈0.2–0.4) whenever the card carries dense
66
+ UI or the take runs long enough to loop visibly. A high-contrast or fast
67
+ background behind a text-heavy screen is a rejection.
68
+ 13. **Tilt is punctuation, not texture.** `doc.tilt` spans lean the card for
69
+ a beat and return to rest — at most one pose change per ~5s, in the
70
+ ±5..18° band, paired with the zoom moments (same `in`/`out` chains the
71
+ two camera moves; `tiltStyle` does this for you). Lean TOWARD the focus
72
+ (right-side focus = negative `ry`). Never oscillate — a wobbling card is
73
+ a rejection — and never leave the card leaning across a whole take: a
74
+ permanent lean reads as a rendering bug, not a style. Spans are the only
75
+ way to pose the card (there is no static tilt), so rest is always flat
76
+ and every lean has to earn its moment.
77
+
78
+ ## Smoothness — the north star
79
+
80
+ A product video's baseline quality is SMOOTHNESS: the frame should always be
81
+ alive, and nothing should change all-at-once unless it's a deliberate cut.
82
+ The two enemies, both measurable:
83
+
84
+ 14. **Dead time.** A still frame is not the defect: a page the viewer is
85
+ reading is content, and a workspace tour is mostly still. What is dead
86
+ is a still frame under a PARKED cursor past the beat it takes to read
87
+ what changed: about 1 s after a page changed, 0.6 s after a control
88
+ did. The record done event reports it per step as `dead` (`ms`, `pct`,
89
+ `steps[]` with how long each hold ran after its page settled and how
90
+ much of that was past the beat; `freezePct` stays as the plain fact of
91
+ stillness, `@vosjs/cli` 0.46 and later). Budget: **≤20% dead**, no
92
+ single dead hold >1.5s. The fix is the step's `ms`: a hold is what it
93
+ takes to read what changed, so cut the named steps to their beat. Then,
94
+ where a flow allows it, keep something alive in frame (a playing
95
+ preview, a live canvas, a scroll), and hover things that respond with
96
+ motion. Never choose a flow to make a stillness number smaller.
97
+ 15. **Full-frame bangs.** Instant UI re-layouts (filter clicks, page
98
+ navigations) read as jump cuts — violent when zoomed. Budget: **≤1 bang
99
+ per ~5s**, and let them happen WIDE (place zoom spans so the camera has
100
+ released before a click that re-layouts; zoom into the RESULT, not the
101
+ trigger). Census: `ffmpeg -vf "select='gt(scene,0.12)'"` on the final
102
+ render — each detection should be a transition you _chose_.
103
+
104
+ ## Judge each still set against
105
+
106
+ - **Blur check** — nothing unintentionally blurred (hover scrims, mid-seek
107
+ frames). If a still looks soft, find out why before shipping.
108
+ - **Chrome check** — browser bar style matches the destination (mac for
109
+ marketing, minimal/none inside app-like framings); no double chrome.
110
+ - **Cursor check** — the dot rests on or beside the subject at every apex,
111
+ never floating in dead space; click effects land under it.
112
+ - **Text legibility** — body text in the footage readable at the render size;
113
+ if not, the zoom level or viewport was wrong.
114
+ - **First/last frame** — both must stand alone as posters (the first frame IS
115
+ the poster; the last frame is what loops into the first).
@@ -0,0 +1,116 @@
1
+ # Troubleshooting
2
+
3
+ ## Exit codes
4
+
5
+ | Code | Meaning | Fix |
6
+ |---|---|---|
7
+ | 0 | ok | |
8
+ | 1 | error | read stderr; `--json` puts the message in the done event |
9
+ | 2 | usage error, or `--strict` failure | see below |
10
+ | 3 | no browser | `npx playwright install chromium`, or set `VOS_BROWSER_PATH` to a Chrome/Chromium binary |
11
+ | 4 | the recorder met a sign-in instead of the page (`@vosjs/cli` 0.41 and later) | it needs a SESSION, not a new script: walk `sessions.md`, then pass `--storage-state <file>`. Nothing was recorded and nothing was cleared. `--allow-wall` only when the sign-in page IS the take |
12
+
13
+ ## The wall (exit 4)
14
+
15
+ `vos record`, `create` and `--dry-run` check where the first navigation
16
+ landed before a frame is captured. Refused always: a 401 or 403, a redirect
17
+ to an identity provider or a sign-in path, a sign-in form rendered in place.
18
+ Refused under `--strict` and in a rehearsal, warned otherwise: a redirect
19
+ somewhere else with no sign-in in sight, which is what a site that shows
20
+ strangers its marketing page looks like.
21
+
22
+ - Do not touch `actions.json`. The script is fine; the browser is a
23
+ stranger. One `curl -sI <url>` before you script tells you the same thing
24
+ sooner: a 30x away from the page you asked for is a wall.
25
+ - A re-record that worked last week and exits 4 today is an EXPIRED session.
26
+ Mint it again; the old footage is still there, the refusal clears nothing.
27
+ - A digest that prints `WALL` is footage recorded past a wall
28
+ (`--allow-wall`, or a redirect without `--strict`): it may be the wrong
29
+ page. Re-record with a session before cutting it.
30
+
31
+ ## `setup #N … the selector never appeared` (exit 2)
32
+
33
+ The off-camera sign-in did not finish, so the take did not start. A
34
+ selector in `setup` is checked the same way a step's is: rehearse with
35
+ `--dry-run`. `the environment variable NAME is not set` means the shell
36
+ that ran `vos record` did not export it; the value is never read from a
37
+ file. A setup that ran and then met the wall (exit 4) signed in with the
38
+ wrong credentials or into the wrong place.
39
+
40
+ ## `EXPOSED in the frame`
41
+
42
+ Not an error: the take recorded. The recorder read the visible text after
43
+ each step and saw something shaped like an email address, a key or a card.
44
+ It reports the kind and the place, never the string. Fix it in the script
45
+ (`mask`, or a demo account) and re-record; a blur added afterwards in
46
+ `doc.json` still leaves the real value in `recording.webm`, which is what
47
+ `vos push` uploads. `MASK hid nothing: <selector>` (exit 2) means the
48
+ selector matched no element, so whatever it was for may be showing.
49
+
50
+ ## Strict-mode failures (exit 2)
51
+
52
+ `vos record --strict` exits 2 when a selector was skipped or a navigation
53
+ never reached networkidle, with `skipped[]` in the `--json` done event.
54
+
55
+ - A skipped selector means the flow is broken: the element wasn't there, the
56
+ selector is unstable (nth-child chains), or the page needed more settle
57
+ time. Fix the actions.json — never ship a take that silently skipped steps.
58
+ - Networkidle timeouts on pages with long-polling/websockets: add explicit
59
+ `wait` steps after navigation instead of relying on networkidle.
60
+ - Always pass `--strict`. The lenient default exits 0 over broken flows,
61
+ which is how bad takes ship.
62
+
63
+ ## Dead time
64
+
65
+ The record done event reports `dead` (`@vosjs/cli` 0.46 and later): a
66
+ still frame under a parked cursor past the beat it takes to read what
67
+ changed, per step, with how long the hold ran after the page settled.
68
+ `freezePct` beside it is the plain fact of stillness and is not a defect
69
+ on its own (a page being read is content). Budget: ≤20% dead, no single
70
+ dead hold >1.5s. The take-ready line names the steps.
71
+
72
+ 1. Cut the named steps' `ms` to their beat: about 1 s after a page
73
+ changed, 0.6 s after a control did.
74
+ 2. Where the flow allows, keep something alive in frame, and hover things
75
+ that respond with motion — those dwells become zooms too.
76
+ 3. Trim dead heads/tails with `segments`; compress slow stretches with
77
+ `speed` spans.
78
+
79
+ Never choose a flow to make `freezePct` smaller.
80
+
81
+ ## Browser and environment
82
+
83
+ - **MP4 output needs system Chrome** (`--format mp4`); Playwright's bundled
84
+ Chromium lacks the H.264 encoder. WebM works everywhere.
85
+ - **Headless WebGL**: if a render hangs at scene init on a CI box, the
86
+ browser may lack GPU/SwiftShader flags. Prefer system Chrome; file an
87
+ issue with the `--json` output if it persists.
88
+ - **Network**: the render page imports three/mediabunny from esm.sh —
89
+ rendering needs outbound network. A fully offline sandbox can record
90
+ nothing and render nothing; surface that limitation rather than shipping a
91
+ broken artifact.
92
+ - Recordings served to a render page must come from a server that supports
93
+ HTTP Range/206 (the CLI's own take server does) — video seeking hangs
94
+ forever without it.
95
+
96
+ ## Performance expectations
97
+
98
+ - Recording is always real-time (it drives a real browser).
99
+ - Render ≈ 1.5× real-time at 1080p with ~5s fixed startup; 2K is ~2× the
100
+ per-frame cost; `--parallel N` pays off past ~30s of footage (ignored when
101
+ audio is muxed).
102
+
103
+ ## Take directory anatomy
104
+
105
+ | Path | What | Keep? |
106
+ |---|---|---|
107
+ | `recording.webm` | the re-render source footage | KEEP |
108
+ | `doc.json` | every editable decision | KEEP — this is the document |
109
+ | `actions.json` | the recipe that recorded it | keep for re-records |
110
+ | `frames/` | encode intermediate (~1GB at 2K) | deletable |
111
+ | `meta.json` | capture geometry | keep |
112
+
113
+ ## Determinism
114
+
115
+ Renders are deterministic: same take + same doc.json = same frames, on any
116
+ machine. If two renders differ, the doc changed (diff it) — not the weather.
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: vos-authoring
3
+ description: Author a vos animation program (VosConfigJson) from scratch or debug one — declarative configs compiling into deterministic Three.js + GSAP-dialect animations on the MIT vos engine (@vosjs/core), validated locally with compileVosConfig. Covers the schema, dialect rules, 2D overlay elements, shaders, and params/presets knobs. Use when asked to write a vos animation program, create a VosConfigJson, make a programmatic motion graphic or logo animation, or fix a config that fails to compile or lint.
4
+ license: MIT
5
+ ---
6
+
7
+ # Vos program authoring
8
+
9
+ You generate VosConfigJson files — declarative animation configs that compile
10
+ into Three.js + GSAP-dialect animations on the vos engine
11
+ ([github.com/vosjs/vos](https://github.com/vosjs/vos), MIT). The preview is
12
+ the render: a config produces the same frames on every machine.
13
+
14
+ ## Workflow
15
+
16
+ 1. **Parse the prompt**: identify the animation concept, mood, colors, and
17
+ any referenced images.
18
+ 2. **Design the approach**: choose camera preset, materials, effects, and
19
+ animation strategy (see the category guide below).
20
+ 3. **Generate the config**: write a complete, valid VosConfigJson to
21
+ `<kebab-case-name>.json` wherever the project keeps configs.
22
+ 4. **Validate locally** (see below), then render or preview it.
23
+
24
+ If the user provides an image, analyze its visual style (colors, mood,
25
+ composition, lighting) and translate those qualities into the scene.
26
+
27
+ ## Validate and render locally
28
+
29
+ ```bash
30
+ npm i @vosjs/core # compiler + lints
31
+ node -e "import('@vosjs/core/compiler').then(async m => { \
32
+ const cfg = JSON.parse(require('fs').readFileSync('my-config.json','utf8')); \
33
+ m.compileVosConfig(cfg); console.error('compiles clean') })"
34
+ ```
35
+
36
+ `compileVosConfig(config)` must not throw — it is the same compiler every
37
+ player and render server runs. For visual checks, the CLI
38
+ (`npm i -D @vosjs/cli`):
39
+
40
+ ```bash
41
+ vos render my-config.json out.webm # deterministic video render
42
+ vos still my-config.json out.webp --time 2.5
43
+ vos preview my-config.json # local playback page
44
+ vos info my-config.json
45
+ ```
46
+
47
+ ## Share for preview (no account needed)
48
+
49
+ Local render is the default — nothing leaves the machine unless the user
50
+ asks for sharing or hosting. When they do want to see it hosted (playable
51
+ link, fine-tuning in the vos.so studio) and no API key is configured, use
52
+ the claimable push:
53
+
54
+ ```bash
55
+ vos push my-config.json --claimable --title "…"
56
+ # → claim: https://vos.so/claim/… expires: <72h from now>
57
+
58
+ # No CLI, or an older one? The same thing over plain HTTP:
59
+ curl -s -X POST https://vos.so/api/claim \
60
+ -H 'content-type: application/json' \
61
+ -d '{"title": "…", "config": <the VosConfigJson>}'
62
+ # → { "claimUrl": "https://vos.so/claim/…", "expiresAt": "…" }
63
+ ```
64
+
65
+ Hand `claimUrl` to the user and nowhere else — it is the only reference and
66
+ the only credential. It lasts 72 hours; unclaimed work is deleted after
67
+ that (re-push if it lapses). Claiming moves the vos into the user's
68
+ library. With a key configured (`VOS_API_KEY` or `vos login`), prefer
69
+ `vos push` — keyed pushes have no expiry. Programs only, config ≤200KB,
70
+ 5 pushes per day per network.
71
+
72
+ ## VosConfigJson structure
73
+
74
+ ```json
75
+ {
76
+ "version": 2,
77
+ "duration": 8,
78
+ "scene": { "background": "#0a0a1a", "fog": { "type": "exp2", "color": 0, "density": 0.02 } },
79
+ "camera": { "preset": "perspective", "fov": 60, "position": [0, 2, 8] },
80
+ "postprocessing": [{ "type": "bloom", "strength": 0.8 }, { "type": "output" }],
81
+ "elements": [{ "type": "text", "id": "title", "content": "Hello", "position": "center", "font": { "size": 64, "color": "#fff" } }],
82
+ "setup": "async (ctx) => { ... return data }",
83
+ "createContent": "(ctx, setupData?) => { ... return { objects, refs, dispose } }",
84
+ "createTimeline": "(ctx, content, duration) => { ... return tl }"
85
+ }
86
+ ```
87
+
88
+ Complete field reference: `references/schema-reference.md`. Working
89
+ examples of every major type: `references/examples.md`.
90
+
91
+ ## Critical rules
92
+
93
+ ### DO
94
+ 1. Functions must be valid **JavaScript strings** — NO TypeScript (no `as`,
95
+ no type annotations)
96
+ 2. Access Three.js via `ctx.THREE`, GSAP-dialect via `ctx.gsap`, scene via
97
+ `ctx.scene`
98
+ 3. `createContent` MUST return `{ objects: [...], refs: {...}, dispose: () => {...} }`
99
+ 4. `createTimeline` MUST return `ctx.gsap.timeline({ paused: true })`
100
+ 5. Always include `dispose()` that cleans up geometries, materials, textures
101
+ 6. Use `ctx.resolution.drawingBufferWidth/drawingBufferHeight` for shader
102
+ `iResolution`
103
+ 7. Set `mesh.frustumCulled = false` for fullscreen shader quads
104
+ 8. Set `depthWrite: false, depthTest: false` on fullscreen ShaderMaterial
105
+ 9. Always include `{ "type": "output" }` as the last entry when using
106
+ postprocessing
107
+ 10. Add all scene objects to both `scene` and the returned `objects` array
108
+ 11. Tween shader uniforms directly:
109
+ `tl.to(uniforms.iTime, { value: duration, duration, ease: 'none' })`
110
+ 12. For per-frame logic (InstancedMesh updates, camera math), use `onUpdate`
111
+ callbacks on tweens
112
+
113
+ ### DON'T
114
+ 1. **NO `${}` template literals** in function strings — they evaluate at
115
+ compile time. Use string concatenation.
116
+ 2. **NO escaped backticks** — write shaders as inline strings, or use
117
+ single-line concatenation
118
+ 3. **NEVER `repeat: -1`** on individual tweens — looping is handled by the
119
+ player; this breaks duration tracking
120
+ 4. **NO CSS-style** `style` objects or `textContent` on elements — elements
121
+ are WebGL planes
122
+ 5. **NO external imports** — everything is on `ctx` (THREE, gsap, loaders,
123
+ utils)
124
+ 6. **NO `requestAnimationFrame`** or `setTimeout` — use `onUpdate` callbacks
125
+ 7. **NO `Date.now()` / `Math.random()`** in frame paths — determinism is the
126
+ contract; derive everything from the timeline's time and `ctx.data`
127
+ 8. **NO `onFrame`** for new configs — put all per-frame logic in `onUpdate`
128
+
129
+ ## Params and Looks (make it remixable)
130
+
131
+ Declare 2–4 knobs via `config.params` — editors render them as live controls,
132
+ and other agents can retune the program without touching code. Each param
133
+ documents a `ctx.data` key the program reads:
134
+
135
+ ```json
136
+ "params": [{ "key": "hue", "label": "Hue", "kind": "number",
137
+ "min": 0, "max": 1, "default": 0.6,
138
+ "hint": "Shifts every color around the wheel",
139
+ "unit": "°", "group": "Color", "order": 1 }]
140
+ ```
141
+
142
+ - kinds: `number` (min/max/step) | `color` | `select` (options) | `toggle`
143
+ - `hint`: ONE sentence on what visibly changes — write it
144
+ - the program reads it where it animates:
145
+ `const d = ctx.data || {}; if (typeof d.hue === 'number') u.uHue.value = d.hue`
146
+ - Name knobs by INTENT (`mood`, `drama`, `pace`), never by implementation
147
+ (`blurRadius`). Every knob must visibly change the output — a dead knob is
148
+ worse than no knob.
149
+
150
+ Ship Looks via `config.presets` — named param-value sets (2–3 when the
151
+ program has 4+ params):
152
+
153
+ ```json
154
+ "presets": [{ "name": "Vivid", "values": { "hue": 0.9, "speed": 2 } },
155
+ { "name": "Calm", "values": { "hue": 0.3, "speed": 0.6 } }]
156
+ ```
157
+
158
+ Values must reference declared param keys with matching types.
159
+
160
+ ## Server-render caution
161
+
162
+ Configs pushed to vos.so get preview-rendered on a software-GL fleet: no
163
+ `THREE.DoubleSide` on transmission materials (hard hang), no `dispersion`
164
+ (too slow), and any fetched asset must be reachable and CORS-open. These are
165
+ fine for purely local renders on a real GPU.
166
+
167
+ ## Design principles
168
+
169
+ - **Visual impact**: bloom for glow (even 0.3–0.5 elevates); emissive
170
+ materials + bloom = light emitters; exp2 fog (0.01–0.05) for depth; dark
171
+ backgrounds make colors pop; glass (transmission + HDR env) reads premium;
172
+ subtle camera drift adds life.
173
+ - **Animation quality**: 4–10s loops, 10–30s narratives; `ease: 'none'` for
174
+ constant motion; `power2.inOut`/`sine.inOut` for transitions; stagger for
175
+ sequential reveals; layer tweens at different start times.
176
+ - **Color**: 2–3 complementary colors max; warm key + cool rim; dark tones
177
+ (`0x0a0a1a`, `0x1a1a2e`) for backgrounds.
178
+ - **Performance**: `InstancedMesh` for 10+ identical objects; 5k–20k Points,
179
+ 100–500 InstancedMesh; dispose everything.
180
+
181
+ ## Category selection guide
182
+
183
+ | Concept | Camera | Technique |
184
+ |---------|--------|-----------|
185
+ | Shader art / fractals | `fullscreen` | ShaderMaterial + uniform tweens |
186
+ | 3D objects | `perspective` | Mesh + Material + lights |
187
+ | Isometric/flat | `orthographic` | InstancedMesh + computed positions |
188
+ | Text showcase | `perspective` or `fullscreen` | TextGeometry (3D) or elements (2D) |
189
+ | Glass/refraction | `perspective` | MeshPhysicalMaterial + HDR env map |
190
+ | Particles | `perspective` | Points + shader or InstancedMesh |
191
+ | Mixed media | `perspective` | 3D scene + elements array |