@jokerized/decksmith 0.1.4 → 0.2.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.
- package/README.md +159 -25
- package/dist/cli.js +3441 -1738
- package/dist/index.js +2226 -647
- package/dist/mcp.js +2250 -604
- package/dist/types/emit/archetypes/annotated-figure.d.ts +3 -2
- package/dist/types/emit/archetypes/data-table.d.ts +0 -19
- package/dist/types/emit/archetypes/pipeline.d.ts +2 -2
- package/dist/types/emit/archetypes/stack.d.ts +13 -1
- package/dist/types/emit/archetypes/title.d.ts +31 -14
- package/dist/types/emit/camera.d.ts +25 -0
- package/dist/types/emit/composition.d.ts +20 -2
- package/dist/types/emit/kit.d.ts +146 -0
- package/dist/types/emit/svg.d.ts +77 -13
- package/dist/types/emit/theme.d.ts +1 -0
- package/dist/types/images/illustrate.d.ts +34 -0
- package/dist/types/images/providers.d.ts +108 -0
- package/dist/types/index.d.ts +27 -8
- package/dist/types/mcp/prereqs.d.ts +18 -0
- package/dist/types/mcp/tools.d.ts +23 -0
- package/dist/types/plan/codex.d.ts +25 -3
- package/dist/types/plan/duration.d.ts +190 -15
- package/dist/types/plan/prompt.d.ts +7 -1
- package/dist/types/plan/refs.d.ts +41 -3
- package/dist/types/prefs.d.ts +21 -6
- package/dist/types/render/capture.d.ts +99 -0
- package/dist/types/render/render.d.ts +18 -0
- package/dist/types/server/options.d.ts +7 -0
- package/dist/types/server/pipeline.d.ts +14 -0
- package/dist/types/server/queue.d.ts +1 -1
- package/dist/types/types.d.ts +152 -4
- package/dist/types/verify/index.d.ts +114 -6
- package/package.json +2 -2
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import type { Format } from "../../types.js";
|
|
25
25
|
import type { Emitter } from "../kit.js";
|
|
26
26
|
import type { Box, Pt } from "../svg.js";
|
|
27
|
+
import { type Face } from "../svg.js";
|
|
27
28
|
export interface FigureNote {
|
|
28
29
|
x: number;
|
|
29
30
|
y: number;
|
|
@@ -78,7 +79,7 @@ export interface FigureLayout {
|
|
|
78
79
|
export declare function planFigure(stageW: number, notes: FigureNote[], fig: {
|
|
79
80
|
width: number;
|
|
80
81
|
height: number;
|
|
81
|
-
}, stageH: number, tall?: boolean): FigureLayout;
|
|
82
|
+
}, stageH: number, tall?: boolean, face?: Face): FigureLayout;
|
|
82
83
|
/**
|
|
83
84
|
* Height the stage may spend, once the chrome and the caption have taken theirs.
|
|
84
85
|
* A budget, not a size — `planFigure` gives back only what it used.
|
|
@@ -99,5 +100,5 @@ export declare function planFigure(stageW: number, notes: FigureNote[], fig: {
|
|
|
99
100
|
* that reports more room than exists does not create room; it only moves which
|
|
100
101
|
* gate notices.
|
|
101
102
|
*/
|
|
102
|
-
export declare function stageBudget(format: Format, eyebrow: string | undefined, headline: string, caption: string): number;
|
|
103
|
+
export declare function stageBudget(format: Format, eyebrow: string | undefined, headline: string, caption: string, face?: Face): number;
|
|
103
104
|
export declare const annotatedFigure: Emitter<"annotated-figure">;
|
|
@@ -1,21 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A table from the source, revealed row by row, then read for the viewer: the
|
|
3
|
-
* highlighted rows light up in the order the argument needs them.
|
|
4
|
-
*
|
|
5
|
-
* Nothing here invents numbers, and nothing here drops them either: every row of
|
|
6
|
-
* the named table is drawn, at 40px or above, or the beat is refused. The type
|
|
7
|
-
* is solved from the WIDTH — upwards from the 40px floor, capped — and the height
|
|
8
|
-
* rule then asks whether those rows, drawn at that size, fit on the canvas. The
|
|
9
|
-
* one lever the height rule owns is the row padding, and it is already shut to
|
|
10
|
-
* `PAD_Y_MIN` whenever this refuses, so "will not fit" is a measurement of this
|
|
11
|
-
* slide rather than a preference about it.
|
|
12
|
-
*
|
|
13
|
-
* WHAT IT DELIBERATELY DOES NOT DO: buy rows by taking the type back down toward
|
|
14
|
-
* 40. That would be legal — invariant 5 is a floor, not a fixed size, and a table
|
|
15
|
-
* declined here at 52px would often fit at 40 — but it makes `cell` solve two
|
|
16
|
-
* constraints at once, and the width solve is the one that has been measured. So
|
|
17
|
-
* the refusal names the size it refused AT, and the choice stays visible to
|
|
18
|
-
* whoever reads the error instead of being buried in this file.
|
|
19
|
-
*/
|
|
20
1
|
import type { Emitter } from "../kit.js";
|
|
21
2
|
export declare const dataTable: Emitter<"data-table">;
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import type { BeatOf } from "../../types.js";
|
|
35
35
|
import type { Emitter } from "../kit.js";
|
|
36
|
-
import { type Track } from "../svg.js";
|
|
36
|
+
import { type Face, type Track } from "../svg.js";
|
|
37
37
|
type Stage = BeatOf<"pipeline">["params"]["stages"][number];
|
|
38
38
|
type Loop = {
|
|
39
39
|
from: number;
|
|
@@ -76,6 +76,6 @@ export interface PipeLayout {
|
|
|
76
76
|
* archetype must never get wrong, and asserting it against numbers beats
|
|
77
77
|
* asserting it against a rendered string.
|
|
78
78
|
*/
|
|
79
|
-
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop): PipeLayout;
|
|
79
|
+
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop, face?: Face): PipeLayout;
|
|
80
80
|
export declare const pipeline: Emitter<"pipeline">;
|
|
81
81
|
export {};
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import type { BeatOf, Format } from "../../types.js";
|
|
25
25
|
import type { Emitter } from "../kit.js";
|
|
26
|
+
import { type Face } from "../svg.js";
|
|
26
27
|
type Params = BeatOf<"stack">["params"];
|
|
27
28
|
export interface StackLayout {
|
|
28
29
|
/**
|
|
@@ -32,6 +33,17 @@ export interface StackLayout {
|
|
|
32
33
|
* fitted and the caller is looking at the least-bad one.
|
|
33
34
|
*/
|
|
34
35
|
fits: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the label column is as wide as its content needs.
|
|
38
|
+
*
|
|
39
|
+
* `fits` is a HEIGHT test and was the only test: `colW` is clamped to
|
|
40
|
+
* `colCap`, so a column whose content wants more simply overflows, and an
|
|
41
|
+
* inline note is drawn with no `maxWidth` at all — one unwrapped line that
|
|
42
|
+
* runs off the slide. A Korean note did exactly that ("크기·대비·선택·자막·번역
|
|
43
|
+
* 검사" at the 40px floor), and the only thing that noticed was
|
|
44
|
+
* `container_overflow` at a hold, after a browser had laid the deck out.
|
|
45
|
+
*/
|
|
46
|
+
wide: boolean;
|
|
35
47
|
/** Notes set beside their label rather than beneath it. See `stackLayout`. */
|
|
36
48
|
inline: boolean;
|
|
37
49
|
/** The svg's own box. `width` is the full content column. */
|
|
@@ -88,6 +100,6 @@ export interface StackLayout {
|
|
|
88
100
|
* composition changes rather than the type: the note moves beside its label, on
|
|
89
101
|
* the same line, right-aligned against the spine.
|
|
90
102
|
*/
|
|
91
|
-
export declare function stackLayout(p: Params, format: Format): StackLayout;
|
|
103
|
+
export declare function stackLayout(p: Params, format: Format, face?: Face): StackLayout;
|
|
92
104
|
export declare const stack: Emitter<"stack">;
|
|
93
105
|
export {};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Format } from "../../types.js";
|
|
10
10
|
import type { Emitter, Theme, Tween, Vars } from "../kit.js";
|
|
11
|
+
import { type Face } from "../svg.js";
|
|
11
12
|
/**
|
|
12
13
|
* Whether this format's content box is taller than it is wide.
|
|
13
14
|
*
|
|
@@ -27,14 +28,30 @@ export declare function isPortrait(format: Format): boolean;
|
|
|
27
28
|
*
|
|
28
29
|
* These are exported because every archetype that draws a diagram has to know
|
|
29
30
|
* how much room the chrome took before it can decide how big the diagram is. A
|
|
30
|
-
* duplicated `const HEADLINE_H =
|
|
31
|
+
* duplicated `const HEADLINE_H = 64` in five files is a type scale that can only
|
|
31
32
|
* ever be changed in one of them, and the other four then overflow or leave a
|
|
32
33
|
* band — both silent.
|
|
33
34
|
*
|
|
34
|
-
*
|
|
35
|
-
* the whole slide read as body text with one slightly
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* 64/42/40, down from 76/42/44. The density pass that set 76 was answering a
|
|
36
|
+
* real complaint — at 66/40 the whole slide read as body text with one slightly
|
|
37
|
+
* larger line — but it overshot the measure. MEASURED at contentW 1700: a 76px
|
|
38
|
+
* headline line holds 45 characters, and the planner is asked for headlines
|
|
39
|
+
* longer than that, so TEN of `demo/storyboard.json`'s twelve wrapped to two
|
|
40
|
+
* lines and each spent 174px of a 912px box saying one sentence. At 64 the same
|
|
41
|
+
* line holds 53: three still wrap, seven stop, and a headline that stops hands
|
|
42
|
+
* 100px back to the body (174 -> 74) while one that still wraps hands back 26
|
|
43
|
+
* (174 -> 148). Across the demo that is 804px, 67 a beat — which is the space
|
|
44
|
+
* the owner said the deck was wasting.
|
|
45
|
+
*
|
|
46
|
+
* EYEBROW_SIZE STAYS AT 42. It is the one line that is already short by
|
|
47
|
+
* contract, so shrinking it buys ~10px and costs the only contrast the chrome
|
|
48
|
+
* has between its two lines.
|
|
49
|
+
*
|
|
50
|
+
* BODY_SIZE goes to 40, which IS the floor. That is deliberate and it is legal:
|
|
51
|
+
* `scanTypeFloor` compares `px < floorPx` (src/verify/typefloor.ts), so 40
|
|
52
|
+
* passes, and the demo already declares 40 in ten places. There is no room under
|
|
53
|
+
* this one — the next archetype that wants a body notch smaller has to take less
|
|
54
|
+
* text instead.
|
|
38
55
|
*/
|
|
39
56
|
export declare const EYEBROW_SIZE = 42;
|
|
40
57
|
export declare const EYEBROW_LH = 1.2;
|
|
@@ -52,11 +69,11 @@ export declare const EYEBROW_H: number;
|
|
|
52
69
|
*/
|
|
53
70
|
export declare const EYEBROW_TRACKING = 0.14;
|
|
54
71
|
export declare const HEADLINE_TRACKING = -0.015;
|
|
55
|
-
export declare const HEADLINE_SIZE =
|
|
72
|
+
export declare const HEADLINE_SIZE = 64;
|
|
56
73
|
export declare const HEADLINE_LH = 1.15;
|
|
57
74
|
export declare const HEADLINE_H: number;
|
|
58
|
-
/** Body copy — notes, captions, claims.
|
|
59
|
-
export declare const BODY_SIZE =
|
|
75
|
+
/** Body copy — notes, captions, claims. ON the floor, deliberately: see above. */
|
|
76
|
+
export declare const BODY_SIZE = 40;
|
|
60
77
|
export declare const BODY_LH = 1.45;
|
|
61
78
|
/**
|
|
62
79
|
* Bind the tail of a headline so it cannot break to a one- or two-word last line.
|
|
@@ -87,7 +104,7 @@ export declare const BODY_LH = 1.45;
|
|
|
87
104
|
* (nothing to borrow from), and one where every candidate is wider than the
|
|
88
105
|
* measure — the browser would break that anyway, and then in mid-word.
|
|
89
106
|
*/
|
|
90
|
-
export declare function unwidow(text: string, width: number, size: number, weight?: number): string;
|
|
107
|
+
export declare function unwidow(text: string, width: number, size: number, weight?: number, face?: Face): string;
|
|
91
108
|
/**
|
|
92
109
|
* Height the chrome will actually occupy, headline wrapping included.
|
|
93
110
|
*
|
|
@@ -95,7 +112,7 @@ export declare function unwidow(text: string, width: number, size: number, weigh
|
|
|
95
112
|
* That is the safe direction: an under-count overflows the canvas, and `.scene`
|
|
96
113
|
* is centred so it overflows off *both* edges at once.
|
|
97
114
|
*/
|
|
98
|
-
export declare function chromeHeight(eyebrow: string | undefined, headline: string, width: number): number;
|
|
115
|
+
export declare function chromeHeight(eyebrow: string | undefined, headline: string, width: number, face?: Face): number;
|
|
99
116
|
/**
|
|
100
117
|
* What is left for the body once the chrome, the body's own top margin, and
|
|
101
118
|
* anything below it (a note, a caption band) have been paid for.
|
|
@@ -108,7 +125,7 @@ export declare function chromeHeight(eyebrow: string | undefined, headline: stri
|
|
|
108
125
|
* is LAST in the queue for space has to be able to hear "there is almost none",
|
|
109
126
|
* so it can pass its own floor and act on the answer.
|
|
110
127
|
*/
|
|
111
|
-
export declare function bodyBudget(format: Format, eyebrow: string | undefined, headline: string, below?: number, top?: number, floor?: number): number;
|
|
128
|
+
export declare function bodyBudget(format: Format, eyebrow: string | undefined, headline: string, below?: number, top?: number, floor?: number, face?: Face): number;
|
|
112
129
|
/**
|
|
113
130
|
* The largest size at which `text` sets in `width` lines of `maxLines`, capped
|
|
114
131
|
* both ends. Used where a headline should grow to meet its box instead of
|
|
@@ -126,7 +143,7 @@ export declare function bodyBudget(format: Format, eyebrow: string | undefined,
|
|
|
126
143
|
* a bisection's midpoint happened to skip. `lo` is a floor, not a target — a
|
|
127
144
|
* string that will not set in `maxLines` at `lo` simply lands there, as before.
|
|
128
145
|
*/
|
|
129
|
-
export declare function fitText(text: string, width: number, maxLines: number, lo: number, hi: number, weight?: number): number;
|
|
146
|
+
export declare function fitText(text: string, width: number, maxLines: number, lo: number, hi: number, weight?: number, face?: Face): number;
|
|
130
147
|
/**
|
|
131
148
|
* The vocabulary's own constructor: a `fromTo` at a rounded position.
|
|
132
149
|
*
|
|
@@ -164,7 +181,7 @@ export declare function holdsWithin(times: number[], seconds: number): number[];
|
|
|
164
181
|
* site from a deliberate one, so the next archetype would inherit the same
|
|
165
182
|
* silent opt-out; requiring it makes the omission a type error instead.
|
|
166
183
|
*/
|
|
167
|
-
export declare function chrome(sid: string, eyebrow: string | undefined, headline: string, width: number): string;
|
|
184
|
+
export declare function chrome(sid: string, eyebrow: string | undefined, headline: string, width: number, face?: Face): string;
|
|
168
185
|
/** Reveals the chrome. Finishes at 0.9s, which is where archetype bodies pick up. */
|
|
169
186
|
export declare function chromeIn(sid: string, eyebrow: boolean): Tween[];
|
|
170
187
|
export declare function chromeCss(t: Theme): string;
|
|
@@ -184,5 +201,5 @@ export declare function noteWidth(format: Format): number;
|
|
|
184
201
|
* measuring against the bare 1600 cap at 9:16 under-counts the lines by half and
|
|
185
202
|
* hands the body a budget the note then overruns.
|
|
186
203
|
*/
|
|
187
|
-
export declare function noteHeight(note: string | undefined, width: number, top?: number): number;
|
|
204
|
+
export declare function noteHeight(note: string | undefined, width: number, top?: number, face?: Face): number;
|
|
188
205
|
export declare const title: Emitter<"title">;
|
|
@@ -153,6 +153,31 @@ export declare function elementId(sid: string, element: string): string;
|
|
|
153
153
|
* archetype that draws a thing with an id can be entered at it.
|
|
154
154
|
*/
|
|
155
155
|
export declare function enterableIds(sid: string, html: string): string[];
|
|
156
|
+
/**
|
|
157
|
+
* THE CAMERA HAZARD, checked. `inside.element` is an index, so naming the right
|
|
158
|
+
* kind of part and the wrong number is not a build error — `stage2` exists, it
|
|
159
|
+
* has a rect, and the deck dives smoothly into whatever happens to be third.
|
|
160
|
+
* That is this project's signature failure shape: correct-looking output that no
|
|
161
|
+
* gate can see is wrong. `inside.label` is what the plan says it is entering,
|
|
162
|
+
* `Scene.parts` is what the archetype drew, and this is the one place they meet.
|
|
163
|
+
*
|
|
164
|
+
* Returns the PREDICATE of a sentence whose subject is the containing beat, so
|
|
165
|
+
* that both callers can keep their own framing — the emitter says "this beat",
|
|
166
|
+
* `assertInsideResolves` names the beat and its archetype — over one comparison.
|
|
167
|
+
* Undefined means nothing to report.
|
|
168
|
+
*
|
|
169
|
+
* A plan that omits `label` is not checked: the field has to stay optional or
|
|
170
|
+
* the 130 committed references stop validating and every stored plan's sha256
|
|
171
|
+
* moves. A plan that SUPPLIES one for a part carrying no label is refused rather
|
|
172
|
+
* than skipped, because a check that silently does nothing is how the six
|
|
173
|
+
* green-gate failures in this repo all began — and by RULE 11 only a pipeline
|
|
174
|
+
* stage, a grid region and a stack layer are enterable anyway.
|
|
175
|
+
*
|
|
176
|
+
* The comparison is deliberately not exact and deliberately not fuzzy. Exact,
|
|
177
|
+
* and a re-typed or re-cased label fails a build over nothing; fuzzy — substring
|
|
178
|
+
* — and "Window" matches "window group", which occurs in this corpus.
|
|
179
|
+
*/
|
|
180
|
+
export declare function partLabelProblem(part: string, expected: string | undefined, parts: Readonly<Record<string, string>> | undefined): string | undefined;
|
|
156
181
|
/**
|
|
157
182
|
* The camera rig, wrapped around one scene's content.
|
|
158
183
|
*
|
|
@@ -67,6 +67,24 @@ export interface DeckOptions {
|
|
|
67
67
|
* failure shape.
|
|
68
68
|
*/
|
|
69
69
|
onBeatError?: (beatId: string, err: Error) => void;
|
|
70
|
+
/**
|
|
71
|
+
* The subsetted bundle's `@font-face` CSS, INLINED rather than linked.
|
|
72
|
+
*
|
|
73
|
+
* The composition sets `font-family: "Noto Sans KR", …` for a CJK deck and
|
|
74
|
+
* used to declare the face in a `<link rel=stylesheet>`. Two things read the
|
|
75
|
+
* composition and neither follows that link: hyperframes' static guard, which
|
|
76
|
+
* reported "Font family used without @font-face declaration … text will fall
|
|
77
|
+
* back to a generic font, producing incorrect typography in the video" on
|
|
78
|
+
* every Korean deck this project has built; and the compiler, which inlines
|
|
79
|
+
* `<script src>` and not stylesheets — the same asymmetry the KaTeX note above
|
|
80
|
+
* records. A face the renderer cannot see is invariant 9 exactly: a font stack
|
|
81
|
+
* naming a family the bundle does not declare falls back silently.
|
|
82
|
+
*
|
|
83
|
+
* Passed in rather than read here because this function does no I/O. Absent,
|
|
84
|
+
* the link is emitted as before, so a caller that has no bundle is unchanged
|
|
85
|
+
* and every non-CJK deck is byte-identical either way.
|
|
86
|
+
*/
|
|
87
|
+
fontCss?: string;
|
|
70
88
|
}
|
|
71
89
|
/**
|
|
72
90
|
* Both artifacts from one layout pass.
|
|
@@ -75,8 +93,8 @@ export interface DeckOptions {
|
|
|
75
93
|
* `render` consume. `page` is the navigable wrapper: it hosts the composition in
|
|
76
94
|
* a player, carries the same island, and inlines our step layer. Two files
|
|
77
95
|
* because they are genuinely two things — one is rendered, one is presented —
|
|
78
|
-
* and because HyperFrames' own navigation is dead at 0.7.71/0.7.72
|
|
79
|
-
* (EXPERIMENT-003), so the presented one has to be ours.
|
|
96
|
+
* and because HyperFrames' own navigation is dead at 0.7.71/0.7.72 and still
|
|
97
|
+
* dead at 0.8.27 as at 0.7.90 (EXPERIMENT-003), so the presented one has to be ours.
|
|
80
98
|
*/
|
|
81
99
|
export declare function emitDeck(storyboard: Storyboard, source: Source, format: Format, runtimeJs: string, opts?: DeckOptions): Deck;
|
|
82
100
|
/**
|
package/dist/types/emit/kit.d.ts
CHANGED
|
@@ -185,6 +185,17 @@ export interface Tween {
|
|
|
185
185
|
readonly at: number;
|
|
186
186
|
}
|
|
187
187
|
export declare function fromTo(target: string, from: Vars, to: Vars, at: number): Tween;
|
|
188
|
+
/**
|
|
189
|
+
* Invariant 10 at the vocabulary's two places.
|
|
190
|
+
*
|
|
191
|
+
* Positions are sums of authored beat times and accumulate float tails —
|
|
192
|
+
* `7.199999999999999` — and a position that prints differently between two
|
|
193
|
+
* builds of one storyboard moves a byte in the composition. `title.ts`'s
|
|
194
|
+
* `tween()` rounds an archetype's positions this way; the verbs at the end of
|
|
195
|
+
* this file need the same rounding and cannot reach it, because `title.ts`
|
|
196
|
+
* imports this file and not the other way round.
|
|
197
|
+
*/
|
|
198
|
+
export declare function sec(n: number): number;
|
|
188
199
|
/**
|
|
189
200
|
* THE ONE PLACE A TWEEN BECOMES GSAP TEXT.
|
|
190
201
|
*
|
|
@@ -198,6 +209,42 @@ export declare function tweenText(t: Tween): string;
|
|
|
198
209
|
export interface Scene {
|
|
199
210
|
/** The scene's inner HTML. The wrapper `<div class="scene clip">` is added by the shell. */
|
|
200
211
|
html: string;
|
|
212
|
+
/**
|
|
213
|
+
* For each id suffix this scene drew that CARRIES A LABEL, the label it drew
|
|
214
|
+
* there: `{ stage0: "Encode", stage1: "Window" }`. Only the archetypes with
|
|
215
|
+
* enterable interiors populate it, and only for the parts a camera can be
|
|
216
|
+
* aimed at.
|
|
217
|
+
*
|
|
218
|
+
* WHY IT EXISTS. `inside.element` is an INDEX — `stage2` is the third thing in
|
|
219
|
+
* `params.stages` — so a reference that names the right kind of part and the
|
|
220
|
+
* wrong number resolves to a real id, measures a real rect and hands GSAP
|
|
221
|
+
* valid numbers. The deck renders a smooth, convincing dive into the wrong
|
|
222
|
+
* box, and `lint`, `check`, the type floor and `drift` are all green over it.
|
|
223
|
+
* `insideSchema.label` is what the plan thought it was entering; this is what
|
|
224
|
+
* the archetype actually drew, and the two are compared before the camera is
|
|
225
|
+
* built. `experiments/015-decision/runs-n32/menu-20/plan.json` is a committed
|
|
226
|
+
* plan where they disagree.
|
|
227
|
+
*
|
|
228
|
+
* WHY NOT AN ATTRIBUTE IN THE HTML, and why not a table in `withCamera`. A
|
|
229
|
+
* `data-` attribute would move the bytes of every built deck, which is the one
|
|
230
|
+
* thing the camera is not allowed to do (`renders byte-identical output when
|
|
231
|
+
* nothing is annotated`). A table in the shell is what `assertInsideResolves`
|
|
232
|
+
* already rejected in its own words — it would drift from the emitter. `Scene`
|
|
233
|
+
* is in-memory and never serialised, so this is archetype-sourced and costs
|
|
234
|
+
* zero emitted bytes.
|
|
235
|
+
*
|
|
236
|
+
* IF YOU ARE WRITING AN ARCHETYPE WITH AN ENTERABLE INTERIOR, RETURN THIS.
|
|
237
|
+
* Omitting it does not merely leave the new archetype unchecked — a supplied
|
|
238
|
+
* `label` over a part reporting none is a build ERROR, not a skip
|
|
239
|
+
* (`partLabelProblem` says why), so an archetype that populates the map and
|
|
240
|
+
* forgets it in the return object REFUSES ITS OWN CORRECT REFERENCES with
|
|
241
|
+
* "does not label ... It labels: nothing". `stack` shipped that way for the
|
|
242
|
+
* length of one review: the map was filled in the layer loop and dropped at
|
|
243
|
+
* the return, and because every test for this was written against `pipeline`,
|
|
244
|
+
* `tsc` and `biome` both saw a merely-unused local. Test the archetype you
|
|
245
|
+
* added, not the one that already worked.
|
|
246
|
+
*/
|
|
247
|
+
parts?: Readonly<Record<string, string>>;
|
|
201
248
|
/**
|
|
202
249
|
* Tweens appended to this scene's own paused timeline, with times RELATIVE to
|
|
203
250
|
* the scene's start. `Tween` is a `fromTo` by construction — `from()` records
|
|
@@ -254,3 +301,102 @@ export declare function mathy(raw: string): string;
|
|
|
254
301
|
export declare const TEX_MARK = "ds-tex";
|
|
255
302
|
/** Escape a string for embedding inside a single-quoted JS literal. */
|
|
256
303
|
export declare function js(s: string): string;
|
|
304
|
+
/**
|
|
305
|
+
* Where a dimmed element rests. Exactly this and never lower: the contrast
|
|
306
|
+
* gate and the type floor were both cleared with text at this opacity, and
|
|
307
|
+
* nothing here can express a darker one. The headline and the eyebrow are
|
|
308
|
+
* never dimmed at all — that is the archetype's promise, kept by never naming
|
|
309
|
+
* `-h` or `-e` to a spotlight.
|
|
310
|
+
*/
|
|
311
|
+
export declare const DIM = 0.62;
|
|
312
|
+
/**
|
|
313
|
+
* How much bigger the part under discussion stands than its neighbours.
|
|
314
|
+
*
|
|
315
|
+
* UP, AND ONLY UP. The obvious way to say "this one matters" is to shrink the
|
|
316
|
+
* others, and it is the one way this project may not: `verify/typefloor.ts`
|
|
317
|
+
* reads DECLARED type sizes and says so in its own header — "text shrunk by a
|
|
318
|
+
* `scale` below 1 at a hold reads as its unscaled size" — so a 0.94 on a panel
|
|
319
|
+
* of 40px text draws 37.6px that the audience floor scores as 40. That is the
|
|
320
|
+
* exact shape of the failure this project keeps writing experiments about: a
|
|
321
|
+
* gate green over output that is wrong. Growing the focus has no such blind
|
|
322
|
+
* spot, and against neighbours already at `DIM` it reads the same.
|
|
323
|
+
*
|
|
324
|
+
* 3%, not 8%: the parts stand in gaps of their own — a pipeline's 120px, a
|
|
325
|
+
* callout's 44 — and a lift big enough to close one would be caught by
|
|
326
|
+
* `content_overlap`, which is not exempted here and should not be.
|
|
327
|
+
*/
|
|
328
|
+
export declare const LIFT = 1.03;
|
|
329
|
+
/**
|
|
330
|
+
* A spotlight over one scene's parts. Built by `spotlighter`, which is the
|
|
331
|
+
* only constructor, because the thing being enforced is a HISTORY: which parts
|
|
332
|
+
* are at `DIM` right now, and which are lit.
|
|
333
|
+
*
|
|
334
|
+
* WHY STATEFUL. Two rules govern every dim: at most one immediate-render
|
|
335
|
+
* `fromTo` per (element, property), and a later tween's `from` equals the
|
|
336
|
+
* earlier tween's `to`. Break the first and a part shows at full opacity
|
|
337
|
+
* before its own reveal; break the second and a part already at 0.62 snaps to
|
|
338
|
+
* 1 and eases back down — a blink, at every gate green. Both are facts about
|
|
339
|
+
* what was emitted BEFORE, so a helper that takes a `first: false` flag from
|
|
340
|
+
* the caller is a helper that trusts the caller to remember. This one
|
|
341
|
+
* remembers.
|
|
342
|
+
*
|
|
343
|
+
* Every tween it emits carries `immediateRender: false`. The elements a
|
|
344
|
+
* spotlight touches enter through their own `opacity: 0 → 1` reveal, and that
|
|
345
|
+
* reveal is the one immediate render opacity gets; a second one would apply
|
|
346
|
+
* `opacity: 1` at build time, on top of the reveal's 0, and the part would be
|
|
347
|
+
* on screen from frame one. An element with no reveal is at 1 already, so
|
|
348
|
+
* withholding the immediate render costs it nothing.
|
|
349
|
+
*
|
|
350
|
+
* Two idioms, and one spotlighter is one or the other (mixing throws):
|
|
351
|
+
*
|
|
352
|
+
* - `lit(keep, at)` — the whole scope is on screen. The first call dims
|
|
353
|
+
* everything in scope but `keep` (`#sid .term:not(#sid-t0)`); later calls
|
|
354
|
+
* move the light: what was kept dims, what is newly kept comes back. For
|
|
355
|
+
* terms, bars, rows, cells.
|
|
356
|
+
* - `dim(target, at)` — parts arrive one at a time and the light stays on the
|
|
357
|
+
* newest, so only the part it just left dims. For stages, layers, panels,
|
|
358
|
+
* notes. `target` is a part suffix or an exact `#sid…` selector.
|
|
359
|
+
*
|
|
360
|
+
* `restore(at)` brings everything dimmed back to 1 in one tween, at a scene's
|
|
361
|
+
* last hold. In `lit` mode that is the scope minus what is lit, so it assumes
|
|
362
|
+
* the whole scope is on screen by then — true at a last hold by construction.
|
|
363
|
+
*/
|
|
364
|
+
export interface Spotlight {
|
|
365
|
+
lit(keep: string | readonly string[], at: number): Tween[];
|
|
366
|
+
dim(target: string, at: number): Tween[];
|
|
367
|
+
restore(at: number): Tween[];
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* `scope` is a selector fragment inside the scene — `.term`, `.stage` — and is
|
|
371
|
+
* required by `lit()` only; `dim()` names its targets outright. Every selector
|
|
372
|
+
* this emits begins with `#sid` (`unscoped_gsap_selector`, invariant 3).
|
|
373
|
+
*/
|
|
374
|
+
export declare function spotlighter(sid: string, scope?: string): Spotlight;
|
|
375
|
+
/**
|
|
376
|
+
* The part under discussion stands a little closer.
|
|
377
|
+
*
|
|
378
|
+
* A pair, always used as a pair: `lift` when the part arrives, `settle` when the
|
|
379
|
+
* next one takes over, so nothing is left standing proud at the beat's end. Both
|
|
380
|
+
* carry `immediateRender: false` and start from where the other left off,
|
|
381
|
+
* because an archetype's entrance usually owns this element's first `scale`
|
|
382
|
+
* render already (`pipeline` pops its boxes in) and a second immediate one would
|
|
383
|
+
* apply the end value at build time, over the entrance's start value.
|
|
384
|
+
*
|
|
385
|
+
* `svgOrigin` for an SVG part, named in BOTH halves or GSAP treats the second
|
|
386
|
+
* mention as an origin CHANGE and compensates it with a translate that never
|
|
387
|
+
* unwinds — the defect `grid` and `annotated-figure` each record in their own
|
|
388
|
+
* words.
|
|
389
|
+
*/
|
|
390
|
+
export declare function lift(target: string, at: number, seconds?: number, origin?: string): Tween;
|
|
391
|
+
export declare function settle(target: string, at: number, seconds?: number, origin?: string): Tween;
|
|
392
|
+
/**
|
|
393
|
+
* One `<span class="w">` per word, for a headline that rises word by word
|
|
394
|
+
* (`#sid-t .w`, staggered). Words are `esc()`ed one at a time and rejoined
|
|
395
|
+
* with single spaces, so the line sets exactly as the plain string did and
|
|
396
|
+
* the type floor sees the same size. No ids: the stagger addresses the class.
|
|
397
|
+
*
|
|
398
|
+
* The archetype's CSS must give the class `display: inline-block` — a
|
|
399
|
+
* transform on an inline box is a no-op, and the rise would render as a plain
|
|
400
|
+
* fade with every gate green.
|
|
401
|
+
*/
|
|
402
|
+
export declare function words(text: string, cls?: string): string;
|
package/dist/types/emit/svg.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* that composes these primitives, and putting it here is how a primitive layer
|
|
14
14
|
* turns into six hand-rolled one-offs wearing a shared import.
|
|
15
15
|
*/
|
|
16
|
-
import { type Vars } from "./kit.js";
|
|
16
|
+
import { type Tween, type Vars } from "./kit.js";
|
|
17
17
|
export interface Pt {
|
|
18
18
|
x: number;
|
|
19
19
|
y: number;
|
|
@@ -47,18 +47,54 @@ export declare function n(v: number): string;
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function nv(v: number): number;
|
|
49
49
|
/**
|
|
50
|
-
* The `from` vars of a stroke draw-on,
|
|
50
|
+
* The `from` and `to` vars of a stroke draw-on, measured by the browser.
|
|
51
51
|
*
|
|
52
|
-
* The
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
52
|
+
* The `drawFrom` this replaced needed the emitter to know the path's length at
|
|
53
|
+
* BUILD time —
|
|
54
|
+
* `Math.hypot(knee.x - start.x, knee.y - start.y) + b.w` in annotated-figure,
|
|
55
|
+
* `perimeter(b)` in grid — which is arithmetic about a shape the browser is
|
|
56
|
+
* going to measure anyway, kept in step by hand. DrawSVG asks the path.
|
|
57
|
+
*
|
|
58
|
+
* It also retires a bug the hand-computed version had. GSAP writes
|
|
59
|
+
* `strokeDashoffset` back as an integer px, so a leader of length 720.21 came to
|
|
60
|
+
* rest at an offset of 720 and left 0.21px of dash exposed at the path start,
|
|
61
|
+
* which `stroke-linecap: round` paints as a full-width dot: a yellow dot sat on
|
|
62
|
+
* the grid one reveal before its own region was drawn, inside the frame, above
|
|
63
|
+
* the type floor, and invisible to every gate. That is why the old `drawFrom`
|
|
64
|
+
* rounded up and added a pixel of slack. A percentage has no remainder to leave.
|
|
65
|
+
*
|
|
66
|
+
* Safe under capture because a plugin's `render()` is part of being seeked,
|
|
67
|
+
* unlike a callback — see `DRAWSVG_SRC` in composition.ts for the measurement.
|
|
68
|
+
* Anything drawn this way still obeys invariant 2: it is a `fromTo`, always.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DRAW_FROM: Vars;
|
|
71
|
+
export declare const DRAW_TO: Vars;
|
|
72
|
+
/**
|
|
73
|
+
* Something travels a polyline — a pulse along an arrow, a marker ring along a
|
|
74
|
+
* curve, a highlight down a divider: one `x`/`y` `fromTo` per leg, each leg's
|
|
75
|
+
* share of `seconds` proportional to its length, `ease: "none"` until the last
|
|
76
|
+
* leg, which settles with `power2.out`. Only the first leg renders
|
|
77
|
+
* immediately; every later one is `immediateRender: false` and starts exactly
|
|
78
|
+
* where the leg before it ended, so the route reads as one motion under any
|
|
79
|
+
* seek. `x`/`y` are TRANSLATIONS, so author the traveller at the origin —
|
|
80
|
+
* `<circle r="6">` — and give the route in the same user space as the parts it
|
|
81
|
+
* passes. A transform rather than `attr: { cx, cy }` so one verb moves an SVG
|
|
82
|
+
* dot and an HTML rule alike; not MotionPath, which is not loaded.
|
|
83
|
+
*
|
|
84
|
+
* ONE CALL PER ELEMENT. The first leg's immediate render is the one that
|
|
85
|
+
* (element, x/y) gets; a second route on the same element would need
|
|
86
|
+
* `immediateRender: false` and a `from` equal to the first route's end, which
|
|
87
|
+
* is not where a second route starts. A pulse per arrow, not one pulse reused.
|
|
88
|
+
*
|
|
89
|
+
* Every leg starts where the last one ended, printed at two places, and lint
|
|
90
|
+
* reads `at + duration` back in floating point: 0.1 + 0.2 is 0.30000000000000004,
|
|
91
|
+
* which `overlapping_gsap_tweens` reports as a 4e-17s overlap with the leg at
|
|
92
|
+
* 0.3 (measured on 0.7.90). So a leg whose float sum overshoots its successor
|
|
93
|
+
* is shortened by a hundredth — a rest at the corner under one frame long —
|
|
94
|
+
* and the corner itself, and the arrival at `at + seconds`, stay where they
|
|
95
|
+
* were scheduled.
|
|
60
96
|
*/
|
|
61
|
-
export declare function
|
|
97
|
+
export declare function travel(target: string, route: readonly Pt[], at: number, seconds: number): Tween[];
|
|
62
98
|
/** Scene-scoped element id. Every timeline selector is built from one of these. */
|
|
63
99
|
export declare function id(sid: string, part: string, i?: number): string;
|
|
64
100
|
/**
|
|
@@ -68,13 +104,36 @@ export declare function id(sid: string, part: string, i?: number): string;
|
|
|
68
104
|
* Every archetype sizes its boxes and padding through this function. When two of
|
|
69
105
|
* them disagree about how wide "Reconstruction" is, one of them clips.
|
|
70
106
|
*/
|
|
71
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Which face actually draws a run.
|
|
109
|
+
*
|
|
110
|
+
* A deck that bundles a CJK family sets EVERY run in it, ASCII included, because
|
|
111
|
+
* `fontStack` puts that family ahead of Inter and it covers Latin. So the face is
|
|
112
|
+
* a property of the DECK, not of the characters in one run — and a run of pure
|
|
113
|
+
* ASCII inside a Korean deck is drawn by the Korean face even though nothing in
|
|
114
|
+
* it is Korean.
|
|
115
|
+
*
|
|
116
|
+
* `"latin"` is not "no CJK in this string"; it is "this deck bundles no CJK
|
|
117
|
+
* family". Passing it for a run that does contain CJK would be a lie, which is
|
|
118
|
+
* why the auto-detection below still runs underneath: the parameter can only
|
|
119
|
+
* widen what a run is charged, never narrow it.
|
|
120
|
+
*/
|
|
121
|
+
export type Face = "latin" | "cjk" | "hangul";
|
|
122
|
+
/**
|
|
123
|
+
* The face a theme's stack will use, from the family `familyFor` put in front.
|
|
124
|
+
*
|
|
125
|
+
* Korean is split out because it is the one character the four bundled families
|
|
126
|
+
* disagree about: KR draws the middle dot at 0.561em where JP, SC and TC draw it
|
|
127
|
+
* on the em grid at 1.0.
|
|
128
|
+
*/
|
|
129
|
+
export declare function faceOf(fontStack: string): Face;
|
|
130
|
+
export declare function textWidth(text: string, fontSize: number, weight?: number, tracking?: number, tabular?: boolean, face?: Face): number;
|
|
72
131
|
/**
|
|
73
132
|
* Greedy word wrap to `maxWidth`. A word wider than the line is broken by
|
|
74
133
|
* character — which is also how Korean and Chinese wrap, since they arrive as
|
|
75
134
|
* one unbroken "word".
|
|
76
135
|
*/
|
|
77
|
-
export declare function wrap(text: string, fontSize: number, maxWidth: number, weight?: number, tracking?: number): string[];
|
|
136
|
+
export declare function wrap(text: string, fontSize: number, maxWidth: number, weight?: number, tracking?: number, face?: Face): string[];
|
|
78
137
|
/** The SVG root. Width and height match the viewBox so px and user units agree. */
|
|
79
138
|
export declare function svg(elementId: string, w: number, h: number, body: string): string;
|
|
80
139
|
export declare function rect(b: Box, a?: Attrs): string;
|
|
@@ -100,6 +159,11 @@ export interface TextOptions {
|
|
|
100
159
|
vAlign?: "baseline" | "middle";
|
|
101
160
|
class?: string;
|
|
102
161
|
id?: string;
|
|
162
|
+
/**
|
|
163
|
+
* The face the deck's stack will draw this in. Only consulted when `maxWidth`
|
|
164
|
+
* makes this text wrap — an unwrapped run is measured by nothing here.
|
|
165
|
+
*/
|
|
166
|
+
face?: Face;
|
|
103
167
|
}
|
|
104
168
|
export declare function text(content: string, at: Pt, o: TextOptions): string;
|
|
105
169
|
export interface ArrowOptions {
|
|
@@ -16,6 +16,7 @@ import { type Scene } from "./kit.js";
|
|
|
16
16
|
import type { DeckTheme } from "./themes/index.js";
|
|
17
17
|
export { type DeckTheme, ink, mono, paper, resolveTheme, THEME_NAMES, THEMES, } from "./themes/index.js";
|
|
18
18
|
/** Where `ingest` writes the subsetted font bundle, relative to the deck. */
|
|
19
|
+
export declare const FONT_BUNDLE_DIR = "assets/fonts";
|
|
19
20
|
export declare const FONT_BUNDLE_HREF = "assets/fonts/fonts.css";
|
|
20
21
|
/** A luminance breath. Safe on an element an entrance tween already moves. */
|
|
21
22
|
export declare const BREATHE = "ds-breathe 6s ease-in-out infinite alternate";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Prefs } from "../prefs.js";
|
|
2
|
+
import { type Source, type Storyboard } from "../types.js";
|
|
3
|
+
import { type ImageProvider } from "./providers.js";
|
|
4
|
+
export interface IllustrateOpts {
|
|
5
|
+
prefs: Prefs;
|
|
6
|
+
/** `<dir of source.json>/assets`. Created if missing. */
|
|
7
|
+
assetsDir: string;
|
|
8
|
+
/** Tests inject; the default is `imageChain(prefs.images, resolveImageBackend())`. */
|
|
9
|
+
chain?: ImageProvider[];
|
|
10
|
+
/** Progress lines. Silent by default, for the reason `buildDeck` gives. */
|
|
11
|
+
onStep?: (message: string) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface Illustrated {
|
|
14
|
+
beatId: string;
|
|
15
|
+
figureId: string;
|
|
16
|
+
/** Which rung drew it. */
|
|
17
|
+
provider: string;
|
|
18
|
+
/** The figure's `src`: a file name under `assetsDir`. */
|
|
19
|
+
src: string;
|
|
20
|
+
/** Found on disk from an earlier run; no provider was asked. */
|
|
21
|
+
cached: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Draw every pending brief. Always finishes: the tool rung is last and pure.
|
|
25
|
+
*
|
|
26
|
+
* Returns fresh objects parsed through the schemas; the ones passed in are not
|
|
27
|
+
* touched, so a caller that keeps the old storyboard around still holds the
|
|
28
|
+
* pending one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function illustrate(storyboard: Storyboard, source: Source, opts: IllustrateOpts): Promise<{
|
|
31
|
+
storyboard: Storyboard;
|
|
32
|
+
source: Source;
|
|
33
|
+
illustrated: Illustrated[];
|
|
34
|
+
}>;
|