@ztffn/presentation-generator-plugin 1.3.3 → 1.3.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.
@@ -1,21 +1,18 @@
1
1
  ---
2
2
  name: graph-json-spec
3
3
  description: >
4
- Complete specification for generating valid graph-based presentation JSON
5
- node schema, edge wiring, positioning grid, layout decisions, and slide recipes.
6
- Used by the presentation-design agent.
4
+ Node schema, edge wiring, and positioning grid for generating valid
5
+ graph-based presentation JSON. Structural specification
6
+ see slide-recipes for design decisions and patterns.
7
7
  user-invocable: false
8
8
  ---
9
9
 
10
10
  # Graph JSON Specification
11
11
 
12
- Complete reference for producing valid presentation graph JSON.
13
- Derived from the renderer at `src/types/presentation.ts` and validated demo JSON.
12
+ Structural reference for producing valid presentation graph JSON.
14
13
 
15
14
  ## Quick Reference
16
15
 
17
- The most common errors and their fixes — read this first.
18
-
19
16
  | Rule | Correct | Wrong |
20
17
  |---|---|---|
21
18
  | Slide title field | `data.label` | ~~title~~, ~~headline~~, ~~heading~~ |
@@ -31,14 +28,14 @@ The most common errors and their fixes — read this first.
31
28
  | Node size | `{ width: 180, height: 70 }` in both `style` and `measured` | any other size |
32
29
  | Edge pairs | Every forward edge needs a return edge | one-way edges |
33
30
 
34
- **Banned data field names** — these are silently ignored by the renderer:
31
+ **Banned data field names** — silently ignored by the renderer:
35
32
  `headline`, `subheadline`, `bullets`, `speakerNote`, `speakerNotes`, `visualHint`, `theme`, `title`, `body`, `text`, `background`, `showLogo`, `keyMessage`, `claim`, `hook`, `description`, `summary`
36
33
 
37
34
  ---
38
35
 
39
36
  ## Node Structure
40
37
 
41
- Every node in the graph has this wrapper:
38
+ Every node wrapper:
42
39
 
43
40
  ```json
44
41
  {
@@ -51,12 +48,11 @@ Every node in the graph has this wrapper:
51
48
  }
52
49
  ```
53
50
 
54
- - `id`: Kebab-case slug (e.g. `"cover"`, `"problem-detail"`, `"feat-3d"`)
55
- - `type`: Always `"huma"` — the custom node type with directional handles
56
- - `position`: `{ x, y }` in graph canvas coordinates (see Positioning Grid below)
57
- - `style.width`: Always `180`. `style.height`: Always `70`.
58
- - `measured`: Must mirror `style` — `{ width: 180, height: 70 }`
59
- - Optional: `style.backgroundColor` — hex string for node background in the graph editor (also applied as slide background)
51
+ - `id`: Kebab-case slug (e.g. `"cover"`, `"problem-detail"`)
52
+ - `type`: Always `"huma"`
53
+ - `position`: `{ x, y }` in canvas coordinates (see Positioning Grid)
54
+ - `style` / `measured`: Always `{ width: 180, height: 70 }`
55
+ - Optional: `style.backgroundColor` — hex string for node/slide background
60
56
 
61
57
  ### SlideNodeData Fields
62
58
 
@@ -64,27 +60,27 @@ Every node in the graph has this wrapper:
64
60
 
65
61
  | Field | Type | Default | Description |
66
62
  |---|---|---|---|
67
- | `label` | `string?` | — | Slide title displayed in graph editor and as slide heading |
68
- | `topic` | `string?` | — | Section badge on the slide (e.g. `"01 / Problem"`, `"Solution"`) |
63
+ | `label` | `string?` | — | Slide title |
64
+ | `topic` | `string?` | — | Section badge (e.g. `"01 / Problem"`) |
69
65
  | `content` | `string?` | — | Markdown body. Supports headings, bullets, bold, code, tables, `[chart:name]` embeds, `![](url)` images/videos, `[text](#nodeId)` navigation links |
70
- | `notes` | `string?` | — | Speaker notes, shown only in the presenter panel |
66
+ | `notes` | `string?` | — | Speaker notes (presenter panel only) |
71
67
 
72
68
  #### Slide Type
73
69
 
74
70
  | Field | Type | Default | Description |
75
71
  |---|---|---|---|
76
- | `type` | `"content" \| "r3f" \| "chart" \| "custom"` | `"content"` | Slide renderer. `"content"` for standard text, `"r3f"` for 3D scene, `"chart"` for full-viewport chart |
72
+ | `type` | `"content" \| "r3f" \| "chart" \| "custom"` | `"content"` | Slide renderer |
77
73
 
78
74
  #### Layout & Display
79
75
 
80
76
  | Field | Type | Default | Description |
81
77
  |---|---|---|---|
82
78
  | `centered` | `boolean?` | `true` | Center content vertically and horizontally |
83
- | `layout` | `"single" \| "two-column"` | `"single"` | Content layout. `"two-column"` splits on `---` delimiter in content |
84
- | `lightText` | `boolean?` | `false` | Force white text for dark backgrounds |
85
- | `brandFont` | `boolean?` | `false` | Use HumaDisplay display font for the title |
79
+ | `layout` | `"single" \| "two-column"` | `"single"` | `"two-column"` splits on `---` delimiter |
80
+ | `lightText` | `boolean?` | `false` | White text for dark backgrounds |
81
+ | `brandFont` | `boolean?` | `false` | Display font for the title |
86
82
  | `showBranding` | `boolean?` | `true` | Show branding overlay |
87
- | `brandingText` | `string?` | — | Bottom-left branding label (e.g. `"huma.energy"`) |
83
+ | `brandingText` | `string?` | — | Bottom-left branding label |
88
84
 
89
85
  #### Background Media
90
86
 
@@ -92,7 +88,7 @@ Every node in the graph has this wrapper:
92
88
  |---|---|---|---|
93
89
  | `backgroundImage` | `string?` | — | URL to background image |
94
90
  | `backgroundImageFit` | `"cover" \| "contain"` | `"cover"` | How image fills the slide |
95
- | `backgroundImageOverlay` | `boolean?` | `false` | Dark scrim over background image for text readability |
91
+ | `backgroundImageOverlay` | `boolean?` | `false` | Dark scrim for text readability |
96
92
  | `backgroundVideo` | `string?` | — | URL to background video |
97
93
  | `backgroundVideoFit` | `"cover" \| "contain"` | `"cover"` | How video fills the slide |
98
94
  | `backgroundVideoLoop` | `boolean?` | `true` | Loop background video |
@@ -101,7 +97,7 @@ Every node in the graph has this wrapper:
101
97
 
102
98
  | Field | Type | Default | Description |
103
99
  |---|---|---|---|
104
- | `inlineVideoControls` | `boolean?` | `true` | Show controls on inline videos in `content` |
100
+ | `inlineVideoControls` | `boolean?` | `true` | Show controls on inline videos |
105
101
  | `inlineVideoAutoplay` | `boolean?` | `true` | Autoplay inline videos |
106
102
  | `inlineVideoLoop` | `boolean?` | `true` | Loop inline videos |
107
103
 
@@ -111,56 +107,40 @@ Every node in the graph has this wrapper:
111
107
  |---|---|---|---|
112
108
  | `scene.component` | `string` | — | Registry key (e.g. `"rotating-cube"`, `"particle-field"`) |
113
109
  | `scene.props` | `Record<string, unknown>?` | — | Props passed to the scene component |
114
- | `scene.controls` | `boolean?` | — | Enable OrbitControls for user interaction |
110
+ | `scene.controls` | `boolean?` | — | Enable OrbitControls |
115
111
  | `scene.background` | `string?` | — | Scene background hex color |
116
112
 
117
113
  #### Charts
118
114
 
119
- **Full-viewport chart** (when `type: "chart"`):
120
-
121
- | Field | Type | Default | Description |
122
- |---|---|---|---|
123
- | `chart.chartType` | `"bar" \| "line" \| "area" \| "pie" \| "radar"` | — | Chart renderer |
124
- | `chart.data` | `Array<Record<string, unknown>>` | — | Data array |
125
- | `chart.config.xKey` | `string?` | — | X-axis data key |
126
- | `chart.config.yKeys` | `string[]?` | — | Y-axis data keys |
127
- | `chart.config.colors` | `string[]?` | — | Series colors |
128
- | `chart.config.showGrid` | `boolean?` | — | Show grid lines |
129
- | `chart.config.showLegend` | `boolean?` | — | Show legend |
130
-
131
- **Inline charts** (referenced via `[chart:name]` in content):
132
-
133
- | Field | Type | Default | Description |
134
- |---|---|---|---|
135
- | `charts` | `Record<string, ChartConfig>` | — | Named chart configurations. Key is referenced in content as `[chart:keyname]` |
115
+ | Field | Type | Description |
116
+ |---|---|---|
117
+ | `chart` | `ChartConfig` | Full-viewport chart (when `type: "chart"`) |
118
+ | `charts` | `Record<string, ChartConfig>` | Named inline charts, referenced via `[chart:keyname]` in content |
136
119
 
137
- Each `ChartConfig` has the same shape as `chart` above (`chartType`, `data`, `config`).
120
+ Each ChartConfig: `{ chartType, data, config: { xKey, yKeys, colors, showGrid, showLegend } }`
138
121
 
139
122
  #### Other
140
123
 
141
- | Field | Type | Default | Description |
142
- |---|---|---|---|
143
- | `sceneGroup` | — | — | Scene group reference |
144
- | `focus` | — | — | Focus state |
124
+ | Field | Type | Description |
125
+ |---|---|---|
126
+ | `sceneGroup` | — | Scene group reference |
127
+ | `focus` | — | Focus state |
145
128
 
146
129
  ### Content Markdown Features
147
130
 
148
- The `content` field supports:
149
-
150
- - `## Heading` — headings (ATX style only, no setext)
151
- - `- bullet` or `* bullet` — unordered lists
131
+ - `## Heading` ATX headings
132
+ - `- bullet` / `* bullet` — unordered lists
152
133
  - `1. item` — ordered lists
153
134
  - `` ```language ``` `` — syntax-highlighted code blocks
154
135
  - `> blockquote` — styled blockquotes
155
- - `**bold**` and `*italic*` — inline formatting
136
+ - `**bold**` and `*italic*`
156
137
  - `| col | col |` — GFM tables
157
138
  - `![alt](url)` — images; `.mp4/.webm/.mov` URLs render as inline video
158
- - `[text](url)` — external links (open in new tab)
139
+ - `[text](url)` — external links
159
140
  - `[text](#nodeId)` — navigation links to other slides
160
- - `[chart:name]` — inline chart embed (must be on its own line)
141
+ - `[chart:name]` — inline chart embed (own line)
161
142
  - `---` — column delimiter when `layout: "two-column"`
162
- - Single newline = visible line break (not collapsed)
163
- - Multiple blank lines = visible vertical spacing
143
+ - Single newline = visible line break
164
144
 
165
145
  ---
166
146
 
@@ -170,95 +150,65 @@ Edges define valid navigation paths — without correct edges, arrow keys won't
170
150
 
171
151
  ### Handle IDs
172
152
 
173
- Eight handle IDs exist, four source and four target:
174
-
175
- | Handle ID | Type | Position | Navigation |
176
- |---|---|---|---|
177
- | `s-right` | source | Right side | Pressing **right arrow** follows this edge |
178
- | `s-left` | source | Left side | Pressing **left arrow** follows this edge |
179
- | `s-bottom` | source | Bottom | Pressing **down arrow** follows this edge |
180
- | `s-top` | source | Top | Pressing **up arrow** follows this edge |
181
- | `t-right` | target | Right side | Arrived via **left arrow** from source |
182
- | `t-left` | target | Left side | Arrived via **right arrow** from source |
183
- | `t-bottom` | target | Bottom | Arrived via **up arrow** from source |
184
- | `t-top` | target | Top | Arrived via **down arrow** from source |
153
+ | Handle ID | Type | Navigation |
154
+ |---|---|---|
155
+ | `s-right` | source | Right arrow follows this edge |
156
+ | `s-left` | source | Left arrow follows this edge |
157
+ | `s-bottom` | source | Down arrow follows this edge |
158
+ | `s-top` | source | Up arrow follows this edge |
159
+ | `t-right` | target | Arrived via left arrow |
160
+ | `t-left` | target | Arrived via right arrow |
161
+ | `t-bottom` | target | Arrived via up arrow |
162
+ | `t-top` | target | Arrived via down arrow |
185
163
 
186
164
  ### Bidirectional Pair Rule
187
165
 
188
- **Every navigation edge must have a return edge** with swapped source/target and swapped handles.
189
-
190
- If the user can press right to go from A to B, they must be able to press left to go from B back to A.
166
+ Every forward edge must have a return edge with swapped source/target and swapped handles.
191
167
 
192
168
  ### Standard Edge Pairs
193
169
 
194
- **Horizontal Forward/Back (Spine Navigation):**
195
-
170
+ **Horizontal (spine):**
196
171
  ```json
197
172
  { "id": "e-a-b", "source": "a", "target": "b", "sourceHandle": "s-right", "targetHandle": "t-left" }
198
173
  { "id": "e-b-a", "source": "b", "target": "a", "sourceHandle": "s-left", "targetHandle": "t-right" }
199
174
  ```
200
175
 
201
- **Drill-Down / Return-to-Parent:**
202
-
176
+ **Drill-down / return:**
203
177
  ```json
204
178
  { "id": "e-parent-child", "source": "parent", "target": "child", "sourceHandle": "s-bottom", "targetHandle": "t-top" }
205
179
  { "id": "e-child-parent", "source": "child", "target": "parent", "sourceHandle": "s-top", "targetHandle": "t-bottom" }
206
180
  ```
207
181
 
208
- **Horizontal Within a Drill-Down Branch (Siblings):**
209
-
210
- ```json
211
- { "id": "e-child1-child2", "source": "child1", "target": "child2", "sourceHandle": "s-right", "targetHandle": "t-left" }
212
- { "id": "e-child2-child1", "source": "child2", "target": "child1", "sourceHandle": "s-left", "targetHandle": "t-right" }
213
- ```
182
+ **Siblings within a drill-down branch:** same horizontal pattern as spine.
214
183
 
215
184
  ### Edge ID Convention
216
185
 
217
- Pattern: `e-{source}-{target}` using the node IDs or abbreviated forms.
218
-
219
- Examples: `e-cover-problem`, `e-problem-cover`, `e-problem-detail`, `e-detail-problem`
220
-
221
- ### Edge Object Structure
222
-
223
- ```json
224
- {
225
- "id": "e-cover-problem",
226
- "source": "cover",
227
- "target": "problem",
228
- "sourceHandle": "s-right",
229
- "targetHandle": "t-left"
230
- }
231
- ```
232
-
233
- All four fields (`source`, `target`, `sourceHandle`, `targetHandle`) are required. Always set handles explicitly.
186
+ Pattern: `e-{source}-{target}` using node IDs.
234
187
 
235
188
  ### Validation Checklist
236
189
 
237
- 1. Every node has at least one outgoing edge — no dead ends
238
- 2. Every forward edge has a paired return edge — source/target swapped, handles swapped
239
- 3. All handle IDs are from the valid set only the 8 IDs listed above
240
- 4. Source handles start with `s-` and target handles start with `t-`
241
- 5. The first spine node has no incoming `s-right` edge — it's the leftmost entry point
242
- 6. The last spine node has no outgoing `s-right` edge (or loops back to cover)
243
- 7. Drill-down children always have `s-top`/`t-bottom` return edge to parent
190
+ 1. Every node has at least one outgoing edge
191
+ 2. Every forward edge has a paired return edge
192
+ 3. All handle IDs from the valid set of 8
193
+ 4. Source handles start with `s-`, target handles with `t-`
194
+ 5. First spine node: no incoming `s-right` edge
195
+ 6. Last spine node: no outgoing `s-right` edge (or loops to cover)
196
+ 7. Drill-down children always have `s-top`/`t-bottom` return to parent
244
197
  8. No duplicate edge IDs
245
198
 
246
199
  ---
247
200
 
248
201
  ## Positioning Grid
249
202
 
250
- ### Grid Constants
251
-
252
203
  | Parameter | Value |
253
204
  |---|---|
254
205
  | Horizontal spacing | 240px |
255
206
  | Vertical spacing | 150px |
256
- | Node width | 180px (in `style` and `measured`) |
257
- | Node height | 70px (in `style` and `measured`) |
207
+ | Node size | 180 x 70 (in `style` and `measured`) |
258
208
 
259
209
  ### Spine Row
260
210
 
261
- All spine nodes sit at `y: 0`, starting at `x: 0`, incrementing by 240 per node.
211
+ All spine nodes at `y: 0`, starting `x: 0`, incrementing by 240:
262
212
 
263
213
  ```
264
214
  x: 0 240 480 720 960 1200
@@ -267,23 +217,8 @@ y: 0 [Cover] [Problem] [Solution] [Value] [Features] [CTA]
267
217
 
268
218
  ### Drill-Down Rows
269
219
 
270
- Drill-down children are placed directly below their parent:
271
-
272
- - First level: `y: 150`
273
- - Second level: `y: 300`
274
-
275
- The first child inherits the parent's `x` position. Additional siblings at the same depth increment `x` by 240.
276
-
277
- ```
278
- x: 720 960
279
- y:0 [Value]
280
- |
281
- y:150 [Case A] [Case B]
282
- ```
283
-
284
- - First child: same `x` as parent (720)
285
- - Second child: parent `x` + 240 (960)
286
- - Third child: parent `x` + 480 (1200)
220
+ Children placed below parent: first level `y: 150`, second level `y: 300`.
221
+ First child inherits parent's `x`. Siblings increment `x` by 240.
287
222
 
288
223
  ### Position Quick Reference
289
224
 
@@ -293,131 +228,32 @@ y:150 [Case A] [Case B]
293
228
  | problem | 240 | 0 | Spine 2 |
294
229
  | problem-detail | 240 | 150 | Drill-down under problem |
295
230
  | solution | 480 | 0 | Spine 3 |
296
- | solution-how | 480 | 150 | Drill-down under solution |
297
231
  | value | 720 | 0 | Spine 4 |
298
- | value-case | 720 | 150 | Drill-down under value |
299
232
  | cta | 960 | 0 | Spine 5 |
300
233
 
301
234
  ### Rules
302
235
 
303
- 1. Position reflects visual layout in the editor — it has no effect on navigation
304
- 2. Navigation is determined solely by edges and their handle assignments
305
- 3. Spine nodes must be at `y: 0` for visual clarity
306
- 4. Drill-downs must be below their parent
307
- 5. No two nodes should overlap (maintain at least 240px horizontal, 150px vertical separation)
236
+ 1. Position reflects visual layout in the editor — no effect on navigation
237
+ 2. Navigation is determined solely by edges and handle assignments
238
+ 3. Spine nodes at `y: 0`; drill-downs below parent
239
+ 4. No two nodes should overlap (240px horizontal, 150px vertical minimum)
308
240
 
309
241
  ---
310
242
 
311
- ## Design Decisions
312
-
313
- ### Slide Type Selection
314
-
315
- | Content Signal | `type` | Key Fields |
316
- |---|---|---|
317
- | Standard text, bullets, headings | `"content"` (default) | `content`, `layout` |
318
- | Full-viewport data visualization | `"chart"` | `chart` (ChartConfig) |
319
- | Interactive 3D scene | `"r3f"` | `scene` (R3FSceneConfig) |
320
-
321
- Most slides are `"content"`. Use `"chart"` or `"r3f"` only when the content is primarily a visualization.
322
-
323
- ### Layout Decisions
324
-
325
- **When to use `two-column`** — set `layout: "two-column"` and split content on `---`:
326
-
327
- - Comparison: before/after, old/new, us/them
328
- - Pros/cons: advantages on left, considerations on right
329
- - Text + chart: explanation on left, `[chart:name]` on right
330
- - Text + media: bullets on left, `![](image)` on right
331
- - Dual evidence: two independent supporting points side by side
332
-
333
- Do not force two-column when content is naturally sequential.
243
+ ## Chart Config Structure
334
244
 
335
- **When to center** set `centered: true` for:
336
-
337
- - Cover slides: Title + subtitle, branded
338
- - Call-to-action slides: Single message, end of presentation
339
- - Single-message impact slides: One powerful statement or quote
340
- - Transition slides: Brief pause between major sections
341
-
342
- Set `centered: false` for content-heavy slides, data slides, comparison slides.
343
-
344
- **When to use brand font** — set `brandFont: true` for:
345
-
346
- - Cover slide (first slide)
347
- - Closing/CTA slide (last slide)
348
- - High-impact single-message slides
349
-
350
- Do not use on content-heavy or data slides — brand fonts are display fonts, not body fonts.
351
-
352
- **When to show branding** — set `showBranding: true` and `brandingText` for:
353
-
354
- - Cover slide, closing slide, slides likely to be screenshotted
355
-
356
- Set `showBranding: false` for immersive slides (R3F, full-bleed video) where the overlay is distracting.
357
-
358
- ### Background Treatment
359
-
360
- **Background image** — use when the outline indicates mood, atmosphere, visual evidence, or impact:
361
-
362
- - Set `backgroundImageOverlay: true` and `lightText: true` when text appears over the image
363
- - Image URLs: use Unsplash with query params `?w=1920&q=80`
364
-
365
- **Background video** — use for cinematic section openers or demo context:
366
-
367
- - Set `backgroundVideo` to a placeholder: `"PLACEHOLDER: [description of needed video]"`
368
- - Set `backgroundVideoFit: "cover"` and `backgroundVideoLoop: true`
369
- - Add to delivery summary for manual upload
370
-
371
- **Inline video** — for videos embedded via `![alt](url)` in content:
372
-
373
- - Set URL to placeholder, configure `inlineVideoControls`, `inlineVideoAutoplay`, `inlineVideoLoop`
374
- - Add to delivery summary
375
-
376
- **Text contrast** — set `lightText: true` whenever background is dark:
377
-
378
- - Dark `style.backgroundColor`
379
- - `backgroundImage` with `backgroundImageOverlay: true`
380
- - `backgroundVideo` slides
381
- - `type: "r3f"` with dark scene background
382
-
383
- ### Chart Decisions
384
-
385
- **Full-viewport chart** (`type: "chart"`) — when the data IS the slide:
245
+ Charts always use the `config` wrapper: `{ chartType, data, config: { xKey, yKeys, ... } }`.
386
246
 
247
+ **Full-viewport** (`type: "chart"`) — the chart IS the slide:
387
248
  ```json
388
- {
389
- "type": "chart",
390
- "chart": {
391
- "chartType": "bar",
392
- "data": [...],
393
- "config": { "xKey": "quarter", "yKeys": ["revenue", "cost"], "showGrid": true, "showLegend": true }
394
- },
395
- "content": "Optional caption below the chart"
396
- }
249
+ { "type": "chart", "chart": { "chartType": "bar", "data": [...], "config": { "xKey": "quarter", "yKeys": ["revenue"], "showGrid": true } } }
397
250
  ```
398
251
 
399
- **Inline chart** (`[chart:name]` in content) — when data supports a text argument:
400
-
252
+ **Inline** (`[chart:name]` in content) — data supports a text argument:
401
253
  ```json
402
- {
403
- "content": "## Why this approach wins\n\n[chart:comparison]\n\nClosing statement.",
404
- "charts": {
405
- "comparison": {
406
- "chartType": "radar",
407
- "data": [...],
408
- "config": { "xKey": "axis", "yKeys": ["ours", "theirs"], "showLegend": true }
409
- }
410
- }
411
- }
254
+ { "content": "## Title\n\n[chart:comparison]", "charts": { "comparison": { "chartType": "radar", "data": [...], "config": { "xKey": "axis", "yKeys": ["ours", "theirs"] } } } }
412
255
  ```
413
256
 
414
- **Chart title rule** — `label` states the insight, not the data category:
415
-
416
- | Weak | Strong |
417
- |---|---|
418
- | "Revenue Data" | "Revenue grew 40% QoQ after the pricing change" |
419
- | "Response Time" | "Response time dropped 60% after caching rollout" |
420
-
421
257
  **Chart type selection:**
422
258
 
423
259
  | Data Pattern | Chart Type |
@@ -427,386 +263,3 @@ Set `showBranding: false` for immersive slides (R3F, full-bleed video) where the
427
263
  | Multi-axis comparison | `"radar"` |
428
264
  | Part-of-whole | `"pie"` |
429
265
  | Volume trend | `"area"` |
430
-
431
- ### R3F Scene Decisions
432
-
433
- Available scenes:
434
- - `"rotating-cube"` — interactive cube, good for tech demos. Set `scene.controls: true`.
435
- - `"particle-field"` — atmospheric particle cloud. Set `scene.controls: false`.
436
-
437
- Always set `lightText: true` for R3F slides (dark backgrounds).
438
-
439
- ### Topic Badge Conventions
440
-
441
- Format: `"NN / Section Name"` for numbered sections:
442
- - `"01 / Problem"`, `"02 / Solution"`, `"03 / Value"`, `"04 / Evidence"`
443
-
444
- Or plain text: `"Huma Showcase"`, `"Technical Deep Dive"`.
445
-
446
- ---
447
-
448
- ## Visual Intent Mapping
449
-
450
- The narrative agent annotates each slide with a `**Visual intent:**` label. Map them to JSON treatments:
451
-
452
- | Visual Intent | type | centered | layout | brandFont | Background | lightText | Notes |
453
- |---|---|---|---|---|---|---|---|
454
- | `bookend` | content | true | single | true | optional image/color | if dark bg | Cover and CTA slides. showBranding: true |
455
- | `chapter-opener` | content | false or true | single | false | backgroundImage + overlay | true | Full-bleed image, sets section mood. Max 3 bullet points. |
456
- | `impact` | content | true | single | false | optional dark color | if dark bg | Single statement, no bullets. Let whitespace do the work. |
457
- | `workhorse` | content | false | single or two-column | false | none | false | Standard bullets/content. The default treatment. |
458
- | `evidence` | content or chart | false | single or two-column | false | none | false | Data-forward: chart, table, or comparison. |
459
- | `breathing-room` | content | true | single | false | backgroundImage + overlay OR style.backgroundColor | true | Minimal text. Visual pause. Resets audience attention. |
460
-
461
- When the outline lacks visual intent annotations, infer them:
462
- - First slide → bookend
463
- - Last slide → bookend
464
- - Slide after 2+ consecutive bullet-heavy slides → breathing-room or impact
465
- - Slide introducing a new topic section → chapter-opener
466
- - Slide with chart data or comparison → evidence
467
- - Everything else → workhorse
468
-
469
- ---
470
-
471
- ## Slide Recipes
472
-
473
- Complete `data` field patterns for common slide types.
474
-
475
- ### Cover / Call to Action
476
-
477
- ```json
478
- {
479
- "label": "[Title or Ask]",
480
- "topic": "[Company or Meeting Context]",
481
- "content": "One sentence: what this is and for whom. (Cover) — or — what happens next and when. (CTA)",
482
- "centered": true,
483
- "brandFont": true,
484
- "showBranding": true,
485
- "brandingText": "[company.domain]"
486
- }
487
- ```
488
-
489
- ### Standard Bullets
490
-
491
- ```json
492
- {
493
- "label": "Headline claim that makes the point",
494
- "topic": "01 / Problem",
495
- "content": "## Headline claim in one sentence\n\n- Specific point with a number or named entity\n- Second point — consequence or contrast\n- Third point — the implication\n\nClosing sentence bridging to the next slide.",
496
- "notes": "Talking point not on screen. Objection signal: 'If they ask X, navigate to [node-id].' Time: 2 minutes max.",
497
- "centered": false
498
- }
499
- ```
500
-
501
- ### Impact Statement
502
-
503
- ```json
504
- {
505
- "label": "The Shift",
506
- "topic": "01 / Problem",
507
- "content": "The market has permanently changed.\n\n**Batch scheduling is structurally incompatible with same-day delivery expectations.**",
508
- "notes": "Pause. Do not advance immediately.",
509
- "centered": true
510
- }
511
- ```
512
-
513
- ### Two-Column Comparison
514
-
515
- ```json
516
- {
517
- "label": "Old Way vs. New Way",
518
- "topic": "02 / Solution",
519
- "content": "## Today\n- 3 systems, no shared state\n- 11 hours/week reconciling exports\n\n---\n\n## With [Product]\n- Single live record across all systems\n- Reports update in real time",
520
- "layout": "two-column",
521
- "centered": false
522
- }
523
- ```
524
-
525
- ### Text + Inline Chart
526
-
527
- ```json
528
- {
529
- "label": "The Data Confirms It",
530
- "topic": "03 / Evidence",
531
- "content": "## Continuous monitoring outperforms batch on every metric\n\nThree field studies. Same result each time.\n\n---\n\n[chart:comparison]",
532
- "charts": {
533
- "comparison": {
534
- "chartType": "bar",
535
- "data": [{ "metric": "Uptime %", "ours": 98.2, "baseline": 91.5 }],
536
- "config": { "xKey": "metric", "yKeys": ["ours", "baseline"], "showGrid": true, "showLegend": true }
537
- }
538
- },
539
- "layout": "two-column",
540
- "centered": false
541
- }
542
- ```
543
-
544
- ### Proof Point / Customer Quote
545
-
546
- ```json
547
- {
548
- "label": "Summit Health: Year One",
549
- "topic": "04 / Proof",
550
- "content": "## 2.3 hours of admin time eliminated per nurse per shift\n\n> \"The conflicts surfaced automatically.\"\n> — VP Operations, Summit Health (42 facilities)\n\n- 38% reduction in shift coverage failures\n- Full rollout across 6 units in 11 weeks",
551
- "notes": "If they ask about rollout timeline, the drill-down below has the full breakdown.",
552
- "centered": false
553
- }
554
- ```
555
-
556
- ### Section Opener with Background Image
557
-
558
- ```json
559
- {
560
- "label": "The Opportunity",
561
- "topic": "03 / Opportunity",
562
- "content": "## A $40B market with no dominant platform\n\nEvery competitor is solving scheduling.\nNobody is solving the coordination layer.",
563
- "backgroundImage": "https://images.unsplash.com/photo-XXXXX?w=1920&q=80",
564
- "backgroundImageFit": "cover",
565
- "backgroundImageOverlay": true,
566
- "lightText": true,
567
- "centered": false
568
- }
569
- ```
570
-
571
- ### Styled Background Color Slide
572
-
573
- ```json
574
- {
575
- "label": "Styling & Branding",
576
- "topic": "04 / Features",
577
- "content": "## Per-slide styling\n\n- Custom background colors\n- Light text mode for dark backgrounds\n- Brand font for titles",
578
- "lightText": true,
579
- "brandFont": true,
580
- "centered": true,
581
- "showBranding": true,
582
- "brandingText": "huma.energy"
583
- }
584
- ```
585
-
586
- With `style.backgroundColor` set at the node level:
587
-
588
- ```json
589
- "style": { "width": 180, "height": 70, "backgroundColor": "#1a1a2e" }
590
- ```
591
-
592
- ---
593
-
594
- ## Complete Reference Example
595
-
596
- A 7-node presentation demonstrating key slide types. This is the minimum viable structure the design agent should know how to produce.
597
-
598
- ```json
599
- {
600
- "meta": {
601
- "name": "Graph-Based Presentations"
602
- },
603
- "nodes": [
604
- {
605
- "id": "cover",
606
- "type": "huma",
607
- "position": { "x": 0, "y": 0 },
608
- "data": {
609
- "label": "Graph-Based Presentations",
610
- "topic": "Huma Showcase",
611
- "content": "Non-linear storytelling for technical teams.\n\nNavigate with arrow keys. Press **down** to drill into any topic.",
612
- "centered": true,
613
- "brandFont": true,
614
- "showBranding": true,
615
- "brandingText": "huma.energy"
616
- },
617
- "style": { "width": 180, "height": 70 },
618
- "measured": { "width": 180, "height": 70 }
619
- },
620
- {
621
- "id": "problem",
622
- "type": "huma",
623
- "position": { "x": 240, "y": 0 },
624
- "data": {
625
- "label": "The Problem",
626
- "topic": "01 / Problem",
627
- "content": "## Linear tools break complex stories\n\n- Cannot branch based on audience questions\n- Skipping sections breaks the flow\n- Presenters get lost navigating dense topics\n- No private view for speaker notes during screen share\n\nEvery meeting is different. The presentation should adapt.",
628
- "notes": "Pause here and ask: 'Does this match what you experience with your current tooling?' Key talking point: the fourth bullet resonates most with technical presenters.",
629
- "centered": false
630
- },
631
- "style": { "width": 180, "height": 70 },
632
- "measured": { "width": 180, "height": 70 }
633
- },
634
- {
635
- "id": "problem-detail",
636
- "type": "huma",
637
- "position": { "x": 240, "y": 150 },
638
- "data": {
639
- "label": "Value Breakdown",
640
- "topic": "03 / Value",
641
- "content": "## Flexible narratives\nAdapt flow to audience type and questions in real time\n\n---\n\n## Consistent style\nReuse front-end web styling and components directly\n\n## Professional delivery\nSpeaker notes and timing without the audience seeing them",
642
- "layout": "two-column",
643
- "centered": false
644
- },
645
- "style": { "width": 180, "height": 70 },
646
- "measured": { "width": 180, "height": 70 }
647
- },
648
- {
649
- "id": "value",
650
- "type": "huma",
651
- "position": { "x": 480, "y": 0 },
652
- "data": {
653
- "label": "The Value",
654
- "topic": "03 / Value",
655
- "content": "## Why this approach wins\n\n[chart:value]\n\nAdapt to the audience. Reuse web components. Deliver with confidence.",
656
- "charts": {
657
- "value": {
658
- "chartType": "radar",
659
- "data": [
660
- { "axis": "Flexibility", "graph": 95, "traditional": 30 },
661
- { "axis": "Visual Quality", "graph": 90, "traditional": 50 },
662
- { "axis": "Interactivity", "graph": 85, "traditional": 20 },
663
- { "axis": "Ease of Use", "graph": 60, "traditional": 90 },
664
- { "axis": "Consistency", "graph": 85, "traditional": 40 },
665
- { "axis": "Tech Signal", "graph": 95, "traditional": 15 }
666
- ],
667
- "config": {
668
- "xKey": "axis",
669
- "yKeys": ["graph", "traditional"],
670
- "showGrid": true,
671
- "showLegend": true
672
- }
673
- }
674
- },
675
- "centered": false
676
- },
677
- "style": { "width": 180, "height": 70 },
678
- "measured": { "width": 180, "height": 70 }
679
- },
680
- {
681
- "id": "feat-3d",
682
- "type": "huma",
683
- "position": { "x": 720, "y": 0 },
684
- "data": {
685
- "label": "3D Scenes",
686
- "topic": "04 / Features",
687
- "type": "r3f",
688
- "scene": {
689
- "component": "rotating-cube",
690
- "controls": true,
691
- "background": "#1a1a2e"
692
- },
693
- "content": "Interactive React Three Fiber scene.\n\nDrag to rotate. Scroll to zoom. Scenes load from a component registry.",
694
- "lightText": true
695
- },
696
- "style": { "width": 180, "height": 70 },
697
- "measured": { "width": 180, "height": 70 }
698
- },
699
- {
700
- "id": "feat-bgimage",
701
- "type": "huma",
702
- "position": { "x": 960, "y": 0 },
703
- "data": {
704
- "label": "Background Image",
705
- "topic": "04 / Features",
706
- "content": "## Full-bleed imagery\n\nUpload any image as a slide background. Toggle overlay for text readability.\n\n- Cover/contain fit modes\n- Dark overlay toggle\n- Light text auto-detection",
707
- "backgroundImage": "https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=1920&q=80",
708
- "backgroundImageFit": "cover",
709
- "backgroundImageOverlay": true,
710
- "lightText": true,
711
- "centered": false
712
- },
713
- "style": { "width": 180, "height": 70 },
714
- "measured": { "width": 180, "height": 70 }
715
- },
716
- {
717
- "id": "end",
718
- "type": "huma",
719
- "position": { "x": 1200, "y": 0 },
720
- "data": {
721
- "label": "Get Started",
722
- "content": "Fork this template to build your own.\n\nEvery slide, chart, and scene is editable in the graph editor.",
723
- "centered": true,
724
- "brandFont": true,
725
- "showBranding": true,
726
- "brandingText": "huma.energy"
727
- },
728
- "style": { "width": 180, "height": 70 },
729
- "measured": { "width": 180, "height": 70 }
730
- }
731
- ],
732
- "edges": [
733
- { "id": "e-cover-problem", "source": "cover", "target": "problem", "sourceHandle": "s-right", "targetHandle": "t-left" },
734
- { "id": "e-problem-cover", "source": "problem", "target": "cover", "sourceHandle": "s-left", "targetHandle": "t-right" },
735
- { "id": "e-problem-value", "source": "problem", "target": "value", "sourceHandle": "s-right", "targetHandle": "t-left" },
736
- { "id": "e-value-problem", "source": "value", "target": "problem", "sourceHandle": "s-left", "targetHandle": "t-right" },
737
- { "id": "e-problem-detail", "source": "problem", "target": "problem-detail", "sourceHandle": "s-bottom", "targetHandle": "t-top" },
738
- { "id": "e-detail-problem", "source": "problem-detail", "target": "problem", "sourceHandle": "s-top", "targetHandle": "t-bottom" },
739
- { "id": "e-value-3d", "source": "value", "target": "feat-3d", "sourceHandle": "s-right", "targetHandle": "t-left" },
740
- { "id": "e-3d-value", "source": "feat-3d", "target": "value", "sourceHandle": "s-left", "targetHandle": "t-right" },
741
- { "id": "e-3d-bgimage", "source": "feat-3d", "target": "feat-bgimage", "sourceHandle": "s-right", "targetHandle": "t-left" },
742
- { "id": "e-bgimage-3d", "source": "feat-bgimage", "target": "feat-3d", "sourceHandle": "s-left", "targetHandle": "t-right" },
743
- { "id": "e-bgimage-end", "source": "feat-bgimage", "target": "end", "sourceHandle": "s-right", "targetHandle": "t-left" },
744
- { "id": "e-end-bgimage", "source": "end", "target": "feat-bgimage", "sourceHandle": "s-left", "targetHandle": "t-right" }
745
- ]
746
- }
747
- ```
748
-
749
- ### Additional Patterns from the Demo
750
-
751
- **Styled background color node** (node-level `style.backgroundColor`):
752
-
753
- ```json
754
- {
755
- "id": "feat-style",
756
- "type": "huma",
757
- "position": { "x": 1920, "y": 150 },
758
- "data": {
759
- "label": "Styling & Branding",
760
- "topic": "04 / Features",
761
- "content": "## Per-slide styling\n\n- Custom background colors\n- Light text mode for dark backgrounds\n- Brand font for titles",
762
- "lightText": true,
763
- "brandFont": true,
764
- "centered": true,
765
- "showBranding": true,
766
- "brandingText": "huma.energy"
767
- },
768
- "style": { "width": 180, "height": 70, "backgroundColor": "#1a1a2e" },
769
- "measured": { "width": 180, "height": 70 }
770
- }
771
- ```
772
-
773
- **Second-level drill-down** (y:300, child of a y:150 node):
774
-
775
- ```json
776
- {
777
- "id": "feat-tables",
778
- "type": "huma",
779
- "position": { "x": 960, "y": 300 },
780
- "data": { "label": "Tables", "topic": "04 / Features", "content": "...", "centered": false },
781
- "style": { "width": 180, "height": 70 },
782
- "measured": { "width": 180, "height": 70 }
783
- }
784
- ```
785
-
786
- Edges: `feat-markdown` (y:150) → `feat-tables` (y:300) via `s-bottom`/`t-top`.
787
-
788
- **Sibling drill-downs connected horizontally** (same y-level, `s-right`/`t-left`):
789
-
790
- ```json
791
- { "id": "e-md-charts", "source": "feat-markdown", "target": "feat-charts", "sourceHandle": "s-right", "targetHandle": "t-left" }
792
- { "id": "e-charts-md", "source": "feat-charts", "target": "feat-markdown", "sourceHandle": "s-left", "targetHandle": "t-right" }
793
- ```
794
-
795
- Both at y:150, connected horizontally within the drill-down branch.
796
-
797
- ---
798
-
799
- ## Media Delivery Summary Format
800
-
801
- After generating the JSON, list any slides with placeholder media:
802
-
803
- ```
804
- Slides requiring manual media upload:
805
- - "Cover" — background video: [description]
806
- - "Demo" — inline video: [description]
807
-
808
- Upload via POST /api/slide-images/upload (multipart/form-data, field: "file")
809
- Returns { ok: true, key, url } — replace placeholder with returned url
810
- Accepted: all image/*, video/mp4, video/webm, video/quicktime
811
- Max size: 50MB
812
- ```