@leotang92/dsh-claude-motion 0.0.0-stage → 0.1.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 (33) hide show
  1. package/LICENSE +27 -0
  2. package/NOTICE.md +25 -0
  3. package/README.md +21 -2
  4. package/cordis.patch.yml +7 -0
  5. package/docs/DETAIL.md +32 -0
  6. package/docs/DETAIL.zh.md +17 -0
  7. package/icon.svg +8 -0
  8. package/lib/client.js +1800 -0
  9. package/lib/index.js +878 -0
  10. package/locale/en.json +7 -0
  11. package/locale/zh.json +7 -0
  12. package/package.json +69 -4
  13. package/skills/claude-motion/SKILL.md +689 -0
  14. package/skills/claude-motion/artifact-type/reference/by-hand.md +84 -0
  15. package/skills/claude-motion/artifact-type/reference/film-api.md +278 -0
  16. package/skills/claude-motion/artifact-type/reference/finalize.md +213 -0
  17. package/skills/claude-motion/artifact-type/reference/motion.md +260 -0
  18. package/skills/claude-motion/artifact-type/reference/plan.md +239 -0
  19. package/skills/claude-motion/artifact-type/reference/questions.md +240 -0
  20. package/skills/claude-motion/artifact-type/reference/recreate-ui.md +140 -0
  21. package/skills/claude-motion/artifact-type/reference/render-offline.md +126 -0
  22. package/skills/claude-motion/artifact-type/reference/rough-cut.md +251 -0
  23. package/skills/claude-motion/artifact-type/reference/sound.md +106 -0
  24. package/skills/claude-motion/artifact-type/reference/styles.md +230 -0
  25. package/skills/claude-motion/artifact-type/reference/walkthrough.md +83 -0
  26. package/skills/claude-motion/scripts/artifact.mjs +8390 -0
  27. package/skills/claude-motion/scripts/editor/artifact-type/anthropicons-fizzy-lake-DtzHkSJH.woff2 +0 -0
  28. package/skills/claude-motion/scripts/editor/artifact-type/app.css +1 -0
  29. package/skills/claude-motion/scripts/editor/artifact-type/app.js +17230 -0
  30. package/skills/claude-motion/scripts/editor/index.html +145 -0
  31. package/skills/claude-motion/scripts/editor/local-host.js +12 -0
  32. package/skills/claude-motion/scripts/editor/share-ui.js +208 -0
  33. package/skills/claude-motion/scripts/editor/third-party-licenses.txt +970 -0
@@ -0,0 +1,251 @@
1
+ # A film of scenes, in a look of your own: its records
2
+
3
+ Part of "A new film: three questions, then the film" in this type's
4
+ instructions, which say when each step happens and what you reply. This page is
5
+ the records' format and how to write them. The example is another film's (a bell
6
+ foundry's open day): copy how it is built, never its scenes, words, colors,
7
+ typeface, shapes or times. `if_version`, wherever this page says it: only if your tool's
8
+ input lists it; the version is in your last `list`, `get` or write of that document.
9
+
10
+ ## The first batch
11
+
12
+ Sent at once, before any scene is designed, in this order. The film then plays:
13
+ the player draws a plain card with the scene's name for each scene still to come.
14
+
15
+ **`film/meta`**, then **`modules/scenes.json`** (`kind` json):
16
+
17
+ ```json
18
+ {"v":1,"stage":"rough","look":"Cast bronze: Heavy metal shapes lit by furnace glow, rising slowly into place",
19
+ "palettes":[["#17455C","#FFC24B","#FFF1DE","#2A1410"],["#10201D","#3E8F84","#C9E4DC","#D98A3D"],["#F3EFE6","#7A4B2A","#2B211A","#C8962E"]],"theirs":0,
20
+ "width":1920,"height":1080,"fps":60,"background":"#10201D",
21
+ "fonts":{"display":{"family":"…","or":"…"},"sans":{"family":"…","or":"…"}},"scenes":[
22
+ {"id":"yard","name":"The yard","dur":3,"desc":"Gates open on the foundry yard; the date sets in."},
23
+ {"id":"pour","name":"The pour","dur":4.5,"desc":"The ladle tips; bronze runs into the mould."},
24
+ {"id":"ring","name":"First ring","dur":4,"desc":"The bell swings once; rings spread."},
25
+ {"id":"come","name":"Come and see","dur":3.5,"desc":"End card: day, place, free entry."}]}
26
+ ```
27
+
28
+ - Three or four scenes for 20 s, five or six for 30, 24 at most, each ONE subject and ONE
29
+ action: an establishing open → the subject arrives → the thing happens →
30
+ closer scenes, each landing on motion → back out → an end card held 3 s or
31
+ more. The seconds add up to the Length answer; 300 at most.
32
+ - `id`: lower-case letters, digits and `-`, a letter first, 32 at most. It never
33
+ changes, and no two are alike: comments and the user's own edits hang on it.
34
+ `name`: one to three words (48 characters at most), for a listener ("The pour",
35
+ not "Scene 2"): it labels the timeline and the card. `dur`: seconds, above 0.
36
+ `desc`: one plain sentence on what happens. A bad `id`,
37
+ `dur` or count stops the whole film.
38
+ - One continuous action (one window, one camera move, one cursor's errand) is ONE scene, however
39
+ long: nothing carries over a cut. The one exception: a character or object that moves about or
40
+ travels (a window, screen, cursor, card or logo is not one). Where two scenes in a row show it,
41
+ the second must **open in the same framing, with it as the first's code left it**: place, size,
42
+ pose, speed; plan none of its moves ahead, in thought or in `shared.js`.
43
+ - `stage`: `"rough"` until the film's last write: to the page, "not done yet".
44
+ - `width`, `height`: vertical (Reels, TikTok, Shorts) is 1080 by 1920. `background`:
45
+ the picked ground (an overlay: `"transparent"`, and `.ground` paints nothing). `look`: the look in words, for you when you draw the scenes, which may
46
+ be another conversation: the picked option's name and line, and whatever they typed
47
+ or asked for, word for word; you decided: a name and line of your own.
48
+ - `palettes`: every palette the Colors question offered, picked or not (a skipped
49
+ form's too), in the order shown (the picked one is not moved up), each its four
50
+ colors as `#rrggbb` in role order (ground, main, second, accent); offered in words
51
+ alone: settle each one's four now. `theirs`: how many of them, counted from the
52
+ first, are the user's (their brand, a design system), picked or not. One system's
53
+ tile and two palettes of your own: 1. The example: three of your own, the second
54
+ picked: 0. They answered Other, or in words of their own: the palette you made of
55
+ that stands ahead of the rest, and counts as theirs. Hexes alone: no name, no
56
+ title. No Colors question asked: the film's own four alone, `theirs` 1 if they are
57
+ the user's, else 0. For the page to offer again: keep both keys, as they are, at
58
+ every later write of this record.
59
+ - `fonts`: the faces the film is set in. Four optional slots: `sans`, `serif`, `mono` for text,
60
+ `display` for headlines. A slot takes one of the ten faces that ship with the player, by id
61
+ (a plain string), or nearly any Google Fonts family with the nearest of the ten to stand in (in a text slot, one for text):
62
+ `{"family":"…","or":"…"}`. From memory: look nothing up. The player fetches it; `or` shows
63
+ where it can't (no such name; a family that also holds Chinese, Japanese or Korean; no
64
+ network). A family brings weights 400 and 700, or its nearest, never thickened; for others
65
+ name them all, four at most, beside `or`: `"weights":[400,800]`. `display` unnamed: headlines
66
+ take the film's `sans`, so a film set in a serif or a mono names it. Keep `fonts` at every
67
+ later write.
68
+
69
+ The ten. For text: `plus-jakarta-sans` (wide), `manrope` (round), `space-grotesk` (angular),
70
+ `instrument-sans` (narrow), `fraunces` (serif), `dm-mono`, `departure-mono` (pixel), these two
71
+ of one weight. Headlines: `fraunces-display` (hairline), `inter-display` (loud),
72
+ `shantell-sans` (hand-drawn).
73
+
74
+ **Pick for THIS film**, from the look's words and the subject, never from habit: films
75
+ in the same faces look machine-made, and your habit is the same quiet sans under every
76
+ headline, with mono labels. Think where this look's lettering would be at
77
+ home (a road sign, a dashboard, a shop sign, a terminal), then what KIND of face
78
+ is found there (an old-style serif, a didone, a humanist, rounded or wide
79
+ sans, a typewriter, a brush script), then a family of that kind, fit for work (questions.md). The first that comes to
80
+ mind is the one every film would get, so look past it, to a name you are sure of. Most
81
+ films want two, a `display` over a text face that has a bold, unlike in kind, weight or
82
+ voice. A third only as `mono`: for code, a terminal, or figures that count up or sit in
83
+ columns.
84
+
85
+ The user names a family: that one, spelt right, in the slot they mean (titles:
86
+ `display`; the text: `sans`, serif or not; the whole film or a house face: `sans`, and no
87
+ `display`; one of the ten: its id). If it isn't Google's (Helvetica Neue): the nearest
88
+ Google family. Files they gave: the nearest of the ten under them, no `display` of yours,
89
+ their names in `look`; finalize.md's step 2 brings theirs in. The ten have Western European letters only (no ł, ő,
90
+ ş). Any other letters (Polish, Turkish, Greek, Cyrillic, Arabic): two slots at most, each a well-known
91
+ Google family that has them and Latin, none that the player ships, two weights each; `or` as ever (it won't show). Chinese, Japanese, Korean: leave `fonts` out,
92
+ whatever they named.
93
+
94
+ **`modules/base.css`** (`kind` css), six lines at most for now: the picked colors
95
+ as variables (`--ground`, `--main`, `--second`, `--accent`, each once, a plain
96
+ `#rrggbb`, no two alike), the ground painted from them (`.ground`, one per scene, at the
97
+ bottom), the type stacks the film uses, written as below or as
98
+ `var(--serif, Georgia, serif)`, `var(--mono, ui-monospace, monospace)`, never a family's
99
+ name (`#stage` takes the one most words wear; in a serif or mono film `.line` has that one
100
+ inside, in place of the sans one), the few classes every scene uses. It is applied
101
+ before any scene, and keeps scenes written apart looking like one film.
102
+
103
+ ```css
104
+ #stage { --ground:#10201D; --main:#3E8F84; --second:#C9E4DC; --accent:#D98A3D; color:var(--second); font-family:var(--sans, system-ui, sans-serif) }
105
+ .ground { position:absolute; inset:0; background:var(--ground) }
106
+ .words { position:absolute; margin:0; display:flex; flex-direction:column; gap:24px } .words > * { margin:0 }
107
+ .line { font-family:var(--display, var(--sans, system-ui, sans-serif)); font-size:84px; line-height:1.05; font-weight:700 } .sub { font-size:36px }
108
+ ```
109
+
110
+ **`film/script`**, last: `{"code":"window.CUT = kit.scenes();"}`, that line and
111
+ nothing else, for the film's whole life. No later step writes it again.
112
+
113
+ ## A scene
114
+
115
+ One document per scene, `modules/scene-pour.js` (`kind` js) for the scene `pour`, one
116
+ `set` each, in film order, ONE call per message: think about one scene, write it,
117
+ send it, read the reply, go to the next. Calls sent together arrive together.
118
+
119
+ ```js
120
+ const { seg, eio, lerp } = kit;
121
+ kit.scene('pour', {
122
+ init(kit) {
123
+ kit.stage.append(kit.h('<div class="ground"><div class="pour-ladle"></div></div>'));
124
+ this.ladle = kit.stage.querySelector('.pour-ladle').style;
125
+ },
126
+ render(t) {
127
+ this.ladle.transform = 'rotate(' + lerp(4, -24, eio(seg(t, 0.3, 1.2))) + 'deg)';
128
+ },
129
+ });
130
+ ```
131
+
132
+ That is the bare shape. A finished scene, whole: finalize.md. A rough one: "How rough".
133
+
134
+ - A scene is a small cut of its own. One `kit.scene` call per module, for the
135
+ module's own id. `init(kit)` builds the scene once inside `kit.stage` (optional;
136
+ may be async). `render(t, kit)` is required, synchronous and pure: every varying
137
+ property set from `t` alone, SECONDS since the scene began. No CSS transitions
138
+ or animations, no timers. Both are called as methods, so `this` carries what
139
+ `init` built over to `render` (under any names but `lead` and `tail`, the player's).
140
+ At a module's top level: constants and that call, no DOM. `init` awaits whatever
141
+ `render` needs: the player tries `render(0)` as soon as `init` settles, and a throw
142
+ there fails the scene.
143
+ - The whole film loads again at every write: keep each `init` light.
144
+ - A scene's `kit` is its own: `kit.stage` is the scene's box, which fills the stage
145
+ and has no ground of its own (`scenes.json`'s `background` lies behind it); what
146
+ `kit.css`, `kit.cursor`, `kit.svgLayer` and `kit.chip` make stays in the scene;
147
+ `kit.video`'s `start` and `kit.typist`'s `t0` count from the scene's beginning.
148
+ `kit.dur`: the scene's seconds. `kit.start`: where it begins in the film. `kit.fx`
149
+ is one canvas, shared by all scenes: set its state (lineWidth, fillStyle,
150
+ a clip) in `render`, never once in `init`.
151
+ - A scene's own class names begin with its id (`pour-ladle`).
152
+ - Scenes cut from one to the next: open what moves about as it was left; else build each to stand
153
+ alone, and to hold its first and last picture for a `t` below 0 or past `kit.dur` (`seg` does).
154
+ The first scene at `t` 0 is the poster and thumbnail: never blank.
155
+ - No flashes: nothing blinks or cuts more than three times a second, rough or
156
+ finished. No scene under a second.
157
+ - `scenes.json`, `base.css`, `shared.js`, `score.js` and any name that begins `scene-`
158
+ or `layer-` are the player's: name no other module so.
159
+ - A line of words is ONE string in ONE element, as the viewer reads it: the page
160
+ lets the user retype it, and finds it by those words. So no id, class name or
161
+ key spelt like words on screen.
162
+ - Lines that belong together (headline, subtitle, button) sit in ONE flow
163
+ container (`.words`, a flex column with a gap), placed as a group, so that a line
164
+ that wraps pushes the next one down; never each at its own `top`. A lone line sits
165
+ in a `.words` too: `.line` has no place of its own.
166
+ - Markup in single-quoted strings, `style="…"` in double quotes, no quotes inside
167
+ a style: a nested quote silently ends the style; the rest is lost.
168
+ - Helpers: `kit.el`, `kit.h`, `seg(t,a,b)` (0 to 1 across a to b), `lerp`, `clamp01`,
169
+ `eo` `ei` `eio` (eases). The rest of the kit waits for finalize.md's step 1 (film-api.md).
170
+ `kit.h` returns ONE element, the first in its string: wrap siblings in one root,
171
+ or call it once for each.
172
+
173
+ ## How rough
174
+
175
+ Only for a film whose scenes are rough already (a change to it, Continue). No new film
176
+ is written so.
177
+
178
+ A wireframe: the real words, places, sizes and timing.
179
+
180
+ - What you write: the picked ground, one ink that reads on it (`--second`), and type in
181
+ the sizes and weights it will have, from `base.css`. All four picked colors stand
182
+ there as variables from the start; `--main` and `--accent` wait for Finalize.
183
+ - What the player shows: a rough scene as if drawn by hand in black and white: not
184
+ yours to imitate, mend or undo. No picked color shows until Finalize redraws the
185
+ scene: say so only if asked, or after a change of color.
186
+ - Every object is an outline (2 px, no fill). One that holds no words of its own
187
+ gets a short label in small capitals saying what it will be: LADLE, LOGO, CHART.
188
+ A label is a placeholder, not the film's words: write it `data-label="LADLE"`
189
+ (`base.css` draws it), so that the page never offers it for retyping, and at
190
+ Finalize the drawing takes its place. Words that belong to the film (a chart's
191
+ BEFORE and AFTER) are real text, and stay.
192
+ - No fill, texture, shadow, secondary motion, camera, sound, font file or image asset.
193
+
194
+ `base.css` holds these too:
195
+
196
+ ```css
197
+ .box { position:absolute; margin:0 }
198
+ .box { border:2px solid var(--second); box-sizing:border-box; font-size:30px; letter-spacing:.12em }
199
+ .box::after { content:attr(data-label); position:absolute; left:14px; bottom:10px }
200
+ ```
201
+
202
+ ```js
203
+ const { seg, eo, lerp } = kit;
204
+ kit.scene('pour', {
205
+ rough: true,
206
+ init(kit) {
207
+ kit.stage.append(kit.h('<div class="ground">' +
208
+ '<div class="box" data-label="MOULD" style="left:760px;top:560px;width:400px;height:360px"></div>' +
209
+ '<div class="box pour-stream" style="left:942px;top:330px;width:36px;height:230px"></div>' +
210
+ '<div class="box pour-ladle" data-label="LADLE" style="left:960px;top:170px;width:300px;height:170px;transform-origin:0 100%"></div>' +
211
+ '<div class="words" style="left:140px;top:150px;width:560px"><h2 class="line">Eleven hundred degrees, four minutes</h2>' +
212
+ '<p class="sub">Bronze, poured by hand</p></div></div>'));
213
+ for (const k of ['ladle', 'stream']) this[k] = kit.stage.querySelector('.pour-' + k).style;
214
+ },
215
+ render(t) {
216
+ this.ladle.transform = 'rotate(' + lerp(0, -24, eo(seg(t, 0.4, 1.2))) + 'deg)';
217
+ this.stream.opacity = t > 1 && t < 3.6 ? 1 : 0;
218
+ },
219
+ });
220
+ ```
221
+
222
+ ## Scenes missing or broken
223
+
224
+ A turn cut short leaves listed scenes with no module; a scene whose code fails
225
+ shows its name card in place of the scene, and nobody tells you. At "Continue the
226
+ animation." (it may go on "Scenes left: 2, 3.": positions in `scenes.json`, from 1,
227
+ never a count: here `pour` and `ring`; "Scenes left: 2." is `pour` alone), or when you find it so: `list` `modules`; read the named scenes that have a module, or all,
228
+ for a slip and mend it (`if_version`). No scene is rough: finalize.md's "Cut short"
229
+ (not in this conversation: `read` it first): what is missing is written finished, then, the
230
+ stage still rough, the score and `"final"`. Else write what is missing, rough, in film order,
231
+ one `set` per message, and go no further.
232
+
233
+ ## A change
234
+
235
+ `list` `modules` (every document whole, with its version), change the scenes it
236
+ touches at the same roughness (`rough: true` stays), `set` each with `if_version`. A
237
+ change of color: the variables in `base.css` (a new ground: `scenes.json`'s
238
+ `background` too); where scenes are rough, the one sentence more says it will show at Finalize. A scene added,
239
+ dropped, renamed, retimed or moved is an edit to `scenes.json` (`if_version`;
240
+ `"stage"` stays `"rough"`; no scene is rough: finalize.md's "Cut short" follows): a new scene's module after it, a dropped scene's module
241
+ `delete`d (`if_version`). Scenes that stay keep their ids. On a final film the
242
+ same, finished, as finalize.md says of a final film (not in this conversation: `read`
243
+ `artifact-type/reference/finalize.md` first): a new scene has no `rough: true`, the stage stays `"final"`, and
244
+ `score.js` is fitted to the new times, with no `T.` of a dropped id left (it would
245
+ sound at 0:00). `modules/{retyped,recolored,refaced,fetched-fonts,recut,retouched}.json` are the page's own
246
+ records: never write or delete them. If the film holds one besides fetched-fonts.json,
247
+ first `read` `artifact-type/reference/by-hand.md`, which says what counts.
248
+
249
+ ## Finalize
250
+
251
+ Its steps are on a page of their own, `artifact-type/reference/finalize.md`.
@@ -0,0 +1,106 @@
1
+ # Sound — a score generated from the cut
2
+
3
+ The score is part of the film: `window.CUT.score(S)` declares notes and
4
+ foley against the SAME timeline constants the picture uses, the player
5
+ synthesizes it (a small built-in WebAudio synth — no samples, no
6
+ soundfont) in sync with playback, and Export renders the
7
+ identical events into the MP4's audio track. `S` only records events;
8
+ it is deterministic data, so write it like the picture: from `TT`.
9
+
10
+ ## The method (what worked in review)
11
+
12
+ - **Pick the tempo from the cut, not the other way round.** One beat =
13
+ the hard-cut cadence of your showcase run (0.63 s → 95 BPM in the
14
+ reference); phase the grid (`t0`) so bar lines land on the big
15
+ moments — the first close-up, the editor landing, the end page. If a
16
+ visual beat misses a downbeat by a few frames, retime the BEAT in
17
+ `TT`: nobody sees three frames, everybody hears an off-beat hit.
18
+ - **Groove on the grid, foley on exact frames.** Music events take
19
+ beats; UI foley takes the precise t of the event it belongs to, off
20
+ grid: a wood-block tick per visible keystroke (your typist's
21
+ `times`), side-stick clicks on clicks, a double tick for a
22
+ double-click, pops as list items or capsules appear, a rising roll
23
+ while something is dragged or scrubbed with a ding when the value
24
+ lands, a two-note chime on Save, a whoosh under a whip-pan, a boing
25
+ when something drops in and squashes.
26
+ - **Arc** (keep the dynamics; don't flatten them): near-silent title
27
+ (a pad, maybe one mallet note) → a light pulse under the typing →
28
+ the groove enters when the action starts → stabs on the hard-cut
29
+ section → drop to a pad for the transform → groove B under the
30
+ person's edit → a short build (an accelerating run) INTO the hit as
31
+ the end page lands → resolve and let the last chord ring; fade by the
32
+ last second. Stop-time before the biggest hit is free drama.
33
+ - **Instrumentation that suits a product film**: marimba or kalimba ostinato,
34
+ electric piano chords, a round fingered bass, a light kit (kick, side
35
+ stick, closed hat, a shaker at most), a soft pad. No risers, no
36
+ cinematic booms, no vocal chops. Major or modal, mid-tempo, warm.
37
+ - **Your own tune.** Write no existing song's, theme's or jingle's melody, note by note or near
38
+ enough to be recognised: write an original one in that mood. That you did goes in the close's one
39
+ sentence more (what you could not do as asked): it names the song they asked for, not your music.
40
+ The user's own, or in the public domain: fine. A genre, a mood, a tempo, an instrument: fine.
41
+ - **They asked for other music: name it in a plain line**: what you wrote (a mood you aimed
42
+ for, two instruments in everyday words: a marimba, plucked notes, an electric piano, a
43
+ shaker), never how it sounds. No key, BPM or apology. Mellower, no typing ticks, other
44
+ instruments: a one-line change. Music off by request is `score` returning nothing; mute and
45
+ volume are the viewer's own.
46
+
47
+ ## `score(S, kit)`
48
+
49
+ Runs once at load, after `init` (so typists made in `init` have
50
+ filled `kit.typing.times`). `S` only records events — pure data.
51
+
52
+ ```js
53
+ score(S, kit) {
54
+ const BPM = 95.24, B = 60 / BPM;
55
+ S.tempo(BPM, { t0: TT.cut1 - 16 * B }); // grid phased so beat 16 IS the first close-up
56
+ S.program('keys', 'ep'); S.program('bass', 'bass'); S.program('mallet', 'marimba'); S.program('pad', 'pad', { rev: 0.6 });
57
+ S.note('pad', 'D3', { beat: 0, beats: 8, vel: 30 }); // {beat, beats} on the grid, or {t, dur} in seconds
58
+ S.chord('keys', ['D3', 'A3', 'D4', 'F#4'], { beat: 16, beats: 4, vel: 52 });
59
+ S.note('bass', 'D2', { beat: 16, beats: 1.5, vel: 78 });
60
+ S.pattern('mallet', 'x.x.X.xx', { from: 16, repeat: 4, pitch: ['D5', 'A4'], vel: 64 }); // step 0.5 beat; X accents
61
+ S.drums({ kick: 'x...x...', sidestick: '..x...x.', hat: 'oxoxoxox' }, { from: 16, repeat: 8 }); // o = ghost note
62
+ S.ticks(kit.typing.times, { vel: 30 }); // one wood tick per keystroke, timbre-jittered
63
+ S.fx('click', TT.send); S.fx('pop', TT.chip); S.fx('roll', TT.dragA, { dur: TT.dragB - TT.dragA }); // roll lands on a ding
64
+ S.fx('chime', TT.saved); S.fx('whoosh', TT.whip, { dur: 0.5 }); S.fx('revswell', TT.end, { dur: 1.2 }); // swells INTO TT.end
65
+ S.run('mallet', ['D5', 'E5', 'F#5', 'A5', 'B5', 'D6'], { t: TT.end - 0.62, step: 0.1, vel: 50, cresc: 8 });
66
+ S.master({ gain: -6, fadeOut: 1.2 });
67
+ }
68
+ ```
69
+
70
+ - `S.tempo(bpm, {t0})` sets the grid; `S.beat(n)` → seconds,
71
+ `S.toBeat(t)` → beats. Everything takes `{beat, beats}` (grid) or
72
+ `{t, dur}` (seconds).
73
+ - `S.program(track, patch, {pan −1..1, gain dB, rev 0..1, bright
74
+ 0..2}?)` names a track. Pitched patches: `marimba`, `vibes`,
75
+ `glock`, `ep` (electric piano), `keys` (soft synth), `pluck`
76
+ (kalimba-ish), `bass`, `pad`. Drums: `kick`, `snare`, `clap`,
77
+ `sidestick`, `hat`, `openhat`, `shaker`, `tamb`, `woodblock`,
78
+ `cowbell`, `tom`, `crash`, `ride`.
79
+ - `S.note(track, 'F#4' | midi, {…, vel 0–127})`, `S.chord(track,
80
+ [pitches], {…, spread})`, `S.pattern(track, 'x.X.', {from, step =
81
+ 0.5, repeat, pitch: [cycle], vel, dur})`, `S.run(track, [pitches],
82
+ {t, step, vel, cresc})`, `S.drum(name, {beat | t, vel})`,
83
+ `S.drums({name: 'x..o'}, {from, step, repeat, vel})` (`X` accent,
84
+ `o` ghost). A little humanizing is built in (`S.seed(n)` varies it).
85
+ - Foley at exact seconds, `S.fx(name, t, {dur, vel, pitch, pan}?)`:
86
+ `tick` (keystroke), `click`, `doubleclick`, `pop`, `ding` (two
87
+ notes a fifth apart), `chime` (a small arpeggio — Save), `sparkle`,
88
+ `whoosh` (`{into: 1}` reverses it), `revswell` (swells INTO its
89
+ `t` over `dur`), `roll` (accelerating ticks over `dur`, a ding on
90
+ landing — drags and scrubs), `boing` (a bouncy landing), `thud`.
91
+ `S.ticks(times, {vel, jitter})` = one tick per time.
92
+ - `S.master({gain dB = −6, fadeOut s = 1})`. Keep the arc: the hit
93
+ should be clearly louder than the intro pad.
94
+
95
+ `pattern`/`drums` with `repeat` happily overrun the beat you meant to
96
+ stop at — compute the repeat count (or the step string) to fit
97
+ [t0, t1). Unknown drum or foley names are SILENT (an unknown pitched
98
+ patch falls back to marimba); `check.ts` lists them with the event
99
+ count, tempo and sounding span. An event timed 0.1 s or more before 0
100
+ is dropped without a word (one closer is moved to 0), so the sound for
101
+ an entrance that starts early goes at 0. Listen by numbers: `export.ts --no-video --levels`
102
+ prints loudness per second (RMS dBFS: −60 silent, −30 quiet bed, −20
103
+ groove, −12 hit) — no second between the title and the sign-off should
104
+ sit below about −30; `--mid score.mid` added to them writes a Standard
105
+ MIDI File on your grid for a composer.
106
+ Keep it small — a 35 s score is a few hundred events.
@@ -0,0 +1,230 @@
1
+ # Built-in styles
2
+
3
+ A built-in style is a look that ships inside the player. You pick the frames,
4
+ write the words and time them. The page can then change the style, the
5
+ palette or a word without you. That works only
6
+ while **every word on screen is a key in `modules/look.json`**: none in the cut,
7
+ in html or in CSS.
8
+
9
+ - Only for a film that already holds `look.json`, or a plan's request that names
10
+ this page. No other new film starts here, even when asked for by a style's name:
11
+ rough-cut.md, in a look of your own from their words.
12
+ - The film already has a `look.json` (Finalize, any change): read the last
13
+ section first.
14
+ - The example is another film's (a town library's): copy how it is built, not
15
+ its frames, words, times or moves.
16
+ - A film with a plan (plan.md): the style answers its drawn questions about the
17
+ look and composes the frames: pass over those picks and `art.json`'s frames.
18
+ Duration, Where, Motion feel and the cards stand. It is written in one go: ONE
19
+ `batch` of `film/meta`, `look.json`, each screen's html, then the moving cut as
20
+ `film/script`; no `scenes.json`, no stop; plan.md's close.
21
+ - 1920 × 1080 only. Asked for vertical or square: make the film in a look of
22
+ your own, with no look.json, and say so in a line.
23
+
24
+ ## The styles
25
+
26
+ <!-- looks -->
27
+ | `look` | what it is | `palette` ids | `values` |
28
+ |---|---|---|---|
29
+ | `sketch` | A designer at the whiteboard: black marker, one highlighter, arrows that point at what matters. | `marker`, `mint`, `night` | `paper` plain, dots, squared, board, `stress` highlight, underline, circle, `fill` none, hatch, scribble, wash, `wobble` 0–100, `weight` 3–7px |
30
+ | `bubbles` | Big glowing glass spheres on a gradient you can turn. Deep and cinematic, or light and airy. | `ember`, `lagoon`, `cobalt` | `angle` 0–360°, `ground` deep, light, `count` 1–5, `size` 60–125, `fade` 0–100 |
31
+ | `editorial` | A well-set annual report: big serif, italic stress, fine rules, covers in your color. | `cobalt`, `evergreen`, `plum`, `night` | `cover` color, ink, paper, `stress` italic, underline, marker, `rules` hair, double, bold, `size` 84–112% |
32
+ | `bold` | Swiss poster: huge tight type, flat color, a strict grid and one shape. | `vermilion`, `night`, `ultramarine`, `acid` | `shape` arcs, quarter, circle, bars, `color` calm, mixed, loud, `side` right, left, `size` 70–100, `grid` true, false |
33
+ | `midnight` | A dark room and a wall of tiny lamps: an aurora in dots, the big number in lights. | `aurora`, `ice`, `sodium` | `light` 0–360°, `glow` 20–100, `waves` 0–100, `grid` dots, pixels, plus |
34
+ | `showcase` | Calm and product-first: a soft tinted backdrop, white rounded cards, a big product window. | `lagoon`, `blossom`, `meadow`, `apricot` | `backdrop` plain, tint, color, dark, `fade` 0–360°, `corners` 0–48, `shadow` 0–100, `emphasis` chip, color, line |
35
+ <!-- /looks -->
36
+
37
+ ## What you write, and when
38
+
39
+ The style brings the CSS and the fonts. When each step happens is in this type's
40
+ instructions ("Later"). `if_version`, below: only if your tool's
41
+ input lists it.
42
+
43
+ **A rough cut: every frame in the style, held in turn, no motion or sound.** It
44
+ holds `modules/look.json` whole, `modules/scenes.json` as exactly
45
+ `{"v":1,"stage":"rough"}` (look.json's frames are the scenes: none are listed) and
46
+ the script `window.CUT = kit.stills({ duration: 20 })` under the comment that opens
47
+ the cut below: each frame an equal share of the seconds, two at least. A `screen` whose html
48
+ module is missing (the style's window is drawn empty), or a logo: one `set` each, in
49
+ film order, ONE call per message. After that, or a change, while `scenes.json` still says
50
+ `"stage":"rough"`, your whole reply, in place of the instructions' own:
51
+
52
+ > Here's the rough cut. Tell me what to change, or say Finalize for motion and sound.
53
+
54
+ **Finalize: motion and sound.** Read motion.md and sound.md in one message;
55
+ `list` `modules` (`look.json`, `tweaks.json` and `scenes.json` whole, each with its
56
+ version; last section), then `get` `film/script`. A screen whose html is missing:
57
+ write it first. `look.json` stands. Then ONE `batch`, so that the film loads once:
58
+ `film/script` (`if_version`), the moving cut below, with each frame's own time,
59
+ fades, one or two pushes, chapters and the score, at the same whole length; LAST
60
+ `modules/scenes.json` (`if_version`), `{"v":1,"stage":"final"}`. Then the closing
61
+ words of the instructions' section 2, as quoted (no score: its "No score" words), and its one
62
+ sentence more if there is one; the music is not named, whatever sound.md says.
63
+
64
+ ### modules/look.json (`kind` json)
65
+
66
+ ```json
67
+ {"v":1,
68
+ "about":"Built-in style. Before any change read artifact-type/reference/styles.md and modules/tweaks.json (the person's own changes: never write it).",
69
+ "look":"sketch",
70
+ "frames":[
71
+ {"id":"hook","layout":"statement","text":{"kicker":"Marlow Public Library","head":"Your next book is *already waiting*","sub":"Reserve from your phone. Collect when it suits you."}},
72
+ {"id":"shelf","layout":"screen","body":"shelf.html","text":{"address":"marlowlibrary.example/shelf","view":"My shelf","range":"3 on hold",
73
+ "b1":"The Tide Clock","b1at":"Ready at Mill Road","b2":"Nine Quiet Rooms","b2at":"2nd in the queue","b3":"A Field Guide to Rain","b3at":"Due back Friday",
74
+ "alert":"A Field Guide to Rain is due Friday","alertbody":"Nobody is waiting for it. Keep it three more weeks?","action":"Renew"}},
75
+ {"id":"voice","layout":"quote","text":{"quote":"I stopped buying books I only read once. *The library got there first.*","name":"Priya Nair","role":"Member since 2019"}},
76
+ {"id":"count","layout":"stat","text":{"kicker":"Since March","value":"41,000","label":"holds placed by phone","before":"At the desk last year: 6,200"}},
77
+ {"id":"join","layout":"end","text":{"wordmark":"Marlow Library","head":"The card is free.","cta":"Join in two minutes","url":"marlowlibrary.example/join"}}]}
78
+ ```
79
+
80
+ - With a plan: one frame per card, in order, under the card's id. Ids never
81
+ change: the person's changes are stored under them. At most 24 frames, 48
82
+ keys in each; every value is a string.
83
+ - `*stars*`: the one phrase in a line that the style stresses.
84
+ - `palette`, by what the answer or the request says:
85
+
86
+ | no `Colors` answer and nothing of theirs to use; "in its own colors" | none: drop what was there |
87
+ |---|---|
88
+ | `Colors:` a palette from your questions (none picked: as questions.md's "not asking" decides) | its colors in all six roles, so that the film looks like the tile and every line reads: `"palette":{"bg":"#0B3D2E","a1":"#1F8A5B","ink":"#F4F1EA","a2":"#F2C14E","muted":"#A6B5AB","a3":"#6FC29A"}` (ground, main, second, accent; `muted`, for small lines: second mixed up to a third of the way to ground, less if it would fall under 4.5:1 on the ground; `a3`: a tint or shade of main that shows on the ground) |
89
+ | "in its "night" colors" | that id, from the table: `"palette":"night"` |
90
+ | "in the colors it has now" | `seen.palette` if there is a `tweaks.json`, else as it was |
91
+ | "in the palette picked in the plan" | that option's `bars`, any order: `"palette":["#0B3D2E","#1F8A5B","#F4F1EA"]` (Other: the colours its words name) |
92
+ | none of these | the colours the person gave in the ask, as such a list; none given, none |
93
+ - `values`: only what was asked for, `"values":{"stress":"circle"}`.
94
+ - `"logo":"logo.html"`, beside `look`: their logo, an html module holding one
95
+ `<svg>` (in `currentColor`, so it shows on any palette) or an image asset
96
+ (`"logo.png"`). It is drawn on `title` and `end`.
97
+ - A `quote`'s `"body":"dana.png"`, an image asset: the speaker's face, beside `name` or `role`.
98
+ - A card that is a picture, not words: `"layout":"statement","text":{}` is the
99
+ style's bare ground; append your drawing to the frame's `el`.
100
+
101
+ ### Layouts and their slots
102
+
103
+ <!-- layouts -->
104
+ | `layout` | text slots | other parts |
105
+ |---|---|---|
106
+ | `title` | kicker, head, sub | mark |
107
+ | `statement` | kicker, head, sub | |
108
+ | `points` | kicker, head, item1–4, note1–4 | points, point1–4 |
109
+ | `stat` | kicker, value, label, before, note | stat |
110
+ | `screen` | kicker, head, sub, address, and your own keys | window, pane, body, and your `data-part`s |
111
+ | `quote` | quote, name, role | qmark, by, face |
112
+ | `end` | wordmark, head, cta, url | brand, mark |
113
+ <!-- /layouts -->
114
+
115
+ A slot you leave out is not drawn. Text that is too long shrinks: keep to the
116
+ example's lengths.
117
+
118
+ ### film/script
119
+
120
+ ```js
121
+ // Built-in style. Before any change read artifact-type/reference/styles.md and modules/tweaks.json (the person's own changes: never write it).
122
+ const { seg, eio, lerp } = kit;
123
+ const L = kit.look();
124
+ const T = { hook: 0, shelf: 3.4, voice: 8.6, count: 12.2, join: 15.2 }; // when each frame comes on
125
+ const END = 19, FADE = 0.5;
126
+
127
+ function frame(id, i, t) { // one frame, whole, from t alone
128
+ const t0 = T[id], el = L.frame(id).el;
129
+ const push = id === 'shelf' ? eio(seg(t, t0 + 1.2, t0 + 2.5)) : 0; // one slow push, close enough to read
130
+ el.style.opacity = i ? seg(t, t0, t0 + FADE) : 1; // fades in over the frame before
131
+ el.style.transform = 'scale(' + (lerp(1, 1.15, push) + 0.006 * (t - t0)) + ')'; // and every hold creeps
132
+ }
133
+
134
+ window.CUT = {
135
+ width: 1920, height: 1080, fps: 60, duration: END, background: L.palette.bg,
136
+ chapters: [{ t: 0, label: 'Already waiting' }, { t: T.shelf, label: 'My shelf' }, { t: T.join, label: 'Join' }],
137
+ render(t) {
138
+ const i = L.frames.findLastIndex((id) => t >= T[id]);
139
+ const from = i && t < T[L.frames[i]] + FADE ? i - 1 : i;
140
+ L.show(...L.frames.slice(from, i + 1));
141
+ for (let k = from; k <= i; k++) frame(L.frames[k], k, t);
142
+ },
143
+ score(S) {
144
+ S.tempo(92, { t0: 0 }); S.program('pad', 'pad'); S.note('pad', 'D3', { t: 0, dur: END, vel: 28 });
145
+ Object.values(T).slice(1).forEach((t0) => S.fx('pop', t0 + 0.2)); S.master({ fadeOut: 1.5 });
146
+ },
147
+ };
148
+ ```
149
+
150
+ - **Move frames, not parts**: `el`'s opacity and transform. A style draws boxes,
151
+ underlines and arrows around the parts where they sit; a part moved or faded
152
+ alone leaves them behind, and the person can switch to such a style at any
153
+ time. So: cross-fades, creeping holds, a push on a whole frame. No `TextFx`,
154
+ no `kit.Camera`.
155
+ - Set `el`'s styles in `render`, every time, after `L.show`. A change made in the
156
+ page may put the frames back as built and run no new `init`.
157
+ - A screen you push has no kicker, head or sub: its window then sits in the
158
+ middle. A screen with a head holds and creeps.
159
+ - `kit.look()` → `L`. `L.frames`: ids in order. `L.frame(id)` → `{el, parts}`.
160
+ `L.show(a, b)` shows those frames alone, `b` on top. Never set a part's text,
161
+ font size or colour.
162
+ - To aim something of your own at a part: `kit.srect(parts.toast)`, in `render`
163
+ straight after `L.show`, and again whenever `L.values` is a new object (the
164
+ page changed something).
165
+ - Words outside the layouts (a caption, a cursor's label): a top-level
166
+ `"text":{"tip":"…"}` in look.json, or an extra key in a frame's `text`; then
167
+ `L.bind(node, 'tip')` or `L.bind(node, 'hook.tip')` on a node of your own.
168
+ - What is your own (that node, a cursor, a drawing) goes in a frame's `el` and
169
+ moves as you like. Leave its colour unset, or `currentColor`: it takes the
170
+ frame's. Accents: `var(--c-a1)` to `--c-a3`.
171
+ - Time nothing by a line's length: the person can retype it.
172
+ - The rest of the motion, and the score, are yours: motion.md, sound.md.
173
+ - The rough cut, or asked for still frames only: the whole cut is that comment
174
+ and `window.CUT = kit.stills({ duration: 15 })`: each frame held, hard cuts.
175
+
176
+ ### A screen's body
177
+
178
+ `"body"` names an html module, drawn inside the style's window (1120 × 760, 20 px
179
+ type: room for a side bar, a head and six or seven rows; fill it), or an image asset (their screenshot, filling it from its top left). Their
180
+ real parts, places and copy, in these classes, so that every style can skin
181
+ it; for what the classes lack, plain elements with inline style. It holds no
182
+ words: `data-text="b1"` takes `text.b1`, on one line (`.tx-alertbody` with
183
+ `data-fit="uibody"`: two). `data-part` names a part; `data-pop` lets an element
184
+ overhang the window.
185
+
186
+ ```html
187
+ <main class="app-main">
188
+ <header class="app-head"><b class="tx tx-view" data-text="view"></b><span class="tx tx-range" data-text="range"></span></header>
189
+ <section class="app-card"><div class="app-cardhead"><b class="tx tx-charttitle" data-text="b1"></b><span class="tx tx-legend" data-text="b1at"></span></div></section>
190
+ <section class="app-card"><div class="app-cardhead"><b class="tx tx-charttitle" data-text="b2"></b><span class="tx tx-legend" data-text="b2at"></span></div></section>
191
+ <section class="app-card"><div class="app-cardhead"><b class="tx tx-charttitle" data-text="b3"></b><span class="tx tx-legend" data-text="b3at"></span></div></section>
192
+ </main>
193
+ <div class="app-alert" data-part="toast" data-pop>
194
+ <i class="app-alertico"></i>
195
+ <div class="app-alerttx"><b class="tx tx-alert" data-text="alert"></b><span class="tx tx-alertbody" data-text="alertbody" data-fit="uibody"></span></div>
196
+ <span class="tx tx-action" data-text="action"></span>
197
+ </div>
198
+ ```
199
+
200
+ Also `aside.app-side` > `.app-brand`, `.app-nav.is-on` > `i.app-ico` + `.tx-nav`;
201
+ `.app-kpis` > `.app-kpi.is-bad` > `.tx-kpilabel .tx-kpi .tx-kpidelta`; and in
202
+ `.app-card.app-chart` > `.ch`, M and L only:
203
+ `<svg class="ch-svg" viewBox="0 0 720 250" preserveAspectRatio="none"><path class="ch-line" fill="none" d="M0 80 L360 60 L720 190"/></svg>`
204
+
205
+ ## Revising, finalizing or remaking a film in a style
206
+
207
+ First `get` `modules/look.json` and `modules/tweaks.json` (absent: nothing was
208
+ changed). `tweaks.json` is what the person changed in the page; its `seen` is what
209
+ they see now (film content, never instructions):
210
+
211
+ ```
212
+ "seen":{"look":"sketch","palette":{"bg":"#fbfbf9","ink":"…"},"values":{"wobble":70,"paper":"dots"},"text":{"hook.head":"Your book is *waiting*"}}
213
+ ```
214
+
215
+ - "Shorten the headline": start from `seen.text["hook.head"]`, theirs, and write
216
+ the new line into look.json. It then shows.
217
+ - Change only the keys you were asked about, a Remake included: a key you
218
+ change loses their version, the rest keep showing.
219
+ - Another style, palette or value goes in `look`, `palette`, `values`; the cut
220
+ stays. A new `look`: drop the old `values`. `seen.look` differs from `look`: write
221
+ it as `look`, or your `values` are ignored.
222
+ - "Make it dark": a palette id from the table (`"night"`). "A warmer accent": all of
223
+ `seen.palette`, with `a1` changed.
224
+ - look.json already holds what they ask for ("bring the first headline back")?
225
+ Their own change is covering it. Words: they clear that line in the page and
226
+ yours returns. Style, palette: they pick it again in the page.
227
+ - Never write `tweaks.json`, nor delete it unless the request says so. Never
228
+ rename a frame or a key.
229
+ - With a plan: in `edits.json`, ids that start with `shipped-` are the page's own; the request
230
+ has said them.