@jokerized/decksmith 0.7.0 → 0.9.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 +144 -8
- package/dist/cli.js +4582 -712
- package/dist/deck-runtime.js +70 -2
- package/dist/index.js +4608 -708
- package/dist/mcp.js +4254 -759
- 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/equation-morph.d.ts +40 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +60 -1
- package/dist/types/emit/archetypes/grid.d.ts +0 -14
- 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 +47 -2
- 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 +104 -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/tex.d.ts +39 -0
- 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 +104 -0
- package/dist/types/emit/type.d.ts +111 -0
- package/dist/types/index.d.ts +39 -2
- package/dist/types/plan/direct.d.ts +66 -0
- package/dist/types/plan/prompt.d.ts +0 -12
- package/dist/types/prefs.d.ts +15 -1
- package/dist/types/render/capture.d.ts +24 -0
- package/dist/types/render/timing.d.ts +16 -0
- package/dist/types/source/fonts.d.ts +11 -1
- package/dist/types/types.d.ts +38 -0
- package/dist/types/verify/budget.d.ts +8 -1
- package/dist/types/verify/fidelity.d.ts +53 -0
- package/dist/types/verify/fill.d.ts +205 -0
- package/dist/types/verify/index.d.ts +17 -0
- package/dist/types/verify/motion.d.ts +33 -0
- package/package.json +4 -1
|
@@ -10,7 +10,14 @@ import type { Term } from "../../types.js";
|
|
|
10
10
|
import type { Emitter, Theme } from "../kit.js";
|
|
11
11
|
export declare const OPTS = "{ displayMode: true, trust: function (c) { return c.command === \"\\\\htmlClass\"; }, strict: false, output: \"html\" }";
|
|
12
12
|
export declare const INLINE_OPTS = "{ displayMode: false, trust: function (c) { return c.command === \"\\\\htmlClass\"; }, strict: false, output: \"html\" }";
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Where `term` sits in `tex`, comparing normal forms. Null when it is absent.
|
|
15
|
+
*
|
|
16
|
+
* The span starts at the first matched character's token — with the `\left`
|
|
17
|
+
* that sizes it, when the term opens on a sized delimiter — and ends where the
|
|
18
|
+
* last matched character's token ends. Never further: whatever follows the term
|
|
19
|
+
* in the equation, a `\right` included, is not the term's.
|
|
20
|
+
*/
|
|
14
21
|
export declare function locate(tex: string, term: string): {
|
|
15
22
|
start: number;
|
|
16
23
|
end: number;
|
|
@@ -36,7 +43,47 @@ cls?: (t: Term) => string): {
|
|
|
36
43
|
tex: string;
|
|
37
44
|
used: Term[];
|
|
38
45
|
missing: Term[];
|
|
46
|
+
broken: Term[];
|
|
39
47
|
};
|
|
48
|
+
/** What `prepareWalk` decided about one equation and its terms. */
|
|
49
|
+
export interface PreparedWalk {
|
|
50
|
+
/** The TeX to render, repaired and term-wrapped. Absent when `plain`. */
|
|
51
|
+
tex?: string;
|
|
52
|
+
/** The repaired TeX without wrappers, for measuring. The source TeX when `plain`. */
|
|
53
|
+
raw: string;
|
|
54
|
+
/** Set when KaTeX refuses the equation even after repair. */
|
|
55
|
+
plain?: {
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
used: Term[];
|
|
59
|
+
missing: Term[];
|
|
60
|
+
notes: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Everything the walk needs to draw one equation, decided in Node — so `plan`
|
|
64
|
+
* can report it and `build` cannot ship a `katex.render` call that throws.
|
|
65
|
+
*
|
|
66
|
+
* The equation is parsed and, if KaTeX refuses it, repaired (`repairTex`). Each
|
|
67
|
+
* term is repaired the same way, so a term spelled with the paper's own macro
|
|
68
|
+
* still finds it once both are drawn as names. Then the terms are wrapped, each
|
|
69
|
+
* wrapping parsed before it is kept.
|
|
70
|
+
*
|
|
71
|
+
* An equation no repair makes parseable is DEGRADED, not dropped and not
|
|
72
|
+
* shipped: `plain` is set and the slide shows the TeX source as text, with the
|
|
73
|
+
* terms still marked so the walk still walks. `notes` says, in words, every
|
|
74
|
+
* repair, degradation and dropped term — `plan` prints them and `build` reports
|
|
75
|
+
* them as the beat's warnings.
|
|
76
|
+
*/
|
|
77
|
+
export declare function prepareWalk(eq: {
|
|
78
|
+
id: string;
|
|
79
|
+
tex: string;
|
|
80
|
+
}, terms: readonly Term[], beatId: string): PreparedWalk;
|
|
81
|
+
/**
|
|
82
|
+
* A legend chip's contents: the term's TeX for KaTeX when it parses inline
|
|
83
|
+
* (repaired if it had to be), and its source as plain text when it does not —
|
|
84
|
+
* a chip is where a viewer reads WHICH symbol, so it is never left empty.
|
|
85
|
+
*/
|
|
86
|
+
export declare function chipSetup(sid: string, term: Term): string;
|
|
40
87
|
/**
|
|
41
88
|
* Display equations want to live between 68px and 108px.
|
|
42
89
|
*
|
|
@@ -58,6 +105,18 @@ cls?: (t: Term) => string): {
|
|
|
58
105
|
export declare function equationSize(tex: string): number;
|
|
59
106
|
/** Estimated rendered width of a display, in ems. */
|
|
60
107
|
export declare function texUnits(tex: string): number;
|
|
108
|
+
/**
|
|
109
|
+
* Grow each legend chip to hold the glyphs KaTeX set in it.
|
|
110
|
+
*
|
|
111
|
+
* A chip is a painted box with 2px of vertical padding, and the layout gate
|
|
112
|
+
* measures text against the nearest painted box. `x^{\text{gt}}_{<i}` or a
|
|
113
|
+
* `\frac` sets glyph boxes above and below the line box, so the chip failed
|
|
114
|
+
* `text_box_overflow` on its own superscript — measured on HypePaper deck
|
|
115
|
+
* 0ef77cae: the `gt` sat 10px above the chip's top edge. Padding is added only
|
|
116
|
+
* where a glyph box actually crosses an edge, so a chip that already held its
|
|
117
|
+
* term is untouched.
|
|
118
|
+
*/
|
|
119
|
+
export declare function chipFit(sid: string, terms: Term[]): string[];
|
|
61
120
|
/** One row per term: a chip KaTeX fills in `setup`, and the label. Shared with the morph. */
|
|
62
121
|
export declare function legendRows(sid: string, terms: Term[], theme: Theme): string;
|
|
63
122
|
export declare function legendCss(theme: Theme): string;
|
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A field, and the windows that move over it.
|
|
3
|
-
*
|
|
4
|
-
* Attention spans, receptive fields, patches, pooling kernels, token sequences —
|
|
5
|
-
* every paper draws these as a rectangle over a lattice, and every slide deck
|
|
6
|
-
* then writes "8x8 shifted windows" in a bullet and calls it explained. The
|
|
7
|
-
* content of the idea is *which cells* and *how many of them*, and a sentence
|
|
8
|
-
* cannot show that; a lattice with the rectangle lit on it shows nothing else.
|
|
9
|
-
*
|
|
10
|
-
* Everything here is solved from the box that is actually free after the chrome
|
|
11
|
-
* and the note, so a 24x16 feature map and a 2x1 pair of patches are the same
|
|
12
|
-
* code. A constant cell size would be right for one grid and wrong for the other
|
|
13
|
-
* twenty-three, and nobody would ever find out which.
|
|
14
|
-
*/
|
|
15
1
|
import type { Emitter } from "../kit.js";
|
|
16
2
|
export declare const grid: Emitter<"grid">;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A chart the source never drew.
|
|
3
|
-
*
|
|
4
|
-
* Papers report sweeps as tables and leave the shape of the curve to the reader.
|
|
5
|
-
* Drawing it is the whole point of this archetype, so everything here is derived
|
|
6
|
-
* from the data — the scale especially. A hardcoded axis is how a chart ends up
|
|
7
|
-
* asserting something the numbers do not.
|
|
8
|
-
*/
|
|
9
1
|
import type { Emitter } from "../kit.js";
|
|
10
2
|
export interface Scale {
|
|
11
3
|
min: number;
|
|
@@ -43,6 +43,8 @@ type Loop = {
|
|
|
43
43
|
export interface PipeLayout {
|
|
44
44
|
/** Label type size. Never below `MIN_FONT`, whatever the labels cost. */
|
|
45
45
|
size: number;
|
|
46
|
+
/** A stage note's type size: `NOTE` in classic, grown with the label in v2. */
|
|
47
|
+
note: number;
|
|
46
48
|
/**
|
|
47
49
|
* Position and extent along the flow axis: `x`/`w` read as x/width across a
|
|
48
50
|
* row, and as y/height down a column. One field rather than two so the emitter
|
|
@@ -69,6 +71,18 @@ export interface PipeLayout {
|
|
|
69
71
|
loopW: number;
|
|
70
72
|
svgH: number;
|
|
71
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* v2: how far the diagram may grow, and into what. Absent is v0.8.0 exactly.
|
|
76
|
+
*
|
|
77
|
+
* `budget` is the height the SVG may take (the region less the wrapper's margin
|
|
78
|
+
* and the note); `region` is the whole body region, which bounds a box's height
|
|
79
|
+
* at `GROWN_BOX_H` of it — the plan's `MAX_BOX_H = 0.8·region.h`. The label's
|
|
80
|
+
* preferred size grows by `GROWTH` and `fitBoxes` still shrinks it to the width.
|
|
81
|
+
*/
|
|
82
|
+
export interface PipeGrow {
|
|
83
|
+
budget: number;
|
|
84
|
+
region: number;
|
|
85
|
+
}
|
|
72
86
|
/**
|
|
73
87
|
* All the geometry, with no SVG in sight.
|
|
74
88
|
*
|
|
@@ -76,6 +90,6 @@ export interface PipeLayout {
|
|
|
76
90
|
* archetype must never get wrong, and asserting it against numbers beats
|
|
77
91
|
* asserting it against a rendered string.
|
|
78
92
|
*/
|
|
79
|
-
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop, face?: Face): PipeLayout;
|
|
93
|
+
export declare function pipeLayout(stageW: number, stages: readonly Stage[], loop?: Loop, face?: Face, grow?: PipeGrow): PipeLayout;
|
|
80
94
|
export declare const pipeline: Emitter<"pipeline">;
|
|
81
95
|
export {};
|
|
@@ -109,6 +109,6 @@ export interface StackLayout {
|
|
|
109
109
|
* composition changes rather than the type: the note moves beside its label, on
|
|
110
110
|
* the same line, right-aligned against the spine.
|
|
111
111
|
*/
|
|
112
|
-
export declare function stackLayout(p: Params, format: Format, face?: Face): StackLayout;
|
|
112
|
+
export declare function stackLayout(p: Params, format: Format, face?: Face, grow?: boolean): StackLayout;
|
|
113
113
|
export declare const stack: Emitter<"stack">;
|
|
114
114
|
export {};
|
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* timeline, wraps whatever the emitter returns, and closes the document.
|
|
12
12
|
*/
|
|
13
13
|
import type { z } from "zod";
|
|
14
|
+
import { type Direction } from "../plan/direct.js";
|
|
14
15
|
import { type Cut } from "../plan/select.js";
|
|
15
|
-
import type { Format, NarrationCanvas, Source, Storyboard, segmentSchema } from "../types.js";
|
|
16
|
+
import type { Design, Format, NarrationCanvas, Source, Storyboard, segmentSchema } from "../types.js";
|
|
17
|
+
import type { FitManifest } from "./fit.js";
|
|
16
18
|
import { type Scene } from "./kit.js";
|
|
17
19
|
/** A built deck. `page` is present only for navigable formats. */
|
|
18
20
|
export interface Deck {
|
|
@@ -27,6 +29,17 @@ export interface Deck {
|
|
|
27
29
|
* PASS. `build` prints every casualty's reason; see src/cli.ts.
|
|
28
30
|
*/
|
|
29
31
|
cut: Cut;
|
|
32
|
+
/**
|
|
33
|
+
* v2 only: each drawn scene's predicted fill, for `fit.json`. Absent on a
|
|
34
|
+
* classic build, which writes no manifest and so is not graded for fill.
|
|
35
|
+
*/
|
|
36
|
+
fit?: FitManifest;
|
|
37
|
+
/**
|
|
38
|
+
* `--design v2` only: the look the Director chose for each kept beat, and the
|
|
39
|
+
* deck's sameness numbers. `build` writes it to `out/look.json`. Absent on a
|
|
40
|
+
* classic build.
|
|
41
|
+
*/
|
|
42
|
+
looks?: Direction;
|
|
30
43
|
}
|
|
31
44
|
type Segment = z.infer<typeof segmentSchema>;
|
|
32
45
|
/**
|
|
@@ -57,6 +70,15 @@ export interface DeckNarration {
|
|
|
57
70
|
export interface DeckOptions {
|
|
58
71
|
/** Overrides `storyboard.theme`. Any name in the registry. */
|
|
59
72
|
theme?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Overrides `storyboard.design`. Absent or `classic` emits v0.8.0's bytes.
|
|
75
|
+
* `v2` marks deck.html for the v2 player (`markV2`), lets the archetypes grow
|
|
76
|
+
* into their regions and fills `Deck.fit` (see `../emit/fit.ts`), and runs
|
|
77
|
+
* the Director (src/plan/direct.ts) over the kept beats so each is drawn in
|
|
78
|
+
* the look it chose (src/emit/look.ts), and turns on the motion grammar in
|
|
79
|
+
* `motion.ts`: varied entrances, seams and emphasis during narration.
|
|
80
|
+
*/
|
|
81
|
+
design?: Design;
|
|
60
82
|
/** Multiplies every duration, hold, and beat length. 1 leaves bytes untouched. */
|
|
61
83
|
speed?: number;
|
|
62
84
|
narration?: DeckNarration;
|
|
@@ -118,7 +140,28 @@ export interface DeckOptions {
|
|
|
118
140
|
* and because HyperFrames' own navigation is dead at 0.7.71/0.7.72 and still
|
|
119
141
|
* dead at 0.8.27 as at 0.7.90 (EXPERIMENT-003), so the presented one has to be ours.
|
|
120
142
|
*/
|
|
121
|
-
export declare function emitDeck(storyboard: Storyboard, source: Source, format: Format, runtimeJs: string,
|
|
143
|
+
export declare function emitDeck(storyboard: Storyboard, source: Source, format: Format, runtimeJs: string, options?: DeckOptions): Deck;
|
|
144
|
+
/**
|
|
145
|
+
* How a v2 deck breaks and tracks CJK text. Empty for any other language.
|
|
146
|
+
*
|
|
147
|
+
* KOREAN BREAKS BETWEEN WORDS. Browsers break Hangul between any two syllables
|
|
148
|
+
* by default, and v2's narrow rail headlines and grown text made that happen on
|
|
149
|
+
* most rail slides: "생성 조건/인", "가장 높은 종/합 점수", "서로 다/른" (review,
|
|
150
|
+
* 2026-10-08: 8 of 8 rail headlines on one deck, against 1 break in total on
|
|
151
|
+
* the classic layouts). `keep-all` breaks only at spaces — which is also how
|
|
152
|
+
* `wrap` in svg.ts has always MEASURED Korean, so the layout's line counts and
|
|
153
|
+
* the browser's now agree — and `break-word` still breaks a single word wider
|
|
154
|
+
* than its line rather than letting it overflow, as `wrap` does.
|
|
155
|
+
*
|
|
156
|
+
* JAPANESE AND CHINESE keep breaking between characters, under the strict
|
|
157
|
+
* kinsoku rules, so a line never opens on small kana or closing punctuation.
|
|
158
|
+
*
|
|
159
|
+
* NO TRACKING ON A CJK EYEBROW. Letter-spacing made for Latin capitals pulls
|
|
160
|
+
* Hangul and kanji apart until "생성 결과의 실패" reads "생 성 결 과 의 실 패".
|
|
161
|
+
* Tracking only ever made the eyebrow wider, so dropping it never adds a line
|
|
162
|
+
* the chrome was not charged for.
|
|
163
|
+
*/
|
|
164
|
+
export declare function cjkTypesetCss(lang: string): string;
|
|
122
165
|
/**
|
|
123
166
|
* The beats this format actually draws, and why the rest are not there.
|
|
124
167
|
*
|
|
@@ -228,4 +271,6 @@ export declare const PLAYER_FILE = "hyperframes-player.global.js";
|
|
|
228
271
|
* determinism scan above all — do not apply to it.
|
|
229
272
|
*/
|
|
230
273
|
export declare const DECK_PAGE = "deck.html";
|
|
274
|
+
/** A literal `</script>` inside the bundle would close the tag that carries it. */
|
|
275
|
+
export declare function closeSafe(code: string): string;
|
|
231
276
|
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `node scripts/measure-faces.mjs --write`. Do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Advance tables for the Latin faces the v2 style packs set, measured in Chrome
|
|
5
|
+
* from the vendored @fontsource-variable packages — the files a built deck
|
|
6
|
+
* ships. Same recipe as Inter's `ADVANCE` in svg.ts: isolated advances with
|
|
7
|
+
* kerning off, max over 400/500/600/700 after dividing by that weight's factor,
|
|
8
|
+
* rounded up. `test/svg.test.ts` holds the result against drawn widths read
|
|
9
|
+
* out of the same browser. Re-run the script, never re-type a number.
|
|
10
|
+
*/
|
|
11
|
+
export interface FaceMetrics {
|
|
12
|
+
/** The CSS family a deck names it by — what `vendorFace` declares. */
|
|
13
|
+
family: string;
|
|
14
|
+
/** Mean advance ratio against 400, per weight the type scales use. */
|
|
15
|
+
weight: Readonly<Record<400 | 500 | 600 | 700, number>>;
|
|
16
|
+
tabularFigure: number;
|
|
17
|
+
tabularSeparator: number;
|
|
18
|
+
kernSlack: number;
|
|
19
|
+
advance: Readonly<Record<string, number>>;
|
|
20
|
+
}
|
|
21
|
+
export type MeasuredFace = "source-serif-4" | "space-grotesk" | "ibm-plex-sans";
|
|
22
|
+
export declare const FACE_METRICS: Readonly<Record<MeasuredFace, FaceMetrics>>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FIT ENGINE'S VOCABULARY — what "full" means, in one place, for both the
|
|
3
|
+
* emitter that predicts it and the gate that measures it.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Every archetype's solver could only SHRINK: `BAR_MAX = 96`,
|
|
6
|
+
* `MAX_BOX_H = 340`, a 50px claim, panels capped at their content plus a fifth.
|
|
7
|
+
* Whatever the region had left over, `.scene`'s `justify-content: center` turned
|
|
8
|
+
* into dead air above and below the slide. Measured on v0.8.0 decks (redesign
|
|
9
|
+
* plan 2026-10-07 §1): bar-compare and pipeline fill 51% of the canvas, and
|
|
10
|
+
* about 39% of final holds are hollow. Under `design: "v2"` an archetype GROWS
|
|
11
|
+
* into its region instead — type, marks and gaps, each up to its own cap — and
|
|
12
|
+
* reports how full it predicts the result to be.
|
|
13
|
+
*
|
|
14
|
+
* THE MEASURE: MAIN-AXIS FILL. The body region is the content box below the
|
|
15
|
+
* chrome: `contentW` wide, `contentH - chromeHeight` tall (`bodyRegion`). The
|
|
16
|
+
* main axis is the VERTICAL one, because that is the axis `.scene` stacks the
|
|
17
|
+
* chrome and the body along and therefore the axis every pixel of centring slack
|
|
18
|
+
* lands on — width is already spent by every landscape archetype (bar rails, a
|
|
19
|
+
* pipeline row, split columns all run the full measure), which is exactly why a
|
|
20
|
+
* width-or-height maximum called those slides FULL while they read as a thin
|
|
21
|
+
* band through the middle. So:
|
|
22
|
+
*
|
|
23
|
+
* fill = (painted extent of the body, top to bottom) / (region height)
|
|
24
|
+
*
|
|
25
|
+
* The body's own top margin is not ink, so a body that uses every pixel it is
|
|
26
|
+
* given reads a little under 1. `verify/fill.ts` measures the same ratio on the
|
|
27
|
+
* rendered frame — pixels, not DOM boxes — and `fit.json` carries this file's
|
|
28
|
+
* prediction beside it, so the model is checked against the browser on every v2
|
|
29
|
+
* build rather than trusted (AGENTS.md: a gate passing is not evidence).
|
|
30
|
+
*
|
|
31
|
+
* THE BANDS are ResearchStudio-Reel's (2607.04438 §A2), with the action the
|
|
32
|
+
* plan assigns each: EMPTY < 0.70 (grow, or the next variant), SPARSE < 0.90
|
|
33
|
+
* (grow to the caps, spread gaps), FULL ≤ 1.00 (leave it), SPILLAGE ≤ 1.10
|
|
34
|
+
* (shrink gaps, then type, toward 40), OVERFLOW beyond (refuse, as today).
|
|
35
|
+
*
|
|
36
|
+
* PURE ARITHMETIC, AND IMPORTS NOTHING FROM AN ARCHETYPE. `title.ts` owns the
|
|
37
|
+
* chrome's type scale and every archetype imports it; a helper here that
|
|
38
|
+
* imported it back would be a cycle. Callers pass the region they computed with
|
|
39
|
+
* `bodyBudget`.
|
|
40
|
+
*/
|
|
41
|
+
import type { Design } from "../types.js";
|
|
42
|
+
/** Below this the frame reads as hollow: a band through the middle of the slide. */
|
|
43
|
+
export declare const EMPTY_BELOW = 0.7;
|
|
44
|
+
/** At or above this the region is used; growth stops being worth its cost. */
|
|
45
|
+
export declare const FULL_AT = 0.9;
|
|
46
|
+
/** Past the region's bottom edge, into the padding. */
|
|
47
|
+
export declare const SPILL_ABOVE = 1;
|
|
48
|
+
/** Past this a layout is not tight, it is wrong. */
|
|
49
|
+
export declare const OVERFLOW_ABOVE = 1.1;
|
|
50
|
+
export type FillBand = "empty" | "sparse" | "full" | "spillage" | "overflow";
|
|
51
|
+
/** Which of the five bands a main-axis fill falls in. */
|
|
52
|
+
export declare function fillBand(fill: number): FillBand;
|
|
53
|
+
/**
|
|
54
|
+
* What an archetype predicts about its own final hold, in reference px.
|
|
55
|
+
*
|
|
56
|
+
* In memory only — `Scene.fit`, like `Scene.parts`, is never serialised into the
|
|
57
|
+
* composition, so a classic build cannot move a byte because of it. The shell
|
|
58
|
+
* collects it into `fit.json` for v2 builds (`FIT_FILE`).
|
|
59
|
+
*/
|
|
60
|
+
export interface Fit {
|
|
61
|
+
/** `ink / region`, rounded to three places (invariant 10's precision). */
|
|
62
|
+
fill: number;
|
|
63
|
+
/** Height of the body region: content box below the chrome. */
|
|
64
|
+
region: number;
|
|
65
|
+
/** Predicted painted extent of the body, top of its first mark to bottom of its last. */
|
|
66
|
+
ink: number;
|
|
67
|
+
}
|
|
68
|
+
/** A prediction, rounded once here so two builds of one storyboard agree to the byte. */
|
|
69
|
+
export declare function fitOf(ink: number, region: number): Fit;
|
|
70
|
+
/**
|
|
71
|
+
* The growth factor the plan names for type and marks: `min(constant × G, …)`.
|
|
72
|
+
*
|
|
73
|
+
* 1.6, from density §2.1 — a 50px claim may set at 72 at most (50 × 1.44 is
|
|
74
|
+
* under it), a 96px bar at 153, a 46px bar label at 73. Each archetype applies
|
|
75
|
+
* it to its OWN caps and then still has to fit the region; G is a ceiling on
|
|
76
|
+
* how far a short beat may be blown up, not a target.
|
|
77
|
+
*/
|
|
78
|
+
export declare const GROWTH = 1.6;
|
|
79
|
+
/**
|
|
80
|
+
* The largest scale in `[lo, hi]` whose layout height fits `budget`.
|
|
81
|
+
*
|
|
82
|
+
* `height(s)` must be monotone non-decreasing in `s`, which every layout here is:
|
|
83
|
+
* bigger type wraps to at least as many lines and each line is taller. Bisection
|
|
84
|
+
* to 1/128 of a step, then FLOORED onto that grid, so the answer is a short
|
|
85
|
+
* decimal that prints identically on every build (invariant 10) and never
|
|
86
|
+
* exceeds the budget it was solved for. `lo` is returned when even `lo` does not
|
|
87
|
+
* fit — the caller's own overflow refusal is what handles that case, exactly as
|
|
88
|
+
* it did before growth existed.
|
|
89
|
+
*/
|
|
90
|
+
export declare function growToFit(height: (scale: number) => number, budget: number, lo?: number, hi?: number): number;
|
|
91
|
+
/**
|
|
92
|
+
* The share of its real measure v2 sets HTML text against when it predicts a
|
|
93
|
+
* line count it is about to GROW into.
|
|
94
|
+
*
|
|
95
|
+
* `wrap` is a width table, and the browser is a line breaker with rules the
|
|
96
|
+
* table does not know: kinsoku keeps `、` and `。` off the start of a line, so a
|
|
97
|
+
* Japanese sentence the table sets on three lines of eleven sets on four of ten.
|
|
98
|
+
* Classic got away with it because nothing it drew was sized to the last line —
|
|
99
|
+
* a 50px claim in a 560px column had air. Growth removes the air on purpose, so
|
|
100
|
+
* a miss of one line becomes a caption pushed 105px through the bottom of the
|
|
101
|
+
* slide: MEASURED on six ja/zh claim-figure and callout beats at 1.08–1.11 fill
|
|
102
|
+
* before this existed. SVG text is exempt — `text()` places its own lines from
|
|
103
|
+
* the same `wrap`, so there the prediction IS the drawing.
|
|
104
|
+
*/
|
|
105
|
+
export declare const MEASURE_SLACK = 0.92;
|
|
106
|
+
/** Whether this emit is the v2 vocabulary. Absent means classic: every test context, every older caller. */
|
|
107
|
+
export declare function isV2(ctx: {
|
|
108
|
+
design?: Design;
|
|
109
|
+
}): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* The manifest a v2 build writes beside `index.html`, and `verify` reads back.
|
|
112
|
+
*
|
|
113
|
+
* Its PRESENCE is what marks a deck as v2 to the gate: `verify` runs on a deck
|
|
114
|
+
* directory, and the only trustworthy statement about how that deck was built is
|
|
115
|
+
* the one the build wrote into it — the same reasoning `readReserve` gives for
|
|
116
|
+
* `timing.json`.
|
|
117
|
+
*/
|
|
118
|
+
export declare const FIT_FILE = "fit.json";
|
|
119
|
+
export interface FitManifest {
|
|
120
|
+
design: "v2";
|
|
121
|
+
scenes: {
|
|
122
|
+
id: string;
|
|
123
|
+
beat: string;
|
|
124
|
+
archetype: string;
|
|
125
|
+
fit?: Fit;
|
|
126
|
+
}[];
|
|
127
|
+
}
|
package/dist/types/emit/kit.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* and nothing else. Adding a domain means adding an emitter — the shell never
|
|
7
7
|
* learns what a camera frustum or an orderbook is.
|
|
8
8
|
*/
|
|
9
|
-
import type { Archetype, BeatOf, Format, Source } from "../types.js";
|
|
9
|
+
import type { Archetype, BeatOf, Design, Format, Source } from "../types.js";
|
|
10
|
+
import type { Fit } from "./fit.js";
|
|
11
|
+
import type { Look } from "./look.js";
|
|
10
12
|
/**
|
|
11
13
|
* `.scene`'s padding, in reference px. `contentW`/`contentH` are what is left.
|
|
12
14
|
*
|
|
@@ -158,6 +160,35 @@ export interface Theme {
|
|
|
158
160
|
d: string;
|
|
159
161
|
};
|
|
160
162
|
fontStack: string;
|
|
163
|
+
/**
|
|
164
|
+
* The chrome's stack — eyebrow, headline, the title slide's headline — when a
|
|
165
|
+
* v2 style pack sets it in a face of its own. Absent means `fontStack`, which
|
|
166
|
+
* is every classic theme. Which spec it belongs to is read off `fontStack`
|
|
167
|
+
* (`faceOf`), so the two are declared together by the pack (`src/emit/type.ts`).
|
|
168
|
+
*/
|
|
169
|
+
displayStack?: string;
|
|
170
|
+
/**
|
|
171
|
+
* A v2 style pack's component forms: how it draws the parts every deck has,
|
|
172
|
+
* so two packs differ in FORM and not only in colour (review, 2026-10-08:
|
|
173
|
+
* "one template in four colours"). Absent on every classic theme, which is
|
|
174
|
+
* what keeps a classic build's bytes v0.8.0's.
|
|
175
|
+
*/
|
|
176
|
+
forms?: PackForms;
|
|
177
|
+
}
|
|
178
|
+
/** How a pack marks a list item. `tick` is the classic coloured bar. */
|
|
179
|
+
export type ListForm = "tick" | "number" | "dot" | "card" | "rule";
|
|
180
|
+
export interface PackForms {
|
|
181
|
+
list?: ListForm;
|
|
182
|
+
/**
|
|
183
|
+
* Which looks this pack leans towards, as score bonuses the Director adds
|
|
184
|
+
* (src/plan/direct.ts): by placement, and by `archetype:variant`. So the
|
|
185
|
+
* same storyboard opens differently under two packs instead of every deck's
|
|
186
|
+
* second slide being the same two-column comparison.
|
|
187
|
+
*/
|
|
188
|
+
affinity?: {
|
|
189
|
+
placement?: Partial<Record<"top" | "rail" | "foot", number>>;
|
|
190
|
+
variant?: Readonly<Record<string, number>>;
|
|
191
|
+
};
|
|
161
192
|
}
|
|
162
193
|
export interface EmitContext {
|
|
163
194
|
source: Source;
|
|
@@ -202,6 +233,24 @@ export interface EmitContext {
|
|
|
202
233
|
* pass exists to measure holds, and every scene it emits is thrown away.
|
|
203
234
|
*/
|
|
204
235
|
start: number;
|
|
236
|
+
/**
|
|
237
|
+
* `prefs.design`. ABSENT MEANS CLASSIC, and that is the contract that keeps
|
|
238
|
+
* v0.8.0's bytes: every hand-built test context, `narrate`, `refs` and
|
|
239
|
+
* `timing` omit it, and an emitter reads it only through `isV2` in `./fit.ts`.
|
|
240
|
+
* Those last three only count stops, and stop counts are identical under both
|
|
241
|
+
* designs by construction (test/fit.test.ts holds it), so they do not need it.
|
|
242
|
+
*/
|
|
243
|
+
design?: Design;
|
|
244
|
+
/**
|
|
245
|
+
* `--design v2` only: the arrangement the Director chose for this beat — a
|
|
246
|
+
* body variant and where the chrome sits. See src/emit/look.ts.
|
|
247
|
+
*
|
|
248
|
+
* ABSENT ON EVERY CLASSIC BUILD, and absent means the classic scene byte for
|
|
249
|
+
* byte. The measuring passes (`planCut`, `narrate`, `timing`, `refs`) never set
|
|
250
|
+
* it either, which is safe because a look may change geometry and never time:
|
|
251
|
+
* the Director rejects any look whose holds or chrome landing differ.
|
|
252
|
+
*/
|
|
253
|
+
look?: Look;
|
|
205
254
|
}
|
|
206
255
|
/**
|
|
207
256
|
* A value in a GSAP vars payload that is JavaScript rather than data.
|
|
@@ -389,6 +438,29 @@ export interface Scene {
|
|
|
389
438
|
holds: number[];
|
|
390
439
|
/** CSS this archetype needs. Deduplicated by the shell, emitted once. */
|
|
391
440
|
css?: string;
|
|
441
|
+
/**
|
|
442
|
+
* The archetype's own prediction of how full its final hold is — v2 only.
|
|
443
|
+
* In memory, never serialised into the composition (like `parts`); the shell
|
|
444
|
+
* collects it into `fit.json` and `verify/fill.ts` holds it against the
|
|
445
|
+
* browser. See `./fit.ts` for what the number means.
|
|
446
|
+
*/
|
|
447
|
+
fit?: Fit;
|
|
448
|
+
/**
|
|
449
|
+
* How much of its body box this scene's body fills along the axis it grows
|
|
450
|
+
* on, 0–1: the drawn height over the height it was given. Optional, in-memory
|
|
451
|
+
* only — never serialised, so it moves no byte. The `--design v2` Director
|
|
452
|
+
* prefers the look that fills more (src/plan/direct.ts); an emitter that does
|
|
453
|
+
* not report it is scored as neither full nor hollow.
|
|
454
|
+
*/
|
|
455
|
+
fill?: number;
|
|
456
|
+
/**
|
|
457
|
+
* The area, in reference px², at which this scene paints the paper's own
|
|
458
|
+
* figure — the image, not the white plate around it. In-memory only. The
|
|
459
|
+
* Director refuses a look that shrinks it well below what the classic look
|
|
460
|
+
* gives (`FIGURE_FLOOR` in src/plan/direct.ts): a figure is often the most
|
|
461
|
+
* informative thing in the deck, and v2 was trading it for a rail headline.
|
|
462
|
+
*/
|
|
463
|
+
figureArea?: number;
|
|
392
464
|
}
|
|
393
465
|
export type Emitter<A extends Archetype> = (beat: BeatOf<A>, ctx: EmitContext) => Scene;
|
|
394
466
|
/** Escape text destined for HTML text content or a quoted attribute. */
|
|
@@ -496,4 +568,34 @@ export declare function settle(target: string, at: number, seconds?: number, ori
|
|
|
496
568
|
* transform on an inline box is a no-op, and the rise would render as a plain
|
|
497
569
|
* fade with every gate green.
|
|
498
570
|
*/
|
|
499
|
-
export declare function words(text: string, cls?: string
|
|
571
|
+
export declare function words(text: string, cls?: string, opts?: {
|
|
572
|
+
unspaced?: boolean;
|
|
573
|
+
}): string;
|
|
574
|
+
/**
|
|
575
|
+
* A word-by-word stagger that lands by the stop, however many atoms there are.
|
|
576
|
+
*
|
|
577
|
+
* The rise is timed against a fixed hold, so its last atom must start in the
|
|
578
|
+
* window eight words used to: a CJK claim split per character is 30-60 atoms,
|
|
579
|
+
* and at a flat 0.05s the second half of the sentence would still be invisible
|
|
580
|
+
* when the deck stopped on it. Past `full` atoms the spacing shrinks so the
|
|
581
|
+
* spread stays `base · (full − 1)`. v2 only; classic keeps its flat stagger.
|
|
582
|
+
*/
|
|
583
|
+
export declare function staggerFor(count: number, base: number, full?: number): number;
|
|
584
|
+
/**
|
|
585
|
+
* The units `words(…, { unspaced: true })` sets, each an `inline-block` that
|
|
586
|
+
* rises on its own and is never broken across a line.
|
|
587
|
+
*
|
|
588
|
+
* WHY A CJK RUN IS NOT ONE ATOM. Split on whitespace alone, "每个查询只选取最相关的"
|
|
589
|
+
* is a single inline-block: it cannot wrap inside itself, so it jumps whole to
|
|
590
|
+
* its own line, and a mixed claim came out "每个查询只选取最 / 相关的 / k 个历史帧;不同 /
|
|
591
|
+
* token / …" (review, 2026-10-08, zh s10). Han and kana break between any two
|
|
592
|
+
* characters, so here each character is its own atom, Latin inside the run
|
|
593
|
+
* ("token", "k") stays one, and kinsoku punctuation rides on its neighbour.
|
|
594
|
+
* Hangul is spaced like Latin and stays word by word.
|
|
595
|
+
*
|
|
596
|
+
* `spaced`: whether whitespace separated this atom from the one before it.
|
|
597
|
+
*/
|
|
598
|
+
export declare function wordAtoms(text: string): {
|
|
599
|
+
text: string;
|
|
600
|
+
spaced: boolean;
|
|
601
|
+
}[];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WHERE THE HEADLINE SITS, AND WHICH ARRANGEMENT A BEAT'S BODY TAKES — the
|
|
3
|
+
* `--design v2` layout vocabulary. Nothing here runs on a classic build.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Measured over the 219 storyboards HypePaper built 2026-10-04..07
|
|
6
|
+
* (3,536 beats): four archetypes are 72% of every beat, each has exactly one
|
|
7
|
+
* arrangement, and 92.7% of scenes open on the same eyebrow + 64px headline block
|
|
8
|
+
* across the top of the slide. The founder's complaint — "the designs are always
|
|
9
|
+
* the same" — is that number. A `Look` is the two choices that change it without
|
|
10
|
+
* touching what a beat SAYS:
|
|
11
|
+
*
|
|
12
|
+
* - `placement`: where the chrome (eyebrow + headline) sits. `top` is today's
|
|
13
|
+
* slide; `rail` sets it in a left column with the body beside it; `foot` puts
|
|
14
|
+
* the body first and anchors the headline under it.
|
|
15
|
+
* - `variant`: one of a small set of arrangements an archetype can draw its
|
|
16
|
+
* body in — bars as rows or as columns, a pipeline as a row, a stair or a
|
|
17
|
+
* column, a claim beside or above its figure, a comparison as columns or rows.
|
|
18
|
+
*
|
|
19
|
+
* THE ONE RULE A LOOK MAY NEVER BREAK: it changes geometry, never TIME. A look
|
|
20
|
+
* draws the same stops at the same seconds as the classic scene (`holds`) and its
|
|
21
|
+
* chrome lands when the classic chrome lands (`openSeconds`). That is what keeps
|
|
22
|
+
* narration aligned: `narrate` counted stops from the classic emitter,
|
|
23
|
+
* `render/timing.ts` re-derives holds from it, and `assertHoldsAgree` refuses a
|
|
24
|
+
* deck whose island disagrees. The Director (`src/plan/direct.ts`) checks this
|
|
25
|
+
* per candidate and drops any look that moves a hold, so a variant with a timing
|
|
26
|
+
* bug degrades to the classic scene instead of shipping misaligned audio.
|
|
27
|
+
*
|
|
28
|
+
* WHY THE CLASSIC LOOK IS `undefined` AND NOT `{variant: "...", placement: "top"}`.
|
|
29
|
+
* A classic build must emit the bytes v0.8.0 emitted. Every emitter treats an
|
|
30
|
+
* absent `ctx.look` and the default look identically (`frameOf` returns the
|
|
31
|
+
* classic frame for both), and only a non-default look writes anything new.
|
|
32
|
+
*
|
|
33
|
+
* INTERFACE FOR THE OTHER v2 TRACKS. `EmitContext.look` is the only field this
|
|
34
|
+
* adds to the emit seam. A track that changes type scale or motion reads the
|
|
35
|
+
* frame it is given; a track that changes fill can report `Scene` numbers the
|
|
36
|
+
* Director may later score on (see `direct.ts`, "fill").
|
|
37
|
+
*/
|
|
38
|
+
import type { Archetype, Beat, Format } from "../types.js";
|
|
39
|
+
import type { EmitContext, Tween } from "./kit.js";
|
|
40
|
+
import { type Face } from "./svg.js";
|
|
41
|
+
export type Placement = "top" | "rail" | "foot";
|
|
42
|
+
/** What `build --design v2` writes beside the deck: the Director's decisions. */
|
|
43
|
+
export declare const LOOK_FILE = "look.json";
|
|
44
|
+
export interface Look {
|
|
45
|
+
/** One of `VARIANTS[archetype]`. The first entry is the classic arrangement. */
|
|
46
|
+
variant: string;
|
|
47
|
+
placement: Placement;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The arrangements each archetype can draw, classic first.
|
|
51
|
+
*
|
|
52
|
+
* Only the four archetypes that carry 72% of beats have more than one. The other
|
|
53
|
+
* nine stay classic: their volume is too small to move the sameness numbers, and
|
|
54
|
+
* every variant is one more geometry that has to stay inside the 40px floor and
|
|
55
|
+
* the canvas with no gate that sees a clip.
|
|
56
|
+
*/
|
|
57
|
+
export declare const VARIANTS: Readonly<Partial<Record<Archetype, readonly string[]>>>;
|
|
58
|
+
/** The classic look for an archetype: its first variant, chrome on top. */
|
|
59
|
+
export declare function classicLook(archetype: Archetype): Look;
|
|
60
|
+
/**
|
|
61
|
+
* The variant an emitter should draw. Absent look → the classic one, so a
|
|
62
|
+
* classic build and a v2 build that chose the classic look are one code path.
|
|
63
|
+
*/
|
|
64
|
+
export declare function variantOf(ctx: EmitContext, archetype: Archetype): string;
|
|
65
|
+
/**
|
|
66
|
+
* `archetype:variant@placement` — what the sameness metrics count. Two beats
|
|
67
|
+
* with the same signature look the same at a glance; two with different ones do
|
|
68
|
+
* not, whatever their text says.
|
|
69
|
+
*/
|
|
70
|
+
export declare function signature(archetype: Archetype, look?: Look): string;
|
|
71
|
+
/**
|
|
72
|
+
* Whether the canvas is wide enough to set a headline in a column beside the
|
|
73
|
+
* body. 16:9 only in practice: at 1:1 the rail would be 292px and a 64px
|
|
74
|
+
* headline would set three words a line.
|
|
75
|
+
*/
|
|
76
|
+
export declare function railable(format: Format): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The looks the Director may choose between for one beat, before any is emitted.
|
|
79
|
+
*
|
|
80
|
+
* Not the full variant × placement product: some pairs are geometry nobody
|
|
81
|
+
* should draw. A claim set beside its figure inside a 1,058px rail body leaves
|
|
82
|
+
* the figure a 440px column, and a pipeline column across the full 1,700px top
|
|
83
|
+
* body is a stack of slabs. Off 16:9 only the classic variant is offered, at
|
|
84
|
+
* either end of the slide — the portrait arrangements ARE the stacked variants.
|
|
85
|
+
*/
|
|
86
|
+
export declare function candidates(beat: Beat, format: Format): Look[];
|
|
87
|
+
/** The rail's share of the content width, and the channel between rail and body. */
|
|
88
|
+
export declare const RAIL_SHARE = 0.34;
|
|
89
|
+
export declare const RAIL_GAP = 72;
|
|
90
|
+
export declare const FOOT_SEP: number;
|
|
91
|
+
/**
|
|
92
|
+
* The box a body lays out in, and how the chrome is set around it.
|
|
93
|
+
*
|
|
94
|
+
* Every emitter that has variants asks for one of these in place of the
|
|
95
|
+
* `contentW` / `bodyBudget` / `chrome()` trio it used before. For the classic
|
|
96
|
+
* frame each member is EXACTLY that trio — same arguments, same strings — which
|
|
97
|
+
* is what keeps a classic build byte-identical (test/look.test.ts holds it).
|
|
98
|
+
*/
|
|
99
|
+
export interface Frame {
|
|
100
|
+
readonly placement: Placement;
|
|
101
|
+
/** Width the body lays out in, reference px. */
|
|
102
|
+
readonly w: number;
|
|
103
|
+
/** The column a closing note under the body sets in. */
|
|
104
|
+
readonly noteW: number;
|
|
105
|
+
/**
|
|
106
|
+
* Height the body may take: the content box less the chrome when the chrome
|
|
107
|
+
* is stacked with the body, less `below` (a note, a caption band), less the
|
|
108
|
+
* body's own top margin `top`. `floor` is `bodyBudget`'s last resort.
|
|
109
|
+
*/
|
|
110
|
+
budget(below?: number, top?: number, floor?: number): number;
|
|
111
|
+
/** The scene's HTML: chrome and body in this placement's arrangement. */
|
|
112
|
+
compose(body: string): string;
|
|
113
|
+
/** Scoped CSS this placement needs. Empty for the classic frame. */
|
|
114
|
+
readonly css: string;
|
|
115
|
+
/** Tweens this placement adds (the rail's accent bar, an aside). Never a hold. */
|
|
116
|
+
readonly tl: Tween[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* What a frame needs to know about the beat beyond its chrome: which figures
|
|
120
|
+
* the body already draws, so an aside never repeats one, and the beat's own
|
|
121
|
+
* evidence, which is the only place an aside may come from.
|
|
122
|
+
*/
|
|
123
|
+
export interface FrameInput {
|
|
124
|
+
eyebrow: string | undefined;
|
|
125
|
+
headline: string;
|
|
126
|
+
/** Figure ids the body draws. */
|
|
127
|
+
drawn?: readonly (string | undefined)[];
|
|
128
|
+
/** The beat's `evidence`. An aside is drawn only from a figure cited here. */
|
|
129
|
+
evidence?: readonly {
|
|
130
|
+
kind: string;
|
|
131
|
+
id: string;
|
|
132
|
+
}[];
|
|
133
|
+
}
|
|
134
|
+
export declare function frameOf(ctx: EmitContext, input: FrameInput): Frame;
|
|
135
|
+
/** The rail column's width for a format. Exported for the viability check. */
|
|
136
|
+
export declare function railWidth(format: Format): number;
|
|
137
|
+
/** The chrome's height set in the rail, accent bar included. */
|
|
138
|
+
export declare function railChromeHeight(format: Format, eyebrow: string | undefined, headline: string, face: Face): number;
|