@litfamily/litmuse 0.1.0 → 0.1.2
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/CHANGELOG.md +17 -0
- package/README.md +115 -26
- package/README_ko-KR.md +112 -23
- package/bin/litmuse.mjs +4 -0
- package/docs/assets/card/after.webp +0 -0
- package/docs/assets/card/idle.webp +0 -0
- package/docs/assets/card/working.webp +0 -0
- package/docs/assets/handoff/latest.webp +0 -0
- package/docs/assets/handoff/saved.webp +0 -0
- package/docs/assets/readme/demo.webp +0 -0
- package/lib/stage-card.mjs +180 -5
- package/package.json +7 -1
- package/plugin/.muse-plugin/plugin.json +26 -11
- package/plugin/commands/lit-status.md +2 -1
- package/plugin/commands/lit.md +1 -1
- package/plugin/hooks/lib/digest.mjs +1 -1
- package/plugin/hooks/lib/next-step.mjs +68 -0
- package/plugin/hooks/lib/routes.mjs +59 -0
- package/plugin/hooks/lib/workflow-stack.mjs +318 -0
- package/plugin/hooks/session-start.mjs +29 -2
- package/plugin/hooks/user-prompt-submit.mjs +138 -7
- package/plugin/lib/capped-fetch.mjs +25 -0
- package/plugin/lib/pinned-fetch.mjs +79 -0
- package/plugin/lib/plan-task-rows.mjs +32 -0
- package/plugin/lib/render_pages.py +29 -1
- package/plugin/lib/rewind/collect.mjs +679 -0
- package/plugin/lib/rewind/gauge.mjs +72 -0
- package/plugin/lib/rewind/next-step.mjs +91 -0
- package/plugin/lib/rewind/redact.mjs +83 -0
- package/plugin/lib/rewind/transcripts.mjs +227 -0
- package/plugin/skills/frontend-ui-ux/SKILL.md +24 -9
- package/plugin/skills/frontend-ui-ux/kit/components.css +414 -0
- package/plugin/skills/frontend-ui-ux/kit/fonts.json +22 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/landing.html +164 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/settings.html +114 -0
- package/plugin/skills/frontend-ui-ux/kit/layouts/workspace.html +115 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/computational-brutalism.json +227 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/field-notes.json +205 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/graphite.json +200 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/meadow.json +199 -0
- package/plugin/skills/frontend-ui-ux/kit/styles/neo-brutalism.json +222 -0
- package/plugin/skills/frontend-ui-ux/kit/ui.js +226 -0
- package/plugin/skills/frontend-ui-ux/references/complete-contract.md +2 -1
- package/plugin/skills/frontend-ui-ux/references/craft-floor.md +9 -4
- package/plugin/skills/frontend-ui-ux/references/default-editorial-pixel.json +59 -7
- package/plugin/skills/frontend-ui-ux/references/house-styles.md +234 -0
- package/plugin/skills/frontend-ui-ux/references/motion-guide.md +3 -3
- package/plugin/skills/frontend-ui-ux/references/slop-register.md +7 -7
- package/plugin/skills/frontend-ui-ux/scripts/design-lock.mjs +400 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-lock.mjs +196 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-page.mjs +31 -10
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-rules.mjs +19 -7
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe-static.mjs +4 -0
- package/plugin/skills/frontend-ui-ux/scripts/interface-probe.mjs +59 -7
- package/plugin/skills/frontend-ui-ux/scripts/pixel-art.mjs +60 -0
- package/plugin/skills/lit-adhd/SKILL.md +188 -0
- package/plugin/skills/lit-adhd/references/card-examples.md +121 -0
- package/plugin/skills/lit-adhd/references/status-taxonomy.md +43 -0
- package/plugin/skills/lit-adhd/scripts/rewind.mjs +11 -0
- package/plugin/skills/lit-diagram-drawer/SKILL.md +3 -3
- package/plugin/skills/lit-diagram-drawer/scripts/diagram-craft-rules.mjs +1 -2
- package/plugin/skills/lit-diagram-drawer/scripts/visual-quality.mjs +0 -1
- package/plugin/skills/lit-docx/SKILL.md +1 -1
- package/plugin/skills/lit-hijack/SKILL.md +589 -0
- package/plugin/skills/lit-hijack/references/capture-and-provenance.md +123 -0
- package/plugin/skills/lit-hijack/references/clean-room-build.md +133 -0
- package/plugin/skills/lit-hijack/references/distil-and-licence.md +128 -0
- package/plugin/skills/lit-hijack/references/gap-and-port-design.md +152 -0
- package/plugin/skills/lit-hijack/references/parity-and-report.md +133 -0
- package/plugin/skills/lit-hijack/scripts/hijack.mjs +879 -0
- package/plugin/skills/lit-pptx/SKILL.md +20 -9
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Bold.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/JetBrainsMono-Regular.ttf +0 -0
- package/plugin/skills/lit-pptx/fonts/jetbrains-mono/OFL.txt +93 -0
- package/plugin/skills/lit-pptx/fonts/provenance.json +32 -1
- package/plugin/skills/lit-pptx/references/direction-step.md +19 -2
- package/plugin/skills/lit-pptx/references/examples/13-engineering-review-computational-brutalism-ko.md +173 -0
- package/plugin/skills/lit-pptx/references/examples/14-launch-pitch-neo-brutalism-ko.md +154 -0
- package/plugin/skills/lit-pptx/references/examples/15-design-lecture-bauhaus-ko.md +139 -0
- package/plugin/skills/lit-pptx/references/examples/16-community-programme-riso-ko.md +158 -0
- package/plugin/skills/lit-pptx/references/examples/17-public-data-briefing-newsroom-ko.md +179 -0
- package/plugin/skills/lit-pptx/references/tonalities/bauhaus.md +112 -0
- package/plugin/skills/lit-pptx/references/tonalities/computational-brutalism.md +118 -0
- package/plugin/skills/lit-pptx/references/tonalities/neo-brutalism.md +125 -0
- package/plugin/skills/lit-pptx/references/tonalities/newsroom.md +115 -0
- package/plugin/skills/lit-pptx/references/tonalities/riso.md +109 -0
- package/plugin/skills/lit-pptx/scripts/compile-deck.js +1 -1
- package/plugin/skills/lit-pptx/scripts/craft_extras.py +136 -92
- package/plugin/skills/lit-pptx/scripts/embed_fonts.py +13 -1
- package/plugin/skills/lit-pptx/scripts/lib/grid-resolver.js +105 -116
- package/plugin/skills/lit-pptx/scripts/lib/layout-resolver.js +9 -15
- package/plugin/skills/lit-pptx/scripts/lib/render-pack.js +368 -561
- package/plugin/skills/lit-pptx/scripts/lib/slide-grid.js +104 -0
- package/plugin/skills/lit-pptx/scripts/lib/template-registry.js +57 -6
- package/plugin/skills/lit-pptx/scripts/qa_deck.py +4 -4
- package/plugin/skills/lit-pptx/templates/tonalities/atlas/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/bauhaus/pack.yaml +98 -0
- package/plugin/skills/lit-pptx/templates/tonalities/chalk/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/computational-brutalism/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/gazette/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/ledger/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/neo-brutalism/pack.yaml +107 -0
- package/plugin/skills/lit-pptx/templates/tonalities/newsroom/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/night/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/paper/pack.yaml +1 -1
- package/plugin/skills/lit-pptx/templates/tonalities/riso/pack.yaml +99 -0
- package/plugin/skills/lit-pptx/templates/tonalities/signal/pack.yaml +2 -2
- package/plugin/skills/lit-pptx/templates/tonalities/studio/pack.yaml +1 -1
- package/plugin/skills/lit-recap/SKILL.md +3 -0
- package/plugin/skills/lit-schematic/SKILL.md +241 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/build.mjs +14 -0
- package/plugin/skills/lit-schematic/examples/iso-heat-exchanger/scene.mjs +8 -0
- package/plugin/skills/lit-schematic/examples/journal-adsorption-rig/build.mjs +22 -0
- package/plugin/skills/lit-schematic/examples/pid-dosing-skid/build.mjs +73 -0
- package/plugin/skills/lit-schematic/references/cutaway-outputs.md +25 -0
- package/plugin/skills/lit-schematic/references/isometric-craft.md +24 -0
- package/plugin/skills/lit-schematic/references/journal-figures.md +19 -0
- package/plugin/skills/lit-schematic/references/kit-api.md +27 -0
- package/plugin/skills/lit-schematic/references/pfd-and-layout.md +20 -0
- package/plugin/skills/lit-schematic/references/pid-drafting.md +26 -0
- package/plugin/skills/lit-schematic/references/pid-symbols.md +27 -0
- package/plugin/skills/lit-schematic/references/verification.md +24 -0
- package/plugin/skills/lit-schematic/scripts/doctor.mjs +13 -0
- package/plugin/skills/lit-schematic/scripts/lib/figure.mjs +49 -0
- package/plugin/skills/lit-schematic/scripts/lib/isa.mjs +18 -0
- package/plugin/skills/lit-schematic/scripts/lib/iso.mjs +58 -0
- package/plugin/skills/lit-schematic/scripts/lib/legend-sheet.mjs +10 -0
- package/plugin/skills/lit-schematic/scripts/lib/qa-core.mjs +241 -0
- package/plugin/skills/lit-schematic/scripts/lib/sheet.mjs +446 -0
- package/plugin/skills/lit-schematic/scripts/lib/svg.mjs +39 -0
- package/plugin/skills/lit-schematic/scripts/lib/theme.mjs +25 -0
- package/plugin/skills/lit-schematic/scripts/motion.mjs +8 -0
- package/plugin/skills/lit-schematic/scripts/qa.mjs +21 -0
- package/plugin/skills/lit-schematic/scripts/render.mjs +38 -0
- package/plugin/skills/lit-schematic/scripts/scaffold.mjs +73 -0
- package/plugin/skills/lit-scientific-visualization/SKILL.md +2 -0
- package/plugin/skills/lit-typographic-motion/SKILL.md +12 -8
- package/plugin/skills/lit-typographic-motion/engine/THIRD_PARTY_NOTICES +11 -0
- package/plugin/skills/lit-typographic-motion/engine/blender/runner.py +218 -0
- package/plugin/skills/lit-typographic-motion/engine/core/presets.mjs +1 -1
- package/plugin/skills/lit-typographic-motion/engine/node/assets.mjs +218 -0
- package/plugin/skills/lit-typographic-motion/engine/node/blender.mjs +144 -0
- package/plugin/skills/lit-typographic-motion/engine/node/board.mjs +136 -0
- package/plugin/skills/lit-typographic-motion/engine/node/done.mjs +42 -3
- package/plugin/skills/lit-typographic-motion/engine/node/gallery.mjs +120 -0
- package/plugin/skills/lit-typographic-motion/engine/node/house.mjs +44 -0
- package/plugin/skills/lit-typographic-motion/engine/node/pipeline.mjs +18 -7
- package/plugin/skills/lit-typographic-motion/engine/node/runtime.mjs +6 -3
- package/plugin/skills/lit-typographic-motion/engine/node/scaffold.mjs +210 -0
- package/plugin/skills/lit-typographic-motion/engine/node/score.mjs +639 -0
- package/plugin/skills/lit-typographic-motion/engine/node/sound-cli.mjs +21 -4
- package/plugin/skills/lit-typographic-motion/engine/node/sound.mjs +126 -27
- package/plugin/skills/lit-typographic-motion/engine/node/stage-serve.mjs +29 -5
- package/plugin/skills/lit-typographic-motion/engine/node/stage.mjs +82 -14
- package/plugin/skills/lit-typographic-motion/engine/node/styles.mjs +214 -0
- package/plugin/skills/lit-typographic-motion/engine/node/treatment.mjs +311 -10
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/LICENSE +21 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/environments/RoomEnvironment.js +144 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/addons/geometries/RoundedBoxGeometry.js +155 -0
- package/plugin/skills/lit-typographic-motion/engine/vendor/three/three.module.js +54571 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/expected.json +69 -0
- package/plugin/skills/lit-typographic-motion/recipes/hero-3d/index.html +139 -0
- package/plugin/skills/lit-typographic-motion/references/depth.md +122 -0
- package/plugin/skills/lit-typographic-motion/references/sound.md +74 -16
- package/plugin/skills/lit-typographic-motion/references/stage.md +29 -5
- package/plugin/skills/lit-typographic-motion/references/studio.md +245 -0
- package/plugin/skills/lit-typographic-motion/references/treatment.md +31 -8
- package/plugin/skills/lit-typographic-motion/scripts/motion.mjs +48 -7
- package/plugin/skills/lit-typographic-motion/styles/paper.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/swiss-signal.json +41 -0
- package/plugin/skills/lit-typographic-motion/styles/terminalcore.json +42 -0
- package/plugin/skills/lit-typographic-motion/styles/tidal.json +41 -0
- package/plugin/skills/readme-studio/SKILL.md +1 -1
- package/plugin/skills/readme-studio/templates/hyperframes-cover/package-lock.json +3 -3
- package/plugin/skills/readme-studio/templates/remotion-cover/package-lock.json +3 -3
- package/plugin/skills/start-work/SKILL.md +6 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Studio: house styles, shots, the board and the house file
|
|
2
|
+
|
|
3
|
+
Read this for every film, after `treatment.md`. It is what makes two films of one request look like
|
|
4
|
+
the same studio made them, and what lets the user see the film before the full render. Covers keep
|
|
5
|
+
their own recipes (`cover.md`); everything here is for films on the stage and type paths.
|
|
6
|
+
|
|
7
|
+
The rule behind every part below: a decision the model used to make again on each run is now data
|
|
8
|
+
the page is built from, or a check the renderer runs. Prose that only asks for consistency does not
|
|
9
|
+
produce it; data and checks do.
|
|
10
|
+
|
|
11
|
+
## The flow
|
|
12
|
+
|
|
13
|
+
1. **Style.** The treatment names one house style. The validator accepts only the style the rules
|
|
14
|
+
choose (below), so the same request always gets the same style.
|
|
15
|
+
2. **Shots.** Each beat of the treatment is a shot with a `layout`, a `hero` move and a `transition`.
|
|
16
|
+
The layouts are one grid shared by every style; the style maps them to pixels per format.
|
|
17
|
+
3. **Scaffold** (stage path). `motion.mjs scaffold --out <dir>` writes `stage/index.html` from the
|
|
18
|
+
treatment. Draw the subject of each shot in its `DRAW` function; keep the rest of the structure.
|
|
19
|
+
4. **Board round** (round 1). `stage --round 1 --stills-only` (or `make ... --stills-only` on the type
|
|
20
|
+
path) writes the stills and `stills/board.png`, the storyboard. On the stage path it also runs the
|
|
21
|
+
board check, the text and style rules of the gate on the board frames, and exits 13 naming each
|
|
22
|
+
failed rule before any film is encoded.
|
|
23
|
+
5. **Film.** The full render, with the style rules in its gate.
|
|
24
|
+
6. **House.** A passed film records its style in `motion-house.json` beside the output folder; the next
|
|
25
|
+
film in that folder takes the same style unless the user names another.
|
|
26
|
+
|
|
27
|
+
## Register: rich is the default
|
|
28
|
+
|
|
29
|
+
Every film is maximal unless the request asks otherwise. `register` is `rich` by default and
|
|
30
|
+
`minimal` only when the request asks for a calm, quiet, simple or minimal film (미니멀, 잔잔, 차분, 조용,
|
|
31
|
+
심플, 절제, minimal, calm, quiet, simple, understated, sparse, serene). The validator refuses
|
|
32
|
+
`minimal` without that ask and `rich` with it.
|
|
33
|
+
|
|
34
|
+
A rich film fills its frame the way the reference studios do:
|
|
35
|
+
|
|
36
|
+
- **A layered world** (`world.layers`, at least 3, back to front): what lives in the far ground, the
|
|
37
|
+
middle and the near ground of the film's place. Every shot shows at least two of them at once.
|
|
38
|
+
- **Labelled parts** (`world.labels`, at least 6, at most 4 words each): the names a viewer reads on
|
|
39
|
+
the drawn parts, readouts, tabs and tags. A named product uses its real names. The text QA's
|
|
40
|
+
`TEXT-LABELS` rule fails when fewer than 75 % of them reach the screen, or when half the beat
|
|
41
|
+
midpoints show fewer than 3 readable text runs.
|
|
42
|
+
- **A filled frame.** At each beat midpoint the text QA cuts the frame into 4 x 3 regions; `RICH-FILL`
|
|
43
|
+
fails when more than one shot leaves over 2 of the 12 empty (the end card may breathe). A 3D hero on a
|
|
44
|
+
dark floor does not fill a frame by itself: put the world's parts around it as panels in the DOM
|
|
45
|
+
(live counters, a log that scrolls, stage pills, a progress readout, the product's own screens at a
|
|
46
|
+
size that reads), the way a dense dashboard frame does.
|
|
47
|
+
- **A moving camera** (`world.camera`, `[{beat, move}]`, in at least half the shots): push-in, truck,
|
|
48
|
+
crane, orbit, rack focus, parallax. A locked shot is not listed.
|
|
49
|
+
- **Accents by role.** Besides its core palette each style has at least three accent colours, each
|
|
50
|
+
for one role (a state, a category, a path); a rich `palette` may add them, at most 14 entries.
|
|
51
|
+
- **Decor text** (readouts, coordinates, part numbers) may fill up to 60 % of the text area.
|
|
52
|
+
- **An authored score**: `sound.mode: score`, layered stems arranged on the film's beats (`sound.md`).
|
|
53
|
+
- **Full 3D where the subject allows**: `depth: 3d`, and `render3d: blender` for path-traced light
|
|
54
|
+
when Blender is installed and the request asks for high-quality, cinematic or photoreal 3D
|
|
55
|
+
(`depth.md`); `scaffold` prints `BLENDER:` when both hold.
|
|
56
|
+
|
|
57
|
+
A minimal film keeps the restraint of a single hero on an open ground: the core palette only, decor
|
|
58
|
+
under 25 %, no world plan, and the style's preset beds (`sound.mode: generated`).
|
|
59
|
+
|
|
60
|
+
The flash audit (MO-C-03) is the same in both registers. Density is met with slower light changes,
|
|
61
|
+
lower-contrast detail behind a move, motion blur and cuts, never by locking the camera.
|
|
62
|
+
|
|
63
|
+
## House styles
|
|
64
|
+
|
|
65
|
+
A style is a file in `styles/<id>.json`: palette roles, faces per type role, a type scale per format,
|
|
66
|
+
leading, motion tokens (eases, durations, staggers, a spring per object class, secondary and ambient
|
|
67
|
+
travel, the hold push), the transitions it hands off with, its ground, its sound beds (a default and
|
|
68
|
+
two alternatives, `sound.md`) and the defaults it never uses. Four ship:
|
|
69
|
+
|
|
70
|
+
| Style | Look | Type | Hand-offs | Default bed |
|
|
71
|
+
|---|---|---|---|---|
|
|
72
|
+
| `swiss-signal` (default) | Ink ground, bone type, one teal signal, one amber moment | Archivo display and headline, Pretendard body, MesloLGS NF labels | cut, wipe, match, push | glass, A minor, 104 BPM |
|
|
73
|
+
| `tidal` | Indigo ground with a slow teal-to-violet flow, off-white type, one coral moment | Archivo 700, Pretendard body | dissolve, match, wipe | felt-strings, D major, 84 BPM |
|
|
74
|
+
| `terminalcore` | Navy terminal, phosphor type at typing speed, one signal hue, no easing | VT323 with Galmuri9 for Hangul, sizes on its 9 px grid | cut, wipe | pulse-synth, E minor, 120 BPM |
|
|
75
|
+
| `paper` | Warm paper ground, near-black ink, terracotta signal, one blue moment, soft springs | Pretendard throughout, MesloLGS NF labels | cut, match, wipe, iris, push | soft-keys, F major, 96 BPM |
|
|
76
|
+
|
|
77
|
+
`swiss-signal`, `tidal` and `terminalcore` are the type engine's presets too, so a style means the same
|
|
78
|
+
palette on both paths. `paper` is stage-only.
|
|
79
|
+
|
|
80
|
+
Type sizes per role (px):
|
|
81
|
+
|
|
82
|
+
| Style | 16:9 display / headline / body / label | 9:16 display / headline / body / label |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| swiss-signal | 128 / 76 / 40 / 26 | 120 / 80 / 46 / 32 |
|
|
85
|
+
| tidal | 116 / 72 / 40 / 26 | 112 / 76 / 46 / 32 |
|
|
86
|
+
| terminalcore | 126 / 81 / 45 / 36 | 117 / 81 / 54 / 36 |
|
|
87
|
+
| paper | 112 / 68 / 40 / 26 | 108 / 74 / 46 / 32 |
|
|
88
|
+
|
|
89
|
+
`motion.mjs styles --out <dir> [--format 16:9|9:16]` renders the same demo film in every style and
|
|
90
|
+
writes `<dir>/styles.png`, the four boards side by side. Show it when the user wants to choose a style,
|
|
91
|
+
or asks what the films will look like.
|
|
92
|
+
|
|
93
|
+
## Choosing the style
|
|
94
|
+
|
|
95
|
+
In this order, first match wins; the validator refuses anything else and says which rule chose:
|
|
96
|
+
|
|
97
|
+
1. The request names a style: its id (`swiss-signal`, `tidal`, `terminalcore`) or `paper style`
|
|
98
|
+
(`페이퍼`, `타이달`, `터미널코어`, `스위스 시그널` too).
|
|
99
|
+
2. The project's house style (`motion-house.json` beside the output folder).
|
|
100
|
+
3. A keyword in the request or the copy: terminal words (터미널, CRT, 해커, terminal, hacker) take
|
|
101
|
+
`terminalcore`; calm or wave words (물결, 파도, 잔잔한, 흐름, gradient, wave, tide, calm) take `tidal`;
|
|
102
|
+
warm or paper words (따뜻한, 밝은, 종이, 손그림, 포근한, 아기자기, warm, cozy, bright, paper, handmade,
|
|
103
|
+
hand-drawn) take `paper` on the stage path.
|
|
104
|
+
4. The genre: an `explainer` takes `paper` (a warm, legible ground for diagrams and steps; on the type
|
|
105
|
+
path, where `paper` has no preset, the rule is skipped) and a `brand-mood` film takes `tidal`.
|
|
106
|
+
5. Otherwise `swiss-signal`.
|
|
107
|
+
|
|
108
|
+
The style fixes more than colour. On the stage path the treatment's `palette` may only list the style's
|
|
109
|
+
colours (a rich film adds its role accents), `typePlan.faces` only its faces, and a minimal film's
|
|
110
|
+
generated bed is one of the style's beds exactly. A score takes the style's genre unless `sound.genre`
|
|
111
|
+
names another.
|
|
112
|
+
On the type path the brief's `style` must equal the treatment's style or be left out.
|
|
113
|
+
|
|
114
|
+
## Shots and layouts
|
|
115
|
+
|
|
116
|
+
Each beat carries three shot fields on the stage path:
|
|
117
|
+
|
|
118
|
+
| Field | Rule |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `layout` | `title`, `split`, `split-alt`, `stage`, `full` or `hero` |
|
|
121
|
+
| `hero` | The one move the eye follows in this shot, as a viewer would describe it |
|
|
122
|
+
| `transition` | How the shot hands off: a word from the style's list; the last shot is `end` |
|
|
123
|
+
| `lines` | Optional: the indices of the copy lines this shot shows (a `full` shot shows none) |
|
|
124
|
+
|
|
125
|
+
The layouts, as `[x, y, w, h]` fractions of the frame:
|
|
126
|
+
|
|
127
|
+
| Layout | 16:9 type zone | 16:9 subject zone | 9:16 type zone | 9:16 subject zone |
|
|
128
|
+
|---|---|---|---|---|
|
|
129
|
+
| `title` | 0.08, 0.20, 0.84, 0.60 | whole frame (backdrop) | 0.09, 0.26, 0.82, 0.48 | whole frame |
|
|
130
|
+
| `split` | 0.08, 0.16, 0.36, 0.68 (left) | 0.48, 0.08, 0.46, 0.84 | 0.09, 0.08, 0.82, 0.26 (top) | 0.06, 0.36, 0.88, 0.56 |
|
|
131
|
+
| `split-alt` | 0.56, 0.16, 0.36, 0.68 (right) | 0.06, 0.08, 0.46, 0.84 | 0.09, 0.66, 0.82, 0.26 (bottom) | 0.06, 0.06, 0.88, 0.56 |
|
|
132
|
+
| `stage` | 0.08, 0.75, 0.84, 0.18 (caption band) | 0.06, 0.07, 0.88, 0.64 | 0.09, 0.79, 0.82, 0.13 | 0.06, 0.06, 0.88, 0.68 |
|
|
133
|
+
| `full` | none: no copy | whole frame | none | whole frame |
|
|
134
|
+
| `hero` | 0.08, 0.76, 0.84, 0.17 (low band) | 0.03, 0.03, 0.94, 0.94 | 0.09, 0.80, 0.82, 0.12 | 0.03, 0.03, 0.94, 0.94 |
|
|
135
|
+
|
|
136
|
+
Choose layouts for the story: `title` for an opening or closing line over a backdrop, `split` and
|
|
137
|
+
`split-alt` for a line beside the thing it is about (alternate them so the eye moves), `stage` when the
|
|
138
|
+
drawing is the shot and the words are a caption, `full` when the picture must speak alone, `hero` when
|
|
139
|
+
the protagonist fills the frame (a close-up, the turn, the product reveal) with one short line under
|
|
140
|
+
it. Composition changes with the story: two adjacent shots never share a layout, and a film of four or
|
|
141
|
+
more shots uses at least three layouts; the validator refuses a treatment that parks the eye. One hero
|
|
142
|
+
move leads each shot and the eye follows it; in a rich film the world around it keeps working:
|
|
143
|
+
secondary moves on the labelled parts travel 50-70 % as far and start 0.1-0.2 s later, the layers drift
|
|
144
|
+
at their own depths, and ambient motion never stops. A minimal film keeps ambient motion under the
|
|
145
|
+
style's ambient travel.
|
|
146
|
+
|
|
147
|
+
## The page: style.css, style.js and the kit
|
|
148
|
+
|
|
149
|
+
The renderer serves two files for the film's style and format:
|
|
150
|
+
|
|
151
|
+
- `/lit/style.css`: custom properties (`--lit-ground`, `--lit-ink`, `--lit-muted`, `--lit-signal`,
|
|
152
|
+
`--lit-accent`, `--lit-line`, `--lit-surface`, the size steps), the body ground, the type-role classes
|
|
153
|
+
`.lit-display`, `.lit-headline`, `.lit-body`, `.lit-label` (face, size, weight, leading) and the colour
|
|
154
|
+
classes `.lit-ink`, `.lit-muted`, `.lit-signal`, `.lit-accent`, `.lit-knockout`.
|
|
155
|
+
- `/lit/style.js`: `window.LitStyle` (the tokens, with the layout zones in pixels for this format) and
|
|
156
|
+
`window.LitTimeline` (the treatment's shots: `t0`, `t1`, `layout`, `transition`, plus fps and length).
|
|
157
|
+
|
|
158
|
+
The kit reads them:
|
|
159
|
+
|
|
160
|
+
- `LitStage.shot(t)` returns the shot at `t`: `index`, `beat`, `layout`, `transition`, `local` (seconds
|
|
161
|
+
into the shot), `length`, `p` (0..1), `enter(delay, dur)` (0 to 1 on the style's entrance ease) and
|
|
162
|
+
`exit(dur)` (0 to 1 over the shot's last `dur` seconds on its exit ease).
|
|
163
|
+
- `LitStage.zone(layout, "type" | "subject")` returns `{ x, y, w, h }` in pixels;
|
|
164
|
+
`LitStage.place(el, layout, part)` positions an element there.
|
|
165
|
+
- `LitStage.tokens()` returns the style's eases as functions (`enter`, `move`, `exit`), durations,
|
|
166
|
+
staggers, a spring per object class (`micro`, `panel`, `headline`, `subject`, `camera`, each a
|
|
167
|
+
function of seconds from 0 to 1), `secondary`, `ambient` and `push`.
|
|
168
|
+
|
|
169
|
+
The page times every shot from `LitTimeline`, so the treatment, the page, the stills, the board, the
|
|
170
|
+
sound cues and the gate read one timeline. Set copy with the classes (or the same values): the gate
|
|
171
|
+
checks the result, not the class names.
|
|
172
|
+
|
|
173
|
+
## Scaffold
|
|
174
|
+
|
|
175
|
+
`motion.mjs scaffold --out <dir>` needs a valid stage treatment and writes `<dir>/stage/index.html`:
|
|
176
|
+
the COPY block with every copy line, one `<section>` per shot with its type placed in the shot's type
|
|
177
|
+
zone (first line headline, or display in a `title` shot; further lines body in the muted colour), an
|
|
178
|
+
empty `<svg>` in the subject zone, entrances on the style's ease, the hand-off of each shot (wipe,
|
|
179
|
+
iris, dissolve, push; match, morph and cut switch on the cut, so carry the matching shape yourself),
|
|
180
|
+
and one `DRAW` function per shot. Fill the `DRAW` functions: they receive the shot (`s`), the shot's
|
|
181
|
+
svg and `t`. Change the structure only for a reason the treatment states. The scaffold never replaces
|
|
182
|
+
an existing page.
|
|
183
|
+
|
|
184
|
+
## The board round
|
|
185
|
+
|
|
186
|
+
Round 1 is the board round. A stills-only render writes the stills set and `stills/board.png`: one
|
|
187
|
+
keyframe per shot with its number, time, layout and hand-off under it and its hero move in words, the
|
|
188
|
+
shot's type zone dashed over the frame, and a header with the style, palette and the film's format,
|
|
189
|
+
length and bed. On the stage path the same render runs the board check, the gate's text and style
|
|
190
|
+
rules on the board frames, writes `stills/check.json` and prints the verdict on the board.
|
|
191
|
+
|
|
192
|
+
- Exit 0: open the board and every still with read_file, record round 1 with its change, and render the
|
|
193
|
+
film.
|
|
194
|
+
- Exit 13 `BOARD_CHECK_FAIL`: the message names each failed rule. Fix the page and render the board
|
|
195
|
+
again before the full render; a full render fails the same rules.
|
|
196
|
+
|
|
197
|
+
The board is a deliverable. The reply names it, so the user can see what was built.
|
|
198
|
+
|
|
199
|
+
## Style rules in the gate
|
|
200
|
+
|
|
201
|
+
All FAIL, on the stage path, on the board round and the full render alike:
|
|
202
|
+
|
|
203
|
+
| Rule | Fails when | Typical fix |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `TEXT-OVERLAP` | Two visible text runs overlap by more than 8 % of the smaller box at a QA sample | Fewer lines per shot, the `split` layout, or let one line leave before the next arrives |
|
|
206
|
+
| `TEXT-ON-STYLE` | Settled copy is in a colour outside the style's text roles, a face outside the style, or a size more than 4 % off the type scale | Use the type-role and colour classes |
|
|
207
|
+
| `TEXT-ZONE` | Settled copy sits outside its shot's type zone (by more than 1.2 % of the frame), or any copy shows in a `full` shot | Shorter copy, a smaller role, or a layout with a larger type zone |
|
|
208
|
+
| `TEXT-OVERLAP` (cut) | Text of the outgoing and the incoming shot overlaps during a hand-off: the QA samples every second frame within 0.4 s of each cut, and the failure is marked `(cut)` | Let the outgoing copy leave before the incoming copy arrives, or move the incoming copy to a different zone |
|
|
209
|
+
| `FLAT-FRAME` (full render) | More than 1 s of frames whose 0.5-99.5 % luminance spread is under 0.02: an empty frame of any colour | Draw the shot; never leave a stub `DRAW` empty |
|
|
210
|
+
| `DEAD-AIR` (full render) | More than 0.5 s in a row (0.4 s more allowed at the opening, 0.6 s more at the close) where under 2.5 % of the frame carries anything: a 48 px cell counts when its pixels vary (sd over 6) or differ from the ground by more than 16 levels | Keep the protagonist or the next shot's subject on screen through the hand-off; enter the next thing before the last one has gone |
|
|
211
|
+
| `ASSET-USED` (named product) | `product.assets` lists real files and the page loads none of them | Show the real logo, screenshot or README image from `stage/assets/` |
|
|
212
|
+
|
|
213
|
+
Non-copy text off the scale is a WARN; decor text (`text(el, { decor: true })`) is exempt. The cut
|
|
214
|
+
samples check overlap only: copy in mid-transition is off its zone and scale by design.
|
|
215
|
+
|
|
216
|
+
`FLAT-FRAME` catches a blank frame; `DEAD-AIR` catches the frame that is not blank but says nothing,
|
|
217
|
+
one thin line or a lone label on an empty ground, held long enough to read as a stall.
|
|
218
|
+
|
|
219
|
+
## Preview
|
|
220
|
+
|
|
221
|
+
When the request asks for a preview or a storyboard first (미리보기, 스토리보드, 콘티, preview,
|
|
222
|
+
storyboard), set `preview: true` in the treatment. The film is then done at the board: a board round
|
|
223
|
+
whose check passed, looked at, with the board in the viewed list. The reply shows the board and says the
|
|
224
|
+
film is not rendered yet; when the user says go, set `preview` to false and render the film. Without
|
|
225
|
+
those words a bare `lit` still asks no questions and renders the film, and the reply still names the
|
|
226
|
+
board.
|
|
227
|
+
|
|
228
|
+
## The house file
|
|
229
|
+
|
|
230
|
+
`motion-house.json` sits beside the output folder (the user's working directory):
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{ "schema": "litmuse.motion-house/v1", "style": "<style id>", "createdAt": "<time>", "films": [{ "dir": "<folder>", "style": "<style id>", "path": "<stage or type>", "format": "<16:9 or 9:16>", "durationSec": 0, "at": "<time>" }] }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The renderer writes it when a full render passes and nothing is withheld; the first film sets the
|
|
237
|
+
house style and later films are appended. While it exists the validator holds new films in that folder
|
|
238
|
+
to the house style; a request that names another style wins for that film and does not move the house
|
|
239
|
+
style. To change the house style, edit `style` in the file or delete it.
|
|
240
|
+
|
|
241
|
+
## What stays free
|
|
242
|
+
|
|
243
|
+
The subject, the idea, the copy, the drawing, the hero moves and the arc stay the model's, with the
|
|
244
|
+
treatment's concept rules (`treatment.md`). The studio fixes how a film looks and is built, never what
|
|
245
|
+
it is about.
|
|
@@ -43,20 +43,27 @@ Once the path is set, load its references. Stage path: `references/stage.md`. Ty
|
|
|
43
43
|
| Field | What the validator requires |
|
|
44
44
|
| --- | --- |
|
|
45
45
|
| `request` | Non-empty: the user's words, verbatim. |
|
|
46
|
-
| `genre` | One of `announcement`, `brand-mood`, `event`, `explainer`, `motion-graphics`, `type-led`, `other`. |
|
|
46
|
+
| `genre` | One of `announcement`, `brand-mood`, `event`, `explainer`, `motion-graphics`, `type-led`, `other`. A genre word in the request fixes it: 설명/원리 or explain for `explainer`, 출시/런칭/티저 or launch/teaser for `announcement`, 행사/공연/축제 or event/festival/concert for `event`; a type compound or a quoted span of two-plus words for `type-led`. The validator refuses a genre that contradicts the request's own word. |
|
|
47
47
|
| `path`, `pathReason` | `type` or `stage` by the rule above, and a sentence on why. |
|
|
48
|
+
| `style`, `register` | The house style id and `rich` (the default) or `minimal`. `references/studio.md` has the choosing order and the rich register; the validator holds the film to both. `minimal` only when the request asks for calm, quiet or minimal. |
|
|
48
49
|
| `idea` | Exactly one sentence describing what the film does. It may not share a run of characters with the request (see Restatement). |
|
|
49
50
|
| `audience`, `channel` | Who watches and where it plays. `channel` also matters for sound: a channel that plays muted by design is one of the two ways `sound.mode: none` is accepted. |
|
|
50
51
|
| `format`, `formatReason` | `16:9` renders 1920×1080 and `9:16` renders 1080×1920. The reason ties the shape to the channel. |
|
|
51
|
-
| `durationSec` | A number from 4 to 90. `announcement
|
|
52
|
-
| `fps` | Optional. 60 by default; 30
|
|
53
|
-
| `
|
|
52
|
+
| `durationSec` | A number from 4 to 90. A stated length (a number with 초, 분, s, sec, seconds, min or minutes) holds within 10 % of itself. Otherwise the genre sets the band: `announcement` 12–18, `brand-mood` 10–15, `event` 10–15, `explainer` 16–24, `motion-graphics` 12–18, `other` 10–15, `type-led` 4–8 s per line. `announcement`, `event`, `explainer` and `motion-graphics` run at least 10 s. |
|
|
53
|
+
| `fps` | Optional. 60 by default; 30 only when the request asks for 30 fps. |
|
|
54
|
+
| `preview` | Optional boolean. `true` only when the request asks for a preview or storyboard first; the film is then done at its board round (see Preview below). |
|
|
55
|
+
| `beats[]` | Objects `{t0, t1, layout, hero, transition, purpose, onScreen, motion, sound}` on the stage path (`layout`/`hero`/`transition` per `references/studio.md`), `{t0, t1, purpose, onScreen, motion, sound}` on the type path. The first starts within 0.25 s of zero, each following beat starts within 0.25 s of the previous end and never overlaps it, every beat lasts at least 1.2 s, and the last ends within 0.25 s of `durationSec`. The text fields are non-empty and not placeholders. |
|
|
54
56
|
| `posterBeat` | Optional beat index whose midpoint becomes the poster; the middle beat otherwise. |
|
|
57
|
+
| `story` | `{expect, turn, beat, payoff}`: what the viewer expects, what happens instead, the index of the shot where the turn lands (after the opening, before the end), and what the ending gives. The turn must differ from the expectation. |
|
|
58
|
+
| `protagonist` | Stage path only. `{name, kind, look, acts[]}` with `kind` `character` or `object`: the drawn thing that acts across the film, what it looks like on screen, and one act per shot (`{beat, action}`) covering at least half the film, including an act on the turn's shot. |
|
|
59
|
+
| `product` | Required when the request links a product, repository or site. `{name, url, assets[]}` with each asset `{path, role, source, sha256}` under `stage/assets/`, or `{unreachable, reason}` when nothing could be fetched — then the film stops with exit 22 until the user supplies the files. Fetch first with `motion.mjs assets --out <dir> --url <link>`. `role` is one of `logo`, `wordmark`, `icon`, `photo`, `screenshot`, `ui`, `readme-image`. A `screen` device may only show an asset listed here. |
|
|
60
|
+
| `depth`, `render3d` | `depth: 3d` with a `depth3d` subject device takes the 3D route (`references/depth.md`); `render3d` is `three` (the GPU rung, default) or `blender` (the offline Cycles rung, needs `stage/scene.py` and `motion.mjs blender --final`). |
|
|
55
61
|
| `subject` | `{name, source, specifics[]}` with `source` `user` or `invented`. A user subject needs one concrete specific; an invented one needs two: what it is or does, for whom, and a detail nobody else has. |
|
|
56
62
|
| `visualDevices[]` | `{kind, role, beats[]}`. Kinds: `illustration`, `diagram`, `chart`, `icon`, `shape`, `path`, `mask`, `depth3d`, `particles`, `grid`, `gradient`, `photo-texture`. Roles: `subject`, `support`, `texture`. `grid`, `gradient`, `particles` and `photo-texture` are always `texture`. `beats` lists valid beat indices. |
|
|
57
63
|
| `typePlan` | `{faces[], hierarchy, maxWordsOnScreen, index?}`. Faces come from the verified set: `Pretendard`, `Archivo`, `VT323`, `Silkscreen`, `Galmuri9`, `MesloLGS NF`. `maxWordsOnScreen` is a positive integer. `index` is a boolean and is true only when the film should carry a running shot counter. |
|
|
58
|
-
| `palette[]` |
|
|
59
|
-
| `
|
|
64
|
+
| `palette[]` | On the stage path, the style's own colours (a rich film may add its role accents, at most 14 entries); on the type path, three to six `{color, role}` entries with `#rgb` or `#rrggbb` colours. |
|
|
65
|
+
| `world` | Stage path, rich register only. `{layers[], labels[], camera[]}`: at least 3 depth layers back to front, at least 6 on-screen labels of at most 4 words each, and camera moves in at least half the beats. `references/studio.md` has the register. |
|
|
66
|
+
| `sound` | `{mode, plan, palette?, events?, file?, genre?}`. Modes `score` (the default for rich films: a layered per-film score arranged on the beats), `generated` (a minimal film's bed, exactly one of the style's beds), `supplied`, `authored`, `none`; `references/sound.md` has the rules. `none` only when the request asks for silence or the channel plays muted. Stage films with `score` or `generated` list their on-screen sound events (`{t, on, kind}`, at least 2, half away from the cuts). |
|
|
60
67
|
| `copy` | `{source, lines[]}` with at least one line. `user` lines must each be found inside the request after normalization; `invented` lines must not restate it. |
|
|
61
68
|
| `inventions[]` | Strings. Required, and non-empty, whenever the subject or the copy is invented; it must contain the invented subject's name. |
|
|
62
69
|
| `ambition` | One or two sentences, in craft terms, on what would make this particular film excellent. |
|
|
@@ -93,20 +100,29 @@ validator refuses it as written, and a treatment that echoes it is refused as `c
|
|
|
93
100
|
"genre": "<genre>",
|
|
94
101
|
"path": "<stage or type>",
|
|
95
102
|
"pathReason": "<why this renderer>",
|
|
103
|
+
"style": "<house style id>",
|
|
104
|
+
"register": "<rich or minimal>",
|
|
96
105
|
"idea": "<one sentence: what this film does>",
|
|
97
106
|
"audience": "<who watches>",
|
|
98
107
|
"channel": "<where it plays>",
|
|
99
108
|
"format": "<16:9 or 9:16>",
|
|
100
109
|
"formatReason": "<the channel's reason for that shape>",
|
|
101
110
|
"durationSec": "<seconds from the arc>",
|
|
111
|
+
"preview": "<true only when the request asks for a preview first>",
|
|
102
112
|
"beats": [
|
|
103
|
-
{ "t0": "<start>", "t1": "<end>", "purpose": "<the beat's job in the arc>", "onScreen": "<what a viewer sees, drawn and written>", "motion": "<how it moves, how it cuts>", "sound": "<pad only, or a hit, rise or cadence>" }
|
|
113
|
+
{ "t0": "<start>", "t1": "<end>", "layout": "<shot layout>", "hero": "<the move the eye follows>", "transition": "<hand-off, end on the last>", "purpose": "<the beat's job in the arc>", "onScreen": "<what a viewer sees, drawn and written>", "motion": "<how it moves, how it cuts>", "sound": "<pad only, or a hit, rise or cadence>" }
|
|
104
114
|
],
|
|
115
|
+
"story": { "expect": "<what the viewer expects>", "turn": "<what happens instead>", "beat": "<the shot where it lands>", "payoff": "<what the ending gives>" },
|
|
116
|
+
"protagonist": { "name": "<name>", "kind": "<character or object>", "look": "<what it looks like on screen>", "acts": [{ "beat": "<index>", "action": "<what it does, as seen>" }] },
|
|
105
117
|
"subject": { "name": "<name>", "source": "<user or invented>", "specifics": ["<what it does and for whom>", "<the distinctive detail>"] },
|
|
118
|
+
"product": { "name": "<name>", "url": "<link>", "assets": [{ "path": "<stage/assets/ file>", "role": "<role>", "source": "<fetched or user>", "sha256": "<hash>" }] },
|
|
119
|
+
"depth": "<2d, or 3d with a depth3d subject>",
|
|
120
|
+
"render3d": "<three or blender>",
|
|
106
121
|
"visualDevices": [{ "kind": "<kind>", "role": "<subject, support or texture>", "beats": ["<index>"] }],
|
|
107
122
|
"typePlan": { "faces": ["<face>"], "hierarchy": "<what reads first, second, third>", "maxWordsOnScreen": "<n>" },
|
|
108
123
|
"palette": [{ "color": "<#hex>", "role": "<what the colour does>" }],
|
|
109
|
-
"
|
|
124
|
+
"world": { "layers": ["<what lives in the back, middle, front>"], "labels": ["<six on-screen names>"], "camera": [{ "beat": "<index>", "move": "<the move>" }] },
|
|
125
|
+
"sound": { "mode": "<mode>", "plan": "<the sound's job>", "palette": { "timbre": "<timbre>", "key": "<pitch class>", "mode": "<major or minor>", "tempo": "<bpm>" }, "events": [{ "t": "<second>", "on": "<what happens on screen>", "kind": "<tap, hit, whoosh or chime>" }] },
|
|
110
126
|
"copy": { "source": "<user or invented>", "lines": ["<a line>"] },
|
|
111
127
|
"inventions": ["<each invented part>"],
|
|
112
128
|
"ambition": "<the craft bar for this film>"
|
|
@@ -150,6 +166,13 @@ show something the previous one did not.
|
|
|
150
166
|
literally see, never a mood word. A beat's `sound` field is where accents are requested: write "hit on the
|
|
151
167
|
cut", "rise into the reveal" or "cadence to close", and the bed places them (`references/sound.md`).
|
|
152
168
|
|
|
169
|
+
## Preview
|
|
170
|
+
|
|
171
|
+
When the request asks for a preview or a storyboard first (미리보기, 스토리보드, 콘티, preview,
|
|
172
|
+
storyboard), set `preview: true`. The film is then done at its board: a stills-only render whose board
|
|
173
|
+
check passed, looked at, with the board in the viewed list. The reply shows the board and says the film
|
|
174
|
+
is not rendered yet; when the user says go, set `preview` back to false and render the film.
|
|
175
|
+
|
|
153
176
|
## Downgrades are recorded
|
|
154
177
|
|
|
155
178
|
The first valid treatment of an output folder is saved to `.run/treatment.first.json`. When the done-check
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// WebGL2, 12 no ffmpeg for video, 13 QA gate FAIL, 14 runtime not pre-warmed, 15 font missing or
|
|
6
6
|
// hash-mismatched, 16 treatment invalid, 17 stage contract, 18 stage nondeterministic, 19 stage
|
|
7
7
|
// network request, 20 sound invalid (2 = usage).
|
|
8
|
-
import { realpathSync } from "node:fs";
|
|
8
|
+
import { realpathSync, writeFileSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
import { EXIT, EXIT_NAME, CREDIT_LINE } from "../engine/core/constants.mjs";
|
|
@@ -13,21 +13,29 @@ import { EXIT, EXIT_NAME, CREDIT_LINE } from "../engine/core/constants.mjs";
|
|
|
13
13
|
export const USAGE = `lit-typographic-motion render CLI (LitMuse)
|
|
14
14
|
|
|
15
15
|
usage:
|
|
16
|
-
motion.mjs
|
|
16
|
+
motion.mjs styles --out <dir> [--format 16:9|9:16] the house-style gallery: one demo board per style
|
|
17
|
+
motion.mjs assets --out <dir> --url <link> [--cover] a named product's real logo, README images and words:
|
|
18
|
+
<dir>/stage/assets/ (cover/assets/), <dir>/product/, <dir>/product.json
|
|
19
|
+
motion.mjs scaffold --out <dir> writes <dir>/stage/index.html from the treatment
|
|
20
|
+
motion.mjs stage --out <dir> [--round N] [--stills-only] stage path: renders the page in <dir>/stage/;
|
|
21
|
+
--stills-only is the board round (storyboard + check)
|
|
17
22
|
motion.mjs cover --out <dir> [--round N] [--window t0,t1] cover mode: the seamless README loop in <dir>/cover/;
|
|
18
23
|
--window re-renders only t0-0.5 s .. t1+0.5 s and sheets it
|
|
19
24
|
motion.mjs cover-gate <cover.webp> [--frames d --frames2 d --masks d --title x,y,w,h] [--strict] [--json]
|
|
20
25
|
the cover gate on a decoded cover file
|
|
21
26
|
motion.mjs make <brief.json> --out <dir> [--round N] [--stills-only] [--word-timing]
|
|
22
27
|
type path: the typographic engine
|
|
23
|
-
motion.mjs
|
|
28
|
+
motion.mjs blender --out <dir> [--final] the offline Blender rung (render3d: blender): preview stills of
|
|
29
|
+
<dir>/stage/scene.py, then with --final the plate in <dir>/stage/plate/
|
|
30
|
+
motion.mjs sound --out <dir> builds the film's score (or a minimal film's bed) from the treatment
|
|
24
31
|
motion.mjs look --out <dir> --round N --answers <file> records one look round in <dir>/look.json
|
|
25
32
|
(--blocked no-vision-tool when no image tool can be reached)
|
|
26
33
|
motion.mjs gate <dir> reruns the full type-path gate on a render
|
|
27
34
|
motion.mjs stills|sheet|video|perf <brief.json> --out <dir> one type-path stage alone
|
|
28
35
|
|
|
29
36
|
Every render reads <dir>/treatment.json first (exit 16 names the field). A render with --stills-only
|
|
30
|
-
writes the beat stills, the transition strips
|
|
37
|
+
writes the beat stills, the transition strips, the contact sheet and the storyboard (stills/board.png),
|
|
38
|
+
and on the stage path runs the board check (exit 13 when it fails), then stops; the full render adds
|
|
31
39
|
the master, preview, poster, reduced-motion still, determinism re-check, sound and the gate. A
|
|
32
40
|
flash-audit FAIL withholds every export in <dir>/withheld/. A cover render (treatment mode: cover)
|
|
33
41
|
captures at 2x, encodes cover.webp and gates the decoded file; it exits 13 until every gate reaches
|
|
@@ -44,7 +52,7 @@ function parse(argv) {
|
|
|
44
52
|
const args = { _: [] };
|
|
45
53
|
for (let i = 0; i < argv.length; i++) {
|
|
46
54
|
const a = argv[i];
|
|
47
|
-
if (["--stills-only", "--word-timing", "--help", "-h", "--foreground", "--worker", "--strict", "--json"].includes(a)) args[a.replace(/^-+/u, "")] = true;
|
|
55
|
+
if (["--stills-only", "--word-timing", "--help", "-h", "--foreground", "--worker", "--strict", "--json", "--cover", "--final"].includes(a)) args[a.replace(/^-+/u, "")] = true;
|
|
48
56
|
else if (a.startsWith("--")) {
|
|
49
57
|
args[a.slice(2)] = argv[i + 1];
|
|
50
58
|
i += 1;
|
|
@@ -65,10 +73,21 @@ export async function main(argv, { env = process.env, stdout = process.stdout, s
|
|
|
65
73
|
const pipeline = await import("../engine/node/pipeline.mjs");
|
|
66
74
|
try {
|
|
67
75
|
let result;
|
|
68
|
-
if (["stage", "cover", "sound", "look"].includes(mode)) {
|
|
76
|
+
if (["stage", "cover", "sound", "look", "scaffold", "styles", "assets", "blender"].includes(mode)) {
|
|
69
77
|
if (!args.out) throw new BlockedError(EXIT.USAGE, `${mode} needs --out <dir>`);
|
|
70
78
|
const out = path.resolve(args.out);
|
|
71
|
-
if (mode === "
|
|
79
|
+
if (mode === "assets") {
|
|
80
|
+
if (!args.url) throw new BlockedError(EXIT.USAGE, "assets needs --url <link>");
|
|
81
|
+
const { fetchAssets } = await import("../engine/node/assets.mjs");
|
|
82
|
+
result = await fetchAssets({ out, url: String(args.url), into: args.cover ? "cover" : "stage" });
|
|
83
|
+
} else if (mode === "scaffold") {
|
|
84
|
+
const { scaffoldCommand } = await import("../engine/node/scaffold.mjs");
|
|
85
|
+
const { asBlocked } = pipeline;
|
|
86
|
+
result = asBlocked(() => scaffoldCommand({ out }));
|
|
87
|
+
} else if (mode === "styles") {
|
|
88
|
+
const { stylesCommand } = await import("../engine/node/gallery.mjs");
|
|
89
|
+
result = await stylesCommand({ out, format: args.format ?? "16:9", env, log });
|
|
90
|
+
} else if (mode === "cover") {
|
|
72
91
|
const cover = await import("../engine/node/cover.mjs");
|
|
73
92
|
const round = Number(args.round ?? 1);
|
|
74
93
|
const run = () => cover.renderCover({ out, round, env, log });
|
|
@@ -94,6 +113,20 @@ export async function main(argv, { env = process.env, stdout = process.stdout, s
|
|
|
94
113
|
result = await stage.renderStageDetached({ out, round, cli: fileURLToPath(import.meta.url), env, log });
|
|
95
114
|
if (result.blocked) throw new BlockedError(result.code, result.message);
|
|
96
115
|
}
|
|
116
|
+
} else if (mode === "blender") {
|
|
117
|
+
const { blenderCommand } = await import("../engine/node/blender.mjs");
|
|
118
|
+
const final = Boolean(args.final);
|
|
119
|
+
if (args.worker) {
|
|
120
|
+
const progress = (frame, frameCount) => writeProgress(out, { phase: "plate", frame, frameCount });
|
|
121
|
+
return await detachedWorker({ run: () => blenderCommand({ out, final: true, env, log, progress }), out, BlockedError });
|
|
122
|
+
}
|
|
123
|
+
// The final plate can take most of an hour, so it runs detached like a full stage render.
|
|
124
|
+
if (!final || args.foreground || env.LITMUSE_MOTION_FOREGROUND === "1") result = await blenderCommand({ out, final, env, log });
|
|
125
|
+
else {
|
|
126
|
+
const { renderStageDetached } = await import("../engine/node/stage.mjs");
|
|
127
|
+
result = await renderStageDetached({ out, round: 1, cli: fileURLToPath(import.meta.url), mode: "blender", env, log });
|
|
128
|
+
if (result.blocked) throw new BlockedError(result.code, result.message);
|
|
129
|
+
}
|
|
97
130
|
} else if (mode === "sound") {
|
|
98
131
|
const { soundCommand } = await import("../engine/node/sound-cli.mjs");
|
|
99
132
|
result = await soundCommand({ out, env, log });
|
|
@@ -138,6 +171,14 @@ export async function main(argv, { env = process.env, stdout = process.stdout, s
|
|
|
138
171
|
}
|
|
139
172
|
}
|
|
140
173
|
|
|
174
|
+
const writeProgress = (out, p) => {
|
|
175
|
+
try {
|
|
176
|
+
writeFileSync(path.join(out, ".run", "progress.json"), JSON.stringify(p));
|
|
177
|
+
} catch {
|
|
178
|
+
// The waiting caller only logs progress; a missed write costs nothing.
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
141
182
|
/** The detached side of a full stage or cover render: run it and leave the result for the waiting caller. */
|
|
142
183
|
async function detachedWorker({ run, out, BlockedError }) {
|
|
143
184
|
const { writeFileSync } = await import("node:fs");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "litmuse.motion-style/v1",
|
|
3
|
+
"id": "paper",
|
|
4
|
+
"name": "Paper",
|
|
5
|
+
"summary": "A warm paper ground, near-black ink, one terracotta signal and one blue moment; soft springs, hand-drawn calm.",
|
|
6
|
+
"light": true,
|
|
7
|
+
"typePreset": null,
|
|
8
|
+
"palette": { "ground": "#F3EEE5", "surface": "#E6DED0", "ink": "#1D1B18", "muted": "#5B564E", "signal": "#B0452A", "accent": "#2B5D8E", "line": "#C9BFAE" },
|
|
9
|
+
"accents": [{ "role": "growth and done", "color": "#3F7D3A" }, { "role": "a second data series", "color": "#7A3E7A" }, { "role": "a warning or a date", "color": "#8A6418" }, { "role": "a third data series", "color": "#1F6F6B" }],
|
|
10
|
+
"textRoles": ["ink", "muted", "signal", "accent", "ground"],
|
|
11
|
+
"faces": {
|
|
12
|
+
"display": { "family": "Pretendard", "weight": 700 },
|
|
13
|
+
"headline": { "family": "Pretendard", "weight": 700 },
|
|
14
|
+
"body": { "family": "Pretendard", "weight": 400 },
|
|
15
|
+
"label": { "family": "MesloLGS NF", "weight": 400 }
|
|
16
|
+
},
|
|
17
|
+
"scale": {
|
|
18
|
+
"16:9": { "display": 112, "headline": 68, "body": 40, "label": 26 },
|
|
19
|
+
"9:16": { "display": 108, "headline": 74, "body": 46, "label": 32 }
|
|
20
|
+
},
|
|
21
|
+
"leading": { "display": 1.15, "headline": 1.25, "body": 1.6, "label": 1.4 },
|
|
22
|
+
"motion": {
|
|
23
|
+
"ease": { "enter": [0.22, 1, 0.36, 1], "move": [0.65, 0, 0.35, 1], "exit": [0.55, 0, 1, 0.45] },
|
|
24
|
+
"dur": { "micro": 0.18, "move": 0.55, "hero": 0.8, "exit": 0.32 },
|
|
25
|
+
"stagger": { "letter": 0.03, "word": 0.09, "item": 0.1 },
|
|
26
|
+
"spring": { "micro": { "zeta": 0.85, "hz": 3.5 }, "panel": { "zeta": 0.8, "hz": 2 }, "headline": { "zeta": 0.95, "hz": 2.2 }, "subject": { "zeta": 0.6, "hz": 1.8 }, "camera": { "zeta": 1, "hz": 0.5 } },
|
|
27
|
+
"secondary": { "delay": 0.15, "travel": 0.6 },
|
|
28
|
+
"ambient": { "travel": 0.04 },
|
|
29
|
+
"push": 0.02
|
|
30
|
+
},
|
|
31
|
+
"transitions": ["cut", "match", "wipe", "iris", "push"],
|
|
32
|
+
"ground": { "kind": "flat" },
|
|
33
|
+
"sound": { "timbre": "soft-keys", "key": "F", "mode": "major", "tempo": 96 },
|
|
34
|
+
"score": { "genre": "garage" },
|
|
35
|
+
"beds": [
|
|
36
|
+
{ "timbre": "soft-keys", "key": "F", "mode": "major", "tempo": 96 },
|
|
37
|
+
{ "timbre": "felt-strings", "key": "G", "mode": "major", "tempo": 88 },
|
|
38
|
+
{ "timbre": "glass", "key": "C", "mode": "major", "tempo": 108 }
|
|
39
|
+
],
|
|
40
|
+
"bans": ["drop shadows that float type", "neon or glow", "gradients on type", "a saturated colour with no role", "bouncy type"]
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "litmuse.motion-style/v1",
|
|
3
|
+
"id": "swiss-signal",
|
|
4
|
+
"name": "Swiss signal",
|
|
5
|
+
"summary": "Ink ground, bone type, one teal signal and one amber moment; type lands precisely and cuts hard.",
|
|
6
|
+
"light": false,
|
|
7
|
+
"typePreset": "swiss-signal",
|
|
8
|
+
"palette": { "ground": "#0C0E13", "surface": "#171B24", "ink": "#E9EBE4", "muted": "#A4A9AF", "signal": "#0F7A82", "accent": "#D9A441", "line": "#4B5058" },
|
|
9
|
+
"accents": [{ "role": "state under review", "color": "#E2725B" }, { "role": "state planned", "color": "#9B8CFF" }, { "role": "state done", "color": "#8FC25A" }, { "role": "state in progress", "color": "#5BA8E0" }],
|
|
10
|
+
"textRoles": ["ink", "muted", "signal", "accent", "ground"],
|
|
11
|
+
"faces": {
|
|
12
|
+
"display": { "family": "Archivo", "weight": 900, "stretch": "100%" },
|
|
13
|
+
"headline": { "family": "Archivo", "weight": 700, "stretch": "100%" },
|
|
14
|
+
"body": { "family": "Pretendard", "weight": 400 },
|
|
15
|
+
"label": { "family": "MesloLGS NF", "weight": 400 }
|
|
16
|
+
},
|
|
17
|
+
"scale": {
|
|
18
|
+
"16:9": { "display": 128, "headline": 76, "body": 40, "label": 26 },
|
|
19
|
+
"9:16": { "display": 120, "headline": 80, "body": 46, "label": 32 }
|
|
20
|
+
},
|
|
21
|
+
"leading": { "display": 1.1, "headline": 1.2, "body": 1.55, "label": 1.4 },
|
|
22
|
+
"motion": {
|
|
23
|
+
"ease": { "enter": [0.16, 1, 0.3, 1], "move": [0.65, 0, 0.35, 1], "exit": [0.7, 0, 0.84, 0] },
|
|
24
|
+
"dur": { "micro": 0.15, "move": 0.45, "hero": 0.6, "exit": 0.28 },
|
|
25
|
+
"stagger": { "letter": 0.025, "word": 0.07, "item": 0.08 },
|
|
26
|
+
"spring": { "micro": { "zeta": 1, "hz": 4 }, "panel": { "zeta": 0.9, "hz": 2.2 }, "headline": { "zeta": 1, "hz": 2.6 }, "subject": { "zeta": 0.8, "hz": 1.8 }, "camera": { "zeta": 1, "hz": 0.5 } },
|
|
27
|
+
"secondary": { "delay": 0.15, "travel": 0.6 },
|
|
28
|
+
"ambient": { "travel": 0.03 },
|
|
29
|
+
"push": 0.02
|
|
30
|
+
},
|
|
31
|
+
"transitions": ["cut", "wipe", "match", "push"],
|
|
32
|
+
"ground": { "kind": "flat", "grid": true },
|
|
33
|
+
"sound": { "timbre": "glass", "key": "A", "mode": "minor", "tempo": 104 },
|
|
34
|
+
"score": { "genre": "house" },
|
|
35
|
+
"beds": [
|
|
36
|
+
{ "timbre": "glass", "key": "A", "mode": "minor", "tempo": 104 },
|
|
37
|
+
{ "timbre": "pulse-synth", "key": "D", "mode": "minor", "tempo": 112 },
|
|
38
|
+
{ "timbre": "soft-keys", "key": "C", "mode": "major", "tempo": 96 }
|
|
39
|
+
],
|
|
40
|
+
"bans": ["glow or bloom on type", "an accent colour with no role", "centred fade-in as the only move", "overshoot or bounce on type", "particles as decoration", "gradients behind type"]
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "litmuse.motion-style/v1",
|
|
3
|
+
"id": "terminalcore",
|
|
4
|
+
"name": "Terminalcore",
|
|
5
|
+
"summary": "A navy terminal: phosphor type that appears at typing speed, one signal hue, hard cuts, nothing eases.",
|
|
6
|
+
"light": false,
|
|
7
|
+
"typePreset": "terminalcore",
|
|
8
|
+
"palette": { "ground": "#05070A", "surface": "#0C1116", "ink": "#39FF6A", "muted": "#7C8B93", "signal": "#39FF6A", "line": "#1E2A33" },
|
|
9
|
+
"accents": [{ "role": "a warning line", "color": "#FFB23F" }, { "role": "an error line", "color": "#FF6B6B" }, { "role": "a path or a link", "color": "#6CA8FF" }],
|
|
10
|
+
"textRoles": ["ink", "muted", "signal", "ground"],
|
|
11
|
+
"faces": {
|
|
12
|
+
"display": { "family": "VT323", "weight": 400 },
|
|
13
|
+
"headline": { "family": "VT323", "weight": 400 },
|
|
14
|
+
"body": { "family": "VT323", "weight": 400 },
|
|
15
|
+
"label": { "family": "MesloLGS NF", "weight": 400 }
|
|
16
|
+
},
|
|
17
|
+
"hangul": { "family": "Galmuri9", "weight": 400 },
|
|
18
|
+
"scale": {
|
|
19
|
+
"16:9": { "display": 126, "headline": 81, "body": 45, "label": 36 },
|
|
20
|
+
"9:16": { "display": 117, "headline": 81, "body": 54, "label": 36 }
|
|
21
|
+
},
|
|
22
|
+
"leading": { "display": 1.1, "headline": 1.2, "body": 1.5, "label": 1.4 },
|
|
23
|
+
"motion": {
|
|
24
|
+
"ease": { "enter": [0, 0, 1, 1], "move": [0, 0, 1, 1], "exit": [0, 0, 1, 1] },
|
|
25
|
+
"dur": { "micro": 0.1, "move": 0.3, "hero": 0.5, "exit": 0.1 },
|
|
26
|
+
"stagger": { "letter": 0.0455, "word": 0.18, "item": 0.25 },
|
|
27
|
+
"spring": { "micro": { "zeta": 1, "hz": 6 }, "panel": { "zeta": 1, "hz": 4 }, "headline": { "zeta": 1, "hz": 4 }, "subject": { "zeta": 1, "hz": 3 }, "camera": { "zeta": 1, "hz": 1 } },
|
|
28
|
+
"secondary": { "delay": 0.1, "travel": 0.6 },
|
|
29
|
+
"ambient": { "travel": 0.02 },
|
|
30
|
+
"push": 0
|
|
31
|
+
},
|
|
32
|
+
"transitions": ["cut", "wipe"],
|
|
33
|
+
"ground": { "kind": "flat", "panel": "surface" },
|
|
34
|
+
"sound": { "timbre": "pulse-synth", "key": "E", "mode": "minor", "tempo": 120 },
|
|
35
|
+
"score": { "genre": "tech-house" },
|
|
36
|
+
"beds": [
|
|
37
|
+
{ "timbre": "pulse-synth", "key": "E", "mode": "minor", "tempo": 120 },
|
|
38
|
+
{ "timbre": "glass", "key": "B", "mode": "minor", "tempo": 132 },
|
|
39
|
+
{ "timbre": "pulse-synth", "key": "A", "mode": "minor", "tempo": 100 }
|
|
40
|
+
],
|
|
41
|
+
"bans": ["falling code rain", "purple-cyan neon haze", "glitch running continuously", "an accent hue with no role", "pixel type too small to read", "eased type"]
|
|
42
|
+
}
|