@folienbaukasten/extension-toolkit 1.0.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.
Files changed (63) hide show
  1. package/README.md +109 -0
  2. package/abi/README.md +20 -0
  3. package/abi/abi.json +151 -0
  4. package/abi/pack.d.ts +196 -0
  5. package/abi/pack.js +126 -0
  6. package/abi/provenance.json +5 -0
  7. package/abi/shims/jsx-runtime.js +13 -0
  8. package/abi/shims/platform.js +48 -0
  9. package/abi/shims/react.js +18 -0
  10. package/abi/types/charts/geometry.d.ts +43 -0
  11. package/abi/types/charts/option.d.ts +15 -0
  12. package/abi/types/charts/scale.d.ts +14 -0
  13. package/abi/types/charts/theme.d.ts +16 -0
  14. package/abi/types/charts/waterfall.d.ts +35 -0
  15. package/abi/types/components/Block.d.ts +31 -0
  16. package/abi/types/components/BlockArrow.d.ts +16 -0
  17. package/abi/types/components/Card.d.ts +25 -0
  18. package/abi/types/components/ChartView.d.ts +4 -0
  19. package/abi/types/components/DriverTree.d.ts +9 -0
  20. package/abi/types/components/FindingsBanner.d.ts +21 -0
  21. package/abi/types/components/HarveyBall.d.ts +12 -0
  22. package/abi/types/components/HeaderTitle.d.ts +19 -0
  23. package/abi/types/components/InsightRail.d.ts +13 -0
  24. package/abi/types/components/KpiTile.d.ts +5 -0
  25. package/abi/types/components/Matrix.d.ts +5 -0
  26. package/abi/types/components/QuoteColumn.d.ts +5 -0
  27. package/abi/types/components/SourceLine.d.ts +6 -0
  28. package/abi/types/components/Table.d.ts +5 -0
  29. package/abi/types/components/Timeline.d.ts +5 -0
  30. package/abi/types/components/slide/ContentSlide.d.ts +29 -0
  31. package/abi/types/components/slide/MissingComponent.d.ts +26 -0
  32. package/abi/types/components/slide/SlideFrame.d.ts +20 -0
  33. package/abi/types/components/slide/SlotView.d.ts +30 -0
  34. package/abi/types/components/slide/registry.d.ts +27 -0
  35. package/abi/types/components/slide/views/AgendaSlideView.d.ts +8 -0
  36. package/abi/types/components/slide/views/CardsSlideView.d.ts +12 -0
  37. package/abi/types/components/slide/views/ChaptersSlideView.d.ts +11 -0
  38. package/abi/types/components/slide/views/ChartSlideView.d.ts +3 -0
  39. package/abi/types/components/slide/views/ComponentSlideView.d.ts +19 -0
  40. package/abi/types/components/slide/views/DriverTreeSlideView.d.ts +3 -0
  41. package/abi/types/components/slide/views/FreeformSlideView.d.ts +3 -0
  42. package/abi/types/components/slide/views/KpiSlideView.d.ts +3 -0
  43. package/abi/types/components/slide/views/MatrixSlideView.d.ts +3 -0
  44. package/abi/types/components/slide/views/QuoteSlideView.d.ts +3 -0
  45. package/abi/types/components/slide/views/QuotesSlideView.d.ts +5 -0
  46. package/abi/types/components/slide/views/SplitSlideView.d.ts +3 -0
  47. package/abi/types/components/slide/views/TableSlideView.d.ts +3 -0
  48. package/abi/types/components/slide/views/TimelineSlideView.d.ts +3 -0
  49. package/abi/types/components/slide/views/TitleSlideView.d.ts +7 -0
  50. package/abi/types/components/slide/views/TwoBlockSlideView.d.ts +3 -0
  51. package/abi/types/components/slide/views/WebSlideView.d.ts +23 -0
  52. package/abi/types/components/slide/views/WhileArrowSlideView.d.ts +14 -0
  53. package/abi/types/deck/data.d.ts +127 -0
  54. package/abi/types/deck/host.d.ts +9 -0
  55. package/abi/types/deck/layouts.d.ts +685 -0
  56. package/abi/types/deck/schema.d.ts +437 -0
  57. package/abi/types/deck/web-body.d.ts +47 -0
  58. package/abi/types/lib/format.d.ts +12 -0
  59. package/abi/types/sets/platform.d.ts +45 -0
  60. package/abi/types/sets/types.d.ts +128 -0
  61. package/dist/fbk-pack.js +756 -0
  62. package/package.json +49 -0
  63. package/tsconfig.extension.json +17 -0
@@ -0,0 +1,685 @@
1
+ import type { AgendaItemSpec, BlockSpec, CardSpec, ChapterSpec, ChartSpec, DriverSpec, FindingSpec, HeaderSpec, InsightSpec, KpiSpec, MatrixSpec, QuoteColumnSpec, QuoteSpec, SlideComponentSpec, TableSpec, TimelineEventSpec } from './schema';
2
+ /** The complete layout vocabulary — every set renders every one of these. */
3
+ export type SharedSlide = TitleSlideSpec | SplitSlideSpec | FreeformSlideSpec | TwoBlockSlideSpec | WhileArrowSlideSpec | ChartSlideSpec | TableSlideSpec | AgendaSlideSpec | TimelineSlideSpec | KpiSlideSpec | MatrixSlideSpec | DriverTreeSlideSpec | QuoteSlideSpec | QuotesSlideSpec | ChaptersSlideSpec | CardsSlideSpec | ComponentSlideSpec | WebSlideSpec;
4
+ /** Opening slide: heading, byline and an optional figure. The optional
5
+ * fields are the union of what sets render — a set's title view ignores the
6
+ * ones it has no place for. */
7
+ export interface TitleSlideSpec {
8
+ layout: 'title';
9
+ props: {
10
+ /** Small uppercase line above the heading, e.g. "Strategic review". */
11
+ eyebrow?: string;
12
+ heading: string;
13
+ subheading?: string;
14
+ /** Short framing labels under the subtitle, e.g. scope or workstreams. */
15
+ chips?: string[];
16
+ author?: string;
17
+ /** Display string; formatting is the author's choice. */
18
+ date?: string;
19
+ /** Title figure on the right side. Whether it is bled to the edge
20
+ * (cover) or shown whole (contain) is the set's choice. */
21
+ imageUrl?: string;
22
+ };
23
+ }
24
+ /** Two content blocks side by side — the workhorse comparison/detail slide. */
25
+ export interface TwoBlockSlideSpec {
26
+ layout: 'two-block';
27
+ props: {
28
+ header: HeaderSpec;
29
+ left: BlockSpec;
30
+ right: BlockSpec;
31
+ finding?: FindingSpec;
32
+ source?: string;
33
+ };
34
+ }
35
+ /** "While a … (left) we need to consider b (right)", joined by a blade. */
36
+ export interface WhileArrowSlideSpec {
37
+ layout: 'while-arrow';
38
+ props: {
39
+ header: HeaderSpec;
40
+ left: BlockSpec;
41
+ /** Rendered with `left`'s variant: the two halves are one sentence
42
+ * across an arrow, so they always share a treatment. */
43
+ right: BlockSpec;
44
+ finding?: FindingSpec;
45
+ source?: string;
46
+ };
47
+ }
48
+ /** One chart given the whole body — bar, stacked-bar, line or waterfall. */
49
+ export interface ChartSlideSpec {
50
+ layout: 'chart';
51
+ props: {
52
+ header: HeaderSpec;
53
+ chart: ChartSpec;
54
+ finding?: FindingSpec;
55
+ source?: string;
56
+ };
57
+ }
58
+ /** A data table filling the body; cells hold text, numbers or harvey balls. */
59
+ export interface TableSlideSpec {
60
+ layout: 'table';
61
+ props: {
62
+ header: HeaderSpec;
63
+ table: TableSpec;
64
+ finding?: FindingSpec;
65
+ source?: string;
66
+ };
67
+ }
68
+ /** Agenda / table of contents: numbered topics on hairline-separated rows,
69
+ * with an optional picture column. Four to six items read best. */
70
+ export interface AgendaSlideSpec {
71
+ layout: 'agenda';
72
+ props: {
73
+ header: HeaderSpec;
74
+ items: AgendaItemSpec[];
75
+ /** Picture on the right side (object-fit: cover), as on the title slide. */
76
+ imageUrl?: string;
77
+ };
78
+ }
79
+ /** Chronology on a horizontal axis; events alternate above and below it.
80
+ * Three to six events fit; more crowd the columns. */
81
+ export interface TimelineSlideSpec {
82
+ layout: 'timeline';
83
+ props: {
84
+ header: HeaderSpec;
85
+ events: TimelineEventSpec[];
86
+ finding?: FindingSpec;
87
+ source?: string;
88
+ };
89
+ }
90
+ /**
91
+ * A row of big-number KPI tiles with labels and optional deltas, over
92
+ * supporting blocks that say what the numbers mean.
93
+ *
94
+ * The tiles alone do not make a slide — four figures floating in the body
95
+ * region read as a fragment. `blocks` is what turns the headline numbers
96
+ * into an argument, and it is where the "so what" belongs; two or three is
97
+ * the natural width. It stays optional so a bare KPI row still renders (the
98
+ * tiles then sit at the top of the body, never centred in it).
99
+ */
100
+ export interface KpiSlideSpec {
101
+ layout: 'kpis';
102
+ props: {
103
+ header: HeaderSpec;
104
+ kpis: KpiSpec[];
105
+ /** Supporting "so what" blocks under the tiles. Two or three. */
106
+ blocks?: BlockSpec[];
107
+ finding?: FindingSpec;
108
+ source?: string;
109
+ };
110
+ }
111
+ /** Options placed on two named axes — the "where do we play" slide. */
112
+ export interface MatrixSlideSpec {
113
+ layout: 'matrix';
114
+ props: {
115
+ header: HeaderSpec;
116
+ matrix: MatrixSpec;
117
+ finding?: FindingSpec;
118
+ source?: string;
119
+ };
120
+ }
121
+ /** One root metric decomposed into the levers that move it. Three branches
122
+ * is the natural width; two and four also fit. */
123
+ export interface DriverTreeSlideSpec {
124
+ layout: 'driver-tree';
125
+ props: {
126
+ header: HeaderSpec;
127
+ /** The metric being decomposed, shown in the accent root box. */
128
+ root: string;
129
+ branches: DriverSpec[];
130
+ finding?: FindingSpec;
131
+ source?: string;
132
+ };
133
+ }
134
+ /** A single quote given the whole slide — the one voice worth pausing on. */
135
+ export interface QuoteSlideSpec {
136
+ layout: 'quote';
137
+ props: {
138
+ /** Optional: a bare quote with no action title is a legitimate beat. */
139
+ header?: HeaderSpec;
140
+ quote: QuoteSpec;
141
+ source?: string;
142
+ };
143
+ }
144
+ /** Verbatim voice-of-customer evidence in two themed columns. */
145
+ export interface QuotesSlideSpec {
146
+ layout: 'quotes';
147
+ props: {
148
+ header: HeaderSpec;
149
+ columns: QuoteColumnSpec[];
150
+ finding?: FindingSpec;
151
+ source?: string;
152
+ };
153
+ }
154
+ /** Section divider: the numbered chapters the deck is about to walk through.
155
+ * Two to four chapters; the columns get cramped beyond that. */
156
+ export interface ChaptersSlideSpec {
157
+ layout: 'chapters';
158
+ props: {
159
+ header: HeaderSpec;
160
+ chapters: ChapterSpec[];
161
+ };
162
+ }
163
+ /** A grid of cards. Two to six cards; `columns` overrides the automatic
164
+ * choice (3 for four or more cards, otherwise one column per card). */
165
+ export interface CardsSlideSpec {
166
+ layout: 'cards';
167
+ props: {
168
+ header: HeaderSpec;
169
+ cards: CardSpec[];
170
+ columns?: 2 | 3;
171
+ /** Optional 3-up takeaway strip under the grid. */
172
+ insights?: InsightSpec[];
173
+ finding?: FindingSpec;
174
+ source?: string;
175
+ };
176
+ }
177
+ /** A slide whose whole body is one extension-supplied component (a bespoke
178
+ * pricing sheet, framework diagram, …). Only useful when the extension that
179
+ * ships the named kind is installed; without it the body shows an install
180
+ * placeholder and the deck still renders. */
181
+ export interface ComponentSlideSpec {
182
+ layout: 'component';
183
+ props: {
184
+ header: HeaderSpec;
185
+ component: SlideComponentSpec;
186
+ source?: string;
187
+ };
188
+ }
189
+ /** A slide body the model authors directly, as a web page fragment.
190
+ *
191
+ * The body rectangle only: header, footer, slide number and canvas belong to
192
+ * the theme, and the palette reaches the markup as CSS custom properties
193
+ * already resolved for the active set — so a web body still follows a theme
194
+ * swap. Raw colours and font faces are rejected for exactly that reason;
195
+ * everything else CSS can do is open, which is the point of the layout.
196
+ *
197
+ * Web Mode only — a hand-authored layout cannot be measured into PowerPoint
198
+ * shapes, so this is the one layout with no PPTX story at all.
199
+ *
200
+ * Reach for it when the vocabulary genuinely cannot draw the slide: a hero,
201
+ * an opener, a diagram. A deck of hand-written bodies has no theme and no
202
+ * consistency, which is what the layouts are for. */
203
+ export interface WebSlideSpec {
204
+ layout: 'web';
205
+ props: {
206
+ header: HeaderSpec;
207
+ /** The body's markup. A fragment, not a document: no <html>, <head>,
208
+ * <script>, <style> or <iframe>, and no network URLs. Tag the meaningful
209
+ * elements `data-pptx` ("text", "shape", "image", …) so the picker lands
210
+ * on them. */
211
+ html: string;
212
+ /** Author styles, injected as one <style> block scoped to this slide's
213
+ * body — selectors cannot reach another slide or the chrome. Theme
214
+ * tokens are already declared: use them (var(--color-accent),
215
+ * var(--font-family)); raw colours and font families are rejected. */
216
+ css?: string;
217
+ /** The body's editable copy, keyed by name. An element written as
218
+ * `<h2 data-edit="text.headline"></h2>` shows `text.headline` and stays
219
+ * double-click editable in the editor — which also means a wording
220
+ * change is a one-key write, not a rewrite of the markup. */
221
+ text?: Record<string, string>;
222
+ /** Own the whole 1280×720 canvas instead of the body band — for a
223
+ * full-bleed section opener. The theme's footer still paints over it, so
224
+ * keep the bottom strip clear. */
225
+ bleed?: boolean;
226
+ /** Draw the theme's footer. Default true, and only meaningful under
227
+ * `bleed` — a banded body always reserves the footer's strip, so there
228
+ * is nothing to decide there.
229
+ *
230
+ * It exists for the converted cover. A `title` slide is the one layout
231
+ * that signs nothing: no page number, no deck note. Converting one has
232
+ * to be able to say that, or every cover comes back from the conversion
233
+ * wearing a footer it never had. */
234
+ footer?: boolean;
235
+ finding?: FindingSpec;
236
+ source?: string;
237
+ };
238
+ }
239
+ /** One chart. */
240
+ export interface ChartSlot {
241
+ kind: 'chart';
242
+ chart: ChartSpec;
243
+ }
244
+ /** One table. */
245
+ export interface TableSlot {
246
+ kind: 'table';
247
+ table: TableSpec;
248
+ }
249
+ /** One content block — the card/callout box. */
250
+ export interface BlockSlot {
251
+ kind: 'block';
252
+ block: BlockSpec;
253
+ }
254
+ /** A stack of KPI tiles. In a split slot two or three fit and four get
255
+ * cramped; in a freeform item they fill the item's width, so let the area
256
+ * say how many. */
257
+ export interface KpisSlot {
258
+ kind: 'kpis';
259
+ kpis: KpiSpec[];
260
+ }
261
+ /** A horizontal timeline — the events alternate above and below the axis,
262
+ * so give it a wide, short area. Freeform only: a timeline in half a slide
263
+ * reads as a list. */
264
+ export interface TimelineSlot {
265
+ kind: 'timeline';
266
+ events: TimelineEventSpec[];
267
+ }
268
+ /** A 2×2 positioning matrix. Wants a near-square area — it has two axes to
269
+ * label and a plot that only reads square. Freeform only. */
270
+ export interface MatrixSlot {
271
+ kind: 'matrix';
272
+ matrix: MatrixSpec;
273
+ }
274
+ /** One or more quote columns under their accent bars. Freeform only; reach
275
+ * for it when verbatim evidence sits beside something else on the slide. */
276
+ export interface QuotesSlot {
277
+ kind: 'quotes';
278
+ columns: QuoteColumnSpec[];
279
+ }
280
+ /** The takeaway strip: equal columns under a hairline rule. A full-width
281
+ * band, so give it the whole body width and little height. Freeform only. */
282
+ export interface InsightsSlot {
283
+ kind: 'insights';
284
+ insights: InsightSpec[];
285
+ }
286
+ /** A driver tree: the root metric on the left, its branches on the right.
287
+ * Freeform only — the root column is a fixed width, so a narrow area leaves
288
+ * the branches nothing. */
289
+ export interface DriverTreeSlot {
290
+ kind: 'driver-tree';
291
+ /** The metric being decomposed. */
292
+ root: string;
293
+ branches: DriverSpec[];
294
+ }
295
+ /** A component an installed extension provides, by namespaced kind — the
296
+ * same reference the `component` layout takes, so a pack component can be
297
+ * one item among many instead of owning the whole body. Without the
298
+ * extension the item shows the install placeholder and the .pptx export is
299
+ * refused rather than written blank. Freeform only. */
300
+ export interface ExtensionSlot {
301
+ kind: 'extension';
302
+ component: SlideComponentSpec;
303
+ }
304
+ /** One side of a split slide — exactly one kind of content. The four that
305
+ * survive half a slide; the rest of the vocabulary is in `SlotSpec`. */
306
+ export type SplitSlotSpec = ChartSlot | TableSlot | BlockSlot | KpisSlot;
307
+ /** Everything a slot can be. A freeform `component` item takes one of
308
+ * these; `split` takes the narrower `SplitSlotSpec`. */
309
+ export type SlotSpec = SplitSlotSpec | TimelineSlot | MatrixSlot | QuotesSlot | InsightsSlot | DriverTreeSlot | ExtensionSlot;
310
+ /** Two independent content slots side by side — a chart next to the table
311
+ * behind it, KPIs beside the trend they summarize, a block arguing what the
312
+ * evidence next to it shows. The composition slide for pairings the fixed
313
+ * layouts don't cover; prefer a fixed layout when one fits. */
314
+ export interface SplitSlideSpec {
315
+ layout: 'split';
316
+ props: {
317
+ header: HeaderSpec;
318
+ left: SplitSlotSpec;
319
+ right: SplitSlotSpec;
320
+ /** Width ratio left:right. Default '1:1'; give a chart or table the
321
+ * wider side. */
322
+ ratio?: '1:1' | '1:2' | '2:1';
323
+ finding?: FindingSpec;
324
+ source?: string;
325
+ };
326
+ }
327
+ /** Where a freeform item sits: fractional placement inside the slide's
328
+ * body region (between the header and footer bands), each value a percent
329
+ * of the body's width or height. Items may overlap; later items draw on
330
+ * top. Spacing between items is the author's choice — nothing is inserted
331
+ * automatically, so leave gutters where the design wants them. */
332
+ export interface FreeformAreaSpec {
333
+ /** Left edge, % of body width; x + w must stay ≤ 100.
334
+ * @minimum 0
335
+ * @maximum 100 */
336
+ x: number;
337
+ /** Top edge, % of body height; y + h must stay ≤ 100.
338
+ * @minimum 0
339
+ * @maximum 100 */
340
+ y: number;
341
+ /** Width, % of body width.
342
+ * @exclusiveMinimum 0
343
+ * @maximum 100 */
344
+ w: number;
345
+ /** Height, % of body height.
346
+ * @exclusiveMinimum 0
347
+ * @maximum 100 */
348
+ h: number;
349
+ }
350
+ /** Type-scale steps a freeform item's text may name — theme steps, never px.
351
+ * One list for all three kinds of label: a `text` run, a `box`'s centred
352
+ * label and a `shape`'s. A second copy of the union is how a callout ends up
353
+ * offering a size the renderer has never heard of.
354
+ *
355
+ * `xs` is the caption step the theme has always defined and this list used
356
+ * to stop short of. It was added because the flow→freeform conversion needs
357
+ * it: a source line is set at `--text-xs`, and with the smallest name
358
+ * available being `sm` the conversion had to round it UP — so converting a
359
+ * slide grew its source line by 2px, which is exactly the kind of silent
360
+ * shift that makes a converted slide read as subtly wrong. Reach for it for
361
+ * captions and footnotes, not for body copy. */
362
+ export type FreeformTextSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
363
+ /**
364
+ * Every colour the theme defines, as one palette.
365
+ *
366
+ * Text and fills used to draw from two lists of different widths — a caption
367
+ * could be `positive` green while a box could not be filled with it, for no
368
+ * reason anyone chose. One palette, and what a token is *for* is the
369
+ * author's business: the inspector paints each swatch in the colour this set
370
+ * resolves it to, so the choice is made by looking rather than by guessing
371
+ * what a name means.
372
+ *
373
+ * Readability is deliberately not enforced. A `faint` label on a `faint` box
374
+ * is unreadable and is also nobody's business but the author's — the swatch
375
+ * showed them both. What the platform still guarantees is that these are
376
+ * TOKENS: a deck survives a set swap, and the exporter resolves every one to
377
+ * a real PowerPoint fill.
378
+ */
379
+ export type FreeformColor = 'text' | 'muted' | 'faint' | 'inverse' | 'accent' | 'accent-deep' | 'accent-soft' | 'positive' | 'negative' | 'bg' | 'box' | 'box-border' | 'edge-muted' | 'highlight' | 'highlight-soft' | 'highlight-deep';
380
+ /** Text colors a freeform item may name — the whole palette. */
381
+ export type FreeformTextColor = FreeformColor;
382
+ /** Fills a freeform box may name — theme tokens, never raw colors. */
383
+ /** Fills a freeform box may name: the palette, plus `none` for no fill at
384
+ * all — which is a box's own state rather than a colour, and the one thing
385
+ * text has no use for. */
386
+ export type FreeformBoxFill = FreeformColor | 'none';
387
+ /** Gradient fills — box only (the SVG preset shapes stay solid). Each is a
388
+ * theme-defined two-stop gradient: `accent-gradient` the saturated hero/tile
389
+ * wash (labels render inverse), `soft-gradient` a barely-there panel wash. */
390
+ export type FreeformGradientFill = 'accent-gradient' | 'soft-gradient';
391
+ /** Corner rounding steps a freeform box may name — theme radii, never px.
392
+ * `pill` fully rounds the shorter edge (stadium/capsule). */
393
+ export type FreeformRadius = 'none' | 'sm' | 'md' | 'lg' | 'pill';
394
+ /** Drop-shadow steps a freeform box may name — theme shadows, never raw
395
+ * values. `soft` is a subtle lift, `lifted` a pronounced card float. */
396
+ export type FreeformShadow = 'none' | 'soft' | 'lifted';
397
+ /** Fields every freeform item carries.
398
+ *
399
+ * `id` is the item's identity across slides: items sharing an id on
400
+ * *adjacent* freeform slides morph into each other when presenting
401
+ * (position, size — reveal.js auto-animate) and the exported PPTX carries
402
+ * a PowerPoint Morph transition matching them the same way. Duplicate a
403
+ * slide, move the items, and the motion designs itself. Omit it and the
404
+ * item simply cuts like everything else. */
405
+ interface FreeformItemBase {
406
+ /** Stable identity for cross-slide morphing. Unique within a slide;
407
+ * reuse the same id on the neighbouring slide to animate between them. */
408
+ id?: string;
409
+ area: FreeformAreaSpec;
410
+ }
411
+ /** Everything an item that the platform *draws* can carry on top of its
412
+ * placement. `chart` and `table` deliberately do not extend this: both
413
+ * export as a PPTX graphicFrame, which carries neither a rotation nor an
414
+ * alpha, so offering either there would be a control that works on screen
415
+ * and is silently dropped in the .pptx. A field that only sometimes
416
+ * survives is worse than one that is not offered. */
417
+ interface FreeformDrawnBase extends FreeformItemBase {
418
+ /** 0–1. Fades the whole item — everything it draws, together: a box's
419
+ * fill, border and label, a picture, a caption, a component's subtree.
420
+ * For washes and de-emphasis. Default 1.
421
+ * @minimum 0
422
+ * @maximum 1 */
423
+ opacity?: number;
424
+ /** Rotation in degrees, clockwise about the item's centre. Small tilts
425
+ * (±4–8°) read as a deliberate accent — a snapshot pinned to the slide, a
426
+ * stamp over a panel; keep anything meant to be read level.
427
+ * @minimum -180
428
+ * @maximum 180 */
429
+ rotate?: number;
430
+ }
431
+ /** A run of text in one grid area. Font, exact sizes and colors come from
432
+ * the theme; the size/weight/color fields pick among its tokens. */
433
+ export interface FreeformTextSpec extends FreeformDrawnBase {
434
+ type: 'text';
435
+ /** Separate strings are separate paragraphs. */
436
+ text: string | string[];
437
+ /** Type-scale step. Default 'md'. */
438
+ size?: FreeformTextSize;
439
+ weight?: 'regular' | 'medium' | 'bold';
440
+ color?: FreeformTextColor;
441
+ align?: 'left' | 'center' | 'right';
442
+ /** Vertical placement inside the area. Default 'top'. */
443
+ valign?: 'top' | 'middle' | 'bottom';
444
+ /**
445
+ * Line spacing, as a ratio of the font size. Default absent, which is the
446
+ * theme's body leading (`--line-height`) — so leaving it off is what every
447
+ * existing slide already does and nothing moves.
448
+ *
449
+ * It exists because display type wants tighter leading than body copy, and
450
+ * a freeform text item had no way to ask: a 44px numeral set at the body's
451
+ * 1.4 sits 9px lower in its line box than the same numeral in a fixed
452
+ * layout, which is what made a converted slide's headings visibly jump
453
+ * against an unconverted one. `tight` for big numerals and display lines,
454
+ * `snug` for headings, `relaxed` for large body copy.
455
+ */
456
+ leading?: 'tight' | 'snug' | 'relaxed' | 'normal';
457
+ }
458
+ /** A rectangle in one grid area: background panel, accent bar (span one
459
+ * row/column for a line), or a labeled shape. With `radius` and `shadow` it
460
+ * becomes a card — the rounded, lifted tile a styled slide is built from. */
461
+ export interface FreeformBoxSpec extends FreeformDrawnBase {
462
+ type: 'box';
463
+ /** Optional label, centered in the box. */
464
+ text?: string;
465
+ /** Type-scale step for that label — a number in a big callout tile and a
466
+ * word on a small badge are not the same size. Ignored when there is no
467
+ * `text`. Default 'md'. */
468
+ size?: FreeformTextSize;
469
+ /** Default 'box' (the neutral panel fill). 'none' with a border is an
470
+ * outline-only frame; the gradient fills are theme-defined washes. */
471
+ fill?: FreeformBoxFill | FreeformGradientFill;
472
+ /** The label's colour. Default 'text'. Worth setting whenever `fill` is
473
+ * dark: the label does not pick a readable ink for you, because "readable"
474
+ * depends on a theme's actual colours and guessing it would quietly
475
+ * override an author who meant it. */
476
+ color?: FreeformColor;
477
+ /** Outline: `true` for the theme's panel border, or a token to pick its
478
+ * colour. Default false. */
479
+ border?: boolean | FreeformColor;
480
+ /** How thick that outline is. Default 'hairline' — 1px, which is what
481
+ * every bordered box drew before this existed, so leaving it off changes
482
+ * nothing.
483
+ *
484
+ * The ladder is the LINE's (`hairline` 1, `regular` 2, `heavy` 4), so the
485
+ * two cannot disagree about what "heavy" means. Note the different
486
+ * defaults: a line defaults to `regular` because a 1px connector is
487
+ * usually too faint to follow, while a border defaults to `hairline`
488
+ * because an outline is a boundary, not a stroke. */
489
+ borderWidth?: 'hairline' | 'regular' | 'heavy';
490
+ /** Corner rounding. Default 'sm' (the theme's base radius). */
491
+ radius?: FreeformRadius;
492
+ /** Drop shadow. Default 'none'. Use on cards over a plain background —
493
+ * never on thin bars or full-bleed panels. */
494
+ shadow?: FreeformShadow;
495
+ }
496
+ /** Shapes beyond the rectangle. Each is a PowerPoint preset, so an exported
497
+ * deck carries a real editable shape rather than a picture of one:
498
+ *
499
+ * - `ellipse` — the oval, and the only circle there is: give it a square
500
+ * area and it *is* a circle (there is no separate `circle` kind)
501
+ * - `ring` — a donut; the hole is a quarter of the width, PowerPoint's default
502
+ * - `triangle` — apex at the top centre
503
+ * - `diamond`
504
+ * - `chevron` — the arrow block a process row is built from
505
+ * - `callout` — a rounded speech bubble with a tail; see `tail` for where
506
+ * the tail points
507
+ * - `arrow` — a right-pointing block arrow; rotate it for the other three
508
+ * - `star` — five-pointed
509
+ * - `hexagon` — flat top and bottom, points left and right
510
+ * - `pentagon` — regular, apex at the top centre
511
+ * - `cylinder` — a can seen from slightly above; the database/volume glyph
512
+ * - `parallelogram` — the leaning rectangle (a flow-chart input step)
513
+ *
514
+ * All stretch to fill their area, exactly as their PowerPoint counterparts
515
+ * do, so a circle needs a square area.
516
+ */
517
+ export type FreeformShapeKind = 'ellipse' | 'ring' | 'triangle' | 'diamond' | 'chevron' | 'callout' | 'arrow' | 'star' | 'hexagon' | 'pentagon' | 'cylinder' | 'parallelogram';
518
+ /** The tip of a `callout`'s tail, in percent of the item's own area. Values
519
+ * outside 0–100 are the point, not a mistake: the bubble fills the area and
520
+ * the tail reaches *out* of it towards whatever is being spoken about — a
521
+ * tip inside the box leaves only a stub. */
522
+ export interface FreeformTailSpec {
523
+ /** % of the item's width; < 0 is left of the bubble, > 100 right of it.
524
+ * @minimum -200
525
+ * @maximum 300 */
526
+ x: number;
527
+ /** % of the item's height; < 0 is above the bubble, > 100 below it.
528
+ * @minimum -200
529
+ * @maximum 300 */
530
+ y: number;
531
+ }
532
+ /** A non-rectangular shape in one area. Fills, borders and the optional
533
+ * centered label work exactly as they do on `box`. */
534
+ export interface FreeformShapeSpec extends FreeformDrawnBase {
535
+ type: 'shape';
536
+ shape: FreeformShapeKind;
537
+ /** Where a `callout`'s tail points — ignored by every other shape.
538
+ * Default `{ x: 25, y: 130 }`, a tail hanging below the bubble's left. */
539
+ tail?: FreeformTailSpec;
540
+ /** Optional label, centered in the shape. Keep it short — a triangle or a
541
+ * ring has far less usable middle than its area suggests. */
542
+ text?: string;
543
+ /** Type-scale step for that label, exactly as on `box`. Ignored when there
544
+ * is no `text`. Default 'md'. */
545
+ size?: FreeformTextSize;
546
+ /** Default 'box' (the neutral panel fill). 'none' with a border is an
547
+ * outline-only shape. */
548
+ fill?: FreeformBoxFill;
549
+ /** The label's colour. Default 'text'; see `FreeformBoxSpec.color`. */
550
+ color?: FreeformColor;
551
+ /** Outline: `true` for the theme's panel border, or a token to pick its
552
+ * colour. Default false. */
553
+ border?: boolean | FreeformColor;
554
+ /** How thick that outline is. Default 'hairline' — 1px, which is what
555
+ * every bordered box drew before this existed, so leaving it off changes
556
+ * nothing.
557
+ *
558
+ * The ladder is the LINE's (`hairline` 1, `regular` 2, `heavy` 4), so the
559
+ * two cannot disagree about what "heavy" means. Note the different
560
+ * defaults: a line defaults to `regular` because a 1px connector is
561
+ * usually too faint to follow, while a border defaults to `hairline`
562
+ * because an outline is a boundary, not a stroke. */
563
+ borderWidth?: 'hairline' | 'regular' | 'heavy';
564
+ }
565
+ /** A straight connector between two points in the body — annotation arrows,
566
+ * flow links, leader lines. Unlike the other items it is placed by its two
567
+ * endpoints, not an area; the exported deck carries a real PowerPoint
568
+ * connector with the same arrowheads.
569
+ *
570
+ * **It deliberately has no `rotate`** — and so deliberately extends neither
571
+ * base: `FreeformItemBase` is `id` + `area`, which a line has no use for,
572
+ * and `FreeformDrawnBase` adds the `rotate` it must not have. A line's
573
+ * rotation IS its endpoints: turning it is moving `to`, and a separate angle
574
+ * would be a second way to say the one thing, with no answer for which wins.
575
+ * Tilt a line by dragging an end, not by typing a number. (`opacity` is
576
+ * absent for the plainer reason that nothing has asked for a faded
577
+ * connector; add it here if that changes.) */
578
+ export interface FreeformLineSpec {
579
+ /** Stable identity for cross-slide morphing (see box/shape `id`). */
580
+ id?: string;
581
+ type: 'line';
582
+ /** Start point, % of body width/height.
583
+ * @minimum 0
584
+ * @maximum 100 */
585
+ from: {
586
+ x: number;
587
+ y: number;
588
+ };
589
+ /** End point, % of body width/height. Arrowheads point here (`arrow:
590
+ * 'end'`). */
591
+ to: {
592
+ x: number;
593
+ y: number;
594
+ };
595
+ /** Default 'text'. */
596
+ color?: FreeformTextColor;
597
+ /** Stroke weight step: hairline 1px, regular 2px, heavy 4px.
598
+ * Default 'regular'. */
599
+ weight?: 'hairline' | 'regular' | 'heavy';
600
+ /** Default 'solid'. */
601
+ style?: 'solid' | 'dashed';
602
+ /** Arrowheads: none, at `to`, or both ends. Default 'none'. */
603
+ arrow?: 'none' | 'end' | 'both';
604
+ }
605
+ /** An image filling one grid area. */
606
+ export interface FreeformImageSpec extends FreeformDrawnBase {
607
+ type: 'image';
608
+ url: string;
609
+ /** cover crops to fill, contain shows the whole image. Default 'cover'. */
610
+ fit?: 'cover' | 'contain';
611
+ }
612
+ /** A chart in one grid area. */
613
+ export interface FreeformChartSpec extends FreeformItemBase {
614
+ type: 'chart';
615
+ chart: ChartSpec;
616
+ }
617
+ /** A table in one grid area. */
618
+ export interface FreeformTableSpec extends FreeformItemBase {
619
+ type: 'table';
620
+ table: TableSpec;
621
+ }
622
+ /** A platform component or an installed pack component, placed as one item
623
+ * — the KPI stack, the timeline, the driver tree, rendered by the same
624
+ * component the dedicated layout uses, with the theme owning every token
625
+ * and CSS owning the internal alignment.
626
+ *
627
+ * This is what to reach for instead of assembling a card out of a box and
628
+ * three text runs: the item carries the component's own spec, so it still
629
+ * follows a theme swap, still answers inline editing, and still exports as
630
+ * real shapes rather than as a drawing of itself.
631
+ *
632
+ * **`area.h` means something different here.** For every other item the
633
+ * four area values are exact. For a component the height is a MINIMUM: the
634
+ * item renders at its natural height and never below `h`, because its
635
+ * content can change under it and a reworded block in a fixed-height box
636
+ * either clips or leaves a hole. Growth runs downward out of the area; it
637
+ * does not push the items below it, because nothing on a freeform slide
638
+ * reflows. `x`, `y` and `w` are exact as everywhere else. Give `h` the
639
+ * height you want the item to occupy when it is empty — a matrix or a
640
+ * driver tree has no natural height of its own and will take exactly `h`. */
641
+ export interface FreeformComponentSpec extends FreeformDrawnBase {
642
+ type: 'component';
643
+ /** Which component, and its spec — see `SlotSpec`. */
644
+ content: SlotSpec;
645
+ }
646
+ export type FreeformItemSpec = FreeformTextSpec | FreeformBoxSpec | FreeformShapeSpec | FreeformLineSpec | FreeformImageSpec | FreeformChartSpec | FreeformTableSpec | FreeformComponentSpec;
647
+ /** Free composition on a 12×8 grid inside the bounded body region — the
648
+ * escape hatch for genuinely bespoke slides. Reach for a fixed layout (or
649
+ * `split`) first: freeform trades their guardrails for placement freedom,
650
+ * and overflowing items are reported, not resolved. Chrome (header, footer,
651
+ * finding, source) still comes from the platform. */
652
+ export interface FreeformSlideSpec {
653
+ layout: 'freeform';
654
+ props: {
655
+ /** Optional: a freeform slide may run header-less, which gives the items
656
+ * the body band the header would have taken. */
657
+ header?: HeaderSpec;
658
+ items: FreeformItemSpec[];
659
+ /** Own the whole 1280x720 canvas instead of the body band, the way the
660
+ * `web` layout's opener does. Every item's `area` is then a percent of
661
+ * the canvas, so items can sit in the safe-area margins and over the
662
+ * chrome bands.
663
+ *
664
+ * This is what a slide converted from a flowed layout runs in: a header
665
+ * and a findings banner sit OUTSIDE the body band, so converting them
666
+ * into items that land where they were needs a coordinate space that
667
+ * reaches them. The header and finding props are dropped by that
668
+ * conversion for the same reason — they are items now, and keeping both
669
+ * would draw them twice. The footer still signs the slide, so keep the
670
+ * bottom strip clear. */
671
+ bleed?: boolean;
672
+ /** Draw the theme's footer. Default true, and only meaningful under
673
+ * `bleed` — a banded body always reserves the footer's strip, so there
674
+ * is nothing to decide there.
675
+ *
676
+ * It exists for the converted cover. A `title` slide is the one layout
677
+ * that signs nothing: no page number, no deck note. Converting one has
678
+ * to be able to say that, or every cover comes back from the conversion
679
+ * wearing a footer it never had. */
680
+ footer?: boolean;
681
+ finding?: FindingSpec;
682
+ source?: string;
683
+ };
684
+ }
685
+ export {};