@jokerized/decksmith 0.8.0 → 0.9.1
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 +333 -4
- package/dist/cli.js +14057 -4749
- package/dist/deck-player-element.js +1 -1
- package/dist/deck-player.js +1 -1
- package/dist/deck-runtime.js +70 -2
- package/dist/index.js +13309 -4178
- package/dist/mcp.js +5131 -765
- package/dist/types/bespoke/art.d.ts +173 -0
- package/dist/types/bespoke/cache.d.ts +69 -0
- package/dist/types/bespoke/callouts.d.ts +76 -0
- package/dist/types/bespoke/contract.d.ts +57 -0
- package/dist/types/bespoke/inspect.d.ts +69 -0
- package/dist/types/bespoke/pipeline.d.ts +207 -0
- package/dist/types/bespoke/probe.d.ts +34 -0
- package/dist/types/bespoke/prompt.d.ts +182 -0
- package/dist/types/bespoke/references.d.ts +73 -0
- package/dist/types/bespoke/repair.d.ts +121 -0
- package/dist/types/bespoke/scene.d.ts +97 -0
- package/dist/types/bespoke/select.d.ts +52 -0
- package/dist/types/bespoke/sheet.d.ts +26 -0
- package/dist/types/bespoke/shots.d.ts +128 -0
- package/dist/types/bespoke/untangle.d.ts +5 -0
- 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/grid.d.ts +0 -14
- package/dist/types/emit/archetypes/index.d.ts +0 -7
- 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 +83 -3
- 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 +121 -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/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 +107 -0
- package/dist/types/emit/type.d.ts +111 -0
- package/dist/types/index.d.ts +52 -1
- package/dist/types/plan/codex.d.ts +36 -0
- package/dist/types/plan/direct.d.ts +66 -0
- package/dist/types/prefs.d.ts +25 -2
- package/dist/types/render/capture.d.ts +6 -0
- package/dist/types/render/timing.d.ts +3 -0
- package/dist/types/source/fonts.d.ts +11 -1
- package/dist/types/types.d.ts +109 -0
- package/dist/types/verify/check.d.ts +1 -1
- package/dist/types/verify/fidelity.d.ts +34 -0
- package/dist/types/verify/fill.d.ts +205 -0
- package/dist/types/verify/index.d.ts +14 -0
- package/dist/types/verify/motion.d.ts +33 -0
- package/dist/types/verify/scenes.d.ts +325 -0
- package/package.json +5 -1
|
@@ -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;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 motion grammar: how a scene ARRIVES, how it HANDS OFF to the next one,
|
|
3
|
+
* and what MOVES while the narrator is talking about it.
|
|
4
|
+
*
|
|
5
|
+
* v0.8.0 had one of each, measured over 124 HypePaper scenes: 114 opened with
|
|
6
|
+
* the same eyebrow-then-headline fade-up, all 115 seams were one 0.4s
|
|
7
|
+
* `power2.in` dissolve, two eases carried 85% of 1,591 tweens, and once a slide
|
|
8
|
+
* had built, nothing on it moved while the voice explained it. That is the
|
|
9
|
+
* founder's "the animation is always the same", and this module is the answer
|
|
10
|
+
* for `design: "v2"` decks only. `classic` never reaches it, so every v0.8.0
|
|
11
|
+
* byte and golden is unchanged.
|
|
12
|
+
*
|
|
13
|
+
* FOUR RULES EVERYTHING HERE OBEYS, each of them an AGENTS.md invariant:
|
|
14
|
+
*
|
|
15
|
+
* 1. Pure and seeded. Every choice is a function of the storyboard (`fnv1a` of
|
|
16
|
+
* the source id and the beat id), never of a clock or `Math.random`, so two
|
|
17
|
+
* builds of one storyboard are byte-identical (invariant 4).
|
|
18
|
+
* 2. `fromTo` only, no callbacks. Every tween is a `Tween`, which cannot be
|
|
19
|
+
* written without its `from` (invariant 2), and nothing here hangs state off
|
|
20
|
+
* `onUpdate` (invariant 11). Emphasis and seam tweens carry
|
|
21
|
+
* `immediateRender: false` so they never paint at build time over an
|
|
22
|
+
* element's own entrance.
|
|
23
|
+
* 3. Holds, `open` and scene lengths never move. A restyled entrance keeps every
|
|
24
|
+
* tween's position and duration, so `openSeconds`, `beatSeconds` and the
|
|
25
|
+
* timing manifest (`holdsFor` in src/render/timing.ts re-emits scenes WITHOUT
|
|
26
|
+
* this module) all still agree. Emphasis lives strictly inside a hold window
|
|
27
|
+
* and is back at rest before the window closes, so every frame a gate
|
|
28
|
+
* captures at a hold is the frame v0.8.0 would have captured there.
|
|
29
|
+
* 4. Up, never down. Emphasis grows a part (`LIFT`) and never shrinks one: the
|
|
30
|
+
* type floor reads declared sizes, so a part scaled below 1 at a hold would
|
|
31
|
+
* pass at a size it is not drawn at.
|
|
32
|
+
*
|
|
33
|
+
* INTERFACE, for the other v2 tracks (player, fit, style, layout):
|
|
34
|
+
*
|
|
35
|
+
* - `planMotion(seed, beats)` → which entrance verb each scene uses, which seam
|
|
36
|
+
* joins each pair, and the order emphasis kinds are tried in. A style pack
|
|
37
|
+
* that wants to bias the grammar should filter `ENTRANCES`/`SEAMS` before
|
|
38
|
+
* calling it rather than post-edit its answer, so the no-repeat rules still
|
|
39
|
+
* hold.
|
|
40
|
+
* - `restyleEntrance(scene, sid, verb)` rewrites an emitted scene's own
|
|
41
|
+
* entrance tweens. It only rewrites tweens it recognises (`isEntrance`), so an
|
|
42
|
+
* archetype added later is left alone rather than broken.
|
|
43
|
+
* - `seamOut`/`seamIn` are the handoff tweens for the outgoing and incoming
|
|
44
|
+
* scene roots; `composition.ts` appends them where it used to append the one
|
|
45
|
+
* dissolve.
|
|
46
|
+
* - `emphasize(...)` adds the during-narration motion and returns the
|
|
47
|
+
* `HoldWindow`s the deck player needs (`deck.html`'s
|
|
48
|
+
* `application/decksmith-motion+json` island, read by src/deck/motion.ts).
|
|
49
|
+
*/
|
|
50
|
+
import type { Archetype, BeatRole } from "../types.js";
|
|
51
|
+
import { type Scene, type Tween } from "./kit.js";
|
|
52
|
+
/**
|
|
53
|
+
* How a scene's parts arrive. Six, so the no-repeat rule always has at least
|
|
54
|
+
* four to choose from after excluding the previous scene's verb and a capped
|
|
55
|
+
* `rise`.
|
|
56
|
+
*
|
|
57
|
+
* | verb | what moves | ease |
|
|
58
|
+
* |-------|----------------------------------------------|---------------|
|
|
59
|
+
* | rise | fade up from below (v0.8.0's, kept, capped) | power3.out |
|
|
60
|
+
* | slide | in from the reading side | expo.out |
|
|
61
|
+
* | snap | scale up from 0.9 with a small overshoot | back.out(1.7) |
|
|
62
|
+
* | focus | out of a blur (HTML) / short rise (SVG) | circ.out |
|
|
63
|
+
* | wipe | clip-path opens left to right (HTML) | power4.inOut |
|
|
64
|
+
* | mask | clip-path opens bottom-up while rising (HTML)| power4.out |
|
|
65
|
+
*
|
|
66
|
+
* `focus`, `wipe` and `mask` touch `filter`/`clip-path`, which are only applied
|
|
67
|
+
* to HTML elements this module can SEE are HTML (an id whose tag it read in the
|
|
68
|
+
* scene's markup). An SVG part or a class selector gets the verb's transform
|
|
69
|
+
* fallback instead, because clip-path on an SVG child clips against a reference
|
|
70
|
+
* box nobody measured.
|
|
71
|
+
*/
|
|
72
|
+
export declare const ENTRANCES: readonly ["rise", "slide", "snap", "focus", "wipe", "mask"];
|
|
73
|
+
export type Entrance = (typeof ENTRANCES)[number];
|
|
74
|
+
/**
|
|
75
|
+
* How one scene hands off to the next. `dive` is the existing camera move and is
|
|
76
|
+
* never chosen here — a beat that is `inside` the previous one keeps it.
|
|
77
|
+
*
|
|
78
|
+
* | seam | outgoing root | incoming root |
|
|
79
|
+
* |----------|----------------------------------|------------------------|
|
|
80
|
+
* | dissolve | opacity → 0, power2.in (v0.8.0) | — |
|
|
81
|
+
* | push | xPercent 0 → −4, opacity → 0 | xPercent 4 → 0 |
|
|
82
|
+
* | lift | yPercent 0 → −4, opacity → 0 | yPercent 4 → 0 |
|
|
83
|
+
* | wipe | clip-path closes left to right | — |
|
|
84
|
+
* | zoom | scale 1 → 1.04, opacity → 0 | scale 0.96 → 1 |
|
|
85
|
+
*
|
|
86
|
+
* 4% is under the scene's own padding at every format (110px of 1920 is 5.7%,
|
|
87
|
+
* 84px of 1080 is 7.8%), so a mid-seam frame never puts content off the canvas
|
|
88
|
+
* for the layout gate's grid samples to find.
|
|
89
|
+
*/
|
|
90
|
+
export declare const SEAMS: readonly ["dissolve", "push", "lift", "wipe", "zoom"];
|
|
91
|
+
export type Seam = (typeof SEAMS)[number] | "dive";
|
|
92
|
+
/** What moves while the narrator talks about a part: grow, glow, underline. */
|
|
93
|
+
export declare const EMPHASES: readonly ["pulse", "glow", "underline"];
|
|
94
|
+
export type Emphasis = (typeof EMPHASES)[number];
|
|
95
|
+
/** Every ease this module writes. A test pins that nothing else appears. */
|
|
96
|
+
export declare const MOTION_EASES: readonly ["power3.out", "expo.out", "back.out(1.7)", "circ.out", "power4.inOut", "power4.out", "power2.in", "power2.inOut", "sine.out", "sine.inOut"];
|
|
97
|
+
/**
|
|
98
|
+
* FNV-1a, 32-bit. The seeded choice every v2 decision is made with: stable
|
|
99
|
+
* across platforms and Node versions, which a `Math.random` with a seed is not.
|
|
100
|
+
*/
|
|
101
|
+
export declare function fnv1a(s: string): number;
|
|
102
|
+
/** What `planMotion` needs to know about a beat. */
|
|
103
|
+
export interface MotionBeat {
|
|
104
|
+
id: string;
|
|
105
|
+
archetype: Archetype;
|
|
106
|
+
role?: BeatRole | undefined;
|
|
107
|
+
inside?: {
|
|
108
|
+
beat: string;
|
|
109
|
+
} | undefined;
|
|
110
|
+
}
|
|
111
|
+
export interface MotionPlan {
|
|
112
|
+
/** One per beat, in order. */
|
|
113
|
+
entrances: Entrance[];
|
|
114
|
+
/** One per adjacent pair: `seams[i]` joins beat `i` to beat `i + 1`. */
|
|
115
|
+
seams: Seam[];
|
|
116
|
+
/** Per beat, the order emphasis kinds are tried in (the first that fits wins). */
|
|
117
|
+
emphases: Emphasis[][];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* At most this share of a deck's scenes open with `rise`, the one verb that
|
|
121
|
+
* reads as v0.8.0's stock fade-up. The plan's target is "modal entrance ≤ 40%".
|
|
122
|
+
*/
|
|
123
|
+
export declare const RISE_SHARE = 0.25;
|
|
124
|
+
/**
|
|
125
|
+
* The deck's motion, decided once for the whole storyboard.
|
|
126
|
+
*
|
|
127
|
+
* ENTRANCES: each beat hashes into the verbs still allowed to it — never the
|
|
128
|
+
* previous beat's, and `rise` only while under `RISE_SHARE`. Hash-then-filter
|
|
129
|
+
* rather than a rotation so two decks with the same archetype sequence still
|
|
130
|
+
* move differently, which is the cross-deck sameness the critic flagged.
|
|
131
|
+
*
|
|
132
|
+
* SEAMS are chosen by the RELATION between the two beats first, then varied:
|
|
133
|
+
* same family (two quantity beats) → `push`, a lateral continuation; a role
|
|
134
|
+
* boundary (`background` → `limitations`) → `zoom`, a chapter break; into or
|
|
135
|
+
* out of a title → `lift`; any other family change → `wipe`; into the closing
|
|
136
|
+
* beat → `dissolve`. A seam that would repeat the previous one is replaced by a
|
|
137
|
+
* hashed pick from the rest. Then, for a deck of 10+ beats with fewer than three
|
|
138
|
+
* kinds, the earliest replaceable seams are rotated through the missing kinds.
|
|
139
|
+
* `inside` is the camera's and stays `dive`.
|
|
140
|
+
*/
|
|
141
|
+
export declare function planMotion(seed: string, beats: readonly MotionBeat[]): MotionPlan;
|
|
142
|
+
/**
|
|
143
|
+
* Rotate missing seam kinds into the earliest positions where the swap keeps
|
|
144
|
+
* both neighbours different and does not remove the last use of a kind.
|
|
145
|
+
* In place; deterministic because it walks in index order.
|
|
146
|
+
*/
|
|
147
|
+
export declare function ensureSeamKinds(seams: Seam[], want: number): void;
|
|
148
|
+
/**
|
|
149
|
+
* A tween this module may restyle: an opacity 0 → 1 reveal whose `from` only
|
|
150
|
+
* moves opacity and position/scale, rendered immediately (the element's own
|
|
151
|
+
* first appearance). Dims, restores, draw-ons, pulses and anything with
|
|
152
|
+
* `immediateRender: false` are someone else's and stay exactly as emitted.
|
|
153
|
+
*/
|
|
154
|
+
export declare function isEntrance(t: Tween): boolean;
|
|
155
|
+
/** The tag of the element an `#id` selector names in this markup, if it is one. */
|
|
156
|
+
export declare function tagOf(html: string, target: string): string | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* One scene's entrances, re-voiced in `verb`. Positions and durations are
|
|
159
|
+
* untouched (rule 3 at the top), so this cannot move a hold or the voice.
|
|
160
|
+
*/
|
|
161
|
+
export declare function restyleEntrance(scene: Scene, sid: string, verb: Entrance): Scene;
|
|
162
|
+
/**
|
|
163
|
+
* When the incoming chrome may start, in unpaced seconds: once the outgoing
|
|
164
|
+
* scene's handoff (`HANDOFF_SECONDS`, 0.4) has taken it below half opacity,
|
|
165
|
+
* which a `power2.in` fade does at 0.32.
|
|
166
|
+
*
|
|
167
|
+
* WHY. v0.8.0's eyebrow starts at 0.15 on GSAP's default `power1.out`, which is
|
|
168
|
+
* still under 0.5 opacity by the time the old scene fades past half. v2's eases
|
|
169
|
+
* are faster off the mark — `expo.out` is at 0.75 a fifth of the way in — so an
|
|
170
|
+
* eyebrow starting at 0.15 is legible over the outgoing headline, the collision
|
|
171
|
+
* `HANDOFF_SECONDS` warns about. So the chrome's START moves to here and its END
|
|
172
|
+
* stays where it was: `openSeconds` reads only the ends, so the voice starts at
|
|
173
|
+
* the same instant. `test/motion.test.ts` checks every verb against every seam.
|
|
174
|
+
*/
|
|
175
|
+
export declare const SEAM_CLEAR = 0.3;
|
|
176
|
+
/**
|
|
177
|
+
* The OUTGOING half of a seam: tweens on `#sid` starting where its slide ends
|
|
178
|
+
* (`at`) and running `over` seconds into the next scene's window, exactly where
|
|
179
|
+
* the v0.8.0 dissolve ran. `dissolve` is byte-for-byte `handoffStatement`'s
|
|
180
|
+
* tween, so a v2 deck whose plan says dissolve hands off as v0.8.0 did.
|
|
181
|
+
*/
|
|
182
|
+
export declare function seamOut(kind: Exclude<Seam, "dive">, sid: string, at: number, over: number): Tween[];
|
|
183
|
+
/**
|
|
184
|
+
* The INCOMING half, on the next scene's own timeline from its 0. Settles by
|
|
185
|
+
* `firstHold - 0.05`, so the frame at the first stop is the frame v0.8.0 drew
|
|
186
|
+
* there; a scene that holds sooner than `over` gets no incoming move at all.
|
|
187
|
+
*/
|
|
188
|
+
export declare function seamIn(kind: Seam, sid: string, over: number, firstHold: number): Tween[];
|
|
189
|
+
/** A narrated stop's quiet stretch, scene-relative. See `emphasize`. */
|
|
190
|
+
export interface HoldWindow {
|
|
191
|
+
/** Stop index within the scene (the narration segment's `stop`). */
|
|
192
|
+
stop: number;
|
|
193
|
+
/** The hold itself: where the deck lands for this stop. */
|
|
194
|
+
at: number;
|
|
195
|
+
/** Where this stop's sentence starts on the scene's clock (may be < `at`). */
|
|
196
|
+
from: number;
|
|
197
|
+
/** The first moment after `at` that belongs to the next stop's reveal, or the scene's end. */
|
|
198
|
+
to: number;
|
|
199
|
+
}
|
|
200
|
+
/** What `emphasize` needs to know about a narration segment. */
|
|
201
|
+
export interface SpokenSegment {
|
|
202
|
+
stop: number;
|
|
203
|
+
seconds: number;
|
|
204
|
+
cues: readonly {
|
|
205
|
+
start: number;
|
|
206
|
+
}[];
|
|
207
|
+
}
|
|
208
|
+
export declare const EMPH_TOTAL: number;
|
|
209
|
+
/** `#rrggbb` → `rgba(r, g, b, a)`, or undefined for anything else. */
|
|
210
|
+
export declare function rgba(hex: string, a: number): string | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* Motion DURING the narration hold, timed to what is being said.
|
|
213
|
+
*
|
|
214
|
+
* There is no word timing, so the clock is the narration's own cue boundaries:
|
|
215
|
+
* each segment speaks about its stop's subject (`subjectOf`), and the subject is
|
|
216
|
+
* emphasised at the first cue boundary — the sentence's start counts — at which
|
|
217
|
+
* it is on screen and settled, inside a stretch where nothing else is arriving.
|
|
218
|
+
*
|
|
219
|
+
* THE STRETCHES. After stop `k`'s hold the scene is quiet until the first tween
|
|
220
|
+
* that starts later (the next stop's reveal); after the LAST hold it is quiet to
|
|
221
|
+
* the scene's end, which is where most narration lands because the reveals play
|
|
222
|
+
* under the voice (`speechPlan`). An emphasis for stop `k` may use any stretch
|
|
223
|
+
* from `k` on — in the video every part is still there — and must be back at
|
|
224
|
+
* rest before the stretch closes, so every hold frame is untouched (rule 3).
|
|
225
|
+
*
|
|
226
|
+
* `windows` is the deck player's half. A presented deck holds still at each stop
|
|
227
|
+
* while its audio plays; `src/deck/motion.ts` seeks the scene through the
|
|
228
|
+
* stop's own stretch on the audio's clock, `from + audio.currentTime`, so the
|
|
229
|
+
* same emphasis lands on the same word there. Only a stop's OWN stretch is
|
|
230
|
+
* offered, because seeking into a later one would show the next reveal early.
|
|
231
|
+
*/
|
|
232
|
+
export declare function emphasize(scene: Scene, sid: string, opts: {
|
|
233
|
+
segments: readonly SpokenSegment[];
|
|
234
|
+
/** `speechPlan(open, holds, segments).starts` — where each segment starts. */
|
|
235
|
+
starts: readonly number[];
|
|
236
|
+
/** The scene's slide length (before any handoff or camera tail). */
|
|
237
|
+
end: number;
|
|
238
|
+
/** Per-scene try order, from `planMotion`. */
|
|
239
|
+
kinds: readonly Emphasis[];
|
|
240
|
+
accent: string;
|
|
241
|
+
}): {
|
|
242
|
+
scene: Scene;
|
|
243
|
+
windows: HoldWindow[];
|
|
244
|
+
};
|
package/dist/types/emit/svg.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Geometry, text metrics and SVG strings. Nothing above that.
|
|
3
|
-
*
|
|
4
|
-
* Six diagrammatic archetypes draw vector graphics that the source document
|
|
5
|
-
* never contained. Left to themselves each would re-derive the same four things
|
|
6
|
-
* — how wide a label renders, where N things go across a canvas, how to draw an
|
|
7
|
-
* arrowhead, how to escape a caption — and they would each get them slightly
|
|
8
|
-
* wrong in a different way. `line-chart` shipped 3.6px from clipping because it
|
|
9
|
-
* guessed its own em-factor; that guess now lives here, once.
|
|
10
|
-
*
|
|
11
|
-
* This module knows about characters, boxes and paths. It does not know what a
|
|
12
|
-
* pipeline stage or a receptive field is — that knowledge belongs to the emitter
|
|
13
|
-
* that composes these primitives, and putting it here is how a primitive layer
|
|
14
|
-
* turns into six hand-rolled one-offs wearing a shared import.
|
|
15
|
-
*/
|
|
16
1
|
import { type Tween, type Vars } from "./kit.js";
|
|
2
|
+
import { type LatinFace, type TypeSpec } from "./type.js";
|
|
17
3
|
export interface Pt {
|
|
18
4
|
x: number;
|
|
19
5
|
y: number;
|
|
@@ -145,7 +131,49 @@ export declare function id(sid: string, part: string, i?: number): string;
|
|
|
145
131
|
* why the auto-detection below still runs underneath: the parameter can only
|
|
146
132
|
* widen what a run is charged, never narrow it.
|
|
147
133
|
*/
|
|
148
|
-
export type
|
|
134
|
+
export type Script = "latin" | "cjk" | "hangul";
|
|
135
|
+
/**
|
|
136
|
+
* A v2 style pack's face: the script as above, the Latin face that draws the
|
|
137
|
+
* run, and the pack's type spec (`./type.ts`).
|
|
138
|
+
*
|
|
139
|
+
* An OBJECT rather than a fourth string, and only ever built by `faceOf` and
|
|
140
|
+
* `displayFace`, because every emitter already threads a `Face` through to
|
|
141
|
+
* `textWidth` and `chrome*` without looking inside it. That is what lets a pack
|
|
142
|
+
* change both the width table and the chrome's scale with no emitter learning
|
|
143
|
+
* packs exist: the measurement rides on the value they already pass.
|
|
144
|
+
*
|
|
145
|
+
* `glyphs` only matters when `script` is `"latin"`. A CJK deck draws its Latin
|
|
146
|
+
* in the bundled Noto face whatever the pack says, and is measured as it always
|
|
147
|
+
* was.
|
|
148
|
+
*/
|
|
149
|
+
export interface PackFace {
|
|
150
|
+
readonly script: Script;
|
|
151
|
+
readonly glyphs: LatinFace;
|
|
152
|
+
readonly type: TypeSpec;
|
|
153
|
+
/**
|
|
154
|
+
* A CJK deck whose pack sets this role in a serif: the bundle's Noto Serif
|
|
155
|
+
* draws the run, not Noto Sans. Han and kana are the same 1.0em in both;
|
|
156
|
+
* Hangul is 0.966em against 0.920, and the Latin inside a run is about 7%
|
|
157
|
+
* wider (measured 2026-10-08, Chrome 145, KR/JP/SC at 400 and 700). See
|
|
158
|
+
* `CJK_SERIF_HANGUL` and `CJK_SERIF_LATIN`.
|
|
159
|
+
*/
|
|
160
|
+
readonly cjkSerif?: boolean;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A bare `Script` is the classic face, exactly as before packs: Inter's table and
|
|
164
|
+
* `CLASSIC_TYPE`. Every theme that is not a v2 pack still measures as one, so
|
|
165
|
+
* its bytes do not move.
|
|
166
|
+
*/
|
|
167
|
+
export type Face = Script | PackFace;
|
|
168
|
+
/** The script a face sets in. */
|
|
169
|
+
export declare function scriptOf(face: Face): Script;
|
|
170
|
+
/** The chrome's type spec. A bare script is classic. */
|
|
171
|
+
export declare function typeOf(face: Face): TypeSpec;
|
|
172
|
+
/**
|
|
173
|
+
* The same face, measured as the pack's DISPLAY face — what the eyebrow, the
|
|
174
|
+
* headline and the title slide's headline are set in. Identity for classic.
|
|
175
|
+
*/
|
|
176
|
+
export declare function displayFace(face: Face): Face;
|
|
149
177
|
/**
|
|
150
178
|
* The face a theme's stack will use, from the family `familyFor` put in front.
|
|
151
179
|
*
|
|
@@ -161,6 +189,16 @@ export declare function textWidth(text: string, fontSize: number, weight?: numbe
|
|
|
161
189
|
* one unbroken "word".
|
|
162
190
|
*/
|
|
163
191
|
export declare function wrap(text: string, fontSize: number, maxWidth: number, weight?: number, tracking?: number, face?: Face): string[];
|
|
192
|
+
/**
|
|
193
|
+
* Whether `wrap` would have to cut a word letter by letter to set `text` at
|
|
194
|
+
* this size and width — "Rectified-flo" / "w". `wrap` does that as a last
|
|
195
|
+
* resort, which is right for a box that cannot be any wider; a layout that is
|
|
196
|
+
* CHOOSING a size (v2's growth, a layout variant) asks this first and takes a
|
|
197
|
+
* size at which no word is cut. A run with Han or kana in it breaks between
|
|
198
|
+
* characters by the script's own rules, so it never counts; Hangul words are
|
|
199
|
+
* space-separated and do.
|
|
200
|
+
*/
|
|
201
|
+
export declare function cutsWord(text: string, fontSize: number, maxWidth: number, weight?: number, face?: Face): boolean;
|
|
164
202
|
/** The SVG root. Width and height match the viewBox so px and user units agree. */
|
|
165
203
|
export declare function svg(elementId: string, w: number, h: number, body: string): string;
|
|
166
204
|
export declare function rect(b: Box, a?: Attrs): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Format, Storyboard } from "../types.js";
|
|
2
2
|
import { type Scene } from "./kit.js";
|
|
3
3
|
import { type DeckTheme } from "./themes/index.js";
|
|
4
|
-
export { type DeckTheme, ink, mono, paper, resolveTheme, THEME_NAMES, THEMES, } from "./themes/index.js";
|
|
4
|
+
export { type DeckTheme, ink, mono, PACKS, paper, resolveTheme, THEME_NAMES, THEMES, } from "./themes/index.js";
|
|
5
5
|
/**
|
|
6
6
|
* The theme a storyboard's scenes are staged with, and the font family it ships.
|
|
7
7
|
*
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { Theme } from "../kit.js";
|
|
14
14
|
import { ink } from "./ink.js";
|
|
15
15
|
import { mono } from "./mono.js";
|
|
16
|
+
import { PACKS } from "./packs.js";
|
|
16
17
|
import { paper } from "./paper.js";
|
|
17
18
|
/**
|
|
18
19
|
* `Theme` plus what only the shared stylesheet reads.
|
|
@@ -25,7 +26,24 @@ import { paper } from "./paper.js";
|
|
|
25
26
|
export interface DeckTheme extends Theme {
|
|
26
27
|
/** `body`'s font-weight. Omitted means 400, which is what `ink` and `paper` want. */
|
|
27
28
|
bodyWeight?: number;
|
|
29
|
+
/**
|
|
30
|
+
* A v2 style pack's ground, so a picker can keep light and dark both in use.
|
|
31
|
+
* Absent on the classic themes, which are never picked — only named.
|
|
32
|
+
*/
|
|
33
|
+
ground?: "dark" | "light";
|
|
34
|
+
/**
|
|
35
|
+
* A v2 pack's finish: CSS appended after the base stylesheet. It may change
|
|
36
|
+
* only what no measurement reads — colour, radius, shadow, decoration, the
|
|
37
|
+
* body's `background-image`, absolutely positioned pseudo-elements. See
|
|
38
|
+
* `./packs.ts`; the rule is enforced in `test/style-packs.test.ts`.
|
|
39
|
+
*/
|
|
40
|
+
skin?: string;
|
|
28
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The classic three, then the v2 packs. Every one is reachable by `--theme`,
|
|
44
|
+
* which is also how a storyboard or a caller forces a pack; `--design v2` picks
|
|
45
|
+
* among `PACKS` when nobody named one (`./pick.ts`).
|
|
46
|
+
*/
|
|
29
47
|
export declare const THEMES: Readonly<Record<string, DeckTheme>>;
|
|
30
48
|
/** Sorted, so an error message and a `--help` listing agree without coordinating. */
|
|
31
49
|
export declare const THEME_NAMES: readonly string[];
|
|
@@ -37,4 +55,4 @@ export declare const THEME_NAMES: readonly string[];
|
|
|
37
55
|
* someone presents it.
|
|
38
56
|
*/
|
|
39
57
|
export declare function resolveTheme(name: string): DeckTheme;
|
|
40
|
-
export { ink, mono, paper };
|
|
58
|
+
export { ink, mono, PACKS, paper };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DeckTheme } from "./index.js";
|
|
2
|
+
/** Magma, HypePaper's brand accent, on a violet-black ground. Grotesk display. */
|
|
3
|
+
export declare const signal: DeckTheme;
|
|
4
|
+
/** Navy drafting film, a cyan rule, a faint grid. IBM Plex throughout. */
|
|
5
|
+
export declare const blueprint: DeckTheme;
|
|
6
|
+
/** Espresso ground, amber accent, a serif headline over Inter. Matted figures. */
|
|
7
|
+
export declare const atlas: DeckTheme;
|
|
8
|
+
/** Cream stock, oxblood accent, Source Serif throughout. A journal page. */
|
|
9
|
+
export declare const folio: DeckTheme;
|
|
10
|
+
/** Cool white, ultramarine accent, a grotesk headline over Plex. Dotted ground. */
|
|
11
|
+
export declare const chalk: DeckTheme;
|
|
12
|
+
/** Sage ground, forest accent, a Plex headline over a serif body. Tabbed plates. */
|
|
13
|
+
export declare const journal: DeckTheme;
|
|
14
|
+
/** The packs `--design v2` chooses among, in a fixed order the picker hashes into. */
|
|
15
|
+
export declare const PACKS: Readonly<Record<string, DeckTheme>>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which v2 pack a deck wears, when nobody said.
|
|
3
|
+
*
|
|
4
|
+
* DETERMINISTIC: a pure function of the storyboard's beats and a seed, so the
|
|
5
|
+
* same deck is the same bytes on every build (invariant 4 is about render time,
|
|
6
|
+
* but a pick that moved between builds would be the same failure one step
|
|
7
|
+
* earlier). The seed is the source id unless the caller passes one — HypePaper
|
|
8
|
+
* should pass the PAPER id, so a paper's en and ko decks share a look.
|
|
9
|
+
*
|
|
10
|
+
* WEIGHTED BY CONTENT, so the hash is not the only voice. Each pack carries an
|
|
11
|
+
* affinity for the four archetype families (`ARCHETYPE_FAMILY`): a deck that is
|
|
12
|
+
* mostly structure leans to the drafting pack, one that is mostly bars and
|
|
13
|
+
* tables to the grotesk one. The lean is mild — every pack keeps a floor
|
|
14
|
+
* weight — because the founder's complaint is sameness across decks, and a
|
|
15
|
+
* strong content rule would put every paper of one field on one pack.
|
|
16
|
+
*
|
|
17
|
+
* DARK ONLY. The pick ranks the dark packs (`PICKED`); a light one is worn only
|
|
18
|
+
* when named. The founder, 2026-10-11: "I prefer dark theme".
|
|
19
|
+
*
|
|
20
|
+
* Rendezvous hashing: each pack draws `-ln(u) / weight` from its own hash of
|
|
21
|
+
* (seed, pack) and the smallest draw wins. That is a weighted choice whose
|
|
22
|
+
* probabilities are exactly the normalised weights, and the full ordering is a
|
|
23
|
+
* ready-made fallback list — the next pack is the next-smallest draw, so a
|
|
24
|
+
* caller that must refuse one (narration staged against another look) moves
|
|
25
|
+
* along it without re-rolling anything.
|
|
26
|
+
*/
|
|
27
|
+
import { type Format, type Source, type Storyboard } from "../../types.js";
|
|
28
|
+
import { type DeckNarration } from "../composition.js";
|
|
29
|
+
/** 32-bit FNV-1a. Stable across platforms and Node versions, unlike any Map order. */
|
|
30
|
+
export declare function fnv1a(text: string): number;
|
|
31
|
+
/** A pack's weight for this deck: 1, plus its affinity times the family mix. */
|
|
32
|
+
export declare function packWeights(beats: Storyboard["beats"]): Record<string, number>;
|
|
33
|
+
/** Every pickable pack, most-preferred first, for this deck and seed. */
|
|
34
|
+
export declare function rankPacks(beats: Storyboard["beats"], seed: string): string[];
|
|
35
|
+
/** The design generation a build emits. `classic` is v0.8.0, byte for byte. */
|
|
36
|
+
export type Design = "classic" | "v2";
|
|
37
|
+
export interface LookChoice {
|
|
38
|
+
/** `--theme` or a config file. Always wins: a named theme is a forced one. */
|
|
39
|
+
stated?: string | undefined;
|
|
40
|
+
storyboard: Pick<Storyboard, "theme" | "beats" | "sourceId">;
|
|
41
|
+
design: Design;
|
|
42
|
+
/** Overrides `storyboard.sourceId` as the hash seed. */
|
|
43
|
+
seed?: string | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Whether this build can wear a pack. `build` passes a check that the pack
|
|
46
|
+
* stages every beat with the stop count the narration was recorded at; a pack
|
|
47
|
+
* it refuses is skipped for the next in the ranking.
|
|
48
|
+
*/
|
|
49
|
+
accepts?: (name: string) => boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The theme name a build, a narration or a timing pass should stage with.
|
|
53
|
+
*
|
|
54
|
+
* Classic: what it always was, `stated ?? storyboard.theme`.
|
|
55
|
+
*
|
|
56
|
+
* v2: a stated theme still wins, and so does a storyboard that names anything
|
|
57
|
+
* but `ink` — that is the storyboard forcing a look. `ink` is the schema's
|
|
58
|
+
* DEFAULT, so it is indistinguishable from "nobody said" once parsed, and is
|
|
59
|
+
* read that way (as `--theme ink` is: the CLI treats a default value as
|
|
60
|
+
* unstated). The v0.8.0 look is `--design classic`, not a v2 deck in ink.
|
|
61
|
+
* Otherwise the first ranked pack `accepts` allows; if it allows none, the
|
|
62
|
+
* storyboard's own theme. Never throws.
|
|
63
|
+
*
|
|
64
|
+
* NOT "what any narration on disk was staged against", which this used to say:
|
|
65
|
+
* once `narrate` ran under v2 it staged under its own pick. `narrate` and
|
|
66
|
+
* `build` agree because both rank with the same seed and both refuse a pack
|
|
67
|
+
* that drops a beat (`pickTheme` below); give them the same `--pack-seed`.
|
|
68
|
+
*/
|
|
69
|
+
export declare function chooseLook(c: LookChoice): string;
|
|
70
|
+
/**
|
|
71
|
+
* The theme every entry point builds with: the CLI's `build` and `narrate`,
|
|
72
|
+
* the library's `buildDeck` and the server pipeline all come here, so the same
|
|
73
|
+
* input is the same deck whichever one made it. (Before, only the CLI picked a
|
|
74
|
+
* pack; a v2 deck from the library or the server stayed in ink.)
|
|
75
|
+
*
|
|
76
|
+
* `narration` is the recording on disk, when there is one: a pack must stage
|
|
77
|
+
* every beat at the stop count it was recorded at. `narrate` itself has none
|
|
78
|
+
* yet and asks only the other half — that no beat is dropped — which is what
|
|
79
|
+
* keeps its pick and `build`'s the same.
|
|
80
|
+
*/
|
|
81
|
+
export declare function pickTheme(storyboard: Storyboard, source: Source, format: Format, opts: {
|
|
82
|
+
stated?: string | undefined;
|
|
83
|
+
design: Design;
|
|
84
|
+
seed?: string | undefined;
|
|
85
|
+
speed: number;
|
|
86
|
+
narration?: DeckNarration | undefined;
|
|
87
|
+
}): string;
|
|
88
|
+
/**
|
|
89
|
+
* The `accepts` a build hands `chooseLook`: a pack may cost this deck nothing.
|
|
90
|
+
*
|
|
91
|
+
* Two ways it could, both found on real decks:
|
|
92
|
+
*
|
|
93
|
+
* - A beat staged with a different stop count than the narration on disk was
|
|
94
|
+
* recorded at. `planCut` throws for that (`assertNarrationStaging`).
|
|
95
|
+
* - A beat the storyboard's own theme draws but the pack refuses. Measured: an
|
|
96
|
+
* en deck in `folio` lost a six-bar bar-compare ("need 660px of the 653px")
|
|
97
|
+
* because the pack's chrome is a few px taller. That is a whole slide gone for
|
|
98
|
+
* a look, which is never a trade worth making silently, so the pack is
|
|
99
|
+
* skipped for the next one in the ranking instead.
|
|
100
|
+
*
|
|
101
|
+
* Pure: it only runs `planCut`, the same pass the build makes, once per pack it
|
|
102
|
+
* is asked about plus once for the baseline.
|
|
103
|
+
*/
|
|
104
|
+
export declare function costsNothing(storyboard: Storyboard, source: Source, format: Format, opts: {
|
|
105
|
+
speed: number;
|
|
106
|
+
narration?: DeckNarration | undefined;
|
|
107
|
+
}): (name: string) => boolean;
|