@jokerized/decksmith 0.9.3 → 0.9.5

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.
Files changed (59) hide show
  1. package/README.md +449 -90
  2. package/dist/animate/LICENSE +21 -0
  3. package/dist/animate/NOTICE +32 -0
  4. package/dist/animate/core.js +530 -0
  5. package/dist/animate/cut-paper.js +206 -0
  6. package/dist/animate/morph.js +131 -0
  7. package/dist/cli.js +13424 -8867
  8. package/dist/ds-animate.js +1 -0
  9. package/dist/index.js +13251 -8697
  10. package/dist/mcp.js +9845 -7577
  11. package/dist/types/bespoke/art.d.ts +172 -6
  12. package/dist/types/bespoke/cache.d.ts +10 -0
  13. package/dist/types/bespoke/cards.d.ts +66 -0
  14. package/dist/types/bespoke/contract.d.ts +0 -8
  15. package/dist/types/bespoke/databuild.d.ts +50 -0
  16. package/dist/types/bespoke/depth.d.ts +127 -0
  17. package/dist/types/bespoke/grammar.d.ts +73 -0
  18. package/dist/types/bespoke/inspect.d.ts +40 -1
  19. package/dist/types/bespoke/pipeline.d.ts +195 -11
  20. package/dist/types/bespoke/probe.d.ts +2 -2
  21. package/dist/types/bespoke/prompt.d.ts +110 -34
  22. package/dist/types/bespoke/references.d.ts +13 -14
  23. package/dist/types/bespoke/repair.d.ts +23 -29
  24. package/dist/types/bespoke/scene.d.ts +55 -10
  25. package/dist/types/bespoke/select.d.ts +28 -21
  26. package/dist/types/bespoke/sheet.d.ts +66 -1
  27. package/dist/types/bespoke/shots.d.ts +225 -22
  28. package/dist/types/build/files.d.ts +7 -0
  29. package/dist/types/build/piece.d.ts +8 -0
  30. package/dist/types/emit/animate-runtime.d.ts +149 -0
  31. package/dist/types/emit/archetypes/claim-figure.d.ts +84 -1
  32. package/dist/types/emit/archetypes/equation-walk.d.ts +2 -2
  33. package/dist/types/emit/archetypes/hero-number.d.ts +21 -0
  34. package/dist/types/emit/archetypes/index.d.ts +2 -2
  35. package/dist/types/emit/archetypes/kinetic.d.ts +2 -0
  36. package/dist/types/emit/archetypes/pipeline.d.ts +14 -2
  37. package/dist/types/emit/archetypes/stage.d.ts +57 -0
  38. package/dist/types/emit/backdrop.d.ts +114 -0
  39. package/dist/types/emit/kit.d.ts +8 -0
  40. package/dist/types/emit/look.d.ts +14 -0
  41. package/dist/types/emit/motion.d.ts +78 -46
  42. package/dist/types/emit/type.d.ts +48 -5
  43. package/dist/types/images/providers.d.ts +6 -0
  44. package/dist/types/plan/codex.d.ts +18 -4
  45. package/dist/types/plan/direct.d.ts +1 -1
  46. package/dist/types/plan/duration.d.ts +1 -1
  47. package/dist/types/plan/prompt.d.ts +1 -1
  48. package/dist/types/plan/refs.d.ts +2 -2
  49. package/dist/types/plan/variety.d.ts +74 -0
  50. package/dist/types/prefs.d.ts +20 -1
  51. package/dist/types/render/captions.d.ts +4 -5
  52. package/dist/types/render/capture.d.ts +7 -0
  53. package/dist/types/types.d.ts +680 -4
  54. package/dist/types/verify/drift.d.ts +5 -0
  55. package/dist/types/verify/fidelity.d.ts +15 -1
  56. package/dist/types/verify/index.d.ts +17 -0
  57. package/dist/types/verify/scenes.d.ts +185 -5
  58. package/package.json +1 -1
  59. package/dist/types/bespoke/callouts.d.ts +0 -76
@@ -139,6 +139,11 @@ export declare function judge(m: {
139
139
  db: number;
140
140
  };
141
141
  motion: Motion;
142
+ /**
143
+ * The worker count of each render, so the passing note says what was
144
+ * compared: `--workers 1` pins both, and must not be reported as two counts.
145
+ */
146
+ workers?: readonly [number | "auto" | undefined, number | "auto" | undefined];
142
147
  }): DriftReport;
143
148
  /**
144
149
  * `frame_000001.png` → `frame_%06d.png`, for ffmpeg's image2 demuxer.
@@ -139,6 +139,7 @@
139
139
  * demo moves 5.81s to 6.32s, and that is a cold cache. A gate too slow to run is
140
140
  * a gate nobody runs; this one costs nothing, so it runs by default.
141
141
  */
142
+ import type { BrokenFrame } from "../emit/animate-runtime.js";
142
143
  import type { Finding } from "../types.js";
143
144
  import { type FillRow } from "./fill.js";
144
145
  /**
@@ -257,7 +258,7 @@ export declare function decodePng(png: Uint8Array): Promise<Frame>;
257
258
  * bounding box, and it does not ask the DOM whether an element is visible; both
258
259
  * of those have been wrong in this project inside the last week.
259
260
  */
260
- export declare function inkBelow(frame: Frame, bandTopPx: number): number;
261
+ export declare function inkBelow(frame: Frame, bandTopPx: number, plate?: Frame | null): number;
261
262
  /**
262
263
  * Where a scene's BODY is, in device px: the part of the frame that is not its
263
264
  * chrome. Classic chrome is a block across the top, so the body is everything
@@ -344,6 +345,19 @@ export declare function collectUnfit(attr: string): Unfit[];
344
345
  * shorten.
345
346
  */
346
347
  export declare function gradeUnfit(rows: readonly Unfit[]): Finding[];
348
+ /**
349
+ * Run IN THE PAGE: every mounted animate piece drawn at each of its own frames
350
+ * (`sweep`, src/emit/animate-runtime.ts). Empty on a deck with no piece, whose
351
+ * page never loads the runtime.
352
+ */
353
+ export declare function collectBrokenPieces(): BrokenFrame[];
354
+ /**
355
+ * An error per piece that throws at ANY of its frames. A drawing error is a
356
+ * page error only where `hyperframes check` happens to seek, and on a long deck
357
+ * it samples coarsely enough to miss a throw confined to a short window — which
358
+ * `render` then draws half-painted and exits 0 over.
359
+ */
360
+ export declare function gradePieces(rows: readonly BrokenFrame[]): Finding[];
347
361
  /**
348
362
  * Seek every declared stop and count the ink below its caption.
349
363
  *
@@ -427,3 +427,20 @@ export declare function scanNarrationLead(beats: readonly Beat[], timing: {
427
427
  }): Finding[];
428
428
  /** Flag render-time nondeterminism in one composition file (invariant 7). */
429
429
  export declare function scanDeterminism(html: string, file: string): Finding[];
430
+ /**
431
+ * Every script under the deck's `assets/` — which is where an animate piece's
432
+ * assembled script lives (src/build/piece.ts), and the only file holding code
433
+ * an AUTHOR wrote. It runs at render time like the composition does, so the
434
+ * determinism scan reads it; the type and budget gates, which read markup, do
435
+ * not.
436
+ */
437
+ export declare function readAssetScripts(dir: string): Promise<Array<[string, string]>>;
438
+ /**
439
+ * ROUND 5, the deck's variety: the camera grammars its illustrated scenes are
440
+ * staged in (`data-ds-grammar`, stamped by the shell) and the builds of its
441
+ * data scenes (`data-ds-build`), in scene order. Two consecutive illustrated
442
+ * scenes in one grammar, or two data scenes that build alike, is the "same
443
+ * animation every card" one level up; fewer distinct grammars than
444
+ * `diversityWanted` is reported.
445
+ */
446
+ export declare function scanStagingVariety(compositions: readonly string[]): Finding[];
@@ -1,3 +1,44 @@
1
+ /**
2
+ * Three gates on a scene's MOTION, which no other gate here looks at, run in
3
+ * the renderer's own browser at the narration's cue boundaries.
4
+ *
5
+ * 1. `static_hold` — the picture must change during every narration cue. A
6
+ * v2 archetype finishes building in about five seconds and then holds still
7
+ * for fifteen to twenty seconds of speech (spike, 2026-10-07: 0 px changed
8
+ * between cue ends on two of four beats). A bespoke scene exists to keep
9
+ * explaining while the voice does, so for one this is an error.
10
+ * 2. `graphic_crosses_text` (with `text_overlap`, `bespoke_type_floor`,
11
+ * `off_canvas`) — a drawn stroke must not run through a label, and a filled
12
+ * shape painted later must not cover one. The spike's worst defect was a
13
+ * giant stray arc across a whole diagram that every gate passed, including a
14
+ * text-on-text overlap check; strokes were simply not text. Strokes are
15
+ * sampled along their geometry and their dash pattern, so a line drawn by
16
+ * DrawSVG is checked only where it is visible at that instant.
17
+ * Round 2 adds what round 1's frames showed a person and no gate: the
18
+ * settled drawing must span `STAGE_FILL` of its box (`stage_fill`) and carry
19
+ * one label of `KEY_TYPE_PX` or more (`type_hierarchy`), none over
20
+ * `MAX_TYPE_PX` (`type_ceiling`, 2026-10-10); an arrowhead must
21
+ * not show where its line is not drawn (`stray_marker`); and a group the
22
+ * scene tags `data-cue="N"` must not show before cue N starts
23
+ * (`early_reveal`, with `cue_groups` failing a scene that tags nothing).
24
+ * Round 6, after the founder's "animated UI elements are old-fashioned, and
25
+ * the fonts are too large": no word in a bespoke scene renders above the
26
+ * deck's headline size (`type_scale`, TYPE_SCALE in src/emit/type.ts —
27
+ * round 2's 64px floor for a key label is gone), and no label, plate,
28
+ * chip, card or bar is animated into place (`ui_motion`, read off the
29
+ * scene's own GSAP timeline).
30
+ * 3. `seek_order` — the frame at time t must not depend on which times were
31
+ * seeked before it. Capture shards a render across workers and the deck
32
+ * player jumps around, so a frame that depends on history is a frame that
33
+ * differs between two renders of one input. Ascending, descending (after
34
+ * jumping past the scene), a fixed scramble, and a cold page are compared.
35
+ * This is what found v2's equation-walk bug: a `fromTo` authored with
36
+ * `immediateRender` set its from-state at BUILD time, so a cold seek showed
37
+ * a swollen term that a warm seek did not.
38
+ *
39
+ * The graders are pure and take what the browser measured, so each is tested
40
+ * without one; `probeScenes` is the one place a browser is opened.
41
+ */
1
42
  import type { DeckPage } from "../render/capture.js";
2
43
  import type { Timing } from "../render/timing.js";
3
44
  import type { Finding } from "../types.js";
@@ -15,7 +56,7 @@ export declare const PIXEL_DELTA = 24;
15
56
  */
16
57
  export declare const SEEK_TOLERANCE_PX = 1500;
17
58
  /** Bump whenever a gate's verdict can change: cached rejections from another version are retried. */
18
- export declare const GATES_VERSION = "gates-5";
59
+ export declare const GATES_VERSION = "gates-7";
19
60
  /**
20
61
  * A cue whose picture changes by less than this share of the frame held still:
21
62
  * about 310 px at 1080p. A frame that does not move renders to the same bytes,
@@ -60,10 +101,42 @@ export declare function gradeStillCues(rows: readonly CueChange[], min?: number)
60
101
  * bottom band empty, at a size the founder called small.
61
102
  */
62
103
  export declare const STAGE_FILL = 0.8;
63
- /** The settled frame's largest label, px at 1080p: one thing on the stage is read first. */
64
- export declare const KEY_TYPE_PX = 64;
104
+ /**
105
+ * The settled frame's largest label, px at 1080p: one label reaches the body
106
+ * size (`TYPE_SCALE`), so something is read first…
107
+ */
108
+ export declare const KEY_TYPE_PX: 44;
109
+ /**
110
+ * …and nothing passes the headline's size. The founder's verdict on the 88-120px
111
+ * focal numbers this gate used to ASK for was "the fonts are too large"
112
+ * (2026-10-10): the picture is the focus, the subtitles carry the words.
113
+ */
114
+ export declare const MAX_TYPE_PX: 56;
115
+ /**
116
+ * The largest a bespoke scene's own words may RENDER, px at 1080p, at any graded
117
+ * frame, camera zoom included (`type_scale`, round 6): `type_ceiling` reads the
118
+ * size a word declares, this one what the audience sees.
119
+ */
120
+ export declare const TYPE_MAX_PX: number;
65
121
  /** A cue group may appear this long before its cue starts (the prompt's own sync window). */
66
122
  export declare const EARLY_SLACK = 0.5;
123
+ /**
124
+ * The longest a seam may leave the stage empty (round 6): r1's final review
125
+ * found 12 of 15 seams showing nothing but the headline for ~0.6s — the
126
+ * outgoing scene gone, the incoming one's first group not yet in.
127
+ */
128
+ export declare const SEAM_BLANK_MAX = 0.15;
129
+ /**
130
+ * The height share of its box a bespoke scene's drawing spans while a later
131
+ * cue holds (`hollow_hold`): the fill module's EMPTY band. r1's final s8 held
132
+ * three calipers across the top 58% for seconds; its settled end frame filled.
133
+ */
134
+ export declare const HOLD_SPAN_MIN = 0.7;
135
+ /**
136
+ * Below this share of the frame painted (headlines aside), the stage is empty:
137
+ * about one 64px mark. r1's blank seams read exactly 0.
138
+ */
139
+ export declare const SEAM_PAINT_MIN = 0.002;
67
140
  /**
68
141
  * At the settled frame, at most this share of a scene's parts may be ones it
69
142
  * showed lit (>= `LIT`) earlier and has left dimmed (< `DIM`). Round 2 kept
@@ -92,6 +165,8 @@ export interface GeoLabel {
92
165
  export interface Geo {
93
166
  w: number;
94
167
  h: number;
168
+ /** The shell camera's scale at this frame (1 when it has none). */
169
+ cs?: number;
95
170
  labels: GeoLabel[];
96
171
  /** Filled shapes and pictures: address, owning label unit, paint order, box. */
97
172
  boxes: Array<{
@@ -102,7 +177,7 @@ export interface Geo {
102
177
  }>;
103
178
  /** Visible stroke samples, body px, with the label unit they belong to. */
104
179
  points: Array<[number, number, string | null]>;
105
- /** Each visible part's opacity and what dims it (`tag:index`, `#id` appended when it has one). */
180
+ /** Each visible part's opacity and what dims it (`tag:index`, `#id` appended when it has one, then `@` its own opacity). */
106
181
  parts: Array<{
107
182
  a: string;
108
183
  al: number;
@@ -125,8 +200,15 @@ export interface Layout {
125
200
  fill?: number;
126
201
  /** Share of a 6x4 grid over the body box that something is drawn in. Reported, not graded. */
127
202
  cells?: number;
128
- /** The largest visible label in the body box, px. */
203
+ /** The largest visible label in the body box, px as drawn (a camera push-in scales it). */
129
204
  maxType?: number;
205
+ /**
206
+ * The largest font-size a visible label in the body box DECLARES, KaTeX
207
+ * excluded (it sets itself at 1.21em of its box): what `type_ceiling` holds
208
+ * to `MAX_TYPE_PX`. Not `maxType`, which a camera push-in or a viewBox zoom
209
+ * grows past the size the scene asked for.
210
+ */
211
+ maxDeclared?: number;
130
212
  /** Share of the body box the settled drawing paints (`paintedShare`). Reported; the rubric probe reads it. */
131
213
  mass?: number;
132
214
  /** Share of the drawn labels and shapes in the body box at under 0.6 opacity. */
@@ -161,6 +243,14 @@ export interface Layout {
161
243
  }>;
162
244
  /** Labels with digits in them sitting on the illustration. */
163
245
  onPicture?: number;
246
+ /** Visible text in the body box under `TEXT_CONTRAST` against its backdrop, as `id [box] ratio`. */
247
+ faint?: string[];
248
+ /** End row: the scene's own tweens that animate a label, plate, chip, card or bar into place (`ui_motion`). */
249
+ uiMotion?: string[];
250
+ /** Height share of the body box the drawing spans (bespoke scenes), for \`hollow_hold\`. */
251
+ span?: number;
252
+ /** Pairs of alike filled dots drawn into each other (\`marks_overlap\`). */
253
+ marksOverlap?: string[];
164
254
  }
165
255
  /**
166
256
  * A label that names a subject sits within this many box px of that
@@ -185,6 +275,12 @@ export interface CamSample {
185
275
  shot: [number, number, number, number, number];
186
276
  /** How many subjects the scene's illustration has. */
187
277
  subjects: number;
278
+ /** The camera grammar the shell stamped on the scene (src/bespoke/grammar.ts). */
279
+ grammar?: string;
280
+ /** The backdrop's scale, x, y (round 5's second layer), when it has one. */
281
+ plate?: [number, number, number];
282
+ /** How much of the box's width a wiped-on picture shows, 0..1, when it is wiped. */
283
+ wipe?: number;
188
284
  }
189
285
  /**
190
286
  * The distinct shots a scene's camera HOLDS: a push-in counts when two
@@ -197,6 +293,12 @@ export declare function shotsOf(samples: readonly CamSample[]): {
197
293
  wide: boolean;
198
294
  close: Array<[number, number]>;
199
295
  };
296
+ /**
297
+ * Whether the camera did what its grammar says (src/bespoke/grammar.ts), read
298
+ * off the samples alone — so a shell that compiled every grammar to round 4's
299
+ * tour would fail here, not pass on the stamp.
300
+ */
301
+ export declare function grammarBroken(grammar: string, list: readonly CamSample[], t0?: number): string[];
200
302
  /**
201
303
  * `shot_variety`: an illustrated scene is staged — it OPENS on the whole
202
304
  * picture (every sample in the first second of its first cue is wide), and the
@@ -205,6 +307,67 @@ export declare function shotsOf(samples: readonly CamSample[]): {
205
307
  * first cue start.
206
308
  */
207
309
  export declare function gradeShots(samples: readonly CamSample[], opens?: ReadonlyMap<string, number>): Finding[];
310
+ /**
311
+ * `text_clipped`: a word the frame cuts in two while it is HELD there. A
312
+ * camera push or a clipped viewBox crops what lies at its edge, and a shape cut
313
+ * off reads as framing — a label cut mid-word does not (r1, 2026-10-10: '…인된
314
+ * 결과' held for 2.5s at s12's left edge, '구조 맵' cropped by s7's push-ins).
315
+ * Sampled every `CAM_STEP`; a text counts when it is partly inside the frame
316
+ * (more than `CUT_MIN` of it) and has lost more than a sliver across (under
317
+ * `CUT_MAX` of its width) or more than its leading down (under `CUT_DEEP` of
318
+ * its height), at `CUT_HOLD` consecutive samples — so a label sliding out
319
+ * during a 1.1s move is not one.
320
+ */
321
+ export declare const CUT_MIN = 0.05;
322
+ /** Under this share of its width inside the frame, a word has lost a glyph. */
323
+ export declare const CUT_MAX = 0.96;
324
+ /** Under this share of its line box's height, a word has lost ink (the rest is leading). */
325
+ export declare const CUT_DEEP = 0.75;
326
+ export declare const CUT_HOLD = 3;
327
+ /** One dense sample of one scene: the texts the frame cuts, as `id (share%)`. */
328
+ export interface CutSample {
329
+ sid: string;
330
+ t: number;
331
+ cut: Array<{
332
+ id: string;
333
+ share: number;
334
+ }>;
335
+ }
336
+ export declare function gradeCuts(samples: readonly CutSample[]): Finding[];
337
+ /**
338
+ * `morph_glitch`: a path tween that throws its shape across the stage on the
339
+ * way. An `attr: { d }` tween pairs the two paths' numbers in order, so between
340
+ * paths of different commands (`V455 L1090 320` against `L1090 405 L1240 495`)
341
+ * an x is tweened into a y and back (r1 s4 at 61.75s: a green wedge over half
342
+ * the frame, between two shapes on its right); a morphSVG between mismatched
343
+ * shapes can do the same. Between matched paths every in-between point lies
344
+ * between its two ends, so the shape stays inside their union. A tween counts
345
+ * when, at any in-between sample of one iteration, the shape is visible and
346
+ * reaches more than `MORPH_REACH` of the larger side of its two ends' union
347
+ * (and 60px at least) past that union.
348
+ */
349
+ export declare const MORPH_REACH = 0.35;
350
+ /** One morph tween's shape box at shares of its duration: [x, y, w, h] in page px, and its opacity. */
351
+ export interface MorphSample {
352
+ sid: string;
353
+ id: string;
354
+ start: number;
355
+ dur: number;
356
+ boxes: Array<{
357
+ f: number;
358
+ b: [number, number, number, number];
359
+ o: number;
360
+ }>;
361
+ }
362
+ export declare function gradeMorphs(samples: readonly MorphSample[]): Finding[];
363
+ /**
364
+ * `dim_text`: a word left under 3:1 contrast with what is behind it, at a held
365
+ * frame (a cue's end, the settled end). Every bespoke label is 40px or more —
366
+ * large text — so 3:1 is the WCAG floor; focus dimming had taken key numbers to
367
+ * 1.6:1 (r1 s13 "75.1 %") and named subjects to 1.5:1 (s8) while the voice was
368
+ * on them.
369
+ */
370
+ export declare const TEXT_CONTRAST = 3;
208
371
  export declare function gradeLayout(rows: readonly Layout[]): Finding[];
209
372
  /**
210
373
  * What the page did that a scene must not, pinned to the scenes it implicates.
@@ -281,6 +444,17 @@ export interface ProbedFrame {
281
444
  t: number;
282
445
  png: Buffer;
283
446
  }
447
+ /** How long the stage stood empty across the seam into one scene (`seam_blank`). */
448
+ export interface SeamSample {
449
+ sid: string;
450
+ /** The longest run, seconds, with under `SEAM_PAINT_MIN` of the frame painted. */
451
+ blank: number;
452
+ /** Scene seconds (negative: before the seam) where that run starts. */
453
+ at: number;
454
+ /** Whether the incoming scene is a generated (bespoke) one. */
455
+ bespoke: boolean;
456
+ }
457
+ export declare function gradeSeams(rows: readonly SeamSample[]): Finding[];
284
458
  export interface Probe {
285
459
  findings: Finding[];
286
460
  /** Ascending-pass frames, for a contact sheet. */
@@ -290,6 +464,12 @@ export interface Probe {
290
464
  seek: SeekDiff[];
291
465
  /** The shell's camera every `CAM_STEP` through each illustrated scene (`shot_variety`). */
292
466
  cams: CamSample[];
467
+ /** The words the frame cuts, every `CAM_STEP` through each bespoke scene (`text_clipped`). */
468
+ cuts: CutSample[];
469
+ /** Each morphSVG tween's shape through its run (`morph_glitch`). */
470
+ morphs: MorphSample[];
471
+ /** The stage across each seam into a probed scene (`seam_blank`). */
472
+ seams: SeamSample[];
293
473
  }
294
474
  export interface ProbeOptions {
295
475
  /** Which gates. Default all three. `seek` alone is what `verify` runs on plain v2 scenes. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokerized/decksmith",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Turn a source document into an animated explanation deck.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,76 +0,0 @@
1
- /**
2
- * The labels of an illustrated scene, drawn by the shell ON their subjects.
3
- *
4
- * WHY THE SHELL AND NOT THE SCENE. Round 3's labels were plates in a row along
5
- * the top of the box. Round 4's first run (2026-10-09, en deck) asked the scene
6
- * to put each label on its subject itself: the labels went on their subjects
7
- * (3-4 of 3-4 within 96px in every draft), and four of five scenes then failed
8
- * on their own plates and leader lines running through their own text — the
9
- * model guesses a label's width, and at 56px a guess is wrong by more than the
10
- * plate's margin. The shell knows the width (the same estimator the archetypes
11
- * lay out with), so the scene says WHAT each subject is called (`labels`) and
12
- * the shell says WHERE: a plate in a zone just above the subject (or across
13
- * its top when there is no room above), a leader line to a dot on it, entering
14
- * when the camera first arrives on that subject. One grammar for every deck.
15
- *
16
- * Zones are fixed from the subjects alone, before the scene is written, so the
17
- * prompt can tell the scene where they are and to keep its own drawing out.
18
- */
19
- import type { Theme } from "../emit/kit.js";
20
- import type { Box, CamMove } from "./shots.js";
21
- /** One subject's label, as the scene names it. */
22
- export interface Label {
23
- subject: number;
24
- text: string;
25
- }
26
- /** Where a subject's label goes: its zone (box px), and the dot its leader ends on. */
27
- export interface Zone extends Box {
28
- subject: number;
29
- /** "above" the subject, or across its "top" inside it. */
30
- side: "above" | "top";
31
- dot: {
32
- x: number;
33
- y: number;
34
- };
35
- }
36
- /** The label's size, and the floor it may shrink to to fit its zone. */
37
- export declare const LABEL_PX = 56;
38
- export declare const LABEL_MIN_PX = 44;
39
- /**
40
- * Each subject's label zone: centred over the subject, as wide as half the
41
- * distance to each neighbour allows (so neighbours' zones never meet), above
42
- * the subject when there is room, else across its top.
43
- */
44
- export declare function calloutZones(subjects: readonly Box[], W: number): Zone[];
45
- /** How a label is set: its size and its line(s). */
46
- export interface Fit {
47
- fs: number;
48
- lines: string[];
49
- }
50
- /**
51
- * How a label fits its zone: one line from `LABEL_PX` down to `LABEL_MIN_PX`,
52
- * else two lines (the plate grows away from the subject, as far as the box
53
- * allows), else not at all.
54
- */
55
- export declare function fitLabel(text: string, zone: Zone, theme: Theme, boxH?: number): Fit | undefined;
56
- /**
57
- * Roughly how many characters fit a zone at the floor size, for the prompt
58
- * ("at most N characters"): Latin lowercase if the deck is Latin, else the
59
- * deck's script, at `LABEL_MIN_PX`. The fit itself is `fitLabel`'s.
60
- */
61
- export declare function longestFit(zone: Zone, theme: Theme): number;
62
- /** What the camera frames for a subject: the subject and its label zone together. */
63
- export declare function withZone(s: Box, z: Zone | undefined): Box;
64
- /** When each subject's label enters: as the camera arrives on it, else at the reveal. */
65
- export declare function entrances(moves: readonly CamMove[], subjects: number): Map<number, number>;
66
- /**
67
- * The labels as markup and GSAP for the shell to put in the scene: a layer of
68
- * `<g data-subject="K">` groups (so `label_anchor` measures them like any
69
- * label), each hidden until its entrance, then the plate rises in, the leader
70
- * draws to the dot. Labels that do not fit their zone are left out (the
71
- * static check refuses a scene that has one, so this is belt and braces).
72
- */
73
- export declare function calloutLayer(sid: string, labels: readonly Label[], zones: readonly Zone[], moves: readonly CamMove[], theme: Theme, W: number, H: number): {
74
- markup: string;
75
- script: string;
76
- };