@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DeckNarration } from "../emit/composition.js";
|
|
2
|
+
import { type LatinFace } from "../emit/type.js";
|
|
2
3
|
import type { Source, Storyboard } from "../types.js";
|
|
3
4
|
/** Progress, for a caller that has somewhere to put it. */
|
|
4
5
|
type Step = (message: string) => void;
|
|
@@ -99,6 +100,17 @@ export declare function copyAudio(from: string, narration: DeckNarration, out: s
|
|
|
99
100
|
* CJK — over-collecting costs a few bytes, under-collecting costs a tofu box.
|
|
100
101
|
*
|
|
101
102
|
* A Latin deck gets `vendorInter` instead: nothing to subset, nothing to fetch.
|
|
103
|
+
* On a v2 style pack it also gets the pack's own faces (`vendorFace`), named by
|
|
104
|
+
* the pack's type spec — `theme` is the name the build resolved, as `--theme`.
|
|
105
|
+
* A CJK deck needs none of them: the Noto bundle is first in every stack and
|
|
106
|
+
* covers Latin, so the pack's faces would ship and never draw.
|
|
102
107
|
*/
|
|
103
|
-
export declare function refreshFont(storyboard: Storyboard, source: Source, out: string, step: Step): Promise<string | undefined>;
|
|
108
|
+
export declare function refreshFont(storyboard: Storyboard, source: Source, out: string, step: Step, theme?: string): Promise<string | undefined>;
|
|
109
|
+
/**
|
|
110
|
+
* A v2 pack's face, vendored exactly the way Inter is: the package's `wght`
|
|
111
|
+
* subsets copied whole, declared under the name the pack's stack uses, `block`
|
|
112
|
+
* not `swap`. These are the files `scripts/measure-faces.mjs` measured, so the
|
|
113
|
+
* width table in `src/emit/faces.ts` describes what the deck draws.
|
|
114
|
+
*/
|
|
115
|
+
export declare function vendorFace(face: Exclude<LatinFace, "inter">, dir: string): Promise<string>;
|
|
104
116
|
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deck player's half of v2 motion: what moves between stops and during them.
|
|
3
|
+
*
|
|
4
|
+
* Two things a presented deck never showed before, both only for a deck whose
|
|
5
|
+
* page carries the `application/decksmith-motion+json` island — a `design: v2`
|
|
6
|
+
* build. A classic deck has no island and every function here answers "nothing
|
|
7
|
+
* to do", so it presents exactly as it did.
|
|
8
|
+
*
|
|
9
|
+
* 1. THE SEAM. A step to the next slide plays only when the span is short
|
|
10
|
+
* (`MAX_SPAN` in runtime.ts). From a narrated slide's last stop it never is —
|
|
11
|
+
* the voice made the scene long — so every slide change CUT to the next
|
|
12
|
+
* slide's first stop, already built: the seam and the whole entrance were
|
|
13
|
+
* never seen in deck.html, only in the mp4. `seamLead` says where to cut TO
|
|
14
|
+
* instead: the next slide's own start, so the glide that follows plays the
|
|
15
|
+
* handoff and the entrance, one to two seconds, as the video does.
|
|
16
|
+
*
|
|
17
|
+
* 2. THE HOLD. At a stop the deck seeks once and holds still while the audio
|
|
18
|
+
* plays, so emphasis written into the scene's quiet stretch (`emphasize` in
|
|
19
|
+
* src/emit/motion.ts) would never run. `holdTime` maps the audio clock onto
|
|
20
|
+
* that stretch — `from + audio.currentTime`, clamped to it — so a part pulses
|
|
21
|
+
* on the word it pulses on in the video. Still SEEK, NOT PLAY (invariant 1):
|
|
22
|
+
* the runtime seeks to a time derived from the audio element's clock, the way
|
|
23
|
+
* captions already follow it, and nothing runs a timeline on its own.
|
|
24
|
+
*
|
|
25
|
+
* Reduced motion turns both off at the call site in runtime.ts: the step cuts and
|
|
26
|
+
* the hold holds, which is what the deck did for everyone before.
|
|
27
|
+
*/
|
|
28
|
+
import type { SlideSpec, Stop } from "./runtime.js";
|
|
29
|
+
export declare const MOTION_ISLAND = "script[type=\"application/decksmith-motion+json\"]";
|
|
30
|
+
/** One narrated stop's quiet stretch, absolute composition seconds. */
|
|
31
|
+
export interface HoldSpan {
|
|
32
|
+
stop: number;
|
|
33
|
+
/** The stop's own time — where the deck lands. */
|
|
34
|
+
at: number;
|
|
35
|
+
/** Where the stop's sentence starts on the composition clock. */
|
|
36
|
+
from: number;
|
|
37
|
+
/** The last time this stop may show before the next stop's reveal begins. */
|
|
38
|
+
to: number;
|
|
39
|
+
}
|
|
40
|
+
export interface Motion {
|
|
41
|
+
/** Glide through seams on slide changes. */
|
|
42
|
+
seams: boolean;
|
|
43
|
+
/** Keyed by scene id. */
|
|
44
|
+
holds: Record<string, HoldSpan[]>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The island, or null. Defensive in the way `parseNarration` is: this ships
|
|
48
|
+
* inside decks that outlive the code that wrote them, so a malformed entry is
|
|
49
|
+
* dropped rather than allowed to break the player.
|
|
50
|
+
*/
|
|
51
|
+
export declare function parseMotion(json: string | null | undefined): Motion | null;
|
|
52
|
+
/** The stretch belonging to this stop, matched by time (fragments are 3dp). */
|
|
53
|
+
export declare function spanFor(motion: Motion, stop: Stop): HoldSpan | undefined;
|
|
54
|
+
/** Composition time to show `seconds` into this stop's audio. */
|
|
55
|
+
export declare function holdTime(span: HoldSpan, seconds: number): number;
|
|
56
|
+
/** Longest entrance a seam glide will play — a slide's first stop is rarely past 2.5s. */
|
|
57
|
+
export declare const MAX_SEAM_SPAN = 4;
|
|
58
|
+
/**
|
|
59
|
+
* Where a forward step onto the next slide should start its glide, or null for
|
|
60
|
+
* "decide as before". Only a step from the previous slide onto this slide's
|
|
61
|
+
* FIRST stop, only when the ordinary policy would have cut (the span is longer
|
|
62
|
+
* than `maxSpan`), and only when the entrance itself is short enough to watch.
|
|
63
|
+
*/
|
|
64
|
+
export declare function seamLead(from: number, prev: Stop | undefined, next: Stop, slides: readonly SlideSpec[], maxSpan: number): number | null;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/** The meta tag a v2 deck page carries. Read by the runtime, written by the emitter and `repack`. */
|
|
2
|
+
export declare const PLAYER_META = "decksmith-player";
|
|
3
|
+
export declare const PLAYER_MARKER = "<meta name=\"decksmith-player\" content=\"2\" />";
|
|
4
|
+
/**
|
|
5
|
+
* `page` with the v2 marker as the first thing in its head. Idempotent: a page
|
|
6
|
+
* that already names a player version is returned with that tag replaced, so
|
|
7
|
+
* marking twice — or repacking a repacked deck — changes nothing.
|
|
8
|
+
*/
|
|
9
|
+
export declare function markV2(page: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Narration speeds a viewer can choose. The list YouTube and video.js offer,
|
|
12
|
+
* minus 0.5 (edge-tts at half speed is a different voice, not a slower one).
|
|
13
|
+
* Gecko mutes outside 0.25–4, so every entry plays everywhere.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RATES: readonly [0.75, 1, 1.25, 1.5, 1.75, 2];
|
|
16
|
+
export type CaptionSize = "s" | "m" | "l" | "xl";
|
|
17
|
+
export declare const CAPTION_SIZES: readonly CaptionSize[];
|
|
18
|
+
/** Multipliers on the M caption font. 80–160%, inside FCC 79.103's 50–200%. */
|
|
19
|
+
export declare const CAPTION_SCALE: Readonly<Record<CaptionSize, number>>;
|
|
20
|
+
/**
|
|
21
|
+
* What a viewer chose. `speed` is the narration's playbackRate and divides
|
|
22
|
+
* every glide and silent dwell; `cc` shows the caption strip; `ccsize` scales it.
|
|
23
|
+
* Mute is deliberately not here: it is a moment's choice, not a preference.
|
|
24
|
+
*/
|
|
25
|
+
export interface PlayerPrefs {
|
|
26
|
+
speed: number;
|
|
27
|
+
cc: boolean;
|
|
28
|
+
ccsize: CaptionSize;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_PREFS: PlayerPrefs;
|
|
31
|
+
/** The localStorage key. Versioned so a later shape never misreads this one. */
|
|
32
|
+
export declare const PREFS_KEY = "decksmith.prefs.v1";
|
|
33
|
+
/**
|
|
34
|
+
* The message a v2 deck posts to its parent whenever the VIEWER changes a
|
|
35
|
+
* preference, and accepts from its parent to apply one. Not on the `CHANNEL`
|
|
36
|
+
* protocol in ./protocol.ts on purpose: that one waits for a `hello` handshake
|
|
37
|
+
* before it says anything, and HypePaper embeds a plain iframe that never sends
|
|
38
|
+
* one. Posted to "*" because it carries nothing but three playback settings —
|
|
39
|
+
* never notes, never position. A host must still check `event.origin`.
|
|
40
|
+
*
|
|
41
|
+
* { type: "decksmith:prefs", speed: 1.25, cc: true, ccsize: "m" }
|
|
42
|
+
*
|
|
43
|
+
* Incoming, every field is optional; whatever is valid is applied and saved.
|
|
44
|
+
*/
|
|
45
|
+
export declare const PREFS_MESSAGE = "decksmith:prefs";
|
|
46
|
+
export interface PrefsMessage extends PlayerPrefs {
|
|
47
|
+
type: typeof PREFS_MESSAGE;
|
|
48
|
+
}
|
|
49
|
+
/** The nearest offered rate, or undefined for anything that is not a positive number. */
|
|
50
|
+
export declare function snapRate(v: unknown): number | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Whatever is valid in `raw`, and nothing else. Reads a stored blob, a host
|
|
53
|
+
* message and a parsed query string alike, so a deck handed garbage keeps its
|
|
54
|
+
* own defaults instead of throwing.
|
|
55
|
+
*/
|
|
56
|
+
export declare function cleanPrefs(raw: unknown): Partial<PlayerPrefs>;
|
|
57
|
+
/** `?speed=1.25&cc=0&ccsize=l`, as a partial. Unknown and invalid params are ignored. */
|
|
58
|
+
export declare function prefsFromQuery(search: string): Partial<PlayerPrefs>;
|
|
59
|
+
/** A stored blob, read defensively: anything unparseable is "nothing stored". */
|
|
60
|
+
export declare function prefsFromStored(text: string | null | undefined): Partial<PlayerPrefs>;
|
|
61
|
+
/**
|
|
62
|
+
* Precedence, lowest first: default, stored, URL. A host message arriving later
|
|
63
|
+
* is applied on top of whatever this produced.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolvePrefs(stored: Partial<PlayerPrefs>, query: Partial<PlayerPrefs>): PlayerPrefs;
|
|
66
|
+
/** One step up or down the rate list, clamped at both ends. */
|
|
67
|
+
export declare function stepRate(current: number, dir: 1 | -1): number;
|
|
68
|
+
/** "1×", "1.25×", "0.75×" — the speed button's face. */
|
|
69
|
+
export declare function rateLabel(rate: number): string;
|
|
70
|
+
/** Caption font as a fraction of the slide's short side, at size M. */
|
|
71
|
+
export declare const CAPTION_RATIO = 0.03;
|
|
72
|
+
/** Below this a caption is not read, whatever the slide size. */
|
|
73
|
+
export declare const CAPTION_MIN_PX = 13;
|
|
74
|
+
export declare const CAPTION_MAX_PX = 56;
|
|
75
|
+
/**
|
|
76
|
+
* The floor per size. Every size has the 13px floor, but on a stage small
|
|
77
|
+
* enough for it to bind, L and XL used to land on it too, and the size control
|
|
78
|
+
* then changed nothing at all on the phone embed — the one place a viewer most
|
|
79
|
+
* needs it. Each larger size keeps a floor of its own instead, so a step up is
|
|
80
|
+
* always a step up. S cannot go under the floor M is on: below 13px a caption
|
|
81
|
+
* is not read.
|
|
82
|
+
*/
|
|
83
|
+
export declare const CAPTION_FLOOR_PX: Readonly<Record<CaptionSize, number>>;
|
|
84
|
+
/**
|
|
85
|
+
* The strip, in caption fonts: two lines at line-height 1.25 (2.5) plus a
|
|
86
|
+
* quarter-font of air above and below. Two lines is the budget `splitCue` and
|
|
87
|
+
* `splitForScreen` cut every cue to.
|
|
88
|
+
*/
|
|
89
|
+
export declare const STRIP_FONTS = 3;
|
|
90
|
+
export declare const CAPTION_LINE_HEIGHT = 1.25;
|
|
91
|
+
/** The smallest control target (plan QW2), and the largest a big screen gets. */
|
|
92
|
+
export declare const BTN_MIN = 40;
|
|
93
|
+
export declare const BTN_MAX = 52;
|
|
94
|
+
/** A bar is its buttons plus this much air. */
|
|
95
|
+
export declare const BAR_AIR = 8;
|
|
96
|
+
/**
|
|
97
|
+
* Where the control bar lives. The rule it exists for: A CONTROL NEVER COVERS
|
|
98
|
+
* TEXT — not a caption, and not the slide's own text either.
|
|
99
|
+
*
|
|
100
|
+
* - `letterbox`: under the slide and its strip, in space the viewport has spare
|
|
101
|
+
* (a portrait phone, a tall embed). Costs nothing.
|
|
102
|
+
* - `pad`: over the slide's bottom PADDING — the band every composition leaves
|
|
103
|
+
* empty of text (`.scene`'s `PAD_Y`, less a margin; see `safeBottomFor`).
|
|
104
|
+
* Only when the bar fits inside it at a full 40px target, i.e. on a slide
|
|
105
|
+
* about 720px tall or more. Costs nothing; it hides when idle.
|
|
106
|
+
* - `band`: a strip of its own under the slide, shared side by side with the
|
|
107
|
+
* captions when they are on, so it costs at most the difference between the
|
|
108
|
+
* bar and the caption strip. Everything smaller than the two cases above.
|
|
109
|
+
*
|
|
110
|
+
* v2 used to put the bar inside the slide's box on every screen. Measured on the
|
|
111
|
+
* preview decks (2026-10-08 review), it covered the foot headline in 12 of 12
|
|
112
|
+
* cases at 390x844 and 11 of 12 at 800x450: on a small slide a 48px bar is a
|
|
113
|
+
* fifth of the height, and no layout can keep a fifth of itself empty.
|
|
114
|
+
*/
|
|
115
|
+
export type Dock = "letterbox" | "pad" | "band";
|
|
116
|
+
export interface Box {
|
|
117
|
+
x: number;
|
|
118
|
+
y: number;
|
|
119
|
+
w: number;
|
|
120
|
+
h: number;
|
|
121
|
+
}
|
|
122
|
+
export interface Stage {
|
|
123
|
+
/** The slide's box, in CSS px. */
|
|
124
|
+
slideW: number;
|
|
125
|
+
slideH: number;
|
|
126
|
+
slideX: number;
|
|
127
|
+
slideY: number;
|
|
128
|
+
/** Caption font and strip height. Both 0 with captions off. */
|
|
129
|
+
capFont: number;
|
|
130
|
+
capH: number;
|
|
131
|
+
/** Where the control bar sits, and its box. */
|
|
132
|
+
dock: Dock;
|
|
133
|
+
bar: Box;
|
|
134
|
+
/** Button size, px. */
|
|
135
|
+
btn: number;
|
|
136
|
+
/** One settings button instead of speed, CC, size and full screen. */
|
|
137
|
+
compact: boolean;
|
|
138
|
+
/** Space between the bar's edges and its outermost buttons. */
|
|
139
|
+
barInset: number;
|
|
140
|
+
/** The caption strip's box, and the padding each side that its text never enters. */
|
|
141
|
+
strip: Box;
|
|
142
|
+
stripPad: number;
|
|
143
|
+
/** Two caption lines at this measure, in caption ems, for `splitForScreen`. */
|
|
144
|
+
cueEm: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Where the slide, its caption strip and the control bar go in a `vw`×`vh`
|
|
148
|
+
* viewport. `safeBottom` is the share of the slide's height at its foot that the
|
|
149
|
+
* composition keeps free of text (`safeBottomFor`); 0 means "assume none".
|
|
150
|
+
*
|
|
151
|
+
* NOT CIRCULAR. The v0.8.0 rule sized the caption from the viewport width and
|
|
152
|
+
* the critic's first fix sized it from the stage rect — but the stage is the
|
|
153
|
+
* viewport minus the strip, and the strip is a multiple of the font. Solving
|
|
154
|
+
* once for the slide height that leaves exactly one strip below it,
|
|
155
|
+
*
|
|
156
|
+
* H0 = min(vw / aspect, vh / (1 + STRIP_FONTS · CAPTION_RATIO · scale · short))
|
|
157
|
+
*
|
|
158
|
+
* (`short` is the slide's short side over its height: 1 for landscape), and
|
|
159
|
+
* deriving the font from THAT, gives a font that does not depend on the result
|
|
160
|
+
* it determines. The floor can only make the strip taller than the solve
|
|
161
|
+
* assumed, so the final height is re-clamped against the real strip.
|
|
162
|
+
*
|
|
163
|
+
* Slide, strip and (in the letterbox) bar are centred as one block: on a
|
|
164
|
+
* portrait phone the caption and the controls sit under the slide rather than
|
|
165
|
+
* at the bottom of the screen, and the slide gives up nothing for either.
|
|
166
|
+
*/
|
|
167
|
+
export declare function stageGeometry(vw: number, vh: number, aspect: number, captions: {
|
|
168
|
+
on: boolean;
|
|
169
|
+
size: CaptionSize;
|
|
170
|
+
}, opts?: {
|
|
171
|
+
safeBottom?: number;
|
|
172
|
+
}): Stage;
|
|
173
|
+
/**
|
|
174
|
+
* The share of a composition's height at its foot that no layout draws text in:
|
|
175
|
+
* `.scene`'s bottom padding (`PAD_Y`, in reference px) less a margin, over the
|
|
176
|
+
* reference height. 16:9 at 1920x1080 is (84 − 16) / 1080 ≈ 6.3%.
|
|
177
|
+
*
|
|
178
|
+
* NOT FOR A FORMAT THAT RESERVES A CAPTION BAND: burned captions live there.
|
|
179
|
+
* Decks never do (`captionReserve` is a video setting), and an unknown shape
|
|
180
|
+
* reads as 0, which only ever moves the bar into a band of its own.
|
|
181
|
+
*/
|
|
182
|
+
export declare function safeBottomFor(width: number, height: number): number;
|
|
183
|
+
/** Reference px kept between the lowest text a layout may set and the bar's top. */
|
|
184
|
+
export declare const SAFE_MARGIN = 16;
|
|
185
|
+
export type KeyAction = "next" | "prev" | "first" | "last" | "play" | "captions" | "faster" | "slower" | "bigger" | "smaller" | "mute" | "notes" | "video" | "escape" | "fullscreen";
|
|
186
|
+
export interface KeyLike {
|
|
187
|
+
key: string;
|
|
188
|
+
/** The physical key, layout-independent: `KeyC` whatever the IME makes of it. */
|
|
189
|
+
code?: string;
|
|
190
|
+
isComposing?: boolean;
|
|
191
|
+
metaKey?: boolean;
|
|
192
|
+
ctrlKey?: boolean;
|
|
193
|
+
altKey?: boolean;
|
|
194
|
+
repeat?: boolean;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The v2 keymap. Founder's call (2026-10-07): Space is NEXT SLIDE, as it is in
|
|
198
|
+
* every presenter; Enter is play/pause.
|
|
199
|
+
*
|
|
200
|
+
* Any key held with Cmd, Ctrl or Alt belongs to the browser — v0.8.0 took
|
|
201
|
+
* Cmd+F for fullscreen and swallowed find. Toggles ignore auto-repeat, so a
|
|
202
|
+
* held `c` does not strobe the captions; stepping keys repeat on purpose.
|
|
203
|
+
*/
|
|
204
|
+
export declare function keyAction(e: KeyLike): KeyAction | null;
|
|
205
|
+
/** One size step, clamped. */
|
|
206
|
+
export declare function stepSize(current: CaptionSize, dir: 1 | -1): CaptionSize;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The islands of a deck page, in order, exactly as written. */
|
|
2
|
+
export declare function islandsOf(page: string): string[];
|
|
3
|
+
/**
|
|
4
|
+
* `page` with `runtimeJs` as its player and the v2 marker in its head.
|
|
5
|
+
* Idempotent: repacking a repacked page with the same runtime returns it unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Throws, rather than writing something half-right, on a page that is not a
|
|
8
|
+
* deck page or whose runtime script cannot be found where the emitter puts it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function repackDeckPage(page: string, runtimeJs: string): string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Narration } from "./subtitles.js";
|
|
1
|
+
import { type Cue, type Narration } from "./subtitles.js";
|
|
2
2
|
/** One entry of the slideshow island's `slides` array. */
|
|
3
3
|
export interface SlideSpec {
|
|
4
4
|
sceneId: string;
|
|
@@ -49,6 +49,7 @@ export interface TransitionPlan {
|
|
|
49
49
|
*/
|
|
50
50
|
export declare function planTransition(fromT: number, toT: number, opts?: {
|
|
51
51
|
reducedMotion?: boolean;
|
|
52
|
+
rate?: number;
|
|
52
53
|
}): TransitionPlan;
|
|
53
54
|
/**
|
|
54
55
|
* How long autoplay waits on the stop it is on, or `null` for "do not set a
|
|
@@ -75,7 +76,27 @@ export declare function dwellMs(opts: {
|
|
|
75
76
|
playing: boolean;
|
|
76
77
|
heard: boolean;
|
|
77
78
|
gapMs: number;
|
|
79
|
+
/** The viewer's speed. Every wait is divided by it, after it is clamped. */
|
|
80
|
+
rate?: number;
|
|
81
|
+
/**
|
|
82
|
+
* v2: the stop has no narration segment at all — silent BY DESIGN, not a
|
|
83
|
+
* segment that failed to play. Its reveal is the whole of its content, so the
|
|
84
|
+
* deck waits for the reveal (`revealMs`, already at the viewer's speed) and a
|
|
85
|
+
* beat to take it in, then moves on. See `SILENT_HOLD`.
|
|
86
|
+
*/
|
|
87
|
+
silent?: boolean;
|
|
88
|
+
revealMs?: number;
|
|
78
89
|
}): number | null;
|
|
90
|
+
/**
|
|
91
|
+
* How long a v2 deck playing itself holds a stop that has nothing to say, after
|
|
92
|
+
* its reveal has played, at 1x. Measured on the four preview decks (review,
|
|
93
|
+
* 2026-10-08): every one has 5–9 such stops, and at MAX_DWELL each they were
|
|
94
|
+
* 40–65s of silence against 227–303s of speech — 15–22% of the deck at 1x and a
|
|
95
|
+
* quarter to a third of it at 2x, the "slow" the founder heard. The linear
|
|
96
|
+
* render folds these stops into the sentence before them; this is the player's
|
|
97
|
+
* nearest equivalent: let the reveal land, give it a moment, go on.
|
|
98
|
+
*/
|
|
99
|
+
export declare const SILENT_HOLD = 1500;
|
|
79
100
|
/** `#3` is slide 3; `#3.2` is slide 3, second fragment. Both 1-based. */
|
|
80
101
|
export declare function formatHash(pos: Pos): string;
|
|
81
102
|
export declare function parseHash(hash: string): Pos | null;
|
|
@@ -145,6 +166,32 @@ export declare function showingAt(slide: SlideSpec, clip: number, t: number): bo
|
|
|
145
166
|
* export does not reach deck.html.
|
|
146
167
|
*/
|
|
147
168
|
export declare function paint(frame: Frame, slides: readonly SlideSpec[], t: number): void;
|
|
169
|
+
export interface ChromeV2 extends ReturnType<typeof mountChrome> {
|
|
170
|
+
controls: HTMLElement;
|
|
171
|
+
speed: HTMLButtonElement;
|
|
172
|
+
speedMenu: HTMLElement;
|
|
173
|
+
cc: HTMLButtonElement;
|
|
174
|
+
size: HTMLButtonElement;
|
|
175
|
+
sizeMenu: HTMLElement;
|
|
176
|
+
fs: HTMLButtonElement;
|
|
177
|
+
/** Compact mode's one settings button, and its menu (speed, captions, size, full screen). */
|
|
178
|
+
more: HTMLButtonElement;
|
|
179
|
+
moreMenu: HTMLElement;
|
|
180
|
+
/** A polite live region: setting changes are announced, cue text never is. */
|
|
181
|
+
say: HTMLElement;
|
|
182
|
+
}
|
|
183
|
+
/** Whether this deck page asked for the v2 player. See `PLAYER_META`. */
|
|
184
|
+
export declare function wantsV2(doc: Pick<Document, "querySelector">): boolean;
|
|
185
|
+
declare function mountChrome(doc: Document): {
|
|
186
|
+
chrome: HTMLDivElement;
|
|
187
|
+
fill: HTMLElement;
|
|
188
|
+
play: HTMLButtonElement;
|
|
189
|
+
video: HTMLButtonElement;
|
|
190
|
+
count: HTMLElement;
|
|
191
|
+
notes: HTMLElement;
|
|
192
|
+
flags: HTMLElement;
|
|
193
|
+
subs: HTMLElement;
|
|
194
|
+
};
|
|
148
195
|
export interface Voice {
|
|
149
196
|
/**
|
|
150
197
|
* Arrive at a stop: cut whatever was speaking, start this stop from zero.
|
|
@@ -161,6 +208,16 @@ export interface Voice {
|
|
|
161
208
|
hush: () => void;
|
|
162
209
|
toggleMute: () => void;
|
|
163
210
|
toggleSubtitles: () => void;
|
|
211
|
+
/** Captions on or off, absolutely. The v2 player's preferences call this; the v0.8.0 key toggles. */
|
|
212
|
+
setSubtitles: (on: boolean) => void;
|
|
213
|
+
/**
|
|
214
|
+
* The narration speed, applied to the element that is speaking NOW as well as
|
|
215
|
+
* to every later one. Both halves matter and each was a separate bug in the
|
|
216
|
+
* plan: setting it only on the next `src` leaves the current sentence at the
|
|
217
|
+
* old speed, and setting only `playbackRate` is undone by the `load()` every
|
|
218
|
+
* stop runs, which resets it to `defaultPlaybackRate`.
|
|
219
|
+
*/
|
|
220
|
+
setRate: (rate: number) => void;
|
|
164
221
|
/** Called when the segment for the CURRENT stop finishes of its own accord. */
|
|
165
222
|
onEnded: (fn: () => void) => void;
|
|
166
223
|
/**
|
|
@@ -181,6 +238,25 @@ export interface Voice {
|
|
|
181
238
|
* load — see `refused` for why the second one is never retried unasked.
|
|
182
239
|
*/
|
|
183
240
|
unlock: () => void;
|
|
241
|
+
/**
|
|
242
|
+
* Called once per animation frame while the current stop's audio plays, with
|
|
243
|
+
* its `currentTime` — the clock v2's hold motion seeks by (src/deck/motion.ts).
|
|
244
|
+
*/
|
|
245
|
+
onProgress: (fn: (seconds: number) => void) => void;
|
|
246
|
+
/**
|
|
247
|
+
* Pause the sentence where it is: the element stops, its clock stops, and the
|
|
248
|
+
* hold motion that seeks by that clock stops with it. v2's Enter. Unlike
|
|
249
|
+
* `hush`, the source is kept, so `resume` can carry on from the same word.
|
|
250
|
+
*/
|
|
251
|
+
pause: () => void;
|
|
252
|
+
/**
|
|
253
|
+
* Carry on with the paused sentence from where it stopped. False when there
|
|
254
|
+
* is nothing to carry on — no segment here, it already finished, or a step
|
|
255
|
+
* has since dropped it — and the caller should speak the stop afresh.
|
|
256
|
+
*/
|
|
257
|
+
resume: () => boolean;
|
|
258
|
+
/** Re-cut the current stop's captions after the strip changed width. */
|
|
259
|
+
refit: () => void;
|
|
184
260
|
}
|
|
185
261
|
/**
|
|
186
262
|
* Was that `play()` rejection the autoplay policy, or an audio file we will
|
|
@@ -246,6 +322,25 @@ export declare function refused(err: unknown): boolean;
|
|
|
246
322
|
export declare function mountVoice(doc: Document, narration: Narration, ui: {
|
|
247
323
|
subs: HTMLElement;
|
|
248
324
|
flags: HTMLElement;
|
|
325
|
+
}, opts?: {
|
|
326
|
+
/**
|
|
327
|
+
* Whether the flag strip says "subtitles off". The v0.8.0 player has no other
|
|
328
|
+
* way to show it; the v2 player's CC button does, and saying it twice is a
|
|
329
|
+
* nag on top of the slide.
|
|
330
|
+
*/
|
|
331
|
+
flagSubtitles?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* The v2 player. Only it has a speed, a hold motion and captions cut to the
|
|
334
|
+
* strip's width, so only it touches `playbackRate`, reports progress, or
|
|
335
|
+
* re-cuts cues. A classic deck's voice is v0.8.0's, call for call.
|
|
336
|
+
*/
|
|
337
|
+
v2?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* How a v2 deck cuts a segment's cues for the strip it has NOW. Asked on
|
|
340
|
+
* every arrival and by `refit`, because the strip's width changes with the
|
|
341
|
+
* window and with the controls beside it.
|
|
342
|
+
*/
|
|
343
|
+
cut?: (cues: readonly Cue[]) => Cue[];
|
|
249
344
|
}): Voice;
|
|
250
345
|
/** One player-page video, keyed in the island by the scene that draws its still. */
|
|
251
346
|
export interface ClipSpec {
|
|
@@ -76,11 +76,39 @@ export declare const CUE_MAX_CHARS = 84;
|
|
|
76
76
|
* that many pieces does the packing: same number of captions, no orphan.
|
|
77
77
|
*/
|
|
78
78
|
export declare function splitCue(cue: Cue, max?: number): Cue[];
|
|
79
|
+
/**
|
|
80
|
+
* Two caption lines' worth of text, in ems, at the narrowest stage the v2
|
|
81
|
+
* player is laid out for: a 358px-wide phone, where the caption is at its 13px
|
|
82
|
+
* floor and the strip's side padding leaves ~329px — 25.3em a line. 44 keeps
|
|
83
|
+
* ~13% for the slack a word-wrapping browser leaves at each line end. Every
|
|
84
|
+
* larger stage has MORE ems a line (the font scales with the slide, the width
|
|
85
|
+
* with the window), so a cue that fits here fits everywhere. Latin text at 0.56
|
|
86
|
+
* em a character comes to ~78 characters, a little under `CUE_MAX_CHARS`.
|
|
87
|
+
*/
|
|
88
|
+
export declare const SCREEN_CUE_EM = 44;
|
|
89
|
+
/** A caption's width in ems, by the same measure `splitForScreen` packs with. */
|
|
90
|
+
export declare function cueEm(text: string): number;
|
|
91
|
+
/**
|
|
92
|
+
* `splitCue` for the v2 player's screen: the budget is two caption LINES in
|
|
93
|
+
* ems, not 84 characters. The difference is every CJK deck. 84 Japanese
|
|
94
|
+
* characters are 84em — four lines on a phone — and `splitCue` cannot split
|
|
95
|
+
* them at all, because it breaks only at spaces and Japanese and Chinese have
|
|
96
|
+
* none. Same even-split rule as `splitCue` (decide the piece count, then pack to
|
|
97
|
+
* the narrowest width that keeps it), so no piece is an orphaned word.
|
|
98
|
+
*
|
|
99
|
+
* Used only by a v2 deck page. The render path and the v0.8.0 player keep
|
|
100
|
+
* `splitCue`, so neither a burned caption nor an old deck moves.
|
|
101
|
+
*/
|
|
102
|
+
export declare function splitForScreen(cue: Cue, budgetEm?: number): Cue[];
|
|
79
103
|
/**
|
|
80
104
|
* Parse the island's text. Anything malformed reads as "no narration", which is
|
|
81
105
|
* the deck we shipped yesterday and is always a safe answer.
|
|
82
106
|
*/
|
|
83
|
-
export declare function parseNarration(text: string | null | undefined
|
|
107
|
+
export declare function parseNarration(text: string | null | undefined,
|
|
108
|
+
/** How a long cue is cut. `splitCue` unless a v2 deck page says `splitForScreen`. */
|
|
109
|
+
opts?: {
|
|
110
|
+
split?: (cue: Cue) => Cue[];
|
|
111
|
+
}): Narration | null;
|
|
84
112
|
/**
|
|
85
113
|
* The cue on screen at `t`, or null in a gap.
|
|
86
114
|
*
|
|
@@ -79,7 +79,7 @@ export interface FigureLayout {
|
|
|
79
79
|
export declare function planFigure(stageW: number, notes: FigureNote[], fig: {
|
|
80
80
|
width: number;
|
|
81
81
|
height: number;
|
|
82
|
-
}, stageH: number, tall?: boolean, face?: Face): FigureLayout;
|
|
82
|
+
}, stageH: number, tall?: boolean, face?: Face, upscale?: number): FigureLayout;
|
|
83
83
|
/**
|
|
84
84
|
* Height the stage may spend, once the chrome and the caption have taken theirs.
|
|
85
85
|
* A budget, not a size — `planFigure` gives back only what it used.
|
|
@@ -1,30 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Magnitudes, drawn.
|
|
3
|
-
*
|
|
4
|
-
* A table of figures makes the viewer do arithmetic: "1.592 against 0.930" is two
|
|
5
|
-
* numbers and a subtraction. The same pair as two bars is one glance. So nothing
|
|
6
|
-
* here is decoration — the bar lengths carry the entire claim, which is why the
|
|
7
|
-
* scale is derived from the data and anchored at zero. A bar chart whose baseline
|
|
8
|
-
* is not zero is a chart that asserts a ratio the numbers do not support, and it
|
|
9
|
-
* would pass every gate we have.
|
|
10
|
-
*
|
|
11
|
-
* Three decisions worth knowing before changing anything:
|
|
12
|
-
*
|
|
13
|
-
* 1. Every bar sits in a full-width rail. The rail is what makes a dwarfed bar
|
|
14
|
-
* legible (see MIN_LEN) — the eye reads "almost none of the extent" rather
|
|
15
|
-
* than "nothing was drawn".
|
|
16
|
-
* 2. Values count up rather than fade in, and they count on the same decimal grid
|
|
17
|
-
* their printed string is on, so the number GSAP lands on is byte-identical to
|
|
18
|
-
* the one in the static SVG.
|
|
19
|
-
* 3. The bars are one reveal, not N. Holding on bar 3 of 8 asks the viewer to
|
|
20
|
-
* compare it against bars that have not been drawn yet.
|
|
21
|
-
*
|
|
22
|
-
* A label gutter beside the rails is a landscape idea. At 9:16 the demo's five
|
|
23
|
-
* names took 340px of an 860px box, so 40% of the width was spent naming bars
|
|
24
|
-
* that then had 60% left to differ in. Portrait puts the label on its own line
|
|
25
|
-
* above the rail instead and hands the whole width to the comparison — which is
|
|
26
|
-
* also where the height it needs comes from, since portrait has height to spare
|
|
27
|
-
* and width it does not.
|
|
28
|
-
*/
|
|
29
1
|
import type { Emitter } from "../kit.js";
|
|
30
2
|
export declare const barCompare: Emitter<"bar-compare">;
|
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Labelled panels side by side — the archetype for the things a paper does not
|
|
3
|
-
* put in a figure: a contradiction between two tables, a caveat, a limit on what
|
|
4
|
-
* was actually tested. Panels appear one at a time so each can be spoken to.
|
|
5
|
-
*/
|
|
6
1
|
import type { Emitter } from "../kit.js";
|
|
7
2
|
export declare const callout: Emitter<"callout">;
|
|
@@ -1,2 +1,17 @@
|
|
|
1
|
+
import { type Fit } from "../fit.js";
|
|
1
2
|
import type { Emitter } from "../kit.js";
|
|
3
|
+
type Mode = "tall" | "wide" | "beside";
|
|
4
|
+
/** One way to draw the beat, predicted. */
|
|
5
|
+
export interface Arrangement {
|
|
6
|
+
mode: Mode;
|
|
7
|
+
claimSize: number;
|
|
8
|
+
plate: {
|
|
9
|
+
w: number;
|
|
10
|
+
h: number;
|
|
11
|
+
};
|
|
12
|
+
fit: Fit;
|
|
13
|
+
}
|
|
14
|
+
/** The arrangement to draw: the classic mode unless another fills clearly more. */
|
|
15
|
+
export declare function choose(options: readonly Arrangement[]): Arrangement;
|
|
2
16
|
export declare const claimFigure: Emitter<"claim-figure">;
|
|
17
|
+
export {};
|
|
@@ -1,2 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An equation becoming the next one.
|
|
3
|
+
*
|
|
4
|
+
* The keyed morph — the 3Blue1Brown move — and the one thing in the vocabulary
|
|
5
|
+
* that shows a DERIVATION rather than a statement: the terms the author keys
|
|
6
|
+
* travel from where they sit in the first line to where they sit in the second,
|
|
7
|
+
* as rigid bodies, and everything else dissolves. Which terms are keyed is the
|
|
8
|
+
* animation: `a^2+b^2=c^2` to `c^2-b^2=a^2` is a glyph riot unkeyed and a
|
|
9
|
+
* three-body exchange keyed, on the same two strings.
|
|
10
|
+
*
|
|
11
|
+
* The keying reuses `equation-walk`'s: a term is located in the TeX by normal
|
|
12
|
+
* form and wrapped in `\htmlClass{term t-<tone> ds-k-<tone>}{...}`, so KaTeX
|
|
13
|
+
* emits a real element the runtime can find. It must be found in BOTH lines
|
|
14
|
+
* to be a key at all — a term on one side only is dropped, with its legend
|
|
15
|
+
* row, exactly as the walk drops a term it cannot place.
|
|
16
|
+
*
|
|
17
|
+
* Geometry is measured in the browser, after fonts (Seam B), and the morph
|
|
18
|
+
* reaches the timeline as ONE typed `fromTo` on the host driving a GSAP plugin.
|
|
19
|
+
* See `src/emit/morph-runtime.ts` for why that, and not a callback.
|
|
20
|
+
*/
|
|
21
|
+
import type { BeatOf, Source, Term } from "../../types.js";
|
|
1
22
|
import type { Emitter } from "../kit.js";
|
|
23
|
+
/**
|
|
24
|
+
* Both lines and their keys, decided in Node — the morph's half of what
|
|
25
|
+
* `prepareWalk` is to the walk, so `plan` can report what `build` will do.
|
|
26
|
+
* Throws where the emitter would: a line KaTeX refuses even repaired, or no key.
|
|
27
|
+
*/
|
|
28
|
+
export declare function prepareMorph(beat: BeatOf<"equation-morph">, source: Source): {
|
|
29
|
+
a: {
|
|
30
|
+
id: string;
|
|
31
|
+
tex: string;
|
|
32
|
+
};
|
|
33
|
+
b: {
|
|
34
|
+
id: string;
|
|
35
|
+
tex: string;
|
|
36
|
+
};
|
|
37
|
+
wa: string;
|
|
38
|
+
wb: string;
|
|
39
|
+
both: Term[];
|
|
40
|
+
notes: string[];
|
|
41
|
+
};
|
|
2
42
|
export declare const equationMorph: Emitter<"equation-morph">;
|