onda-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,580 @@
1
+ import { ComponentType, ReactElement } from 'react';
2
+
3
+ /** A time: seconds (number) or a spec string — `"2s"`, `"500ms"`, `"0:02"`, `"90f"`. */
4
+ type TimeSpec = string | number;
5
+ /** An entry's motion: an `@onda-engine/components` choreography pattern name + params. */
6
+ interface EntryAnimation {
7
+ pattern: string;
8
+ params?: Record<string, unknown>;
9
+ }
10
+ /** Per-entry cinematic EFFECTS — the same sugar props @onda-engine/react's `<Group>`
11
+ * accepts (each maps 1:1 to the engine `Effect` enum). The renderer spreads
12
+ * these onto a wrapping `<Group>` so they render on Vello in EXPORT (twin of the
13
+ * Studio preview renderer's `entryEffectsSchema`). */
14
+ interface EntryEffects {
15
+ blur?: number;
16
+ directionalBlur?: {
17
+ sigma: number;
18
+ angle?: number;
19
+ };
20
+ bloom?: number | {
21
+ sigma: number;
22
+ threshold?: number;
23
+ intensity?: number;
24
+ };
25
+ grade?: {
26
+ exposure?: number;
27
+ contrast?: number;
28
+ saturation?: number;
29
+ temperature?: number;
30
+ tint?: number;
31
+ };
32
+ grain?: number | {
33
+ intensity: number;
34
+ size?: number;
35
+ seed?: number;
36
+ };
37
+ vignette?: number | {
38
+ amount: number;
39
+ softness?: number;
40
+ };
41
+ chromaticAberration?: number;
42
+ posterize?: number;
43
+ duotone?: {
44
+ shadow: string;
45
+ highlight: string;
46
+ };
47
+ chromaKey?: {
48
+ color: string;
49
+ threshold?: number;
50
+ smoothness?: number;
51
+ };
52
+ goo?: number | {
53
+ sigma: number;
54
+ threshold?: number;
55
+ };
56
+ backdropBlur?: number | {
57
+ sigma: number;
58
+ tint?: string;
59
+ brightness?: number;
60
+ saturation?: number;
61
+ };
62
+ lightWrap?: number | {
63
+ sigma: number;
64
+ strength?: number;
65
+ };
66
+ blendMode?: string;
67
+ shadow?: {
68
+ color: string;
69
+ blur: number;
70
+ offsetX?: number;
71
+ offsetY?: number;
72
+ spread?: number;
73
+ };
74
+ }
75
+ /** AE-style 3D placement for an entry (rendered inside a `<Scene3D>`). */
76
+ interface Transform3D {
77
+ position3d?: [number, number, number];
78
+ rotation3d?: [number, number, number];
79
+ anchor3d?: [number, number];
80
+ extrude?: number | {
81
+ depth: number;
82
+ };
83
+ }
84
+ /** Track matte: reveal an entry through a stencil component subtree. */
85
+ interface EntryMatte {
86
+ component: string;
87
+ props?: Record<string, unknown>;
88
+ mode?: 'alpha' | 'luminance';
89
+ }
90
+ /** Clip an entry to a region. */
91
+ interface EntryClip {
92
+ shape: 'rect' | 'ellipse' | 'path';
93
+ width?: number;
94
+ height?: number;
95
+ cornerRadius?: number;
96
+ data?: string;
97
+ }
98
+ /** An entry's attention weight — the contract the inspector's hierarchy,
99
+ * collision, and density checks run on. Consumers (the Studio director)
100
+ * ASSIGN roles; nothing infers them. */
101
+ type EntryRole = 'focal' | 'support' | 'ambient';
102
+ /** One element on a track: a component placed at `at` for `for`, with optional motion. */
103
+ interface Entry {
104
+ at: TimeSpec;
105
+ for: TimeSpec;
106
+ component: string;
107
+ /** Attention weight: `'focal'` = the one thing the viewer should be reading /
108
+ * watching right now (≤1 visible at a time; entrances must not collide),
109
+ * `'support'` = secondary content, `'ambient'` = atmosphere (backgrounds,
110
+ * grain, washes — exempt from density budgets). Drives the inspector's
111
+ * hierarchy / collision / density checks. Absent = `'support'`. */
112
+ role?: EntryRole;
113
+ props?: Record<string, unknown>;
114
+ animate?: EntryAnimation[];
115
+ /** Per-entry cinematic effects (bloom/grade/grain/blur/vignette/…). */
116
+ effects?: EntryEffects;
117
+ /** 2.5D depth for composition `dof` rack-focus. */
118
+ depth?: number;
119
+ /** AE-style 3D placement (position3d / rotation3d / extrude). */
120
+ transform3d?: Transform3D;
121
+ /** Track matte: reveal through a stencil component (media-through-type). */
122
+ matte?: EntryMatte;
123
+ /** Clip to a rect/ellipse/path region. */
124
+ clip?: EntryClip;
125
+ /** Magic-move continuity key. When the SAME `morphKey` appears on an entry in
126
+ * two ADJACENT scenes, the element MORPHS its position/scale across the cut
127
+ * (one continuous move) instead of cross-fading — Keynote Magic Move / a
128
+ * matched cut. The morphing instance is built from the destination scene's
129
+ * entry (same component + props). */
130
+ morphKey?: string;
131
+ id?: string;
132
+ label?: string;
133
+ }
134
+ interface Track {
135
+ id?: string;
136
+ label?: string;
137
+ entries: Entry[];
138
+ }
139
+ interface SceneTransition {
140
+ /** A transition slug, e.g. `"cross-fade"`, `"iris"`, `"push"`. */
141
+ type: string;
142
+ durationInFrames?: number;
143
+ /** Per-transition options forwarded to the presentation factory (direction /
144
+ * color / scaleAmount / bars) — each transition reads what it knows. */
145
+ options?: {
146
+ direction?: string;
147
+ color?: string;
148
+ scaleAmount?: number;
149
+ bars?: number;
150
+ };
151
+ }
152
+ /** A camera keyframe: zoom (1 = neutral), focus x/y (canvas fractions, 0.5 =
153
+ * center), rotate (deg). Omitted keys default. */
154
+ interface CameraKeyframe {
155
+ zoom?: number;
156
+ x?: number;
157
+ y?: number;
158
+ rotate?: number;
159
+ }
160
+ /** A camera move eased over a scene's duration (push-in / pan / roll). */
161
+ interface CameraMove {
162
+ from?: CameraKeyframe;
163
+ to?: CameraKeyframe;
164
+ }
165
+ interface Scene {
166
+ id: string;
167
+ label?: string;
168
+ for?: TimeSpec;
169
+ transition?: SceneTransition;
170
+ tracks: Track[];
171
+ /** A cinematic camera move over this scene. */
172
+ camera?: CameraMove;
173
+ /** The canvas this scene's content was AUTHORED for. When it differs from the
174
+ * composition canvas, the renderer uniformly scales + centers the scene's
175
+ * content to `fit` the output (e.g. a 4:3 template scene into a 16:9 video). */
176
+ designWidth?: number;
177
+ designHeight?: number;
178
+ /** How to re-frame the design canvas into the output:
179
+ * - `contain` — uniformly scale + center, letterbox.
180
+ * - `cover` — uniformly scale + center, fill + crop.
181
+ * - `responsive` — "Magic Resize": re-anchor each element individually (pin to
182
+ * edge / center per axis, size scaled uniformly) so one master adapts to any
183
+ * aspect ratio without per-format variants. */
184
+ fit?: 'contain' | 'cover' | 'responsive';
185
+ }
186
+ /** A composition-level layer entry — absolute-timed, spans scene cuts. */
187
+ interface LayerEntry {
188
+ at?: TimeSpec;
189
+ for?: TimeSpec;
190
+ component: string;
191
+ props?: Record<string, unknown>;
192
+ animate?: EntryAnimation[];
193
+ /** Per-entry cinematic effects (bloom/grade/grain/blur/vignette/…). */
194
+ effects?: EntryEffects;
195
+ /** 2.5D depth for composition `dof` rack-focus. */
196
+ depth?: number;
197
+ /** AE-style 3D placement (position3d / rotation3d / extrude). */
198
+ transform3d?: Transform3D;
199
+ /** Track matte: reveal through a stencil component (media-through-type). */
200
+ matte?: EntryMatte;
201
+ /** Clip to a rect/ellipse/path region. */
202
+ clip?: EntryClip;
203
+ id?: string;
204
+ label?: string;
205
+ }
206
+ interface Layer {
207
+ id?: string;
208
+ label?: string;
209
+ /** `true` = behind the scene spine (a background); else over it (an overlay). */
210
+ under?: boolean;
211
+ entries: LayerEntry[];
212
+ }
213
+ /** Optional brand override — surface tokens, mapped to the engine theme. */
214
+ interface Brand {
215
+ bg?: string;
216
+ surface?: string;
217
+ surface2?: string;
218
+ border?: string;
219
+ borderLit?: string;
220
+ text?: string;
221
+ dim?: string;
222
+ faint?: string;
223
+ accent?: string;
224
+ accentSoft?: string;
225
+ fontDisplay?: string;
226
+ fontBody?: string;
227
+ }
228
+ /** Composition-level cinematic FINISH — the linear-HDR + ACES "looks-shot"
229
+ * output transform (GPU/export-only). Twin of the Studio `compositionFinishSchema`. */
230
+ interface CompositionFinish {
231
+ exposure?: number;
232
+ bloom?: {
233
+ sigma: number;
234
+ threshold?: number;
235
+ intensity?: number;
236
+ };
237
+ halation?: number;
238
+ temperature?: number;
239
+ contrast?: number;
240
+ saturation?: number;
241
+ vignette?: number;
242
+ grain?: number;
243
+ /** A cinematic 3D color LUT applied as the FINAL finish step (after grade + ACES
244
+ * + sRGB), as a trilinear lookup. `table` holds `size³` RGB triples in 0..1 with
245
+ * RED varying fastest, then green, then blue. Honored by BOTH backends. */
246
+ lut?: {
247
+ size: number;
248
+ table: number[];
249
+ };
250
+ }
251
+ interface CompositionPayload {
252
+ fps: number;
253
+ width: number;
254
+ height: number;
255
+ scenes: Scene[];
256
+ layers?: Layer[];
257
+ brand?: Brand;
258
+ /** Opt into the cinematic LINEAR + ACES color pipeline (GPU/export only). */
259
+ linear?: boolean;
260
+ /** Composition-level cinematic finish (ACES tone-map look; GPU/export only). */
261
+ finish?: CompositionFinish;
262
+ /** Per-object motion blur via temporal supersampling (export only). */
263
+ motionBlur?: boolean | {
264
+ shutter?: number;
265
+ samples?: number;
266
+ };
267
+ /** Depth-of-field / rack-focus over per-layer `depth`. */
268
+ dof?: {
269
+ focus: number;
270
+ aperture?: number;
271
+ range?: number;
272
+ maxBlur?: number;
273
+ };
274
+ }
275
+
276
+ /** Parse a TimeSpec to seconds. `fps` is needed for frame specs (`"90f"`). */
277
+ declare function timeSpecToSeconds(spec: TimeSpec | undefined, fps: number): number;
278
+ /** A TimeSpec in frames. */
279
+ declare function toFrames(spec: TimeSpec | undefined, fps: number): number;
280
+ /** A scene's resolved position on the composition timeline, in frames. */
281
+ interface ScenePlacement {
282
+ /** Absolute frame the scene starts (the start of its incoming overlap). */
283
+ start: number;
284
+ /** Scene duration in frames. */
285
+ durationInFrames: number;
286
+ /** Frames the scene's incoming transition overlaps the previous scene
287
+ * (0 for the first scene / no transition). The transition window is
288
+ * `[start, start + overlapIn)` in absolute frames. */
289
+ overlapIn: number;
290
+ }
291
+ /** Resolve every scene's absolute start + duration — the SAME placement
292
+ * `buildComposition`'s `<TransitionSeries>` computes (a scene starts where the
293
+ * previous one ends MINUS its incoming transition overlap). Shared by the
294
+ * renderer (magic-move planning) and the inspector so the two can't drift. */
295
+ declare function scenePlacements(scenes: Scene[], fps: number): ScenePlacement[];
296
+ /** The composition's total length in frames (scene durations minus overlaps). */
297
+ declare function totalFrames(payload: CompositionPayload, fps: number): number;
298
+
299
+ /** Minimum contrast ratio for body-size text (WCAG 2.x SC 1.4.3 AA). */
300
+ declare const CONTRAST_MIN_BODY = 4.5;
301
+ /** Minimum contrast ratio for large text (WCAG 2.x SC 1.4.3 AA). */
302
+ declare const CONTRAST_MIN_LARGE = 3;
303
+ /** Seconds of display per word (240 wpm — Brysbaert 2019 silent-reading mean). */
304
+ declare const READ_SECONDS_PER_WORD = 0.25;
305
+ /** Fixed orientation beat: find the text + ride out its entrance. PRODUCT DECISION. */
306
+ declare const READ_ORIENTATION_SECONDS = 0.6;
307
+ /** Absolute floor for any readable text. PRODUCT DECISION (BBC-consistent). */
308
+ declare const READ_MIN_SECONDS = 1.2;
309
+ /** Seconds a text of `wordCount` words must stay visible:
310
+ * `max(1.2, 0.25 × words + 0.6)`. */
311
+ declare function readingTimeSeconds(wordCount: number): number;
312
+ /** The delivery formats the inspector knows safe areas for. */
313
+ type FormatId = '16:9' | '9:16' | '1:1' | '4:5';
314
+ /** Per-side safe-area inset as a FRACTION of the canvas axis. */
315
+ interface SafeAreaPreset {
316
+ top: number;
317
+ bottom: number;
318
+ left: number;
319
+ right: number;
320
+ }
321
+ /** Safe-area presets per format.
322
+ *
323
+ * - `16:9` — EBU R95 / SMPTE ST 2046-1 GRAPHICS-safe for 16:9 TV: 5% vertical,
324
+ * 10% horizontal (action-safe is 3.5%; graphics/title content uses the
325
+ * stricter band) — https://tech.ebu.ch/publications/r095.
326
+ * - `9:16` — the UNION of the three vertical-social UI overlays at 1080×1920,
327
+ * so one preset clears TikTok + Instagram Reels + YouTube Shorts
328
+ * (per-platform 2025/26 overlay guides — kreatli.com/guides/safe-zone-guide,
329
+ * zeely.ai/blog/tiktok-safe-zones):
330
+ * · TikTok: top ~140px (profile bar), bottom ~324px (caption/sound, ~370px
331
+ * with an ad CTA), right ~164px (like/comment/share rail), left ~60px.
332
+ * · Reels: top ~108–220px, bottom ~320–420px (caption + CTA), right ~120px.
333
+ * · Shorts: top ~180px, bottom ~390px (channel + CTA), right ~120px.
334
+ * Union: top 220 / bottom 420 / left 60 / right 164 px.
335
+ * - `1:1`, `4:5` — feed placements; platforms overlay far less UI in-feed, but
336
+ * Meta crops covers and overlays the caption/CTA strip at the bottom.
337
+ * PRODUCT DECISION informed by the 9:16 research (no platform publishes
338
+ * feed-overlay pixel specs). */
339
+ declare const SAFE_AREAS: Record<FormatId, SafeAreaPreset>;
340
+ /** Nearest known format for a canvas size (by aspect-ratio distance). */
341
+ declare function inferFormat(width: number, height: number): FormatId;
342
+ /** Minimum body font size in px AT a 1080 min-dimension canvas, per format. */
343
+ declare const FONT_FLOOR_PX: Record<FormatId, number>;
344
+ /** The font floor in px for a format on an actual canvas. */
345
+ declare function fontFloorPx(format: FormatId, width: number, height: number): number;
346
+ /** Two FOCAL entrances beginning within this window compete for one attention
347
+ * slot. Backed by the attentional-blink literature: a second target appearing
348
+ * 200–500ms after a first is frequently missed outright (Raymond, Shapiro &
349
+ * Arnell 1992; review: Dux & Marois 2009, Atten Percept Psychophys 71:1683).
350
+ * 250ms sits inside that window. */
351
+ declare const FOCAL_COLLISION_WINDOW_SECONDS = 0.25;
352
+ /** Scene-transition duration budget. Material Design caps complex multi-element
353
+ * transitions at 500–700ms and finds >400ms "too slow" for simple ones
354
+ * (https://m2.material.io/design/motion/speed.html); film dissolves run longer,
355
+ * so 0.6s — inside Material's complex band — is the PRODUCT DECISION budget. */
356
+ declare const TRANSITION_BUDGET_SECONDS = 0.6;
357
+ /** Max non-ambient entries visible at once in a scene. */
358
+ declare const DENSITY_MAX_NON_AMBIENT = 5;
359
+ /** Max FOCAL entries visible at once (one thing to look at). */
360
+ declare const DENSITY_MAX_FOCAL = 1;
361
+
362
+ /** A scene with its absolute timeline placement (frames). */
363
+ interface ResolvedScene {
364
+ scene: Scene;
365
+ index: number;
366
+ /** Absolute start frame (= the start of its incoming transition overlap). */
367
+ start: number;
368
+ durationInFrames: number;
369
+ }
370
+ /** One entry resolved onto the absolute timeline. */
371
+ interface ResolvedEntry {
372
+ /** `'scene'` = a track entry; `'layer'` = a composition-level layer entry. */
373
+ kind: 'scene' | 'layer';
374
+ component: string;
375
+ /** Raw payload props. */
376
+ props: Record<string, unknown>;
377
+ /** Effective props after the Studio→engine translation (size roles → px,
378
+ * prop-name aliases) — what the component actually receives. */
379
+ adapted: Record<string, unknown>;
380
+ /** Attention role; absent = `'support'` (the contract on `Entry.role`). */
381
+ role: EntryRole;
382
+ /** `entry.id` when present, else the payload path (stable + addressable). */
383
+ targetId: string;
384
+ /** Payload path (`scenes[0].tracks[1].entries[2]`). */
385
+ path: string;
386
+ sceneId?: string;
387
+ sceneIndex?: number;
388
+ trackIndex?: number;
389
+ entryIndex?: number;
390
+ /** Start frame: scene-local for scene entries, absolute for layer entries. */
391
+ localStart: number;
392
+ /** Absolute start frame on the composition timeline. */
393
+ absStart: number;
394
+ /** Requested duration in frames. */
395
+ durationInFrames: number;
396
+ /** Frames actually on screen: duration clamped to the scene window (scene
397
+ * entries) / composition end (layer entries). */
398
+ visibleFrames: number;
399
+ /** Layer entries only: `true` = renders UNDER the scene spine (a background). */
400
+ under?: boolean;
401
+ raw: Entry | LayerEntry;
402
+ }
403
+ /** A transition's absolute capture window. */
404
+ interface TransitionWindow {
405
+ /** Index of the scene the transition leads INTO. */
406
+ sceneIndex: number;
407
+ sceneId: string;
408
+ type: string;
409
+ /** Absolute first frame of the overlap. */
410
+ start: number;
411
+ /** Effective overlap length in frames (after the ⅓-of-shorter-scene clamp). */
412
+ durationInFrames: number;
413
+ }
414
+ /** The payload resolved to frames — everything the checks consume. */
415
+ interface ResolvedComposition {
416
+ payload: CompositionPayload;
417
+ fps: number;
418
+ width: number;
419
+ height: number;
420
+ totalFrames: number;
421
+ scenes: ResolvedScene[];
422
+ /** Scene (track) entries. */
423
+ entries: ResolvedEntry[];
424
+ /** Composition-level layer entries (absolute-timed). */
425
+ layerEntries: ResolvedEntry[];
426
+ transitions: TransitionWindow[];
427
+ }
428
+ /** Resolve `payload` the way `buildComposition` does (same helpers), producing
429
+ * the measurable timeline model. Assumes a structurally valid payload — run
430
+ * `validateComposition` first for structural diagnostics. */
431
+ declare function resolveComposition(payload: CompositionPayload): ResolvedComposition;
432
+
433
+ /** Brand-resolved theme colors the blocks fall back to. */
434
+ interface InspectTheme {
435
+ text: string;
436
+ textMuted: string;
437
+ background: string;
438
+ }
439
+ /** One readable string on an entry. */
440
+ interface TextBlock {
441
+ /** The prop carrying the string (`title`, `text`, …). */
442
+ textProp: string;
443
+ content: string;
444
+ /** Resolved px font size (explicit > manifest default > 48). */
445
+ fontSize: number;
446
+ /** The prop that carries the size — the mechanical `fix` target. */
447
+ sizeProp?: string;
448
+ /** Resolved color string, when determinable. */
449
+ color?: string;
450
+ /** True when the color came from an explicit prop (vs a theme fallback). */
451
+ colorExplicit: boolean;
452
+ fontWeight: number;
453
+ fontFamily?: string;
454
+ letterSpacing?: number;
455
+ /** The component's own auto-fit contract, when it has one. */
456
+ fit?: 'none' | 'frame';
457
+ maxWidth?: number;
458
+ }
459
+ /** Total word count across an entry's text blocks. */
460
+ declare function totalWords(blocks: TextBlock[]): number;
461
+ /** Extract the readable text blocks of an entry, with sizes/colors resolved
462
+ * (explicit props win; manifest defaults and theme tokens fill the gaps).
463
+ * Empty for unknown components or entries with no string text props. */
464
+ declare function textBlocks(entry: ResolvedEntry, theme: InspectTheme): TextBlock[];
465
+
466
+ /** Every check the inspector runs. */
467
+ type CheckId = 'text.legibility' | 'layout.overflow' | 'timing.readingTime' | 'timing.collisions' | 'density.score' | 'frames.transitionCapture';
468
+ /** One measured violation. `fix` is MECHANICAL metadata only (a minimum font
469
+ * size, a safe frame index) — never a taste call. */
470
+ interface Violation {
471
+ check: CheckId;
472
+ severity: 'error' | 'warn' | 'info';
473
+ /** The offending entry's `id` (when set) else its payload path; a scene id
474
+ * for scene-level violations. */
475
+ targetId: string;
476
+ sceneId?: string;
477
+ message: string;
478
+ fix?: {
479
+ prop: string;
480
+ suggested: unknown;
481
+ };
482
+ }
483
+ /** Inspector options. */
484
+ interface InspectOptions {
485
+ /** Delivery format (drives safe areas + font floors). Default: inferred from
486
+ * the canvas aspect ratio. */
487
+ format?: FormatId;
488
+ /** Frame indices a consumer intends to capture (thumbnails) — checked
489
+ * against transition windows. */
490
+ frames?: number[];
491
+ }
492
+ /** Per-scene density metrics (always reported, violation or not). */
493
+ interface SceneDensity {
494
+ sceneId: string;
495
+ /** Peak concurrently-visible non-ambient entries. */
496
+ peakNonAmbient: number;
497
+ /** Peak concurrently-visible focal entries. */
498
+ peakFocal: number;
499
+ /** Scene-local frame where the non-ambient peak first occurs. */
500
+ peakFrame: number;
501
+ }
502
+ /** What `inspect` returns: the violations plus the measured context. */
503
+ interface InspectReport {
504
+ violations: Violation[];
505
+ summary: {
506
+ error: number;
507
+ warn: number;
508
+ info: number;
509
+ };
510
+ format: FormatId;
511
+ fps: number;
512
+ totalFrames: number;
513
+ density: SceneDensity[];
514
+ }
515
+ /** Everything a check family gets to measure against. */
516
+ interface CheckContext {
517
+ payload: CompositionPayload;
518
+ resolved: ResolvedComposition;
519
+ format: FormatId;
520
+ safe: SafeAreaPreset;
521
+ theme: InspectTheme;
522
+ opts: InspectOptions;
523
+ }
524
+ /** A check family: context in, violations out. */
525
+ type Check = (ctx: CheckContext) => Violation[];
526
+
527
+ /** An sRGB color, channels 0..1. */
528
+ interface Rgb {
529
+ r: number;
530
+ g: number;
531
+ b: number;
532
+ /** Alpha 0..1 (1 when the hex had no alpha channel). */
533
+ a: number;
534
+ }
535
+ /** Parse `#rgb` / `#rrggbb` / `#rrggbbaa` (case-insensitive). Null otherwise. */
536
+ declare function parseColor(value: unknown): Rgb | null;
537
+ /** WCAG relative luminance of an sRGB color (0 = black, 1 = white). */
538
+ declare function relativeLuminance(c: Rgb): number;
539
+ /** WCAG contrast ratio between two colors — 1:1 (identical) to 21:1 (B/W). */
540
+ declare function contrastRatio(a: Rgb, b: Rgb): number;
541
+
542
+ /** The check registry, in the order they run. */
543
+ declare const CHECKS: Record<CheckId, Check>;
544
+ /**
545
+ * Measure a composition payload against the quality checks. Deterministic —
546
+ * the same payload + options always yields the same report. Assumes a
547
+ * structurally valid payload (run `validateComposition` first; `inspect`
548
+ * tolerates but does not re-diagnose structural errors).
549
+ */
550
+ declare function inspect(payload: CompositionPayload, opts?: InspectOptions): InspectReport;
551
+
552
+ type Registry = Record<string, ComponentType<Record<string, unknown>>>;
553
+ interface BuildOptions {
554
+ /** Component lookup. Default: every `@onda-engine/components` component. */
555
+ registry?: Registry;
556
+ }
557
+ declare function buildComposition(payload: CompositionPayload, opts?: BuildOptions): ReactElement;
558
+ interface Diagnostic {
559
+ /** `error` = won't render correctly (fix it); `warning` = renders, but off or
560
+ * fragile; `info` = an FYI an agent should weigh (e.g. a degraded component). */
561
+ level: 'error' | 'warning' | 'info';
562
+ path: string;
563
+ message: string;
564
+ }
565
+ /**
566
+ * Check a payload before rendering — the tight feedback loop an agent (ONDA
567
+ * Studio's MCP) self-corrects against. Flags structural issues, unknown
568
+ * components (with a did-you-mean), unknown patterns/transitions, malformed
569
+ * timing, and — from the fidelity contract — components that are GPU-only,
570
+ * degraded, or imitate a browser feature, so the agent picks engine-native
571
+ * components and avoids surprises. Returns `[]` when the composition is clean.
572
+ *
573
+ * Unknown-props policy: WARN, don't strip. An unknown prop on a known
574
+ * component yields a `warning` diagnostic and the prop is PRESERVED through
575
+ * `buildComposition` (the component ignores what it doesn't know) — never
576
+ * silently dropped. Unknown COMPONENTS stay errors (with a did-you-mean).
577
+ */
578
+ declare function validateComposition(payload: CompositionPayload, opts?: BuildOptions): Diagnostic[];
579
+
580
+ export { type Brand, type BuildOptions, CHECKS, CONTRAST_MIN_BODY, CONTRAST_MIN_LARGE, type CameraKeyframe, type CameraMove, type Check, type CheckContext, type CheckId, type CompositionFinish, type CompositionPayload, DENSITY_MAX_FOCAL, DENSITY_MAX_NON_AMBIENT, type Diagnostic, type Entry, type EntryAnimation, type EntryClip, type EntryEffects, type EntryMatte, type EntryRole, FOCAL_COLLISION_WINDOW_SECONDS, FONT_FLOOR_PX, type FormatId, type InspectOptions, type InspectReport, type Layer, type LayerEntry, READ_MIN_SECONDS, READ_ORIENTATION_SECONDS, READ_SECONDS_PER_WORD, type Registry, type ResolvedComposition, type ResolvedEntry, type ResolvedScene, type Rgb, SAFE_AREAS, type SafeAreaPreset, type Scene, type SceneDensity, type ScenePlacement, type SceneTransition, TRANSITION_BUDGET_SECONDS, type TextBlock, type TimeSpec, type Track, type Transform3D, type TransitionWindow, type Violation, buildComposition, contrastRatio, fontFloorPx, inferFormat, inspect, parseColor, readingTimeSeconds, relativeLuminance, resolveComposition, scenePlacements, textBlocks, timeSpecToSeconds, toFrames, totalFrames, totalWords, validateComposition };