zdymak 0.7.0 → 0.13.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.
@@ -0,0 +1,559 @@
1
+ /**
2
+ * Type definitions for zdymak — store screenshots, App Preview / Play promo videos and reels,
3
+ * composed from your captures.
4
+ *
5
+ * The main reason these exist: a project's `zdymak.config.mjs` is the ONLY project-specific input, and
6
+ * it deserves autocomplete + checking. Wrap it in `defineConfig` (works in plain `.mjs` — no build step,
7
+ * no TypeScript in your project):
8
+ *
9
+ * import { defineConfig } from 'zdymak';
10
+ * export default defineConfig({ brand: { … }, scenes: [ … ], targets: ['appstore-preview'] });
11
+ *
12
+ * Or, without the import, via a JSDoc type annotation:
13
+ *
14
+ * /** @type {import('zdymak').Config} *\/
15
+ * export default { … };
16
+ */
17
+
18
+ // ─── Targets ────────────────────────────────────────────────────────────────────────────────────
19
+
20
+ /** Video targets — produce an `.mp4` (`zdymak video` / `build`). */
21
+ export type VideoTargetId =
22
+ | 'appstore-preview'
23
+ | 'appstore-preview-ipad'
24
+ | 'appstore-preview-mac'
25
+ | 'play-promo'
26
+ | 'social-reel'
27
+ | 'premium-reel';
28
+
29
+ /** Screenshot / graphic targets — produce still PNGs (`zdymak screenshots` / `build`). */
30
+ export type ImageTargetId =
31
+ | 'appstore-iphone-6.9'
32
+ | 'appstore-iphone-6.5'
33
+ | 'appstore-ipad-13'
34
+ | 'appstore-watch'
35
+ | 'appstore-mac'
36
+ | 'play-phone'
37
+ | 'play-tablet'
38
+ | 'play-wear'
39
+ | 'play-feature-graphic'
40
+ | 'play-icon';
41
+
42
+ export interface VideoTargetSpec {
43
+ store: string;
44
+ w: number;
45
+ h: number;
46
+ fps: number;
47
+ profile: string;
48
+ level: string;
49
+ minSec: number | null;
50
+ maxSec: number | null;
51
+ style: RenderStyle;
52
+ slot: string;
53
+ }
54
+
55
+ export interface ImageTargetSpec {
56
+ store: string;
57
+ /** Exact width, when the store mandates one size. */
58
+ w?: number;
59
+ /** Exact height, when the store mandates one size. */
60
+ h?: number;
61
+ /** Alternative accepted sizes, when the store takes any of several (e.g. Apple Watch). */
62
+ accepts?: Array<[number, number]>;
63
+ alpha: boolean;
64
+ format: 'png';
65
+ /** A single branded banner (the Play feature graphic), not a per-scene shot. */
66
+ graphic?: boolean;
67
+ label: string;
68
+ }
69
+
70
+ export declare const VIDEO_TARGETS: Record<VideoTargetId, VideoTargetSpec>;
71
+ export declare const IMAGE_TARGETS: Record<ImageTargetId, ImageTargetSpec>;
72
+
73
+ /** Resolve a video target by id; throws with the list of valid ids. */
74
+ export declare function videoTarget(id: VideoTargetId | (string & {})): VideoTargetSpec;
75
+
76
+ // ─── Config ─────────────────────────────────────────────────────────────────────────────────────
77
+
78
+ /** Ken-Burns motion applied to a scene. Omit and zdymak rotates through a varied default sequence. */
79
+ export type SceneMove =
80
+ | 'pushIn'
81
+ | 'pushInSlow'
82
+ | 'pullBack'
83
+ | 'pullBackSlow'
84
+ | 'driftUp'
85
+ | 'driftDown'
86
+ | 'driftLeft'
87
+ | 'driftRight'
88
+ | 'still';
89
+
90
+ /**
91
+ * Transition INTO a scene (reel only). The default `cut` is deliberately plain — reaching for a different
92
+ * decorative transition at every boundary is the loudest amateur tell. `auto` is a deterministic rotation
93
+ * that stays mostly plain. See the transition table in the README.
94
+ */
95
+ export type CutId = 'cut' | 'dissolve' | 'cinematic-dissolve' | 'match-cut' | 'fade-through-black' | 'dip-to-white' | 'soft-flash' | 'slow-zoom-through' | 'pull-out' | 'soft-zoom-punch' | 'frame-fill' | 'push' | 'push-up' | 'page-slide' | 'warp-slide' | 'whip-pan' | 'polaroid-drop' | 'blur-dissolve' | 'zoom-punch' | 'clean-line-wipe' | 'edge-wipe-soft' | 'iris-circle' | 'iris-split' | 'mirror-split' | 'heart-wipe' | 'flip' | 'spin-3d' | 'page-peel' | 'tearing-paper' | 'light-leak-wipe' | 'glare-sweep' | 'floodlight-sweep' | 'clouds-wipe' | 'glitch-cut' | 'clean-circle-wipe' | 'auto';
96
+
97
+ /** Per-scene look (reel only — never applied to store screenshots). See the effect table in the README. */
98
+ export type EffectId = 'none' | 'bw' | 'sepia' | 'cool' | 'vibrant' | 'soft-faded' | 'warm-film' | 'duotone' | 'vignette' | 'soft-glow' | 'dreamy-haze' | 'bokeh' | 'glare' | 'floodlight' | 'light-leak' | 'film-grain' | 'scanlines' | 'dust-scratches' | 'camera-shake' | 'letterbox' | 'falling-snow' | 'sparkles-fireflies' | 'heart-drift' | 'confetti-drift' | 'clouds-drift';
99
+
100
+ /** Still-rendering style. Inferred per target (framed device / premium window); override per shot. */
101
+ export type RenderStyle = 'framed' | 'premium' | 'bleed' | 'reel';
102
+
103
+ /** Device bezel drawn around a capture. Inferred from the target; override with `frame`. */
104
+ export type FrameId = 'phone' | 'iphone' | 'android' | 'ipad' | 'tablet' | 'watch' | 'mac';
105
+
106
+ export interface Brand {
107
+ /** Near-black base colour. */
108
+ ink?: string;
109
+ /** Headline colour. */
110
+ title?: string;
111
+ /** Subhead colour. */
112
+ sub?: string;
113
+ /** Custom TTF/OTF files to register, relative to the config file. */
114
+ fontPaths?: string[];
115
+ /** App name — cold-open + end-card of the device-framed `social-reel`. */
116
+ name?: string;
117
+ /** One-line positioning, used on the reel cold-open and the Play feature graphic. */
118
+ tagline?: string;
119
+ /** End-card headline. */
120
+ endline?: string;
121
+ /** End-card subhead (e.g. the honest offer). */
122
+ endsub?: string;
123
+ /** Logo path (relative to the config file) for the reel + feature graphic. */
124
+ logo?: string | null;
125
+ }
126
+
127
+ /**
128
+ * Matte + caption styling. `theme` styles VIDEOS; `stillTheme` styles SCREENSHOTS (falling back to
129
+ * `theme`); a per-shot / per-device `theme` overrides both for that item.
130
+ */
131
+ export interface Theme {
132
+ /** Matte gradient, top colour. */
133
+ bgTop?: string;
134
+ /** Matte gradient, bottom colour. */
135
+ bgBottom?: string;
136
+ /** Radial glow colour behind the device. */
137
+ glow?: string;
138
+ /** Glow strength, 0–1. */
139
+ glowAlpha?: number;
140
+ /** Corner darkening, 0–1. Use 0 on a light matte. */
141
+ vignette?: number;
142
+ /** Padding between the device and the frame edge, in px. */
143
+ inset?: number;
144
+ /** Draw the headline in a dark pill (`true`) or as plain text (`false`). */
145
+ label?: boolean;
146
+ /** Headline colour. */
147
+ labelColor?: string;
148
+ /** Subhead colour. */
149
+ subColor?: string;
150
+ /** Persistent social handle drawn at the top (e.g. `'@asilak'`). */
151
+ handle?: string;
152
+ /** Caption position. Screenshots default to `'top'`. */
153
+ captionAnchor?: 'top' | 'bottom';
154
+ /** How the capture fills its area — `'contain'` shows a whole window, `'cover'` crops. */
155
+ fit?: 'contain' | 'cover';
156
+ /** Multiplier on the headline size. */
157
+ headlineScale?: number;
158
+ /** Device bezel to draw; overrides the target's inferred frame. */
159
+ frame?: FrameId;
160
+ /** Let the capture bleed to the frame edge (no matte margin). */
161
+ bleed?: boolean;
162
+ /**
163
+ * Paint a clean status bar (time · signal · wifi · full battery) into the empty status-bar band some
164
+ * captures carry — an Android Compose capture reserves the inset but can't contain the system UI.
165
+ * `'auto'` (default) draws it only when such a band is detected; `true` forces it; `false` skips.
166
+ */
167
+ statusBar?: 'auto' | boolean;
168
+ /** Clock shown in that status bar. Defaults to `'9:41'`. */
169
+ statusBarTime?: string;
170
+ /**
171
+ * Draw cell-signal bars. Inferred by device class — on for phones, off for tablets (ours are Wi-Fi
172
+ * models) and landscape captures. Set it explicitly for a cellular tablet.
173
+ */
174
+ statusBarCellular?: boolean;
175
+ /** Cover-fit anchor when the capture is taller than the slot. `'top'` crops from the bottom only. */
176
+ anchor?: 'center' | 'top';
177
+ }
178
+
179
+ export interface Scene {
180
+ /** Capture id — resolves to `<capturesDir>/<id><suffix>.png`. Required unless `image` is given. */
181
+ id?: string;
182
+ /** Explicit image path (relative to the config file), instead of `id`. */
183
+ image?: string;
184
+ /** Headline. */
185
+ title?: string;
186
+ /** Subhead. */
187
+ sub?: string;
188
+ /** Ken-Burns motion for video targets. */
189
+ move?: SceneMove;
190
+ /** Reel only — how the video cuts INTO this scene. Defaults to a hard `cut`. */
191
+ cut?: CutId;
192
+ /** Reel only — the per-scene look (colour grade and/or overlay). */
193
+ effect?: EffectId;
194
+ /**
195
+ * Reel only — give THIS scene the one earned camera move: an overdamped spring push-in on the device,
196
+ * with the caption pinned. Use it on at most one scene; motion on every beat is the tell it isn't.
197
+ */
198
+ push?: boolean;
199
+ }
200
+
201
+ /**
202
+ * One locale's captions: scene id → the headline/subhead to draw. The reserved `$brand` key localizes
203
+ * the wordmark copy used by the feature graphic (colours and logo stay global).
204
+ */
205
+ export type SceneCaption = { title?: string; sub?: string };
206
+
207
+ /** The localizable wordmark copy, under a caption table's reserved `$brand` key. */
208
+ export type BrandCaptions = Pick<Brand, 'name' | 'tagline' | 'endline' | 'endsub'>;
209
+
210
+ export type CaptionTable = { $brand?: BrandCaptions } & {
211
+ [sceneId: string]: SceneCaption | BrandCaptions | undefined;
212
+ };
213
+
214
+ export interface Music {
215
+ /** Audio file, relative to the config file. */
216
+ path: string;
217
+ /** Seconds to skip into the track. */
218
+ offset?: number;
219
+ fadeIn?: number;
220
+ fadeOut?: number;
221
+ /** 0–1. */
222
+ volume?: number;
223
+ }
224
+
225
+ export interface ScreenshotSpec {
226
+ target: ImageTargetId | (string & {});
227
+ /** Output subfolder; defaults to the target id. Lets one target render twice (e.g. styled + plain). */
228
+ dir?: string;
229
+ /** `false` renders the app interface alone — no headline/subhead. Required styling for Play uploads. */
230
+ caption?: false;
231
+ /** Exact output size, when a slot accepts several. */
232
+ size?: [number, number];
233
+ /** Override the inferred style (e.g. `'bleed'` for a raw full-frame Watch shot). */
234
+ style?: RenderStyle;
235
+ /** Override the inferred device bezel. */
236
+ frame?: FrameId;
237
+ /** Per-shot matte override. */
238
+ theme?: Theme;
239
+ }
240
+
241
+ /** How an asset is rendered — independent of where it's going. */
242
+ export type PresetId = 'full-bleed' | 'framed' | 'premium';
243
+
244
+ export interface VideoSpec {
245
+ /**
246
+ * Where it's going. DESTINATION decides only what the store accepts — pixel size, codec, duration
247
+ * bounds, file cap. Use this with `preset` for the split form.
248
+ */
249
+ destination?: VideoTargetId | (string & {});
250
+ /**
251
+ * How it looks. `full-bleed` (no bezel — required for App Previews), `framed` (device + brand
252
+ * bookends), `premium` (floating on the matte). Defaults to whatever the destination implies.
253
+ */
254
+ preset?: PresetId;
255
+ /**
256
+ * Bring your own vocabulary: scenes that don't name a `cut` cycle through this list instead of the
257
+ * preset's default. Same for `effects`.
258
+ */
259
+ transitions?: CutId[];
260
+ effects?: EffectId[];
261
+ /** Shorthand for `{ destination, preset }` — every existing config keeps working. */
262
+ target?: VideoTargetId | (string & {});
263
+ /** Exact output size — e.g. a landscape Mac reel at `[2880, 1800]`. */
264
+ size?: [number, number];
265
+ theme?: Theme;
266
+ }
267
+
268
+ /**
269
+ * One device class. An app configures ONLY the devices it ships; a device whose captures are missing
270
+ * skips cleanly (screenshots AND videos), so a partial capture still composes what it has.
271
+ */
272
+ export interface DeviceGroup {
273
+ /** Where this device's captures live, relative to the config file. Defaults to `screenshotsDir`. */
274
+ capturesDir?: string;
275
+ /** Filename suffix for this device (e.g. `'-light'`). Defaults to the top-level `suffix`. */
276
+ suffix?: string;
277
+ /** Scene override for this device. Defaults to the shared top-level `scenes`. */
278
+ scenes?: Scene[];
279
+ /** Store screenshot slots to render. A bare string is shorthand for `{ target }`. */
280
+ screenshots?: Array<ScreenshotSpec | ImageTargetId | (string & {})>;
281
+ /** Video targets rendered from THIS device's captures. A bare string is shorthand for `{ target }`. */
282
+ videos?: Array<VideoSpec | VideoTargetId | (string & {})>;
283
+ /** Matte override for everything in this group. */
284
+ theme?: Theme;
285
+ }
286
+
287
+ /** One beat of a live-footage reel: a recording, a still, or an image sequence. */
288
+ export interface ReelSegment {
289
+ /** A video recording (real motion), relative to the config file. */
290
+ clip?: string;
291
+ /** A single still — gets a slow push-in. */
292
+ image?: string;
293
+ /** An image sequence shown in order. */
294
+ images?: string[];
295
+ caption?: { title?: string; sub?: string };
296
+ }
297
+
298
+ /**
299
+ * Live-footage reel (real motion, not Ken Burns) — built by `zdymak reel`, independent of `targets`.
300
+ */
301
+ export interface ReelConfig {
302
+ size?: [number, number];
303
+ fps?: number;
304
+ profile?: string;
305
+ level?: string;
306
+ /** Seconds per beat. Overrides the `bpm`/`beatsPerCut` calculation. */
307
+ sceneDur?: number;
308
+ /** Music tempo, used with `beatsPerCut` to derive the cut rhythm. */
309
+ bpm?: number;
310
+ /** Beats held per segment. */
311
+ beatsPerCut?: number;
312
+ /** `'cut'` for beat-matched hard cuts; omit for the default dissolve. */
313
+ transition?: 'cut' | 'dissolve';
314
+ /** Dissolve length in seconds. */
315
+ xfadeDur?: number;
316
+ /** Matte override. Defaults to the LIGHT bed with the caption on top. */
317
+ theme?: Theme;
318
+ music?: Music;
319
+ segments: ReelSegment[];
320
+ }
321
+
322
+ /** Reel-mode timeline override for the device-framed `social-reel` target. */
323
+ export interface Timing {
324
+ coldOpen?: number;
325
+ /** Seconds per scene. Ignored when `bpm` is set. */
326
+ scene?: number;
327
+ endCard?: number;
328
+ xfade?: number;
329
+ /** Music tempo — cuts land on the beat: hold = `beatsPerCut × 60 / bpm`. */
330
+ bpm?: number;
331
+ /** Beats held per scene (default 4). */
332
+ beatsPerCut?: number;
333
+ }
334
+
335
+ /**
336
+ * A project's `zdymak.config.mjs`. Every path is resolved relative to the config file.
337
+ *
338
+ * Provide `scenes`, a `devices` map, or a `reel` block — at least one must supply content.
339
+ */
340
+ export interface Config {
341
+ brand?: Brand;
342
+ /** Default captures dir for the top-level `scenes`. Defaults to the config file's own directory. */
343
+ screenshotsDir?: string;
344
+ /** Appended to `scene.id` to form the filename: `<id><suffix>.png`. */
345
+ suffix?: string;
346
+ /** The shared scene list — used by the top-level `targets` and by any device without its own. */
347
+ scenes?: Scene[];
348
+ /** Video targets rendered from the top-level `scenes`. Defaults to `['appstore-preview']`. */
349
+ targets?: Array<VideoTargetId | (string & {})>;
350
+ /** Seconds per scene in a video. */
351
+ sceneDur?: number;
352
+ /** Cross-fade length between scenes, in seconds. */
353
+ xfade?: number;
354
+ /** Timeline override for the device-framed reel style. */
355
+ timing?: Timing;
356
+ /** Matte + caption styling for VIDEOS. */
357
+ theme?: Theme;
358
+ /** Matte + caption styling for SCREENSHOTS; falls back to `theme`. */
359
+ stillTheme?: Theme;
360
+ /** Music bed shared by every video target. Silent when omitted. */
361
+ music?: Music;
362
+ /** Per-device screenshots + videos, keyed by a name of your choosing (`iphone`, `android`, …). */
363
+ devices?: Record<string, DeviceGroup>;
364
+ /**
365
+ * Localized screenshot sets, keyed by store locale. Each value is either a JSON file path or an
366
+ * inline table of `sceneId → { title, sub }`. A locale renders to `<out>/<locale>/<target>/…`;
367
+ * scenes it doesn't translate keep their base caption (reported, never silent).
368
+ */
369
+ captions?: Record<string, string | CaptionTable>;
370
+ /** Live-footage reel, built by `zdymak reel`. */
371
+ reel?: ReelConfig;
372
+ /** Output directory. Defaults to `store-assets`. */
373
+ out?: string;
374
+ }
375
+
376
+ /**
377
+ * Identity helper — returns the config unchanged, but gives editors the type in a plain `.mjs` file.
378
+ */
379
+ export declare function defineConfig(config: Config): Config;
380
+
381
+ // ─── Resolved config (what `loadConfig` returns) ────────────────────────────────────────────────
382
+
383
+ export interface ResolvedScene {
384
+ id: string;
385
+ /** Absolute path. */
386
+ image: string;
387
+ title: string;
388
+ sub: string;
389
+ move?: SceneMove;
390
+ }
391
+
392
+ export interface ResolvedDevice {
393
+ /** The key this group had in the `devices` map. */
394
+ name: string;
395
+ scenes: ResolvedScene[];
396
+ screenshots: ScreenshotSpec[];
397
+ videos: VideoSpec[];
398
+ theme?: Theme;
399
+ }
400
+
401
+ export interface ResolvedConfig {
402
+ brand: Required<Pick<Brand, 'ink' | 'title' | 'sub'>> & Brand & { fontPaths: string[] };
403
+ reel?: ReelConfig;
404
+ scenes: ResolvedScene[];
405
+ devices: ResolvedDevice[];
406
+ /** Caption tables with every JSON path already read. */
407
+ captions?: Record<string, CaptionTable>;
408
+ music?: Music;
409
+ targets: Array<VideoTargetId | (string & {})>;
410
+ sceneDur: number;
411
+ xfade: number;
412
+ timing?: Timing;
413
+ theme?: Theme;
414
+ stillTheme?: Theme;
415
+ /** Absolute output directory. */
416
+ out: string;
417
+ /** Absolute directory of the config file — the root every relative path resolved against. */
418
+ baseDir: string;
419
+ }
420
+
421
+ /** Load + normalize a `zdymak.config.mjs` / `.json`, resolving every path against the config file. */
422
+ export declare function loadConfig(configPath: string): Promise<ResolvedConfig>;
423
+
424
+ // ─── Programmatic API ───────────────────────────────────────────────────────────────────────────
425
+
426
+ /** Encoder spec for a video build. */
427
+ export interface EncodeSpec {
428
+ w: number;
429
+ h: number;
430
+ fps?: number;
431
+ profile?: string;
432
+ level?: string;
433
+ store?: string;
434
+ minSec?: number | null;
435
+ maxSec?: number | null;
436
+ }
437
+
438
+ export interface VideoResult {
439
+ outFile: string;
440
+ totalDur: number;
441
+ frames: number;
442
+ warnings: string[];
443
+ }
444
+
445
+ /** Full-bleed Ken-Burns video (App Preview / Play promo). */
446
+ export declare function buildVideo(opts: {
447
+ scenes: ResolvedScene[];
448
+ spec: EncodeSpec;
449
+ brand: Brand;
450
+ outFile: string;
451
+ sceneDur?: number;
452
+ xfade?: number;
453
+ music?: Music;
454
+ }): Promise<VideoResult>;
455
+
456
+ /** Device-framed marketing reel (cold-open + scenes + end-card). */
457
+ export declare function buildReel(opts: {
458
+ scenes: ResolvedScene[];
459
+ spec: EncodeSpec;
460
+ brand: Brand;
461
+ outFile: string;
462
+ timing?: Timing;
463
+ music?: Music;
464
+ }): Promise<VideoResult>;
465
+
466
+ /** Premium style — full-bleed screens on a brand matte with Apple-style editing. */
467
+ export declare function buildPremium(opts: {
468
+ scenes: ResolvedScene[];
469
+ spec: EncodeSpec;
470
+ brand: Brand;
471
+ theme?: Theme;
472
+ outFile: string;
473
+ sceneDur?: number;
474
+ music?: Music;
475
+ }): Promise<VideoResult>;
476
+
477
+ export interface WrittenScreenshot {
478
+ file: string;
479
+ W: number;
480
+ H: number;
481
+ style: RenderStyle | 'graphic';
482
+ }
483
+
484
+ /** Render every screenshot for one resolved device group. */
485
+ export declare function buildDeviceScreenshots(opts: {
486
+ device: ResolvedDevice;
487
+ brand: Brand;
488
+ theme?: Theme;
489
+ outDir: string;
490
+ }): Promise<WrittenScreenshot[]>;
491
+
492
+ /** The 1024×500 Play feature graphic — a brand banner, not a per-scene shot. */
493
+ export declare function buildFeatureGraphic(opts: {
494
+ W?: number;
495
+ H?: number;
496
+ brand: Brand;
497
+ theme?: Theme;
498
+ /** Capture framed as the tilted hero device. */
499
+ heroPath?: string;
500
+ outFile: string;
501
+ frame?: FrameId;
502
+ }): Promise<void>;
503
+
504
+ /** Render one still at an exact size. Returns a canvas (see `rgbPngBuffer` to encode it). */
505
+ export declare function renderStill(
506
+ style: RenderStyle,
507
+ opts: {
508
+ W: number;
509
+ H: number;
510
+ imgPath: string;
511
+ caption?: { title?: string; sub?: string };
512
+ brand: Brand;
513
+ theme?: Theme;
514
+ frame?: FrameId | null;
515
+ },
516
+ ): Promise<unknown>;
517
+
518
+ /** A transition: `paint` composites two painted layers at progress `p` (0→1). */
519
+ export interface Transition {
520
+ dur: number;
521
+ label: string;
522
+ paint(ctx: unknown, prev: unknown, next: unknown, p: number, size: { W: number; H: number }): void;
523
+ }
524
+
525
+ /** A look: a CSS filter on the capture, an overlay over the finished frame, or both. */
526
+ export interface Effect {
527
+ label: string;
528
+ filter?: string;
529
+ overlay?(ctx: unknown, o: { W: number; H: number; t: number; p: number }): void;
530
+ }
531
+
532
+ /** The transition registry — add an entry to add a cut; nothing else changes. */
533
+ export declare const TRANSITIONS: Record<string, Transition>;
534
+ export declare const TRANSITION_IDS: string[];
535
+ /** Resolve a cut id; `auto` picks from a deterministic rotation by scene index. */
536
+ export declare function transitionFor(id?: CutId, index?: number): Transition;
537
+
538
+ /** The effect registry. */
539
+ export declare const EFFECTS: Record<string, Effect>;
540
+ export declare const EFFECT_IDS: string[];
541
+ export declare function effectFor(id?: EffectId): Effect;
542
+
543
+ /** Apply a locale's caption table to a scene list; untranslated scenes keep their base caption. */
544
+ export declare function localizeScenes(scenes: ResolvedScene[], table?: CaptionTable): ResolvedScene[];
545
+
546
+ /** Scene ids the given locale doesn't translate — what a run reports as having fallen back. */
547
+ export declare function untranslatedScenes(scenes: ResolvedScene[], table?: CaptionTable): string[];
548
+
549
+ /** Apply a caption table's reserved `$brand` block to the brand copy. */
550
+ export declare function localizeBrand(brand: Brand, table?: CaptionTable): Brand;
551
+
552
+ /** Encode a canvas as a store-safe **no-alpha** PNG. */
553
+ export declare function rgbPngBuffer(canvas: unknown): Buffer;
554
+
555
+ /** Register custom TTF/OTF files so text renders identically everywhere. */
556
+ export declare function registerFonts(fontPaths?: string[]): void;
557
+
558
+ /** Run the CLI programmatically. */
559
+ export declare function run(argv?: string[]): Promise<void>;