@jokerized/decksmith 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 (78) hide show
  1. package/README.md +794 -0
  2. package/dist/cli.js +8906 -0
  3. package/dist/deck-runtime.js +55 -0
  4. package/dist/index.js +8590 -0
  5. package/dist/mcp.js +7809 -0
  6. package/dist/server/errors.js +73 -0
  7. package/dist/server/http.js +504 -0
  8. package/dist/server/main.js +91 -0
  9. package/dist/server/options.js +198 -0
  10. package/dist/server/pipeline.js +356 -0
  11. package/dist/server/queue.js +195 -0
  12. package/dist/server/ui.js +1614 -0
  13. package/dist/server/upload.js +232 -0
  14. package/dist/types/cli.d.ts +1 -0
  15. package/dist/types/deck/runtime.d.ts +59 -0
  16. package/dist/types/deck/subtitles.d.ts +101 -0
  17. package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
  18. package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
  19. package/dist/types/emit/archetypes/callout.d.ts +7 -0
  20. package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
  21. package/dist/types/emit/archetypes/data-table.d.ts +21 -0
  22. package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
  23. package/dist/types/emit/archetypes/grid.d.ts +16 -0
  24. package/dist/types/emit/archetypes/index.d.ts +19 -0
  25. package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
  26. package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
  27. package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
  28. package/dist/types/emit/archetypes/stack.d.ts +93 -0
  29. package/dist/types/emit/archetypes/title.d.ts +188 -0
  30. package/dist/types/emit/camera.d.ts +397 -0
  31. package/dist/types/emit/composition.d.ts +191 -0
  32. package/dist/types/emit/island.d.ts +19 -0
  33. package/dist/types/emit/kit.d.ts +256 -0
  34. package/dist/types/emit/svg.d.ts +177 -0
  35. package/dist/types/emit/theme.d.ts +65 -0
  36. package/dist/types/emit/themes/index.d.ts +40 -0
  37. package/dist/types/emit/themes/ink.d.ts +12 -0
  38. package/dist/types/emit/themes/mono.d.ts +20 -0
  39. package/dist/types/emit/themes/paper.d.ts +18 -0
  40. package/dist/types/index.d.ts +200 -0
  41. package/dist/types/mcp/main.d.ts +2 -0
  42. package/dist/types/mcp/prereqs.d.ts +22 -0
  43. package/dist/types/mcp/tools.d.ts +212 -0
  44. package/dist/types/narrate/narrate.d.ts +87 -0
  45. package/dist/types/narrate/tts.d.ts +134 -0
  46. package/dist/types/narrate/voices.d.ts +29 -0
  47. package/dist/types/pack/media.d.ts +61 -0
  48. package/dist/types/pack/pack.d.ts +16 -0
  49. package/dist/types/plan/codex.d.ts +24 -0
  50. package/dist/types/plan/duration.d.ts +394 -0
  51. package/dist/types/plan/prompt.d.ts +47 -0
  52. package/dist/types/plan/refs.d.ts +22 -0
  53. package/dist/types/plan/select.d.ts +116 -0
  54. package/dist/types/prefs.d.ts +43 -0
  55. package/dist/types/render/captions.d.ts +108 -0
  56. package/dist/types/render/ffmpeg.d.ts +129 -0
  57. package/dist/types/render/render.d.ts +123 -0
  58. package/dist/types/render/timing.d.ts +290 -0
  59. package/dist/types/server/errors.d.ts +11 -0
  60. package/dist/types/server/http.d.ts +57 -0
  61. package/dist/types/server/main.d.ts +1 -0
  62. package/dist/types/server/options.d.ts +90 -0
  63. package/dist/types/server/pipeline.d.ts +21 -0
  64. package/dist/types/server/queue.d.ts +105 -0
  65. package/dist/types/server/ui.d.ts +9 -0
  66. package/dist/types/server/upload.d.ts +107 -0
  67. package/dist/types/source/assets.d.ts +11 -0
  68. package/dist/types/source/fonts.d.ts +15 -0
  69. package/dist/types/source/markdown.d.ts +8 -0
  70. package/dist/types/types.d.ts +1992 -0
  71. package/dist/types/verify/budget.d.ts +41 -0
  72. package/dist/types/verify/check.d.ts +78 -0
  73. package/dist/types/verify/drift.d.ts +158 -0
  74. package/dist/types/verify/fidelity.d.ts +247 -0
  75. package/dist/types/verify/index.d.ts +207 -0
  76. package/dist/types/verify/overprint.d.ts +133 -0
  77. package/dist/types/verify/typefloor.d.ts +50 -0
  78. package/package.json +84 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The architecture overview, redrawn as motion.
3
+ *
4
+ * Every paper has this figure and every paper renders it as a flat PNG the
5
+ * reader has to decode all at once. Drawn here instead, one stage at a time with
6
+ * its connector arriving first, the viewer watches the method assemble in the
7
+ * order it actually runs — which is the only reading order a static diagram
8
+ * cannot impose.
9
+ *
10
+ * Two decisions carry the whole archetype:
11
+ *
12
+ * Boxes are all one width, solved from the widest label (`pipeLayout`). Boxes
13
+ * sized individually to their own text is what a generator produces; a row of
14
+ * equal boxes on a shared centreline is what a designer draws. When the labels
15
+ * genuinely do not fit on one line the answer is more lines, not smaller type —
16
+ * type stops at `MIN_FONT` and the box grows downwards, because a stage nobody
17
+ * in the third row can read is not a stage.
18
+ *
19
+ * The two connector reveals are different on purpose. A stage-to-stage arrow is
20
+ * ~120px, so it fades in as one object and its head is never orphaned. The
21
+ * feedback loop is a ~1000px elbow, and something that long has to be watched
22
+ * travelling: it is revealed by a clip sweeping against the flow, so the path
23
+ * appears in the direction it runs and its arrowhead lands last, on the stage it
24
+ * returns to.
25
+ *
26
+ * A row is a landscape idea. In portrait the same four stages get 860px to share
27
+ * and come out as columns — the demo's "Window" broke to "Windo/w" and "DQ-CTM"
28
+ * to "DQ-CT/M", which is a diagram that has stopped being one. So portrait turns
29
+ * the flow ninety degrees and runs it down the page instead: full-width boxes,
30
+ * arrows pointing down, and the return path up a channel on the right. See
31
+ * `columnLayout` — the two arrangements share their type, their reveal and their
32
+ * furniture, and nothing else.
33
+ */
34
+ import type { BeatOf } from "../../types.js";
35
+ import type { Emitter } from "../kit.js";
36
+ import { type Track } from "../svg.js";
37
+ type Stage = BeatOf<"pipeline">["params"]["stages"][number];
38
+ type Loop = {
39
+ from: number;
40
+ to: number;
41
+ label: string;
42
+ };
43
+ export interface PipeLayout {
44
+ /** Label type size. Never below `MIN_FONT`, whatever the labels cost. */
45
+ size: number;
46
+ /**
47
+ * Position and extent along the flow axis: `x`/`w` read as x/width across a
48
+ * row, and as y/height down a column. One field rather than two so the emitter
49
+ * paints both arrangements with one expression — a second `rows` array is how
50
+ * the landscape and portrait geometries drift apart.
51
+ */
52
+ boxes: Track[];
53
+ /** The cross-axis extent every box shares: its height in a row, its width in a column. */
54
+ boxH: number;
55
+ boxX: number;
56
+ boxW: number;
57
+ /** True when the flow runs down the page. Decides every axis below it. */
58
+ vertical: boolean;
59
+ /** Width available to text inside a box — what every `wrap` here is given. */
60
+ innerW: number;
61
+ /** Wrapped lines per stage. The box height is derived from these, not guessed. */
62
+ labelLines: string[][];
63
+ noteLines: string[][];
64
+ /** Box bottom to the feedback leg. Zero when there is no loop. */
65
+ loopDrop: number;
66
+ loopLines: string[];
67
+ /** The loop's label column, and where it starts. Portrait only. */
68
+ loopX: number;
69
+ loopW: number;
70
+ svgH: number;
71
+ }
72
+ /**
73
+ * All the geometry, with no SVG in sight.
74
+ *
75
+ * Exported because "the text stays inside the box" is the one thing this
76
+ * archetype must never get wrong, and asserting it against numbers beats
77
+ * asserting it against a rendered string.
78
+ */
79
+ export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop): PipeLayout;
80
+ export declare const pipeline: Emitter<"pipeline">;
81
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Emitter } from "../kit.js";
2
+ export declare const splitCompare: Emitter<"split-compare">;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * A stack, drawn as a stack.
3
+ *
4
+ * An encoder stack, a feature pyramid, a protocol layering: the source states
5
+ * these as a list and leaves the reader to hold the ordering in their head.
6
+ * Drawing them as offset planes receding up-right puts the ordering on the
7
+ * screen — and the build order *is* the explanation, so each plane arrives on
8
+ * its own beat instead of the pile fading in whole.
9
+ *
10
+ * Two decisions are worth knowing before changing anything here.
11
+ *
12
+ * The labels sit on a fixed right-hand spine, one per plane, so they read as a
13
+ * column rather than as seven captions scattered across a slanted pile. That
14
+ * spine is what makes the slide legible with the headline covered.
15
+ *
16
+ * The pile grows upward, so the top plane and its label are where this
17
+ * overflows — and the overflow is not a rounding error, it is structural: seven
18
+ * layers each carrying a note is more text than 1080px has room for once the
19
+ * chrome and the slide note have taken their share. So the rise is *solved*
20
+ * from the height that is actually left rather than assumed and hoped for, and
21
+ * the solve reserves the largest plane it might later choose so that clamping
22
+ * `sy` can never push the pile back out of the box.
23
+ */
24
+ import type { BeatOf, Format } from "../../types.js";
25
+ import type { Emitter } from "../kit.js";
26
+ type Params = BeatOf<"stack">["params"];
27
+ export interface StackLayout {
28
+ /**
29
+ * False when the rise the canvas allows is shorter than the tallest label
30
+ * block — i.e. adjacent labels would overlap. `stackLayout` re-composes rather
31
+ * than shipping that, so a returned layout is only `!fits` when no composition
32
+ * fitted and the caller is looking at the least-bad one.
33
+ */
34
+ fits: boolean;
35
+ /** Notes set beside their label rather than beneath it. See `stackLayout`. */
36
+ inline: boolean;
37
+ /** The svg's own box. `width` is the full content column. */
38
+ width: number;
39
+ height: number;
40
+ /** Vertical room the svg was allowed, after chrome, its margin and the note. */
41
+ avail: number;
42
+ chromeH: number;
43
+ noteH: number;
44
+ /** Distance between adjacent planes' front edges. */
45
+ rise: number;
46
+ /** The recede vector, and the slab's thickness. */
47
+ sx: number;
48
+ sy: number;
49
+ t: number;
50
+ /** Plane geometry: left edge of the front face, and its width. */
51
+ x0: number;
52
+ w: number;
53
+ /** Label spine: left edge and width of the label column. */
54
+ labelX: number;
55
+ colW: number;
56
+ labelSize: number;
57
+ /** Tallest label+note block. Adjacent labels collide once `rise` drops below it. */
58
+ blockH: number;
59
+ /** Half `blockH`, floored — the outermost labels overhang the pile by this. */
60
+ pad: number;
61
+ /** Front-face y of the bottom plane. Layer `i` sits `i * rise` above it. */
62
+ yBase: number;
63
+ /**
64
+ * Per-layer wrapped text and the width each ran against, so the emitter and
65
+ * the fit test cannot disagree about how many lines a label took.
66
+ */
67
+ lines: {
68
+ label: string[];
69
+ note: string[];
70
+ noteW: number;
71
+ labelMaxW: number;
72
+ }[];
73
+ }
74
+ /**
75
+ * Solve the whole composition before a byte of SVG exists.
76
+ *
77
+ * Exported because every way this archetype can fail is a number in here: the
78
+ * pile leaving the canvas, a label set below `MIN_FONT`, a label column narrower
79
+ * than its longest label. Asserting those against rendered markup means parsing
80
+ * text extents back out of SVG, which is a second implementation of the metrics
81
+ * and would agree with the first right up until it mattered.
82
+ *
83
+ * The hard case is seven layers that each carry a note. Two lines of audience
84
+ * type per layer is ~106px, seven of those want 740px of rise, and after an
85
+ * eyebrow, a headline and a slide note there are barely 580px left — the
86
+ * stacked label simply does not fit, and clamping the rise to make it fit is
87
+ * how notes end up printed through the label below. So when it does not fit the
88
+ * composition changes rather than the type: the note moves beside its label, on
89
+ * the same line, right-aligned against the spine.
90
+ */
91
+ export declare function stackLayout(p: Params, format: Format): StackLayout;
92
+ export declare const stack: Emitter<"stack">;
93
+ export {};
@@ -0,0 +1,188 @@
1
+ /**
2
+ * The opening beat — and the slide chrome the other five archetypes reuse.
3
+ *
4
+ * A title slide is that chrome, enlarged and centred, so the eyebrow/headline
5
+ * block and its theme-derived CSS live here rather than being written out five
6
+ * more times. `kit.ts` owns the seam between shell and vocabulary; it does not
7
+ * own the vocabulary's typography.
8
+ */
9
+ import type { Format } from "../../types.js";
10
+ import type { Emitter, Theme, Tween, Vars } from "../kit.js";
11
+ /**
12
+ * Whether this format's content box is taller than it is wide.
13
+ *
14
+ * The one question an archetype has to ask before it lays anything out. At
15
+ * `short-9x16` the box is 860x1752 against 16:9's 1700x912 — half as wide and
16
+ * nearly twice as tall — so a two-column comparison wants to become two stacked
17
+ * rows and a chart wants its readout underneath rather than beside it. Clamping
18
+ * a wide arrangement to the narrower width fits and still looks wrong.
19
+ *
20
+ * Read off the format rather than off `contentW`/`contentH` so `post-1x1`
21
+ * (1080x1080, box 860x912) stays on the landscape branch: it is square, and the
22
+ * landscape arrangements are the ones that survive a square box.
23
+ */
24
+ export declare function isPortrait(format: Format): boolean;
25
+ /**
26
+ * The chrome's type scale, and the line boxes that fall out of it.
27
+ *
28
+ * These are exported because every archetype that draws a diagram has to know
29
+ * how much room the chrome took before it can decide how big the diagram is. A
30
+ * duplicated `const HEADLINE_H = 76` in five files is a type scale that can only
31
+ * ever be changed in one of them, and the other four then overflow or leave a
32
+ * band — both silent.
33
+ *
34
+ * 76/42, not 66/40. The floor is 40 and most of the deck was sitting on it, so
35
+ * the whole slide read as body text with one slightly larger line. Growing type
36
+ * is the safe direction (invariant 5 is a minimum), and the budget arithmetic
37
+ * below is what keeps the extra height from pushing a scene off both edges.
38
+ */
39
+ export declare const EYEBROW_SIZE = 42;
40
+ export declare const EYEBROW_LH = 1.2;
41
+ /** One line box. The block is this per line, plus `margin-bottom` once. */
42
+ export declare const EYEBROW_LINE: number;
43
+ export declare const EYEBROW_GAP = 22;
44
+ /** One line box plus `margin-bottom` — the one-line case, which is the common one. */
45
+ export declare const EYEBROW_H: number;
46
+ /**
47
+ * The eyebrow is drawn UPPERCASE at `.14em` of tracking, and the headline at
48
+ * `-.015em`. Neither is decoration to the arithmetic below: measured against the
49
+ * browser, a 60-character eyebrow sets on two lines where an untracked
50
+ * lowercase measurement predicts one, and a 138-character headline sets on three
51
+ * where an untracked one predicts four.
52
+ */
53
+ export declare const EYEBROW_TRACKING = 0.14;
54
+ export declare const HEADLINE_TRACKING = -0.015;
55
+ export declare const HEADLINE_SIZE = 76;
56
+ export declare const HEADLINE_LH = 1.15;
57
+ export declare const HEADLINE_H: number;
58
+ /** Body copy — notes, captions, claims. One notch off the floor, deliberately. */
59
+ export declare const BODY_SIZE = 44;
60
+ export declare const BODY_LH = 1.45;
61
+ /**
62
+ * Bind the tail of a headline so it cannot break to a one- or two-word last line.
63
+ *
64
+ * Gluing the final words with U+00A0 pulls one more word down with the orphan:
65
+ * "…the carrier is read / through" becomes "…the carrier is / read through". The
66
+ * line count does not change — greedy wrapping only ever moves words forward — so
67
+ * `chromeHeight` needs no adjustment and keeps measuring the raw string. (`wrap`
68
+ * splits on `\s`, which U+00A0 is, so it would not see the binding anyway; the
69
+ * width guard below is what keeps the browser's line count in step.)
70
+ *
71
+ * The run bound is the WIDEST that fits the measure, from `tail + 1` words down
72
+ * to two, rather than `tail + 1` alone. `wrap` errs wide by construction, so its
73
+ * last line is not the browser's: it broke "…reports three / different numbers"
74
+ * where the browser broke "…reports three different / numbers", and the three
75
+ * words it asked for were both too wide for the measure and aimed at an orphan
76
+ * that was not the one on screen. Any run that fits protects the same seam, and
77
+ * binding words that already sit together costs nothing.
78
+ *
79
+ * A candidate is REJECTED if it would leave some earlier line raggeder than the
80
+ * orphan it removes. Binding "dense output" in the demo's title pushed the run
81
+ * off line three and stranded "with" there by itself — a one-word line in the
82
+ * MIDDLE of a headline, which is worse than the one at the end. So each candidate
83
+ * is re-wrapped with the bound run held together as a single token, and kept only
84
+ * if the shortest line does not get shorter and no line is added.
85
+ *
86
+ * Two cases are left alone rather than forced: a headline that IS the orphan
87
+ * (nothing to borrow from), and one where every candidate is wider than the
88
+ * measure — the browser would break that anyway, and then in mid-word.
89
+ */
90
+ export declare function unwidow(text: string, width: number, size: number, weight?: number): string;
91
+ /**
92
+ * Height the chrome will actually occupy, headline wrapping included.
93
+ *
94
+ * `wrap` errs wide, so this over-counts lines rather than under-counting them.
95
+ * That is the safe direction: an under-count overflows the canvas, and `.scene`
96
+ * is centred so it overflows off *both* edges at once.
97
+ */
98
+ export declare function chromeHeight(eyebrow: string | undefined, headline: string, width: number): number;
99
+ /**
100
+ * What is left for the body once the chrome, the body's own top margin, and
101
+ * anything below it (a note, a caption band) have been paid for.
102
+ *
103
+ * `floor` IS A LAST RESORT, NOT A BUDGET, and the difference has already cost a
104
+ * defect. `claim-figure` asked for the space left under a 216-character claim,
105
+ * was told 320 when the true remainder was 163, sized the figure to fit 320, and
106
+ * drew the caption 7px below the canvas — with every gate green, because the
107
+ * gate was sampling nine midpoints and never looked at that hold. A caller that
108
+ * is LAST in the queue for space has to be able to hear "there is almost none",
109
+ * so it can pass its own floor and act on the answer.
110
+ */
111
+ export declare function bodyBudget(format: Format, eyebrow: string | undefined, headline: string, below?: number, top?: number, floor?: number): number;
112
+ /**
113
+ * The largest size at which `text` sets in `width` lines of `maxLines`, capped
114
+ * both ends. Used where a headline should grow to meet its box instead of
115
+ * sitting at one authored size with 600px of air beside it.
116
+ *
117
+ * The division solves for perfect packing — every line filled to the last pixel —
118
+ * which no real line break achieves. At 1700 the slack is a few percent and the
119
+ * answer holds; at 860 it is not, because one long word is a fifth of the
120
+ * measure. The demo's title came back at 116px, set on FOUR lines against a cap
121
+ * of three, and the extra line was a stranded "with". So the quotient is an
122
+ * upper bound, and `wrap` is asked whether it is true.
123
+ *
124
+ * Stepping down rather than bisecting: the answer is within a few px of the
125
+ * bound in every case that matters, and a monotone scan cannot pick a size that
126
+ * a bisection's midpoint happened to skip. `lo` is a floor, not a target — a
127
+ * string that will not set in `maxLines` at `lo` simply lands there, as before.
128
+ */
129
+ export declare function fitText(text: string, width: number, maxLines: number, lo: number, hi: number, weight?: number): number;
130
+ /**
131
+ * The vocabulary's own constructor: a `fromTo` at a rounded position.
132
+ *
133
+ * `kit.fromTo` is the bare structure; this is what an archetype calls, and the
134
+ * only thing it adds is invariant 10. Every archetype's positions are sums of
135
+ * authored beat times, so they accumulate float tails — `7.199999999999999` —
136
+ * and a position that prints differently between two builds of one storyboard
137
+ * moves a byte in the composition. Rounded HERE rather than in the serialiser
138
+ * because `camera.ts` rounds its own positions to three places against a clock
139
+ * the shell owns, and a second rounding on the way out would quietly truncate
140
+ * them.
141
+ *
142
+ * Every tween is `fromTo` — `from()` captures its end state when the timeline
143
+ * is constructed, which is wrong the moment the deck seeks somewhere else, and
144
+ * seeking is all deck navigation does. That is now the `Tween` type's doing
145
+ * rather than this comment's: there is no shape here that omits `from`.
146
+ */
147
+ export declare function tween(target: string, from: Vars, to: Vars, at: number): Tween;
148
+ /**
149
+ * Hold points become absolute island fragment times and must land inside the
150
+ * slide's window, so a beat planned shorter than its own reveal schedule gets
151
+ * its holds clamped rather than silently pushed into the next slide.
152
+ */
153
+ export declare function holdsWithin(times: number[], seconds: number): number[];
154
+ /**
155
+ * The eyebrow + headline block. Ids are scoped by `sid`, as every selector must be.
156
+ *
157
+ * `width` is the measure the headline will set in — always `contentW(ctx.format)`,
158
+ * since `.headline` is a block child of `.scene` and so spans the content box.
159
+ *
160
+ * Required, not optional. It was optional for one round while eleven emitters
161
+ * were being changed by different hands, and the six that never got the argument
162
+ * were exactly the six still printing one-word last lines ("…is read / through",
163
+ * "…not the / smallest"). A defaulted measure is indistinguishable at the call
164
+ * site from a deliberate one, so the next archetype would inherit the same
165
+ * silent opt-out; requiring it makes the omission a type error instead.
166
+ */
167
+ export declare function chrome(sid: string, eyebrow: string | undefined, headline: string, width: number): string;
168
+ /** Reveals the chrome. Finishes at 0.9s, which is where archetype bodies pick up. */
169
+ export declare function chromeIn(sid: string, eyebrow: boolean): Tween[];
170
+ export declare function chromeCss(t: Theme): string;
171
+ /**
172
+ * The note that closes a slide. Eight archetypes each declared their own copy of
173
+ * this at 40px with a different class name; the sizes then drifted apart by
174
+ * archetype, which is a hierarchy nobody chose.
175
+ */
176
+ export declare function noteCss(cls: string, t: Theme, top?: number): string;
177
+ /** The column a `.<cls>` note actually sets in: the cap, or the box if that is narrower. */
178
+ export declare function noteWidth(format: Format): number;
179
+ /**
180
+ * One `.<cls>` line box, wrapping included — what `bodyBudget`'s `below` wants.
181
+ *
182
+ * `width` is the caller's because two archetypes set their note in a column of
183
+ * their own rather than across the box. Everyone else passes `noteWidth(format)`;
184
+ * measuring against the bare 1600 cap at 9:16 under-counts the lines by half and
185
+ * hands the body a budget the note then overruns.
186
+ */
187
+ export declare function noteHeight(note: string | undefined, width: number, top?: number): number;
188
+ export declare const title: Emitter<"title">;