@jokerized/decksmith 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +333 -4
- package/dist/cli.js +14057 -4749
- package/dist/deck-player-element.js +1 -1
- package/dist/deck-player.js +1 -1
- package/dist/deck-runtime.js +70 -2
- package/dist/index.js +13309 -4178
- package/dist/mcp.js +5131 -765
- package/dist/types/bespoke/art.d.ts +173 -0
- package/dist/types/bespoke/cache.d.ts +69 -0
- package/dist/types/bespoke/callouts.d.ts +76 -0
- package/dist/types/bespoke/contract.d.ts +57 -0
- package/dist/types/bespoke/inspect.d.ts +69 -0
- package/dist/types/bespoke/pipeline.d.ts +207 -0
- package/dist/types/bespoke/probe.d.ts +34 -0
- package/dist/types/bespoke/prompt.d.ts +182 -0
- package/dist/types/bespoke/references.d.ts +73 -0
- package/dist/types/bespoke/repair.d.ts +121 -0
- package/dist/types/bespoke/scene.d.ts +97 -0
- package/dist/types/bespoke/select.d.ts +52 -0
- package/dist/types/bespoke/sheet.d.ts +26 -0
- package/dist/types/bespoke/shots.d.ts +128 -0
- package/dist/types/bespoke/untangle.d.ts +5 -0
- package/dist/types/build/files.d.ts +13 -1
- package/dist/types/deck/motion.d.ts +64 -0
- package/dist/types/deck/playback.d.ts +206 -0
- package/dist/types/deck/repack.d.ts +10 -0
- package/dist/types/deck/runtime.d.ts +96 -1
- package/dist/types/deck/subtitles.d.ts +29 -1
- package/dist/types/emit/archetypes/annotated-figure.d.ts +1 -1
- package/dist/types/emit/archetypes/bar-compare.d.ts +0 -28
- package/dist/types/emit/archetypes/callout.d.ts +0 -5
- package/dist/types/emit/archetypes/claim-figure.d.ts +15 -0
- package/dist/types/emit/archetypes/grid.d.ts +0 -14
- package/dist/types/emit/archetypes/index.d.ts +0 -7
- package/dist/types/emit/archetypes/line-chart.d.ts +0 -8
- package/dist/types/emit/archetypes/pipeline.d.ts +15 -1
- package/dist/types/emit/archetypes/stack.d.ts +1 -1
- package/dist/types/emit/composition.d.ts +83 -3
- package/dist/types/emit/faces.d.ts +22 -0
- package/dist/types/emit/fit.d.ts +127 -0
- package/dist/types/emit/kit.d.ts +121 -2
- package/dist/types/emit/look.d.ts +138 -0
- package/dist/types/emit/motion.d.ts +244 -0
- package/dist/types/emit/svg.d.ts +54 -16
- package/dist/types/emit/theme.d.ts +1 -1
- package/dist/types/emit/themes/index.d.ts +19 -1
- package/dist/types/emit/themes/packs.d.ts +15 -0
- package/dist/types/emit/themes/pick.d.ts +107 -0
- package/dist/types/emit/type.d.ts +111 -0
- package/dist/types/index.d.ts +52 -1
- package/dist/types/plan/codex.d.ts +36 -0
- package/dist/types/plan/direct.d.ts +66 -0
- package/dist/types/prefs.d.ts +25 -2
- package/dist/types/render/capture.d.ts +6 -0
- package/dist/types/render/timing.d.ts +3 -0
- package/dist/types/source/fonts.d.ts +11 -1
- package/dist/types/types.d.ts +109 -0
- package/dist/types/verify/check.d.ts +1 -1
- package/dist/types/verify/fidelity.d.ts +34 -0
- package/dist/types/verify/fill.d.ts +205 -0
- package/dist/types/verify/index.d.ts +14 -0
- package/dist/types/verify/motion.d.ts +33 -0
- package/dist/types/verify/scenes.d.ts +325 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -220,6 +220,49 @@ Arrow keys, Space and PageUp/PageDown step; clicking the left or right third doe
|
|
|
220
220
|
notes; `f` is fullscreen; `m` mutes the voice and `s` hides the subtitles. Every step is
|
|
221
221
|
deep-linkable (`#3` is slide 3, `#3.2` its second reveal).
|
|
222
222
|
|
|
223
|
+
### The v2 player (`--design v2`)
|
|
224
|
+
|
|
225
|
+
`build --design v2` (or `"design": "v2"` in the storyboard or config) gives `deck.html` the
|
|
226
|
+
v2 player; everything else about the build is unchanged. A deck that is already built
|
|
227
|
+
gets it without rebuilding:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
decksmith repack out/ # writes out/deck2.html beside deck.html, never over it
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`repack` swaps the inlined runtime, marks the page and checks that every JSON island is
|
|
234
|
+
byte-identical — no Chrome, no TTS, no re-render.
|
|
235
|
+
|
|
236
|
+
- **One control bar**: play/pause, the counter, narration speed (0.75–2×), `CC`, caption
|
|
237
|
+
size (S/M/L/XL) and fullscreen — or, in a window too narrow for those beside a caption,
|
|
238
|
+
play and one settings menu. **It never covers text.** It docks under the slide in spare
|
|
239
|
+
letterbox (a portrait phone); over the slide's empty bottom padding when that holds a
|
|
240
|
+
40px target (a slide about 720px tall or more), where it hides 2.5s after the last
|
|
241
|
+
pointer move while the deck plays; otherwise in a band under the slide that it shares
|
|
242
|
+
side by side with the captions, whose text is padded in past the buttons.
|
|
243
|
+
`test/deck-page.test.ts` measures controls against caption text and against every glyph
|
|
244
|
+
the slide draws, at six viewports.
|
|
245
|
+
- **Play/pause** pauses the voice where it is and carries on from the same word. A deck
|
|
246
|
+
opens paused; stepping while paused moves the slide silently.
|
|
247
|
+
- **Speed** sets the narration's `playbackRate` live, on the sentence already playing, and
|
|
248
|
+
divides the reveal glides and every autoplay wait by the same factor so they keep pace.
|
|
249
|
+
A stop with no narration holds for its reveal and 1.5s at 1×, not the author's gap.
|
|
250
|
+
- **Captions** are sized from the slide (3% of its height, 13px floor; L and XL keep floors
|
|
251
|
+
of 16 and 19px so the size control works on a phone) and the slide gives up only one
|
|
252
|
+
two-line strip for them: 84% of the frame at 1080p, 83% in an 800×450 embed (v0.8.0: 77%
|
|
253
|
+
and 69%), 100% in a portrait letterbox. Long cues are cut to two lines of the strip's
|
|
254
|
+
actual width, which also splits Japanese and Chinese.
|
|
255
|
+
- **Keys:** Space steps forward (Space is the presenter's key, not play); `Enter` plays and
|
|
256
|
+
pauses; `c` (or `s`) captions; `<`/`>` speed; `+`/`-` caption size. Letter keys fall back
|
|
257
|
+
to the physical key under a Korean or Japanese IME. Anything held with Cmd, Ctrl or Alt
|
|
258
|
+
is left to the browser.
|
|
259
|
+
- **Preferences** — speed, captions on/off, size — come from `?speed=1.25&cc=0&ccsize=l`
|
|
260
|
+
(this visit only), else from `localStorage` (`decksmith.prefs.v1`), else the defaults.
|
|
261
|
+
When the viewer changes one, the deck posts
|
|
262
|
+
`{ type: "decksmith:prefs", speed, cc, ccsize }` to its parent so an embedding site can
|
|
263
|
+
keep it per user (a cross-site iframe's storage is partitioned), and it applies the same
|
|
264
|
+
message when the parent sends it. Check `event.origin` on your side.
|
|
265
|
+
|
|
223
266
|
Stepping forward *plays* the reveal rather than cutting to it — the step layer sweeps the
|
|
224
267
|
composition's timelines across frames instead of seeking once. Backward steps, `Home`/`End`
|
|
225
268
|
and deep links cut, because entrance tweens run in reverse look like elements un-drawing
|
|
@@ -784,8 +827,11 @@ invalidates a storyboard you have already edited.
|
|
|
784
827
|
| `lang` | the source's | plan | BCP-47. Drives the copy, the voice, and the font subset |
|
|
785
828
|
| `tone` | `plain` | plan | `plain` · `academic` · `conversational` · `punchy` |
|
|
786
829
|
| `density` | `normal` | plan | `sparse` · `normal` · `dense` — how much text a slide may carry |
|
|
787
|
-
| `theme` | `ink` | emit | `ink` · `paper` · `mono
|
|
830
|
+
| `theme` | `ink` | emit | `ink` · `paper` · `mono`, or a v2 style pack by name (see Themes) |
|
|
831
|
+
| `design` | `classic` | narrate, emit | `classic` is the v0.8.0 look byte for byte · `v2` is the redesign: the v2 player (see "The v2 player") and a style pack per deck when no theme is named |
|
|
832
|
+
| `packSeed` | the source id | narrate, emit | what `design: v2` hashes to pick a pack; pass a paper id so its languages share one |
|
|
788
833
|
| `animationSpeed` | `1` | emit | multiplies every duration, hold and beat length. Below 1 is faster |
|
|
834
|
+
| `design` | `classic` | emit | `classic` (the 0.8 look, byte-identical) · `v2` (the redesign — see [v2 motion](#v2-motion)) |
|
|
789
835
|
| `narration.voice` | picked for `lang`+`tone` | narrate | an explicit edge-tts voice id |
|
|
790
836
|
| `narration.rate` | `+0%` | narrate | edge-tts prosody |
|
|
791
837
|
| `narration.pitch` | `+0Hz` | narrate | edge-tts prosody |
|
|
@@ -832,8 +878,8 @@ why `slideCount` did nothing.
|
|
|
832
878
|
decksmith.config.json: unknown preference "narration.speed". Valid: enabled, voice, rate, pitch, subtitles.
|
|
833
879
|
```
|
|
834
880
|
|
|
835
|
-
On the command line: `--slides --lang --tone --density` on `plan`, `--theme --
|
|
836
|
-
`build
|
|
881
|
+
On the command line: `--slides --lang --tone --density` on `plan`, `--theme --design
|
|
882
|
+
--pack-seed --speed` on `build` (`--design --pack-seed` on `narrate` too), `--voice --rate --pitch --no-subtitles` on `narrate`, `--images --image-provider
|
|
837
883
|
--image-model --image-style --image-max` on `plan` and `illustrate`, and all but the image
|
|
838
884
|
flags on `pack`, which records the preferences the deck was made under — whether it was
|
|
839
885
|
illustrated is read off the storyboard itself, the way `narration.enabled` is read off the
|
|
@@ -841,9 +887,255 @@ narration beside it.
|
|
|
841
887
|
|
|
842
888
|
A preference sitting at its default says nothing, so a stored artifact wins over it and
|
|
843
889
|
loses to anything you type. `plan` stamps `lang` and `theme` into the storyboard it
|
|
844
|
-
writes; `build` then uses the storyboard's
|
|
890
|
+
writes (and `design`, only when one was asked for); `build` then uses the storyboard's
|
|
891
|
+
unless `--theme`/`--design` or a config file restates one.
|
|
845
892
|
Language is never overridden at build time — it describes copy that is already written.
|
|
846
893
|
|
|
894
|
+
`--design v2` (or `"design": "v2"` in the config file) lets the build vary each beat's
|
|
895
|
+
layout: bars as rows or columns, a pipeline as a row, a stair or a column, a claim beside,
|
|
896
|
+
mirrored against or above its figure, a comparison as columns or rows, and the headline on
|
|
897
|
+
top, in a left rail or under the body. The choice is made by code, deterministically, per
|
|
898
|
+
paper — never by the planner — and it never moves a stop, so narration stays aligned.
|
|
899
|
+
`build` writes the choices to `out/look.json`. Without the flag the deck is the classic one,
|
|
900
|
+
byte for byte. See `.planning/2026-10-07-v2-layout-director.md`.
|
|
901
|
+
|
|
902
|
+
## v2 motion
|
|
903
|
+
|
|
904
|
+
`--design v2` replaces the 0.8 deck's single motion — every scene fading up the same
|
|
905
|
+
eyebrow and headline, every seam the same 0.4s dissolve, nothing moving once a slide has
|
|
906
|
+
built — with a grammar planned per deck (`src/emit/motion.ts`):
|
|
907
|
+
|
|
908
|
+
- **Entrances.** Six verbs — `rise`, `slide`, `snap`, `focus`, `wipe`, `mask` — one per
|
|
909
|
+
scene, chosen from the source id and beat id, never the same twice in a row, with the
|
|
910
|
+
stock `rise` capped at a quarter of the deck.
|
|
911
|
+
- **Seams.** `dissolve`, `push`, `lift`, `wipe`, `zoom`, picked from how two neighbouring
|
|
912
|
+
beats relate (same family → push, a role boundary → zoom, a title → lift, into the close
|
|
913
|
+
→ dissolve), never repeated back to back, and at least three kinds in a deck of ten or
|
|
914
|
+
more beats. A beat `inside` the one before keeps the camera dive.
|
|
915
|
+
- **Emphasis while the narrator talks.** The part a sentence is about pulses, glows or is
|
|
916
|
+
underlined, starting on a cue boundary of that sentence inside the quiet stretch after
|
|
917
|
+
its stop, and is back at rest before the next reveal — so every frame a gate captures
|
|
918
|
+
at a stop is unchanged.
|
|
919
|
+
|
|
920
|
+
All of it is `fromTo` tweens with no callbacks, seeded rather than random, and keeps every
|
|
921
|
+
hold, every scene window and `timing.json` exactly as `classic` writes them. In `deck.html`
|
|
922
|
+
a v2 deck also glides through the seam into the next slide (0.8 cut to its first stop,
|
|
923
|
+
already built) and, while a stop's audio plays, seeks the scene through that stop's quiet
|
|
924
|
+
stretch on the audio clock so the emphasis lands on the same word as in the video. Both
|
|
925
|
+
are off under `prefers-reduced-motion`.
|
|
926
|
+
|
|
927
|
+
## v2 bespoke scenes (`--bespoke`)
|
|
928
|
+
|
|
929
|
+
The archetypes finish building in about five seconds and then hold still while the voice
|
|
930
|
+
keeps talking. `build --design v2 --bespoke` hands four to six beats per deck to Codex
|
|
931
|
+
instead: for each, a GSAP + SVG scene written for that beat, keyed to its narration cues,
|
|
932
|
+
that keeps explaining for as long as the sentence runs. Everything else stays v2. It is off
|
|
933
|
+
unless asked for; v2 without it, and classic, emit the bytes they did.
|
|
934
|
+
|
|
935
|
+
```bash
|
|
936
|
+
decksmith build storyboard.json --source source.json -o deck --design v2 --bespoke
|
|
937
|
+
# bespoke: b03-action-loop.draft in 121s …
|
|
938
|
+
# bespoke: b05-backbone — every gate passed and the rubric probe is clean; no critique call
|
|
939
|
+
# bespoke: 4 of 5 beats drawn bespoke, 7 Codex call(s), 190985 tokens, 343s
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
**Which beats.** A deterministic rule (`src/bespoke/select.ts`): mechanism archetypes
|
|
943
|
+
(pipeline, equation-walk/-morph, line-chart, stack, grid, split-/bar-compare, and figures
|
|
944
|
+
last), mechanism words in en/ko/ja/zh, a cited equation, and the beat's weight; ties
|
|
945
|
+
broken by a hash. Never a title, callout or table, a beat with no narration, or a beat a
|
|
946
|
+
camera dives through. `plan --bespoke` lets the planner mark beats `bespoke: true|false`;
|
|
947
|
+
`false` is obeyed, `true` is a strong hint.
|
|
948
|
+
|
|
949
|
+
**Per beat:** illustration → draft → static contract → probe deck (a real build with every
|
|
950
|
+
`verify` gate plus the motion gates, photographed at every cue) → the rubric probe → at most
|
|
951
|
+
one critique-and-fix call with the contact sheet and the measures attached (`codex exec -i`)
|
|
952
|
+
→ static contract → probe again → repair rounds. A draft that passed every gate and is clean
|
|
953
|
+
by the rubric probe is kept without the critique call; one that fails only on what the
|
|
954
|
+
repair fixes, and is otherwise clean, is repaired instead of critiqued. A beat that does not
|
|
955
|
+
pass keeps its archetype — the deck is never failed. If the fix round breaks a draft that
|
|
956
|
+
passed, the draft is kept.
|
|
957
|
+
|
|
958
|
+
**The illustration** (`src/bespoke/art.ts`, `src/bespoke/inspect.ts`). Every bespoke beat
|
|
959
|
+
but a data beat gets ONE picture from the image tool of the same Codex account
|
|
960
|
+
(`codex exec` with every tool off but `image_generation`; no API key — the account's own
|
|
961
|
+
login, as for the scenes), all of a deck's pictures asked for at once before the drafts
|
|
962
|
+
queue for their lanes. The illustrator is given the beat (fenced, untrusted), the pack's
|
|
963
|
+
colours and one fixed style paragraph — flat 2D vector, solid fills with hard edges; no
|
|
964
|
+
gradients, shading, shadows, gloss, 3D, clay or toy renders, by name — and asked for three
|
|
965
|
+
or four subjects doing the method, big, left to right, with empty ground between them and
|
|
966
|
+
NO text. Each picture is then inspected locally, for free: its subjects are the connected
|
|
967
|
+
regions that are not its flat ground (boxed, left to right); its flatness is how much of
|
|
968
|
+
it sits in its 16 commonest colours minus how much of it is soft shading (`FLAT_MIN`,
|
|
969
|
+
calibrated on round 3's 21 renders against 12 flat pictures); and its writing is read by
|
|
970
|
+
macOS Vision, once per script (en, ko, ja, zh), through a small Swift helper compiled once
|
|
971
|
+
into `~/.cache/decksmith/tools`. A picture with writing, a shaded render or one whose
|
|
972
|
+
subjects touch is redrawn ONCE with the reason; of two refused pictures the one without
|
|
973
|
+
writing and the flatter is kept, and one with writing never is. Off macOS the text check
|
|
974
|
+
reports `unchecked` in `bespoke.json` rather than passing. The deck gets a WebP with its
|
|
975
|
+
edges feathered into transparency for its box (~30-75 KB against 1.3 MB of PNG; a CSS mask
|
|
976
|
+
on the picture cost ~0.7s a probe frame); the draft call is shown the PNG and a copy with
|
|
977
|
+
the subjects boxed and numbered (`-i`). The shell places it: `<image data-art="1">` with no
|
|
978
|
+
href gets the href and a fixed placement covering the body box (`slice`), so the subjects'
|
|
979
|
+
boxes are known in box px before the scene is written. It is read only from
|
|
980
|
+
`$CODEX_HOME/generated_images/` (after symlinks), only as a PNG by its magic bytes, under
|
|
981
|
+
12 MB. `--bespoke-art <n>` (default 6; `bespoke.art`) caps pictures per deck, counted apart
|
|
982
|
+
from `--bespoke-calls`; a beat whose picture cannot be had is drawn without one.
|
|
983
|
+
|
|
984
|
+
**Data beats** (line-chart, bar-compare, data-table) get no picture: their scene is asked
|
|
985
|
+
for the chart that builds with the voice — axes draw, bars grow with their counters, the
|
|
986
|
+
line traces, the named value lights — from the beat's own numbers, never a table painted
|
|
987
|
+
over an illustration (`data_over_picture`).
|
|
988
|
+
|
|
989
|
+
**The camera** of an illustrated scene is the shell's, staged in one grammar
|
|
990
|
+
(`src/bespoke/shots.ts`). The scene names its `shots` — on which cue, how far into it, which
|
|
991
|
+
subject — and the shell compiles them: an ESTABLISHING shot of the whole picture for at
|
|
992
|
+
least 1.6s, a PUSH IN (1.1s, power3.inOut) that frames the named subject and its label at
|
|
993
|
+
1.6-2.2x, a move straight to the next subject, a 3% creep while a shot is held, and a
|
|
994
|
+
REVEAL back to the whole picture at the last cue; shots closer than 1.6s are dropped. Every
|
|
995
|
+
camera tween is a `fromTo` with explicit from-values, and no two touch. The scene's own
|
|
996
|
+
script may not move `#sN-cam` (`script_camera`). A scene without a picture still moves the
|
|
997
|
+
wrapper itself (`scale`/`x`/`y`, arithmetic in the prompt). A scene with a moving camera is
|
|
998
|
+
clipped to its box and marked `data-ds-clip`, so `verify` does not count what a push-in
|
|
999
|
+
carries past the canvas edge as off-canvas.
|
|
1000
|
+
|
|
1001
|
+
**The labels** of an illustrated scene are the shell's too (`src/bespoke/callouts.ts`). The
|
|
1002
|
+
scene says what each subject is called (`labels`); the shell gives each subject a zone just
|
|
1003
|
+
above it (or across its top when there is no room), as wide as half the gap to each
|
|
1004
|
+
neighbour, and sets the name there on a plate — 56px, shrinking to 44, then two lines —
|
|
1005
|
+
with a leader line to a dot on the subject, entering as the camera first arrives on it (or
|
|
1006
|
+
at the reveal). The zones are in the draft prompt, which tells the scene to keep its own
|
|
1007
|
+
drawing out of them; a name that does not fit even on two lines at 44px is refused
|
|
1008
|
+
statically (`labels`). Round 4's first run, with the scene placing its own labels on the
|
|
1009
|
+
subjects, put them there and then failed four scenes in five on their own plates and
|
|
1010
|
+
leader lines crossing their text.
|
|
1011
|
+
|
|
1012
|
+
**Repair** (`src/bespoke/repair.ts`), no model call. On `text_overlap`,
|
|
1013
|
+
`graphic_crosses_text` or `svg_text_overprint`, each colliding label's unit (the text, or
|
|
1014
|
+
the smallest `<g>` holding it and its plate) gets one constant offset — the smallest, in
|
|
1015
|
+
8px rings over sixteen directions in a fixed order — that clears every other label by 16px,
|
|
1016
|
+
every visible stroke sample by 10px and every shape painted over it, inside the box, at
|
|
1017
|
+
every frame the probe measured, camera scale included; it is written as a wrapping
|
|
1018
|
+
`<g transform="translate()">`. A label never moves farther than 0.8 of its own height
|
|
1019
|
+
(at least 32px): a collision that needs more is left to the critique round. On
|
|
1020
|
+
`end_dimmed`, the elements dimming parts the scene had lit are tweened back to full
|
|
1021
|
+
strength just before the end; on `camera_end`, the camera is tweened home. On `seek_order`,
|
|
1022
|
+
two tweens on one target animating one property over overlapping time — which a timeline
|
|
1023
|
+
resolves one way seeking forward and the other seeking back — are untangled in favour of
|
|
1024
|
+
the later one (the earlier is shortened to end where it starts, or loses that target or
|
|
1025
|
+
property when both start together; `src/bespoke/untangle.ts`). A repaired scene goes
|
|
1026
|
+
through the static contract and is gated again (at most two repair rounds).
|
|
1027
|
+
|
|
1028
|
+
**What the model is shown** (`src/bespoke/prompt.ts`): the beat, its cues, the paper's
|
|
1029
|
+
excerpts (fenced, untrusted), the contract, motion-design rules with numbers (one focal
|
|
1030
|
+
element per cue at 88-120px, the rest dimmed but kept; fill the box; paint it with filled
|
|
1031
|
+
shapes; at least three kinds of motion, one of flow, camera, counter or morph; the end
|
|
1032
|
+
frame a summary), text widths measured in the pack's own font, and two of four hand-made
|
|
1033
|
+
reference scenes (`src/bespoke/references.ts`: a routing mechanism with particle flow and
|
|
1034
|
+
a counter, a camera zoom into one block, a traced curve whose gap morphs into the headline
|
|
1035
|
+
number, a scatter whose dots travel into a bell, and — first, for a beat with an
|
|
1036
|
+
illustration — a scene built on its picture), picked for the beat's archetype and painted
|
|
1037
|
+
in the pack's colours. `test/bespoke-references.test.ts` runs each reference
|
|
1038
|
+
through every gate and the rubric probe in a real deck, so a reference that stops passing
|
|
1039
|
+
stops being one. Parts a cue introduces are `<g data-cue="N">` groups (semantic grouping,
|
|
1040
|
+
after Vector Prism, arXiv 2512.14336). MorphSVG is registered for a deck only when one of
|
|
1041
|
+
its scenes morphs.
|
|
1042
|
+
|
|
1043
|
+
**The rubric probe** (`rubricProbe`, free: it reads what the probe measured) sends a
|
|
1044
|
+
passing draft to the critique round when any of these is off: the drawing paints under 12%
|
|
1045
|
+
of its box at the end (round 1's scenes painted 4-16%), more than half its parts are still
|
|
1046
|
+
dimmed at the end, no label reaches 88px (not asked of an illustrated scene, whose picture
|
|
1047
|
+
is its focus and whose names are the shell's), fewer than three kinds of motion or none of
|
|
1048
|
+
flow/camera/counter/morph, an illustrated scene that holds fewer than two push-ins, does not
|
|
1049
|
+
open wide, or names fewer than two subjects on them, a cue whose picture changes by under
|
|
1050
|
+
0.5% of the frame, or a warning about the scene. The critique round scores the frames against a six-line rubric
|
|
1051
|
+
(fills the stage, one focus per cue, motion explains, legible hierarchy, the pack, sync),
|
|
1052
|
+
is told the measures and every finding with the colliding labels' coordinates, and returns
|
|
1053
|
+
the fixed scene.
|
|
1054
|
+
|
|
1055
|
+
**Caps**, checked before every call: `--bespoke-calls` (default 12; two per beat) and
|
|
1056
|
+
`--bespoke-seconds` (default 1800). A quota or rate-limit answer stops every further call.
|
|
1057
|
+
Codex runs only through the account's own CLI (`--bespoke-cli`, `--bespoke-model`), never
|
|
1058
|
+
an API key, five calls in flight (`bespoke.concurrency` in the config file), at
|
|
1059
|
+
`bespoke.effort` reasoning (default `medium`; `default` keeps the account's), from the
|
|
1060
|
+
scratch directory, and with its tools off: shell, apps, plugins, browser, image
|
|
1061
|
+
generation (the illustration call alone keeps it), web search and every MCP server the
|
|
1062
|
+
account configured. A bespoke call is a
|
|
1063
|
+
text transform; with tools on, the agent spent its first turns reading the account's own
|
|
1064
|
+
instruction files and skills (measured: 56,679 input tokens over several turns and
|
|
1065
|
+
135-488s for a draft that tool-less took one turn of 18,247 and 166s). A paced deck (`--speed`/`--duration` other than 1×) and non-16:9 formats are
|
|
1066
|
+
skipped, with a line saying so.
|
|
1067
|
+
|
|
1068
|
+
**Cache.** Keyed by everything a scene is drawn from — the beat's words and params, the
|
|
1069
|
+
quoted excerpts, the cue windows, the stops, the box, the pack, the prompt and contract
|
|
1070
|
+
versions, the model, the illustration's key — under `~/.cache/decksmith/bespoke`
|
|
1071
|
+
(`--bespoke-cache`); pictures under its `art/`, keyed by the beat's words, the pack's
|
|
1072
|
+
colours, the art prompt's version and the model. A rebuild costs no calls. A beat the gates rejected after its critique round is cached as rejected;
|
|
1073
|
+
a fallback the beat did not earn (cap, quota, timeout) is not.
|
|
1074
|
+
|
|
1075
|
+
**What a generated scene may run.** Three layers. (1) A static walk before anything is
|
|
1076
|
+
built or opened (`src/bespoke/contract.ts`, acorn): GSAP timeline calls with literal vars
|
|
1077
|
+
at explicit seconds, `gsap.set`, scoped `root.querySelector`, `Math` minus `random`, local
|
|
1078
|
+
code; `window`, `document`, `fetch`, `eval`, timers, storage, navigation, callbacks and
|
|
1079
|
+
function-valued tween vars are refused by name; CSS must be scoped to the scene with no
|
|
1080
|
+
at-rules, `url()` or animation; markup is SVG and inline HTML with no handlers, SMIL or
|
|
1081
|
+
external references, and `<image>` only as the beat's illustration (`data-art="1"`, no
|
|
1082
|
+
href — the shell writes it); path data must be numbers and commands (a word in a `d`
|
|
1083
|
+
attribute is a console error that used to fail every scene probed with it). (2) A CSP `<meta>` in every composition that carries one —
|
|
1084
|
+
`connect-src 'none'`, no `unsafe-eval`, local files only — and a probe that fails a scene
|
|
1085
|
+
on any page error, request or navigation (an error that names no scene fails every scene
|
|
1086
|
+
probed with it, unless the value it quotes is in exactly one candidate, which is then the
|
|
1087
|
+
one failed). (3) The player's frame is sandboxed
|
|
1088
|
+
(`allow-scripts allow-same-origin allow-downloads`). The paper's text reaches the model
|
|
1089
|
+
fenced as untrusted data. The static walk is not a proof against obfuscation — a
|
|
1090
|
+
property name assembled from parts at run time gets past it — which is what (2) and (3)
|
|
1091
|
+
are for; and (3) isolates only when decks are served from another origin than the host.
|
|
1092
|
+
|
|
1093
|
+
**The motion gates** (`src/verify/scenes.ts`) run on every scene `bespoke.json` lists, as
|
|
1094
|
+
errors: `static_hold` (the picture must change during every cue), `graphic_crosses_text`
|
|
1095
|
+
(a visible stroke through a label — a curve whose bounding box contains a label is not
|
|
1096
|
+
its plate — or a shape painted over one), `seek_order` (the frame must not depend on what
|
|
1097
|
+
was seeked before it, beyond 1500px of rasterising noise — counted on changed AREAS: a pixel
|
|
1098
|
+
counts when it and its 8 neighbours changed, so a picture re-rasterised a hair differently
|
|
1099
|
+
on its first paint, an outline round every subject, 12,698px raw, counts 309), `stage_fill` (the settled
|
|
1100
|
+
drawing's bounding box covers 80% of its box), `type_hierarchy` (one label of 64px or
|
|
1101
|
+
more at the end; not asked of an illustrated scene), `stray_marker` (an SVG marker painted where its line is not drawn),
|
|
1102
|
+
`early_reveal` (a `data-cue="N"` group showing more than 0.5s before cue N),
|
|
1103
|
+
`shot_variety` (an illustrated scene opens on the whole picture and the camera — sampled
|
|
1104
|
+
every 0.5s, no screenshot — HOLDS push-ins at 1.5x or closer on at least two different
|
|
1105
|
+
subjects; round 3's 1.1-1.4x pans are not push-ins), `label_anchor` (a label that names a
|
|
1106
|
+
subject sits within 96px of its box and covers no other subject by more than a quarter of
|
|
1107
|
+
itself, and at the end at least two subjects are named that way), `data_over_picture`
|
|
1108
|
+
(five or more numbers on the illustration),
|
|
1109
|
+
`cue_groups` (no such groups, or one naming a cue the scene does not have), `end_dimmed`
|
|
1110
|
+
(at the settled frame, more than 10% of the parts the scene had shown lit are left under
|
|
1111
|
+
0.6 opacity — a part drawn translucent from the start is not counted) and `camera_end` (the
|
|
1112
|
+
camera, or a viewBox, not home at the settled frame). Content
|
|
1113
|
+
outside a clipping `<svg>` — a camera zoomed in — is neither off the frame nor crossing
|
|
1114
|
+
anything. Overlap and crossing findings carry the labels' boxes in the body box's px.
|
|
1115
|
+
`seek_order` also runs on every other scene of a v2 deck, as a warning; it is what found
|
|
1116
|
+
the equation-walk bug fixed alongside this (22,037px before, 0 after). Below the tolerance
|
|
1117
|
+
and not traced: v2's equation-walk on the narrated demo differs by 529px on one KaTeX
|
|
1118
|
+
glyph when reached from past the scene's end.
|
|
1119
|
+
|
|
1120
|
+
`build` writes `bespoke.json` beside the deck: what was drawn and from where (cache,
|
|
1121
|
+
draft, critique), why each fallback, calls, tokens, seconds, the stages' wall clock, each
|
|
1122
|
+
picture's inspection (subjects, flatness, writing, draws) and each scene's staging
|
|
1123
|
+
measures. The full motion probe that `verify` would run again on a generated scene is
|
|
1124
|
+
skipped by `build` when the pass already ran it on the same bytes at the same scene id
|
|
1125
|
+
(`gatedAt`); `build` still seeks it with the deck's other scenes. `decksmith verify <dir>`
|
|
1126
|
+
always runs it. Set
|
|
1127
|
+
`DECKSMITH_BESPOKE_WORK=<dir>` to keep the prompts, replies and contact sheets.
|
|
1128
|
+
|
|
1129
|
+
**Costs** (measured on four HypePaper decks, twice each, see
|
|
1130
|
+
[`.planning/2026-10-09-v2-bespoke-round4.md`](.planning/2026-10-09-v2-bespoke-round4.md)):
|
|
1131
|
+
an illustration takes 60-120s, a draft 90-220s and a critique 70-120s; a deck's pass took
|
|
1132
|
+
380-730s for 5-8 scene calls plus 3-7 pictures (redraws included) and 153-227k tokens, and
|
|
1133
|
+
the whole `build` 466-812s on a Mac with 9-23% memory free — three builds of eight under ten
|
|
1134
|
+
minutes. The pictures add 128-348 KB of WebP to a deck (round 3: 6-7 MB of PNG). Round 3
|
|
1135
|
+
took 593-742s and 214-325k tokens; round 1 13-31 minutes and 540-705k. A rebuild from the
|
|
1136
|
+
cache makes 0 calls. None of that fits a 300-second build timeout: a host running this has
|
|
1137
|
+
to give `build` that long, or run it off the request path.
|
|
1138
|
+
|
|
847
1139
|
## Themes
|
|
848
1140
|
|
|
849
1141
|
Three, each a position rather than a hue.
|
|
@@ -862,6 +1154,41 @@ naming a family the deck does not declare falls back silently.
|
|
|
862
1154
|
A theme is a name and a palette, and that is the whole extension point: a new one is a
|
|
863
1155
|
file in `src/emit/themes/` plus a line in `THEMES`. No archetype learns it exists.
|
|
864
1156
|
|
|
1157
|
+
### v2 style packs
|
|
1158
|
+
|
|
1159
|
+
Six more, each a whole look rather than a palette: its own typeface pairing and type
|
|
1160
|
+
scale, ground and accent, eyebrow treatment, figure framing and surface.
|
|
1161
|
+
|
|
1162
|
+
| Pack | Ground | Headline / body | Title | Lists | Bars | Leans to |
|
|
1163
|
+
|---|---|---|---|---|---|---|
|
|
1164
|
+
| `signal` | violet-black, Magma accent | Space Grotesk / Inter | glowing headline | ticks | pills | foot headlines |
|
|
1165
|
+
| `blueprint` | navy, cyan | IBM Plex Sans / IBM Plex Sans | drawing frame with corner marks | numbers | square, on dashed tracks | rail headlines |
|
|
1166
|
+
| `atlas` | espresso, amber | Source Serif 4 / Inter | centred, frontispiece rule | dots | slightly rounded | top headlines |
|
|
1167
|
+
| `folio` | cream, oxblood | Source Serif 4 / Source Serif 4 | masthead rules | numbers | square, no tracks | top headlines, versus |
|
|
1168
|
+
| `chalk` | cool white, ultramarine | Space Grotesk / IBM Plex Sans | highlighter under each word | cards | rounded, outlined | foot headlines, tables |
|
|
1169
|
+
| `journal` | sage, forest | IBM Plex Sans / Source Serif 4 | side bar down the title | ruled rows | slightly rounded, outlined tracks | rail headlines |
|
|
1170
|
+
|
|
1171
|
+
The forms are the pack's `forms` (list marks, Director affinity) and its `skin` (title,
|
|
1172
|
+
bars), which may set only paint: colours, shadows, radii, outlines and SVG stroke/fill
|
|
1173
|
+
opacity, never a box. In a Korean, Japanese or Chinese deck a pack's serif roles are set
|
|
1174
|
+
in Noto Serif KR/JP/SC, bundled beside the sans.
|
|
1175
|
+
|
|
1176
|
+
`--theme <pack>` forces one. `--design v2` picks one when nobody named a theme (a
|
|
1177
|
+
storyboard's default `ink` counts as nobody): a weighted, deterministic hash of the
|
|
1178
|
+
source id (or `--pack-seed`), leaning mildly toward packs that suit the deck's mix of
|
|
1179
|
+
archetypes. Over the 176 HypePaper storyboards on disk the busiest pack carries 20.5% of
|
|
1180
|
+
decks. With narration on disk, a pack that would stage a beat with a different stop count
|
|
1181
|
+
than the narration was recorded at, or that would leave out a beat the storyboard's own
|
|
1182
|
+
theme draws, is skipped for the next one; if none fits, the storyboard's own theme is kept.
|
|
1183
|
+
So `--design v2` never breaks a narrated rebuild and never costs a slide.
|
|
1184
|
+
|
|
1185
|
+
Each pack's faces ship beside a Latin deck, vendored from `@fontsource-variable/*` like
|
|
1186
|
+
Inter, and are measured by their own width tables (`src/emit/faces.ts`, written by
|
|
1187
|
+
`node scripts/measure-faces.mjs --write`). A CJK deck keeps its Noto bundle first in every
|
|
1188
|
+
stack — Noto Serif for a role the pack sets in a serif, Noto Sans otherwise — so the glyph
|
|
1189
|
+
shapes change with the pack in every language.
|
|
1190
|
+
The packs' interfaces are written up in `.planning/2026-10-07-v2-style-packs.md`.
|
|
1191
|
+
|
|
865
1192
|
## Narration
|
|
866
1193
|
|
|
867
1194
|
```sh
|
|
@@ -1278,6 +1605,8 @@ src/prefs.ts the three-layer preference resolver
|
|
|
1278
1605
|
src/emit/kit.ts the seam between the deck shell and the archetype emitters
|
|
1279
1606
|
src/emit/archetypes/ one emitter per archetype
|
|
1280
1607
|
src/emit/themes/ one palette per file; the registry is the extension point
|
|
1608
|
+
src/emit/type.ts type specs: the v2 packs' faces and chrome scale, read by chromeHeight and chromeCss
|
|
1609
|
+
src/emit/faces.ts measured width tables for the packs' Latin faces (generated)
|
|
1281
1610
|
src/images/ the three rungs a brief is drawn through, and the illustrate step
|
|
1282
1611
|
src/narrate/ edge-tts, one segment per stop
|
|
1283
1612
|
src/pack/ the .deck container and its bake/link/embed policy
|