@jokerized/decksmith 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +794 -0
- package/dist/cli.js +8906 -0
- package/dist/deck-runtime.js +55 -0
- package/dist/index.js +8590 -0
- package/dist/mcp.js +7809 -0
- package/dist/server/errors.js +73 -0
- package/dist/server/http.js +504 -0
- package/dist/server/main.js +91 -0
- package/dist/server/options.js +198 -0
- package/dist/server/pipeline.js +356 -0
- package/dist/server/queue.js +195 -0
- package/dist/server/ui.js +1614 -0
- package/dist/server/upload.js +232 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/deck/runtime.d.ts +59 -0
- package/dist/types/deck/subtitles.d.ts +101 -0
- package/dist/types/emit/archetypes/annotated-figure.d.ts +103 -0
- package/dist/types/emit/archetypes/bar-compare.d.ts +30 -0
- package/dist/types/emit/archetypes/callout.d.ts +7 -0
- package/dist/types/emit/archetypes/claim-figure.d.ts +9 -0
- package/dist/types/emit/archetypes/data-table.d.ts +21 -0
- package/dist/types/emit/archetypes/equation-walk.d.ts +2 -0
- package/dist/types/emit/archetypes/grid.d.ts +16 -0
- package/dist/types/emit/archetypes/index.d.ts +19 -0
- package/dist/types/emit/archetypes/line-chart.d.ts +22 -0
- package/dist/types/emit/archetypes/pipeline.d.ts +81 -0
- package/dist/types/emit/archetypes/split-compare.d.ts +2 -0
- package/dist/types/emit/archetypes/stack.d.ts +93 -0
- package/dist/types/emit/archetypes/title.d.ts +188 -0
- package/dist/types/emit/camera.d.ts +397 -0
- package/dist/types/emit/composition.d.ts +191 -0
- package/dist/types/emit/island.d.ts +19 -0
- package/dist/types/emit/kit.d.ts +256 -0
- package/dist/types/emit/svg.d.ts +177 -0
- package/dist/types/emit/theme.d.ts +65 -0
- package/dist/types/emit/themes/index.d.ts +40 -0
- package/dist/types/emit/themes/ink.d.ts +12 -0
- package/dist/types/emit/themes/mono.d.ts +20 -0
- package/dist/types/emit/themes/paper.d.ts +18 -0
- package/dist/types/index.d.ts +200 -0
- package/dist/types/mcp/main.d.ts +2 -0
- package/dist/types/mcp/prereqs.d.ts +22 -0
- package/dist/types/mcp/tools.d.ts +212 -0
- package/dist/types/narrate/narrate.d.ts +87 -0
- package/dist/types/narrate/tts.d.ts +134 -0
- package/dist/types/narrate/voices.d.ts +29 -0
- package/dist/types/pack/media.d.ts +61 -0
- package/dist/types/pack/pack.d.ts +16 -0
- package/dist/types/plan/codex.d.ts +24 -0
- package/dist/types/plan/duration.d.ts +394 -0
- package/dist/types/plan/prompt.d.ts +47 -0
- package/dist/types/plan/refs.d.ts +22 -0
- package/dist/types/plan/select.d.ts +116 -0
- package/dist/types/prefs.d.ts +43 -0
- package/dist/types/render/captions.d.ts +108 -0
- package/dist/types/render/ffmpeg.d.ts +129 -0
- package/dist/types/render/render.d.ts +123 -0
- package/dist/types/render/timing.d.ts +290 -0
- package/dist/types/server/errors.d.ts +11 -0
- package/dist/types/server/http.d.ts +57 -0
- package/dist/types/server/main.d.ts +1 -0
- package/dist/types/server/options.d.ts +90 -0
- package/dist/types/server/pipeline.d.ts +21 -0
- package/dist/types/server/queue.d.ts +105 -0
- package/dist/types/server/ui.d.ts +9 -0
- package/dist/types/server/upload.d.ts +107 -0
- package/dist/types/source/assets.d.ts +11 -0
- package/dist/types/source/fonts.d.ts +15 -0
- package/dist/types/source/markdown.d.ts +8 -0
- package/dist/types/types.d.ts +1992 -0
- package/dist/types/verify/budget.d.ts +41 -0
- package/dist/types/verify/check.d.ts +78 -0
- package/dist/types/verify/drift.d.ts +158 -0
- package/dist/types/verify/fidelity.d.ts +247 -0
- package/dist/types/verify/index.d.ts +207 -0
- package/dist/types/verify/overprint.d.ts +133 -0
- package/dist/types/verify/typefloor.d.ts +50 -0
- package/package.json +84 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One camera move, between two beats the SOURCE says are nested.
|
|
3
|
+
*
|
|
4
|
+
* This is the whole of "keep the stop, lose the cut" (ARCHITECTURE-CANVAS §1).
|
|
5
|
+
* It is deliberately not a canvas and not a general camera: a beat may declare
|
|
6
|
+
* that it happens `inside` one named part of the beat immediately before it, and
|
|
7
|
+
* when it does, the containing beat's window is extended by a dive that lands
|
|
8
|
+
* with that part filling the frame, so the incoming beat opens on the rect the
|
|
9
|
+
* outgoing one left on. Where the source supplies no such relation there is no
|
|
10
|
+
* move, because a smooth pan between unrelated diagrams is a fancier cut and
|
|
11
|
+
* measurably worse than the cut it replaces (Prezi's motion-sickness complaint
|
|
12
|
+
* is the market's own version of this finding).
|
|
13
|
+
*
|
|
14
|
+
* Three things here are load-bearing and each of them cost a render to learn:
|
|
15
|
+
*
|
|
16
|
+
* 1. THE MOVE IS TWO `fromTo`s AND NOTHING ELSE. `seek()` passes
|
|
17
|
+
* `suppressEvents`, so a GSAP `onUpdate` never fires under capture. The first
|
|
18
|
+
* camera in experiment 008 drove `viewBox` from an `onUpdate`, played
|
|
19
|
+
* perfectly in a browser, and rendered 900 frames of a frozen `0 0 1920 1080`
|
|
20
|
+
* with every gate green (invariant 11). State must be applied by the thing
|
|
21
|
+
* being seeked.
|
|
22
|
+
*
|
|
23
|
+
* 2. THE EASES ARE CLOSED FORM AND THEY ARE NOT DECORATION. `zoomEase` travels
|
|
24
|
+
* scale in log space — a linear scale tween reads as a lurch, and every
|
|
25
|
+
* smooth-zoom implementation since Van Wijk 2004 fixes it the same way.
|
|
26
|
+
* `panEase` is the normalised integral of 1/k over the same path, which is
|
|
27
|
+
* exactly the condition "the target moves at constant SCREEN speed"; without
|
|
28
|
+
* it a pan at 5x whips. Both are pure functions of the tween's own progress:
|
|
29
|
+
* nothing reads a clock, a frame delta, or an element's current value, so
|
|
30
|
+
* `seek(x)` gives the same transform for a given x on every run and in any
|
|
31
|
+
* order.
|
|
32
|
+
*
|
|
33
|
+
* 3. THE CONTAINING DIAGRAM IS AT FULL OPACITY FOR THE WHOLE FLIGHT. The spike
|
|
34
|
+
* faded its world out DURING the move by accident and made the mid-flight
|
|
35
|
+
* frame an empty world — a camera over nothing is not continuity, it is a
|
|
36
|
+
* slow wipe. So the fade here starts only once the camera has stopped, and
|
|
37
|
+
* lasts `FADE_SECONDS`: the deck dives into the region, comes to rest on it,
|
|
38
|
+
* and dips through its own background into the beat that lives there.
|
|
39
|
+
*
|
|
40
|
+
* WHAT THE DIP COSTS, MEASURED, so nobody has to find it twice. Luma sampled
|
|
41
|
+
* every 1/30s across the handoff of a `pipeline`→`grid` deck: the dive lands
|
|
42
|
+
* at 10.4s, the dip runs to the scene's window end at 10.8s and hands off at
|
|
43
|
+
* max luma 63 over a background of 13 — and then the frame is FLAT BACKGROUND
|
|
44
|
+
* FOR FIVE FRAMES, 10.8s through 10.933s, before the incoming scene reaches
|
|
45
|
+
* 23 at 10.967s. Those five frames are not the camera's. They are the
|
|
46
|
+
* incoming scene's own opening: `chromeIn` (`title.ts`) puts its first tween
|
|
47
|
+
* at 0.15s, so every scene in the deck opens on 150ms of nothing, and a plain
|
|
48
|
+
* cut in the demo measures the same four-to-five dark frames at 10.233s.
|
|
49
|
+
* Nothing this file can do reaches them: `paint()` and the engine both swap
|
|
50
|
+
* scene visibility on one instant, so retiming the dip only moves the hole —
|
|
51
|
+
* verified over dip lengths of 0, 0.25 and 0.4, all five frames.
|
|
52
|
+
*
|
|
53
|
+
* THAT HOLE IS NOW CLOSED, and it was closed the way this note predicted:
|
|
54
|
+
* extend the OUTGOING scene's clip past the incoming scene's start and run
|
|
55
|
+
* the dip on into it. `composition.ts` no longer derives the scene div's
|
|
56
|
+
* window, the island's window and the next scene's start from one number —
|
|
57
|
+
* the div's `data-duration` is `duration + HANDOFF_SECONDS` while the island
|
|
58
|
+
* and the deck's running clock still see `duration`. Doing it on the island
|
|
59
|
+
* as well still fails the gate — `slideshow_unresolved_ref: main-line slides
|
|
60
|
+
* "s1" and "s2" overlap`, still true on 0.7.71 — which is the same §4
|
|
61
|
+
* `planTransition`/`paint()` work described below, and is why the overlap
|
|
62
|
+
* lives on the clip and nowhere else. The 150ms proposed here turned out to
|
|
63
|
+
* be short by half; `HANDOFF_SECONDS` carries the luma trace that fixes it.
|
|
64
|
+
*
|
|
65
|
+
* That dip is a compromise and it is worth naming. The spike's version keeps
|
|
66
|
+
* the container lit while the contained scene arrives inside it, which needs
|
|
67
|
+
* both scenes on screen at once — and `hyperframes lint` rejects that
|
|
68
|
+
* outright: `slideshow_unresolved_ref — main-line slides "s1" and "s2"
|
|
69
|
+
* overlap`. Overlapping windows need the island to place one of them off the
|
|
70
|
+
* main line and `paint()` to stop owning `display`, which is exactly the
|
|
71
|
+
* `planTransition`/`paint()` work ARCHITECTURE-CANVAS §4 scoped and deferred.
|
|
72
|
+
* So what is here is a dive that lands on the incoming beat's frame and dips
|
|
73
|
+
* to background across the seam — items 1 and 4 of §2, "do first" and "the
|
|
74
|
+
* cheap half of lose the cut" — and not item 5. Called by its right name it
|
|
75
|
+
* is a match cut with a camera in front of it, not a continuous world.
|
|
76
|
+
*
|
|
77
|
+
* The geometry is measured in the browser rather than computed here, because the
|
|
78
|
+
* region's page rect depends on how much room the headline above it took, which
|
|
79
|
+
* depends on font metrics. Measuring during parse frames the deck on fallback
|
|
80
|
+
* metrics and lands the camera in the wrong place, silently, in a deck that still
|
|
81
|
+
* renders twice to the same bytes because both renders take the same wrong path.
|
|
82
|
+
* So the measurement is DEFERRED into the ready gate's barrier and taken exactly
|
|
83
|
+
* once — see `cameraMeasure`, which carries the render that made it necessary.
|
|
84
|
+
*/
|
|
85
|
+
import type { Format } from "../types.js";
|
|
86
|
+
import { type Tween } from "./kit.js";
|
|
87
|
+
/** How long the dive takes. The spike's MOVE1; long enough to read, short
|
|
88
|
+
* enough that deck mode's `MAX_SPAN` still sweeps the step around it. */
|
|
89
|
+
export declare const MOVE_SECONDS = 1.4;
|
|
90
|
+
/** How long the landed framing takes to dip to the deck's background, after
|
|
91
|
+
* which the incoming scene's window begins. Below ~0.25 it reads as a pop; much
|
|
92
|
+
* above and the seam becomes a noticeable hole in the deck. */
|
|
93
|
+
export declare const FADE_SECONDS = 0.4;
|
|
94
|
+
/**
|
|
95
|
+
* How far a scene's CLIP outlasts its own slide, so the next scene opens
|
|
96
|
+
* underneath it instead of on an empty plate. Zero on the last scene.
|
|
97
|
+
*
|
|
98
|
+
* This is the number that closes the hole described at the top of this file,
|
|
99
|
+
* and 0.4 is measured rather than chosen. Every archetype opens through
|
|
100
|
+
* `chromeIn`, which puts the eyebrow at 0.15s and the headline at 0.3s, so a
|
|
101
|
+
* scene's first frames carry no ink at all. Luma sampled every 1/30s across the
|
|
102
|
+
* first handoff of the vertical demo, as mean frame luma above the deck's
|
|
103
|
+
* background of 27:
|
|
104
|
+
*
|
|
105
|
+
* +0.163s 0.007 +0.263s 0.053 +0.363s 0.809 +0.463s 1.801
|
|
106
|
+
*
|
|
107
|
+
* — against 7.73 for the outgoing scene's last lit frame. So the incoming
|
|
108
|
+
* scene is, measurably, EMPTY for its first tenth of a second and near-empty
|
|
109
|
+
* for a third of one, and an overlap that ends before ~0.36s only MOVES the
|
|
110
|
+
* hole rather than closing it: at 0.15 the floor across the seam was 0.007,
|
|
111
|
+
* indistinguishable from background. At 0.4 the floor is 1.40 and the frames
|
|
112
|
+
* either side of it are a legible dissolve.
|
|
113
|
+
*
|
|
114
|
+
* Do not raise it much further. The two scenes are absolutely positioned on
|
|
115
|
+
* top of each other, so a long dissolve is two headlines superimposed — at
|
|
116
|
+
* 0.45 the outgoing eyebrow is still at 58% while the incoming's is up, and
|
|
117
|
+
* the two lines of type collide legibly.
|
|
118
|
+
*/
|
|
119
|
+
export declare const HANDOFF_SECONDS = 0.4;
|
|
120
|
+
/**
|
|
121
|
+
* The dissolve that carries a scene across the seam, for a scene with no camera
|
|
122
|
+
* on it. Starts where the slide ends and runs into the next slide's window, so
|
|
123
|
+
* it is only ever seen against the incoming scene rather than against nothing.
|
|
124
|
+
*
|
|
125
|
+
* `power2.in` for the same reason the dip uses it: the outgoing scene holds
|
|
126
|
+
* most of its brightness through the first half of the overlap, which is
|
|
127
|
+
* exactly the half in which the incoming scene has nothing to give.
|
|
128
|
+
*
|
|
129
|
+
* The target is the scene div itself. There is no wrapper to fade instead, and
|
|
130
|
+
* adding one would put a second copy of the scene box in the tree — the camera
|
|
131
|
+
* rig already carries that cost and only earns it because it needs the
|
|
132
|
+
* transforms.
|
|
133
|
+
*/
|
|
134
|
+
export declare function handoffStatement(sid: string, at: number, over: number): Tween;
|
|
135
|
+
/** A dive, in the containing scene's own time base. All three already scaled by `speed`. */
|
|
136
|
+
export interface Dive {
|
|
137
|
+
/** Seconds from the scene's start at which the camera leaves the wide shot. */
|
|
138
|
+
t0: number;
|
|
139
|
+
/** Travel time. */
|
|
140
|
+
dur: number;
|
|
141
|
+
/** The dip that follows the landing, and ends this scene's window. */
|
|
142
|
+
fade: number;
|
|
143
|
+
}
|
|
144
|
+
/** How much longer a scene's window is because a camera leaves from it. */
|
|
145
|
+
export declare function diveTail(d: Dive): number;
|
|
146
|
+
/** The DOM id an archetype gives one of its parts: `s2` + `stage1` -> `s2-stage1`. */
|
|
147
|
+
export declare function elementId(sid: string, element: string): string;
|
|
148
|
+
/**
|
|
149
|
+
* The parts of a scene a camera could enter, read back out of the emitted HTML.
|
|
150
|
+
*
|
|
151
|
+
* Only used to write a useful error. There is no registry of enterable regions —
|
|
152
|
+
* `src/emit/svg.ts`'s `id(sid, part, i)` is the whole convention, and an
|
|
153
|
+
* archetype that draws a thing with an id can be entered at it.
|
|
154
|
+
*/
|
|
155
|
+
export declare function enterableIds(sid: string, html: string): string[];
|
|
156
|
+
/**
|
|
157
|
+
* The camera rig, wrapped around one scene's content.
|
|
158
|
+
*
|
|
159
|
+
* Two nested transforms and a plate, in that order, because the order is what
|
|
160
|
+
* makes the neutral framing the IDENTITY transform: scaling about the frame's
|
|
161
|
+
* centre and then translating the plate by `centre - target` frames the target
|
|
162
|
+
* for any scale, and both tweens start at 0/1 — the values an element has before
|
|
163
|
+
* a `fromTo` with `immediateRender: false` has ever run. A rig whose rest state
|
|
164
|
+
* needed setting would show the wrong framing for every frame before the move.
|
|
165
|
+
*
|
|
166
|
+
* The plate carries `class="scene"` so it lays out through the one rule in
|
|
167
|
+
* `theme.ts` rather than a copy of the padding kept in step by hand.
|
|
168
|
+
*/
|
|
169
|
+
export declare function rigHtml(inner: string): string;
|
|
170
|
+
/**
|
|
171
|
+
* THE TRANSIT WINDOW, in absolute composition seconds — `[t0, t0+dur+fade+over]`
|
|
172
|
+
* measured from the deck's start — written onto the cameraed scene's own div as
|
|
173
|
+
* `data-ds-transit`. Nothing in the browser reads it, and there is exactly one
|
|
174
|
+
* of them per camera.
|
|
175
|
+
*
|
|
176
|
+
* `over` IS PART OF THE WINDOW, and leaving it out was a live bug. `diveTail` is
|
|
177
|
+
* how much the camera lengthens the scene's SLIDE — `dur + fade`, deliberately
|
|
178
|
+
* ignorant of the handoff — but the dip `diveStatements` writes lasts
|
|
179
|
+
* `fade + over`, so the rig is displaced and still visible for `over` seconds
|
|
180
|
+
* after that. Publishing `dur + fade` therefore describes less than the move:
|
|
181
|
+
* `menu-10` of `experiments/015-decision` dives at 10s and clips at 12.2s, the
|
|
182
|
+
* gate sampled 11.978s, and the window said `10,11.8` — thirteen
|
|
183
|
+
* `canvas_overflow` errors on one legitimate mid-move frame, which was one of
|
|
184
|
+
* the two failures in that experiment's arm MENU
|
|
185
|
+
* (`out/f-menu10-11.978.png`; deleting the beat's `inside` made the same plan
|
|
186
|
+
* PASS). The window must be the move: everything the rig is displaced for.
|
|
187
|
+
*
|
|
188
|
+
* It is here because a camera is the first thing in this project that puts
|
|
189
|
+
* MOTION between two stops, and the layout gate samples a uniform grid that
|
|
190
|
+
* knows nothing about stops. `hyperframes check` on
|
|
191
|
+
* `demo/fixtures/camera.storyboard.json` samples 1.1, 3.3, … 18.7 across a 19.8s
|
|
192
|
+
* deck; exactly one of those, 9.9, lands inside the dive, and at 9.9 the frame
|
|
193
|
+
* is a legitimate mid-flight crop — headline sliced, outer stages gone past the
|
|
194
|
+
* edge. Upstream calls all eight of those findings `info` and returns `ok:true`,
|
|
195
|
+
* because a single-sample dynamic issue is a transient to it. `regrade` in
|
|
196
|
+
* `src/verify/check.ts` promotes every `canvas_overflow` to `error` on the
|
|
197
|
+
* stated premise that "every DeckSmith frame the audience sees is a static plate
|
|
198
|
+
* at a hold" — which is true, and which this window is the exception to. So a
|
|
199
|
+
* legal storyboard builds a deck that fails our own gate on seven errors, none
|
|
200
|
+
* of which is a frame anybody sees.
|
|
201
|
+
*
|
|
202
|
+
* WHY NOT `data-layout-allow-overflow`, which is the vocabulary upstream's own
|
|
203
|
+
* fixHint offers. Because it hides real overflow, measured rather than argued.
|
|
204
|
+
* The audit exempts a text node when `element.closest("[data-layout-allow-
|
|
205
|
+
* overflow]")` matches — an ancestor test with no notion of time. Every glyph a
|
|
206
|
+
* camera moves is inside the rig, so the flag can only go on the rig, and there
|
|
207
|
+
* it exempts the scene at every sample instead of only the ones inside the move.
|
|
208
|
+
* Three builds of this fixture with the containing beat's `note` replaced by a
|
|
209
|
+
* 130-character unbreakable word, which overflows the canvas at scale 1, at
|
|
210
|
+
* every stop, forever:
|
|
211
|
+
*
|
|
212
|
+
* no `inside`, no flag FAIL 1 error #s1-note t=5s
|
|
213
|
+
* `inside`, no flag FAIL 8 errors #s1-note t=5.5s and t=9.9s, + 6 transit
|
|
214
|
+
* `inside`, flag PASS
|
|
215
|
+
*
|
|
216
|
+
* (Those three were measured before this exemption existed; see the re-measured
|
|
217
|
+
* pair below, which is the same experiment through today's grading.)
|
|
218
|
+
*
|
|
219
|
+
* The third line is a deck shipping with its own copy sliced through and every
|
|
220
|
+
* gate green — the failure shape this project has now found by hand eleven
|
|
221
|
+
* times. So the flag is not written. The exemption is published as DATA the
|
|
222
|
+
* caller can compare a finding's `time` against, and the teeth stay in because
|
|
223
|
+
* the SECOND line is what a real defect looks like through this window: the note
|
|
224
|
+
* is reported at 5.5s as well as at 9.9s, and 5.5 is outside the window.
|
|
225
|
+
* Anything genuinely wrong with the plate is wrong at rest, and at rest the gate
|
|
226
|
+
* is sampling it. The emitter's half of the same guarantee is
|
|
227
|
+
* `assertStopsOutsideMove`: no stop is ever inside the window to begin with.
|
|
228
|
+
*
|
|
229
|
+
* WIDENING A WINDOW IS THE CHANGE THAT COULD EXCUSE A REAL DEFECT, so the `over`
|
|
230
|
+
* term was checked both ways rather than argued.
|
|
231
|
+
*
|
|
232
|
+
* The teeth, RE-MEASURED with `over` in the window — the same three builds, now
|
|
233
|
+
* against `9,11.2`. Line two is the one that matters, and it is the number that
|
|
234
|
+
* did NOT move:
|
|
235
|
+
*
|
|
236
|
+
* no `inside`, no flag FAIL 1 error #s1-note t=5s
|
|
237
|
+
* `inside`, no flag FAIL 1 error + 7 info #s1-note t=5.5s, 7 at 9.9s
|
|
238
|
+
*
|
|
239
|
+
* (The middle column reads 1+7 rather than the 8 errors recorded above only
|
|
240
|
+
* because this exemption now exists; the ERROR at 5.5s is unchanged, and 5.5 is
|
|
241
|
+
* outside the widened window as it was outside the narrow one.)
|
|
242
|
+
*
|
|
243
|
+
* THE ADVERSARIAL DECK, which `.planning/DECISION.md` §5.1 named as the control
|
|
244
|
+
* this change must survive: `regrade` exempts by TIME ALONE, with no scene scope,
|
|
245
|
+
* so the extra `over` seconds exempt the whole deck — including the INCOMING
|
|
246
|
+
* scene, whose first stop can land inside them. Constructed rather than argued
|
|
247
|
+
* about: the incoming beat is 0.5s long, so `holdsWithin` clamps its stop to
|
|
248
|
+
* 0.35s (inside the 0.4s tail), its eyebrow AND headline are 130-character
|
|
249
|
+
* unbreakable words, and a third 0.3s beat tunes the deck's total so that one of
|
|
250
|
+
* the layout gate's nine uniform samples lands at 10.956s — inside the tail. That
|
|
251
|
+
* deck's stop really is inside the published window: `[9, 11.2]` against a
|
|
252
|
+
* fragment at 11.15.
|
|
253
|
+
*
|
|
254
|
+
* One render of it, graded twice, which is the only way to attribute a change to
|
|
255
|
+
* the change:
|
|
256
|
+
*
|
|
257
|
+
* window [9, 10.8] (the bug) FAIL 7 errors, all `#s1-*` at t=10.956s
|
|
258
|
+
* window [9, 11.2] (fixed) PASS
|
|
259
|
+
* findings naming `#s2` ZERO, at any severity, in both gradings
|
|
260
|
+
*
|
|
261
|
+
* So everything the wider window forgave was the dipping rig's own displacement,
|
|
262
|
+
* and nothing about the incoming scene was forgiven — because during the handoff
|
|
263
|
+
* the incoming scene has nothing to forgive. `chromeIn` starts its eyebrow at
|
|
264
|
+
* 0.15s over 0.5s and its headline at 0.3s over 0.6s, so at 0.156s into a scene
|
|
265
|
+
* there is no text at a measurable opacity yet. That is the same 150ms of nothing
|
|
266
|
+
* the handoff exists to cover, seen from the other side.
|
|
267
|
+
*
|
|
268
|
+
* The corpus agrees: across the 36 built decks of
|
|
269
|
+
* `experiments/015-decision/out/decks` (36 windows, one per deck), widening every
|
|
270
|
+
* published window by 0.4s AND by 0.8s swallows ZERO stops, and the earliest stop
|
|
271
|
+
* any of their 60 scenes has is 1.55s into itself.
|
|
272
|
+
*
|
|
273
|
+
* WHAT IS STILL NOT GUARANTEED, said out loud because the exemption's own message
|
|
274
|
+
* overstates it. `regrade` prints "no hold is inside this window"; what the
|
|
275
|
+
* emitter guarantees is that no hold of the DIPPING scene is
|
|
276
|
+
* (`assertStopsOutsideMove`), and an incoming scene's hold inside the `over` tail
|
|
277
|
+
* is reachable — the deck above has one. It is harmless only because nothing of
|
|
278
|
+
* that scene is visible yet. Closing it for good means scoping the exemption to
|
|
279
|
+
* the dipping rig's own subtree, which is `regrade`'s job in
|
|
280
|
+
* `src/verify/check.ts`: the sid is right there in the selector it already has.
|
|
281
|
+
*
|
|
282
|
+
* `over` is a required argument, not one defaulting to 0. A default is what let
|
|
283
|
+
* the sole call site drift out of step with `diveStatements` in the first place;
|
|
284
|
+
* making it required means the compiler asks.
|
|
285
|
+
*/
|
|
286
|
+
export declare function transitWindow(start: number, d: Dive, over: number): string;
|
|
287
|
+
export declare function cameraCss(): string;
|
|
288
|
+
/**
|
|
289
|
+
* THE MEASUREMENT, taken ONCE, inside the ready gate's barrier — after
|
|
290
|
+
* `document.fonts.ready` and before this scene's timeline exists. Emitted as
|
|
291
|
+
* `Scene.measure` (see `sceneHtml`), so these statements run in the scene's own
|
|
292
|
+
* builder closure and the tweens below read `dsFramed` as a plain number.
|
|
293
|
+
*
|
|
294
|
+
* WHY NOT LAZILY, which is what this did until the twelfth case of a green gate
|
|
295
|
+
* over wrong output. `hyperframes render` shards frames CONTIGUOUSLY, one span
|
|
296
|
+
* per worker, so worker k's first seek lands MID-DECK — and a value first read on
|
|
297
|
+
* a tween's first render is therefore read under different conditions in every
|
|
298
|
+
* worker. `experiments/014-seam-b` measured that standalone: `lazy` moved 286 of
|
|
299
|
+
* 360 frames when only `--workers` changed, `defer` 0 of 360.
|
|
300
|
+
*
|
|
301
|
+
* WHAT THE NUMBER ATTRIBUTED TO THIS FILE ACTUALLY WAS, because the first reading
|
|
302
|
+
* of it was wrong and the correction is the useful part. The twelfth case reported
|
|
303
|
+
* `demo/fixtures/camera.storyboard.json` differing in 201 of 594 frames at 1 vs 3
|
|
304
|
+
* workers, worst 51.52 dB, and blamed the lazy read here. It is not the lazy read.
|
|
305
|
+
* Three controls, each varying one thing:
|
|
306
|
+
*
|
|
307
|
+
* defer vs lazy, same worker count 594 of 594 frames BYTE-IDENTICAL
|
|
308
|
+
* lazy, 1 vs 3 workers 201 of 594, worst 51.52 dB — the same
|
|
309
|
+
* frames and the same dB as `defer`
|
|
310
|
+
* the same two beats, camera REMOVED 180 of 540, worst 51.52 dB, and the
|
|
311
|
+
* differing frames are exactly worker 3's
|
|
312
|
+
* contiguous shard
|
|
313
|
+
*
|
|
314
|
+
* So the fixture's 201 is 198 frames of the `grid` scene's cell fill landing one
|
|
315
|
+
* LSB apart in a worker whose first seek is inside that scene, plus 3 frames of 3
|
|
316
|
+
* pixels at 113 dB. It has nothing to do with the camera and this change does not
|
|
317
|
+
* move it.
|
|
318
|
+
*
|
|
319
|
+
* AND WHY THAT FIXTURE CANNOT WITNESS THE CLASS AT ALL, which is worth knowing
|
|
320
|
+
* before anyone points it at this again. The dive tweens carry
|
|
321
|
+
* `immediateRender: false`, so under a lazy read GSAP never touches them before
|
|
322
|
+
* `t0`; the read therefore happens on each worker's first frame at or after `t0`,
|
|
323
|
+
* and by then everything it measures — `#s1-stage1`, revealed at 2.4s over 0.5s —
|
|
324
|
+
* has stopped moving. Every worker that reaches the dive measures the same rects.
|
|
325
|
+
* Point a camera at something that is STILL MOVING at `t0` and the class appears
|
|
326
|
+
* at once. Measured, target given a 4s `y` tween running through `t0` and the
|
|
327
|
+
* shard boundary forced inside the dive at 2 workers:
|
|
328
|
+
*
|
|
329
|
+
* lazy 39 of 594 frames differ, worst 14.96 dB — the camera lands 18px off
|
|
330
|
+
* defer 7 of 594 frames differ, worst 62.64 dB — antialiasing on a scaled edge
|
|
331
|
+
*
|
|
332
|
+
* 14.96 dB is a visibly different frame. That is the defect this file no longer
|
|
333
|
+
* has, and the 51.52 dB one belongs to somebody else.
|
|
334
|
+
*
|
|
335
|
+
* Deferring is not merely earlier. It is the difference between a value GSAP
|
|
336
|
+
* evaluates when it feels like it and a NUMBER in the vars payload — which is why
|
|
337
|
+
* the eases below are now closed-form functions built once from a fixed ratio
|
|
338
|
+
* rather than wrappers that re-read a memo on every call.
|
|
339
|
+
*
|
|
340
|
+
* `dsFramed` measures the target RELATIVE TO THE PLATE and divides out the
|
|
341
|
+
* plate's scale, so the answer is a property of the LAYOUT and not of the moment
|
|
342
|
+
* it was taken. Nothing has moved the rig when this runs, but a non-1920 canvas
|
|
343
|
+
* carries a scale on `.scene` regardless (`zoomOf`), so the division is load-
|
|
344
|
+
* bearing in portrait and it is also what keeps a second reading — if anything
|
|
345
|
+
* ever takes one — equal to the first.
|
|
346
|
+
*
|
|
347
|
+
* A missing target degrades to the neutral framing rather than to `NaN`: a
|
|
348
|
+
* camera that does not move is a cut, and a cut is what we had. The emitter
|
|
349
|
+
* refuses to write a camera at a target that is not in the HTML, so this branch
|
|
350
|
+
* only fires if something removed the element after emit.
|
|
351
|
+
*/
|
|
352
|
+
export declare function cameraMeasure(sid: string, element: string, format: Format): string[];
|
|
353
|
+
/**
|
|
354
|
+
* The move itself. Two `fromTo`s on the rig, plus the container's fade.
|
|
355
|
+
*
|
|
356
|
+
* Every value that depends on the measurement is an EXPRESSION over `dsFramed`,
|
|
357
|
+
* which `cameraMeasure` has already assigned by the time these statements run.
|
|
358
|
+
* So GSAP is handed numbers, and the timeline is as seekable as one written here
|
|
359
|
+
* at emit time — no function value evaluated on a first render whose position in
|
|
360
|
+
* the deck depends on how many workers the renderer happened to start.
|
|
361
|
+
*
|
|
362
|
+
* `immediateRender: false` is not optional: with it, the rig has no transform at
|
|
363
|
+
* all before `t0` — which is the neutral framing — and GSAP holds the landing
|
|
364
|
+
* transform after the move ends, which is the framing the incoming scene opens
|
|
365
|
+
* on.
|
|
366
|
+
*/
|
|
367
|
+
export declare function diveStatements(sid: string, format: Format, d: Dive, over?: number): Tween[];
|
|
368
|
+
/**
|
|
369
|
+
* THE TYPE FLOOR UNDER A CAMERA.
|
|
370
|
+
*
|
|
371
|
+
* The 40px rule is about what the audience can READ, so under a camera it has to
|
|
372
|
+
* be stated in final rendered pixels rather than in the emitter's font-size:
|
|
373
|
+
*
|
|
374
|
+
* At every stop time t_s, for every text node n intersecting the viewport,
|
|
375
|
+
* effOpacity(n, t_s) == 0 OR finalPx(n, t_s) >= 40
|
|
376
|
+
* where finalPx = computedFontSize x screenScale and effOpacity is the product
|
|
377
|
+
* of computed opacity to the root. TRANSIT IS EXEMPT, and is exempt only
|
|
378
|
+
* because the deck never rests between stops.
|
|
379
|
+
*
|
|
380
|
+
* That reading is what lets a cropping camera reach 1.55-1.75x where a camera
|
|
381
|
+
* that had to keep everything inside a safe box was stuck at 1.127x: text that
|
|
382
|
+
* leaves the frame is not small, it is absent, and nobody is asked to read it.
|
|
383
|
+
*
|
|
384
|
+
* This function is the part of that rule an emitter can enforce WITHOUT a
|
|
385
|
+
* browser, and it enforces it by making the general case unnecessary: if no stop
|
|
386
|
+
* falls inside the move, then at every stop of a cameraed scene the rig is at
|
|
387
|
+
* scale 1 (before `t0`, because `immediateRender: false` leaves no transform) or
|
|
388
|
+
* at the landing scale with the region filling the frame (after the landing,
|
|
389
|
+
* where every surviving glyph is LARGER than it was authored). Either way the
|
|
390
|
+
* static 40px floor the archetypes already satisfy is still the true floor, and
|
|
391
|
+
* the browser-measured rule above is only needed once a stop lands mid-camera.
|
|
392
|
+
*
|
|
393
|
+
* Throws rather than warns: a stop inside a camera move is a frame where the
|
|
394
|
+
* audience is asked to read text at an arbitrary scale, and that is exactly the
|
|
395
|
+
* class of defect that ships green.
|
|
396
|
+
*/
|
|
397
|
+
export declare function assertStopsOutsideMove(sid: string, holds: readonly number[], d: Dive): void;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storyboard + Source + Format -> one HyperFrames composition.
|
|
3
|
+
*
|
|
4
|
+
* This module owns the deck's structure, and structure is where deck mode fails
|
|
5
|
+
* silently: EXPERIMENT-002 produced a deck that passed `lint`, served, rendered,
|
|
6
|
+
* and had zero navigable slides. Nothing in the gate stack said so. So the
|
|
7
|
+
* renderer's structural rules live here as code rather than as prose an agent is
|
|
8
|
+
* asked to remember, and every one of them is annotated with what breaks.
|
|
9
|
+
*
|
|
10
|
+
* The shell never learns what an archetype means — it lays beats out on a
|
|
11
|
+
* timeline, wraps whatever the emitter returns, and closes the document.
|
|
12
|
+
*/
|
|
13
|
+
import type { z } from "zod";
|
|
14
|
+
import { type Cut } from "../plan/select.js";
|
|
15
|
+
import type { Format, Source, Storyboard, segmentSchema } from "../types.js";
|
|
16
|
+
import { type Scene } from "./kit.js";
|
|
17
|
+
/** A built deck. `page` is present only for navigable formats. */
|
|
18
|
+
export interface Deck {
|
|
19
|
+
composition: string;
|
|
20
|
+
page?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Which beats went in, which did not, and why each one did not.
|
|
23
|
+
*
|
|
24
|
+
* Returned rather than logged here because `emit` is a pure function and the
|
|
25
|
+
* caller owns the terminal — but it is not optional to LOOK at: a cut nobody
|
|
26
|
+
* prints is a third of the explanation missing from a deck that still says
|
|
27
|
+
* PASS. `build` prints every casualty's reason; see src/cli.ts.
|
|
28
|
+
*/
|
|
29
|
+
cut: Cut;
|
|
30
|
+
}
|
|
31
|
+
type Segment = z.infer<typeof segmentSchema>;
|
|
32
|
+
/**
|
|
33
|
+
* Narration as the deck page needs it: the segments, keyed by BEAT id because
|
|
34
|
+
* that is what `narrate` produced, plus where the audio sits relative to the
|
|
35
|
+
* page. The beat→scene mapping is `layout`'s to make — a scene id is a position
|
|
36
|
+
* over the beats a format kept, and no other layer knows which those are.
|
|
37
|
+
*/
|
|
38
|
+
export interface DeckNarration {
|
|
39
|
+
voice: string;
|
|
40
|
+
/** Directory holding the mp3s, relative to `deck.html`. */
|
|
41
|
+
dir: string;
|
|
42
|
+
beats: Record<string, Segment[]>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The look-and-pace decisions `prefs` owns, handed down rather than read here:
|
|
46
|
+
* `emit` is a pure function of its arguments, and a module that loads a config
|
|
47
|
+
* file is a module you cannot render twice and compare.
|
|
48
|
+
*/
|
|
49
|
+
export interface DeckOptions {
|
|
50
|
+
/** Overrides `storyboard.theme`. Any name in the registry. */
|
|
51
|
+
theme?: string;
|
|
52
|
+
/** Multiplies every duration, hold, and beat length. 1 leaves bytes untouched. */
|
|
53
|
+
speed?: number;
|
|
54
|
+
narration?: DeckNarration;
|
|
55
|
+
/**
|
|
56
|
+
* What to do when one beat cannot be drawn.
|
|
57
|
+
*
|
|
58
|
+
* Absent, an emitter's error propagates and the build fails — right for a
|
|
59
|
+
* library caller and for every test, and what this did for months. It is the
|
|
60
|
+
* wrong answer for someone who uploaded a document: one beat the planner
|
|
61
|
+
* over-filled took a whole twelve-slide deck with it, at the last stage, after
|
|
62
|
+
* the planner had already been paid for.
|
|
63
|
+
*
|
|
64
|
+
* Dropped rather than repaired, deliberately. A missing slide is visibly
|
|
65
|
+
* missing; a slide silently shrunk to fit is a slide that lies about what the
|
|
66
|
+
* source said, and this project has spent a lot of its life on exactly that
|
|
67
|
+
* failure shape.
|
|
68
|
+
*/
|
|
69
|
+
onBeatError?: (beatId: string, err: Error) => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Both artifacts from one layout pass.
|
|
73
|
+
*
|
|
74
|
+
* `composition` is the HyperFrames document — what `check`, `snapshot` and
|
|
75
|
+
* `render` consume. `page` is the navigable wrapper: it hosts the composition in
|
|
76
|
+
* a player, carries the same island, and inlines our step layer. Two files
|
|
77
|
+
* 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.
|
|
80
|
+
*/
|
|
81
|
+
export declare function emitDeck(storyboard: Storyboard, source: Source, format: Format, runtimeJs: string, opts?: DeckOptions): Deck;
|
|
82
|
+
/**
|
|
83
|
+
* The beats this format actually draws, and why the rest are not there.
|
|
84
|
+
*
|
|
85
|
+
* TWO rules, and the second one is new here. The FLOOR — `format.minWeight`,
|
|
86
|
+
* raised for one invocation by `--min-weight` — is the author's own statement
|
|
87
|
+
* about what survives a shorter cut. The BUDGET is the format's: a `short-9x16`
|
|
88
|
+
* that runs four minutes is not a short, it is a tall video nobody can post
|
|
89
|
+
* (`DESTINATIONS` in src/types.ts). Until this call existed the budget could only
|
|
90
|
+
* be REPORTED, by `verify`, after the over-long deck had already been written, so
|
|
91
|
+
* one storyboard could not produce both a deck and a short without a human doing
|
|
92
|
+
* the arithmetic and feeding a threshold back in. `selectBeats` picks a better
|
|
93
|
+
* cut than a threshold can express — it protects the ends and one beat of every
|
|
94
|
+
* archetype family, and weighs length against weight — and every beat it drops
|
|
95
|
+
* arrives with a sentence saying why (src/plan/select.ts).
|
|
96
|
+
*
|
|
97
|
+
* WHY THIS MEASURES BEFORE IT SELECTS. A beat's real length is its NARRATED
|
|
98
|
+
* length, `beatSeconds` below, which is only known once the scene has been
|
|
99
|
+
* emitted and its holds are in hand. The measured scenes are then thrown away:
|
|
100
|
+
* a scene id is a position over the beats that SURVIVE (`s1`, `s2`, …) and every
|
|
101
|
+
* timeline selector is scoped by it (invariant 3), so a scene emitted before the
|
|
102
|
+
* cut is known carries ids for a deck that is not the one being built. Holds are
|
|
103
|
+
* times and do not depend on the id, which is what makes measuring with
|
|
104
|
+
* provisional ids exact rather than approximate.
|
|
105
|
+
*
|
|
106
|
+
* ONLY THE FLOOR'S SURVIVORS ARE MEASURED. Emitting a beat the floor has already
|
|
107
|
+
* dropped buys nothing and adds a way to fail: `emitScene` throws on a beat it
|
|
108
|
+
* cannot draw, and a beat nobody was ever going to draw must not be able to fail
|
|
109
|
+
* a build that never wanted it.
|
|
110
|
+
*
|
|
111
|
+
* AND THIS IS WHERE A BEAT THAT CANNOT BE DRAWN IS CAUGHT. It has to be: this is
|
|
112
|
+
* the FIRST place every surviving beat is emitted, so a `layout` that only caught
|
|
113
|
+
* the throw on its own second pass never saw one — `emitScene` is deterministic,
|
|
114
|
+
* so a beat that throws here throws identically there, and the deck died in the
|
|
115
|
+
* measuring pass with `onBeatError` never called. Dropping it here also keeps the
|
|
116
|
+
* returned `Cut` honest: a beat that is not in the deck is not in `kept`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function planCut(storyboard: Storyboard, source: Source, format: Format, opts?: DeckOptions): Cut;
|
|
119
|
+
/**
|
|
120
|
+
* How long the finished picture is held after the last word, before the cut.
|
|
121
|
+
*
|
|
122
|
+
* `HANDOFF_SECONDS`, deliberately: the outgoing scene's dissolve begins where its
|
|
123
|
+
* slide ends, so the settled frame gets exactly one handoff of stillness before
|
|
124
|
+
* it starts fading. Two scenes therefore have `SETTLE + open` between them — a
|
|
125
|
+
* breath, against the 1.86-3.44s of dead air measured on the deck this replaces.
|
|
126
|
+
*/
|
|
127
|
+
export declare const SETTLE_SECONDS = 0.4;
|
|
128
|
+
/**
|
|
129
|
+
* When the voice may start inside a scene: as soon as the headline has landed.
|
|
130
|
+
*
|
|
131
|
+
* MEASURED off the scene rather than fixed, which is the whole point. The chrome
|
|
132
|
+
* is `#sid-e` at 0.15s over 0.5s and `#sid-h` at 0.3s over 0.6s, so it settles at
|
|
133
|
+
* 0.9s unpaced — but `pace` has already scaled the scene by the time this is
|
|
134
|
+
* asked, so at the 0.417 a 60-second target derives the same headline lands at
|
|
135
|
+
* 0.375s. A constant would have been right at one speed and wrong at every other,
|
|
136
|
+
* and starting the voice over a 46%-opacity headline is the failure it would have
|
|
137
|
+
* shipped.
|
|
138
|
+
*
|
|
139
|
+
* Falls back to the first hold when a scene draws no chrome, which is the same
|
|
140
|
+
* answer the deck gives today.
|
|
141
|
+
*/
|
|
142
|
+
export declare function openSeconds(scene: Scene): number;
|
|
143
|
+
/**
|
|
144
|
+
* The scene as the deck actually contains it: paced, then filled.
|
|
145
|
+
*
|
|
146
|
+
* ONE function because three callers have to agree exactly — `planCut` measures
|
|
147
|
+
* with it, `layout` emits with it, and `holdsFor` in src/render/timing.ts builds
|
|
148
|
+
* the manifest with it. `assertHoldsAgree` cross-checks the last against the
|
|
149
|
+
* island the first two wrote, but ONLY on a navigable format; `video-16x9` and
|
|
150
|
+
* `short-9x16` carry no island, so on the artifact anybody actually watches a
|
|
151
|
+
* divergence between them is invisible. Sharing the code is the only thing that
|
|
152
|
+
* makes them the same answer.
|
|
153
|
+
*/
|
|
154
|
+
export declare function stageScene(scene: Scene, speed: number): {
|
|
155
|
+
scene: Scene;
|
|
156
|
+
open: number;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* When each sentence of a beat is spoken, on one continuous clock.
|
|
160
|
+
*
|
|
161
|
+
* THE FIRST SENTENCE NEVER WAITS. It describes the beat arriving, the headline is
|
|
162
|
+
* already up by `open`, and making it wait for the first reveal is what put
|
|
163
|
+
* 0.65-1.52s of silence at the head of every scene — which ran straight into the
|
|
164
|
+
* tail of the one before it and became a 1.0-3.4s hole at every slide change.
|
|
165
|
+
*
|
|
166
|
+
* A LATER SENTENCE WAITS FOR THE REVEAL IT NAMES, and only when the clock has not
|
|
167
|
+
* already passed it. That is the whole synchronisation rule and it is one
|
|
168
|
+
* `Math.max`. When speech is dense — a short target, the case this was built for —
|
|
169
|
+
* the clock is always ahead, so the sentences run back to back with no gap at all.
|
|
170
|
+
* When the animation is deliberately slowed (`--speed 2`), the build outruns the
|
|
171
|
+
* voice and each sentence waits for its own reveal rather than describing a
|
|
172
|
+
* picture that is still seconds away. The gap that opens there is not dead air:
|
|
173
|
+
* it is the motion the author asked to be slow enough to watch.
|
|
174
|
+
*
|
|
175
|
+
* Shared by `beatSeconds` and `place` so the room RESERVED and the room USED are
|
|
176
|
+
* one piece of arithmetic rather than two statements of it that can drift apart.
|
|
177
|
+
*/
|
|
178
|
+
export declare function speechPlan(open: number, holds: readonly number[], segments: readonly Segment[]): {
|
|
179
|
+
starts: number[];
|
|
180
|
+
end: number;
|
|
181
|
+
};
|
|
182
|
+
export declare function emitComposition(storyboard: Storyboard, source: Source, format: Format, opts?: DeckOptions): string;
|
|
183
|
+
/** Shipped beside the deck so a built artifact needs no network to navigate. */
|
|
184
|
+
export declare const PLAYER_FILE = "hyperframes-player.global.js";
|
|
185
|
+
/**
|
|
186
|
+
* The wrapper's filename, single-sourced because `verify` has to exclude it.
|
|
187
|
+
* It is a presented page, never a rendered one, so render-time rules — the
|
|
188
|
+
* determinism scan above all — do not apply to it.
|
|
189
|
+
*/
|
|
190
|
+
export declare const DECK_PAGE = "deck.html";
|
|
191
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slideshow island — the deck's navigation map.
|
|
3
|
+
*
|
|
4
|
+
* Fragment times are ABSOLUTE positions on the deck timeline, not offsets into a
|
|
5
|
+
* slide, which is easy to get subtly wrong and produces a deck that lints clean
|
|
6
|
+
* and navigates to the wrong place. A fragment outside its slide's window fails
|
|
7
|
+
* `hyperframes lint`, so the conversion is checked here rather than at the gate.
|
|
8
|
+
*/
|
|
9
|
+
export interface SlideInput {
|
|
10
|
+
/** The scene's composition id, e.g. `"s3"`. */
|
|
11
|
+
sid: string;
|
|
12
|
+
start: number;
|
|
13
|
+
duration: number;
|
|
14
|
+
/** Presenter note, already plain text. */
|
|
15
|
+
notes: string;
|
|
16
|
+
/** Hold points in seconds from the scene's start. */
|
|
17
|
+
holds: number[];
|
|
18
|
+
}
|
|
19
|
+
export declare function emitIsland(slides: SlideInput[]): string;
|