lumina-slides 8.9.2 → 8.9.4
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.
- package/IMPLEMENTATION.md +270 -44
- package/dist/lumina-slides.js +4808 -4800
- package/dist/lumina-slides.umd.cjs +80 -80
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/IMPLEMENTATION.md
CHANGED
|
@@ -1,44 +1,46 @@
|
|
|
1
1
|
# Implementing lumina-slides
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guide for integrating the `lumina-slides` library into your application. For developers and **code agents** (Cursor, Copilot, etc.) that write the code that consumes the library.
|
|
4
|
+
|
|
5
|
+
**Vanilla JavaScript library:** use `import { Lumina } from "lumina-slides"`, `new Lumina(selector)`, and `engine.load(deck)`. The host app can be vanilla JS, React, or any framework; **Vue is not required**. All control (render, element control, animations, events) is done via the engine API and the deck JSON. The API is designed for vanilla JS.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
7
|
-
## 1.
|
|
9
|
+
## 1. Installation
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
npm install lumina-slides
|
|
11
|
-
#
|
|
13
|
+
# Optional, only if you use "chart" slides:
|
|
12
14
|
npm install lumina-slides chart.js
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
17
|
-
## 2.
|
|
19
|
+
## 2. Main imports
|
|
18
20
|
|
|
19
21
|
```ts
|
|
20
22
|
import { Lumina, parsePartialJson, generateSystemPrompt, getLuminaJsonSchema } from "lumina-slides";
|
|
21
23
|
import "lumina-slides/style.css";
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
Other useful exports: `createDebouncedLoader`, `isSlideReady`, `generateThemePrompt`, types `Deck`, `LuminaOptions`, `LuminaEventMap`, etc.
|
|
25
27
|
|
|
26
28
|
---
|
|
27
29
|
|
|
28
|
-
## 3.
|
|
30
|
+
## 3. Create the engine and load a deck
|
|
29
31
|
|
|
30
32
|
```ts
|
|
31
33
|
const engine = new Lumina("#app", {
|
|
32
|
-
theme: "ocean", //
|
|
34
|
+
theme: "ocean", // presets: default, ocean, midnight, forest, cyber, latte, sunset, monochrome
|
|
33
35
|
loop: true,
|
|
34
36
|
navigation: true,
|
|
35
37
|
touch: true,
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
engine.load({
|
|
39
|
-
meta: { title: "
|
|
41
|
+
meta: { title: "My presentation" },
|
|
40
42
|
slides: [
|
|
41
|
-
{ type: "statement", title: "
|
|
43
|
+
{ type: "statement", title: "Title", subtitle: "Subtitle" },
|
|
42
44
|
{ type: "features", title: "Features", features: [
|
|
43
45
|
{ title: "A", desc: "Desc A", icon: "star" },
|
|
44
46
|
{ title: "B", desc: "Desc B", icon: "zap" },
|
|
@@ -49,34 +51,34 @@ engine.load({
|
|
|
49
51
|
|
|
50
52
|
---
|
|
51
53
|
|
|
52
|
-
## 4.
|
|
54
|
+
## 4. Update the deck (patch)
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
For incremental updates without reloading:
|
|
55
57
|
|
|
56
58
|
```ts
|
|
57
|
-
engine.patch({ meta: { title: "
|
|
58
|
-
// slides
|
|
59
|
-
// engine.patch({ slides: [...
|
|
59
|
+
engine.patch({ meta: { title: "New title" } });
|
|
60
|
+
// slides are replaced entirely if provided:
|
|
61
|
+
// engine.patch({ slides: [...newSlides] });
|
|
60
62
|
```
|
|
61
63
|
|
|
62
64
|
---
|
|
63
65
|
|
|
64
|
-
## 5.
|
|
66
|
+
## 5. Events
|
|
65
67
|
|
|
66
68
|
```ts
|
|
67
|
-
engine.on("ready", (deck) => { /* deck
|
|
68
|
-
engine.on("slideChange", ({ index, previousIndex, slide }) => { /*
|
|
69
|
+
engine.on("ready", (deck) => { /* deck loaded */ });
|
|
70
|
+
engine.on("slideChange", ({ index, previousIndex, slide }) => { /* slide changed */ });
|
|
69
71
|
engine.on("action", (p) => {
|
|
70
|
-
// p: { type, label?, value? } —
|
|
72
|
+
// p: { type, label?, value? } — e.g. button clicks
|
|
71
73
|
});
|
|
72
|
-
engine.on("error", (err) => { /* error
|
|
74
|
+
engine.on("error", (err) => { /* internal error */ });
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
To unsubscribe: `engine.off("slideChange", handler)` with the same function reference.
|
|
76
78
|
|
|
77
79
|
---
|
|
78
80
|
|
|
79
|
-
## 6.
|
|
81
|
+
## 6. Programmatic navigation
|
|
80
82
|
|
|
81
83
|
```ts
|
|
82
84
|
engine.next();
|
|
@@ -87,19 +89,19 @@ const i = engine.currentSlideIndex;
|
|
|
87
89
|
|
|
88
90
|
---
|
|
89
91
|
|
|
90
|
-
## 7.
|
|
92
|
+
## 7. State for the agent / LLM
|
|
91
93
|
|
|
92
94
|
```ts
|
|
93
95
|
const state = engine.exportState();
|
|
94
96
|
// { status, currentSlide: { index, id, type, title }, narrative, engagementLevel, history }
|
|
95
|
-
//
|
|
97
|
+
// Use in LLM context: "User is on slide X, did Y".
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
---
|
|
99
101
|
|
|
100
|
-
## 8. Streaming
|
|
102
|
+
## 8. Streaming from an LLM
|
|
101
103
|
|
|
102
|
-
|
|
104
|
+
When the model streams JSON in chunks:
|
|
103
105
|
|
|
104
106
|
```ts
|
|
105
107
|
import { Lumina, parsePartialJson } from "lumina-slides";
|
|
@@ -107,44 +109,44 @@ import { Lumina, parsePartialJson } from "lumina-slides";
|
|
|
107
109
|
const engine = new Lumina("#app", { theme: "midnight" });
|
|
108
110
|
let buffer = "";
|
|
109
111
|
|
|
110
|
-
|
|
112
|
+
function onStream(chunk) {
|
|
111
113
|
buffer += chunk;
|
|
112
114
|
const json = parsePartialJson(buffer);
|
|
113
115
|
if (json) engine.load(json);
|
|
114
116
|
}
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
|
|
119
|
+
Or with debounce to reduce flicker:
|
|
118
120
|
|
|
119
121
|
```ts
|
|
120
122
|
import { createDebouncedLoader } from "lumina-slides";
|
|
121
123
|
|
|
122
124
|
const onChunk = createDebouncedLoader((deck) => engine.load(deck), 80);
|
|
123
|
-
//
|
|
125
|
+
// In the stream loop: onChunk(buffer);
|
|
124
126
|
```
|
|
125
127
|
|
|
126
128
|
---
|
|
127
129
|
|
|
128
|
-
## 9.
|
|
130
|
+
## 9. Generate the system prompt for an LLM that creates slides
|
|
129
131
|
|
|
130
|
-
|
|
132
|
+
If an LLM will generate the deck JSON:
|
|
131
133
|
|
|
132
134
|
```ts
|
|
133
135
|
const systemPrompt = generateSystemPrompt({
|
|
134
|
-
mode: "fast", // 'reasoning'
|
|
136
|
+
mode: "fast", // 'reasoning' for CoT before JSON
|
|
135
137
|
includeSchema: true,
|
|
136
138
|
includeTheming: true,
|
|
137
139
|
});
|
|
138
|
-
//
|
|
140
|
+
// Use systemPrompt as the system message in OpenAI, Anthropic, etc.
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
Theming only: `generateThemePrompt()`.
|
|
142
144
|
|
|
143
145
|
---
|
|
144
146
|
|
|
145
|
-
## 10. Schema
|
|
147
|
+
## 10. JSON Schema
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
To validate or pass to the LLM:
|
|
148
150
|
|
|
149
151
|
```ts
|
|
150
152
|
const schema = getLuminaJsonSchema();
|
|
@@ -152,9 +154,9 @@ const schema = getLuminaJsonSchema();
|
|
|
152
154
|
|
|
153
155
|
---
|
|
154
156
|
|
|
155
|
-
## 11.
|
|
157
|
+
## 11. Destroy the instance
|
|
156
158
|
|
|
157
|
-
|
|
159
|
+
When unmounting or leaving the view:
|
|
158
160
|
|
|
159
161
|
```ts
|
|
160
162
|
engine.destroy();
|
|
@@ -162,11 +164,19 @@ engine.destroy();
|
|
|
162
164
|
|
|
163
165
|
---
|
|
164
166
|
|
|
165
|
-
##
|
|
167
|
+
## Deck structure
|
|
166
168
|
|
|
167
169
|
```ts
|
|
168
170
|
interface Deck {
|
|
169
|
-
meta: {
|
|
171
|
+
meta: {
|
|
172
|
+
title: string;
|
|
173
|
+
author?: string;
|
|
174
|
+
theme?: string;
|
|
175
|
+
themeConfig?: ThemeConfig;
|
|
176
|
+
elementControl?: { defaultVisible?: boolean }; // false = all hidden by default
|
|
177
|
+
initialElementState?: { [id: string]: { visible?: boolean; opacity?: number; transform?: string; class?: string; style?: Record<string, string | number> } };
|
|
178
|
+
[k: string]: any;
|
|
179
|
+
};
|
|
170
180
|
slides: Array<
|
|
171
181
|
| { type: "statement"; title: string; subtitle?: string; tag?: string; notes?: string }
|
|
172
182
|
| { type: "features"; title: string; features: { title: string; desc: string; icon?: string }[]; description?: string; notes?: string }
|
|
@@ -181,12 +191,228 @@ interface Deck {
|
|
|
181
191
|
}
|
|
182
192
|
```
|
|
183
193
|
|
|
184
|
-
|
|
194
|
+
Detailed types in the package: `Deck`, `SlideStatement`, `SlideFeatures`, `LuminaOptions`, `LuminaEventMap`, etc.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Element IDs and `data-lumina-id` (vanilla JS / JSON API)
|
|
199
|
+
|
|
200
|
+
The library assigns a `data-lumina-id` attribute to each controllable element when **rendering the deck from JSON**. You use only `engine.load(deck)` and the engine API; no framework or component code is required.
|
|
201
|
+
|
|
202
|
+
**ID resolution order:** (1) `slide.id` for the root; (2) if the object at that path has `id: string`, it is used; (3) `slide.ids[path]` if present; (4) fallback `s{N}-{path}` (e.g. `s0-tag`, `s1-features-2`).
|
|
203
|
+
|
|
204
|
+
**Discovering IDs from vanilla JS:** `engine.elements(slideIndex)` returns the array of ids for that slide. To predict ids without mounting: `getElementIds(slide, slideIndex)` or `resolveId(slide, slideIndex, path)` / `elemId(slideIndex, ...path)` (exported for advanced use).
|
|
205
|
+
|
|
206
|
+
**Paths per slide type** (default id is `s{N}-{path}`):
|
|
207
|
+
|
|
208
|
+
| Type | Paths (e.g. `s0-tag`, `s1-features-0`) |
|
|
209
|
+
|----------|----------------------------------------|
|
|
210
|
+
| statement| `tag`, `title`, `subtitle` |
|
|
211
|
+
| features | `header`, `features.0`, `features.1`… |
|
|
212
|
+
| half | `media`, `tag`, `title`, `paragraphs`, `cta` |
|
|
213
|
+
| timeline | `title`, `subtitle`, `timeline.0`… |
|
|
214
|
+
| steps | `header`, `steps.0`, `steps.1`… |
|
|
215
|
+
| flex | `elements.0`, `elements.0.elements.0`… |
|
|
216
|
+
| chart | `title`, `subtitle`, `chart` |
|
|
217
|
+
| video | `video`, `title` |
|
|
218
|
+
| diagram | `nodes.0`, `nodes.1`…, `edges.0`… |
|
|
219
|
+
| custom | (none by default) |
|
|
220
|
+
|
|
221
|
+
**Control from JSON:**
|
|
222
|
+
|
|
223
|
+
- `meta.initialElementState`: `{ [id]: { visible?: false, opacity?, transform?, class?, style? } }` — initial state when the deck loads.
|
|
224
|
+
- `meta.elementControl`: `{ defaultVisible?: boolean }` — if `false`, all elements start hidden; override per id with `initialElementState`.
|
|
225
|
+
|
|
226
|
+
**APIs (vanilla JS):** `engine.element(id)`, `engine.element(slideIndex, path)`, `engine.getElementById(id)`, `engine.elements(slideIndex)`.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Element control and animations (vanilla JS)
|
|
231
|
+
|
|
232
|
+
Use `engine.element(id)` or `engine.element(slideIndex, path)` to get an **ElementController**:
|
|
233
|
+
|
|
234
|
+
- **Visibility:** `.show()`, `.hide()`, `.toggle(force?)`
|
|
235
|
+
- **Style:** `.opacity(n)`, `.transform(s)`, `.css({ ... })`, `.addClass(name)`, `.removeClass(name)`
|
|
236
|
+
- **Animations:** `.animate({ from?, to, duration?, ease?, onComplete? })`, `.animateAsync(...)`
|
|
237
|
+
|
|
238
|
+
`engine.getElementById(id)` returns the DOM node with `data-lumina-id`, or `null` if the slide is not mounted. Animations (GSAP) are no-op when the node is null.
|
|
239
|
+
|
|
240
|
+
**Avoiding null / no visible effect:** The element must be in the DOM when you call `.show()` or `.animate()`. Pattern in **vanilla JS**:
|
|
241
|
+
|
|
242
|
+
1. **Register `ready` and `slideChange` before `engine.load(deck)`** so callbacks run when the deck/slide is mounted.
|
|
243
|
+
|
|
244
|
+
2. **In `ready`:** use `setTimeout` (e.g. `(i + 1) * 700` ms per element) before each `engine.element(id).show()` for the first slide.
|
|
245
|
+
|
|
246
|
+
3. **In `slideChange`:** when `index` is the slide with controlled elements, repeat the same `setTimeout` + `.show()` for that slide’s ids.
|
|
247
|
+
|
|
248
|
+
4. **Order:** `new Lumina(...)` → `engine.on('ready', ...)` → `engine.on('slideChange', ...)` → `engine.load(deck)`.
|
|
249
|
+
|
|
250
|
+
**Example (vanilla JS) — simple reveal with delay:**
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
const engine = new Lumina("#app", { theme: "midnight" });
|
|
254
|
+
|
|
255
|
+
engine.on("ready", (deck) => {
|
|
256
|
+
const ids = engine.elements(0);
|
|
257
|
+
ids.forEach((id, i) => {
|
|
258
|
+
setTimeout(() => engine.element(id).show(), (i + 1) * 500);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
engine.on("slideChange", ({ index }) => {
|
|
263
|
+
const ids = engine.elements(index);
|
|
264
|
+
ids.forEach((id, i) => {
|
|
265
|
+
setTimeout(() => engine.element(id).show(), (i + 1) * 500);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
engine.load({
|
|
270
|
+
meta: {
|
|
271
|
+
title: "Reveal",
|
|
272
|
+
elementControl: { defaultVisible: false },
|
|
273
|
+
initialElementState: { "s0-tag": { visible: false }, "s0-title": { visible: false }, "s0-subtitle": { visible: false } },
|
|
274
|
+
},
|
|
275
|
+
slides: [{ type: "statement", title: "Hello", subtitle: "World", tag: "Intro" }],
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Delay-controlled animations (vanilla JS, no Vue)
|
|
282
|
+
|
|
283
|
+
Staggered reveals and per-element animations are driven from vanilla JS using `setTimeout`, `engine.element(id).show()`, and `engine.element(id).animate()`. Register `ready` and `slideChange` **before** `engine.load(deck)`.
|
|
284
|
+
|
|
285
|
+
**Helper to reveal a slide’s elements with delay and optional animation:**
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
/**
|
|
289
|
+
* Reveal all elements of a slide with staggered delay. Pure JavaScript, no Vue.
|
|
290
|
+
* @param {import('lumina-slides').Lumina} engine
|
|
291
|
+
* @param {number} slideIndex
|
|
292
|
+
* @param {Object} [opts]
|
|
293
|
+
* @param {number} [opts.delayMs=500] - Delay between each element (ms).
|
|
294
|
+
* @param {boolean} [opts.animate=true] - If true, animate from { opacity: 0, y: 20 } to { opacity: 1, y: 0 }.
|
|
295
|
+
* @param {number} [opts.animDuration=0.5] - Animation duration in seconds.
|
|
296
|
+
*/
|
|
297
|
+
function revealSlideWithDelay(engine, slideIndex, opts = {}) {
|
|
298
|
+
const { delayMs = 500, animate = true, animDuration = 0.5 } = opts;
|
|
299
|
+
const ids = engine.elements(slideIndex);
|
|
300
|
+
if (!ids || !ids.length) return;
|
|
301
|
+
|
|
302
|
+
ids.forEach((id, i) => {
|
|
303
|
+
const t = (i + 1) * delayMs;
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
const el = engine.element(id);
|
|
306
|
+
el.show();
|
|
307
|
+
if (animate) {
|
|
308
|
+
el.animate({
|
|
309
|
+
from: { opacity: 0, y: 20 },
|
|
310
|
+
to: { opacity: 1, y: 0 },
|
|
311
|
+
duration: animDuration,
|
|
312
|
+
ease: "power2.out",
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}, t);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Full HTML + vanilla JS example (delay-controlled animations):**
|
|
321
|
+
|
|
322
|
+
```html
|
|
323
|
+
<!DOCTYPE html>
|
|
324
|
+
<html lang="en">
|
|
325
|
+
<head>
|
|
326
|
+
<meta charset="UTF-8" />
|
|
327
|
+
<title>Lumina – delay-controlled animations (vanilla JS)</title>
|
|
328
|
+
<link rel="stylesheet" href="node_modules/lumina-slides/dist/style.css" />
|
|
329
|
+
</head>
|
|
330
|
+
<body>
|
|
331
|
+
<div id="app" style="width:100vw;height:100vh;"></div>
|
|
332
|
+
<script type="module">
|
|
333
|
+
import { Lumina } from "lumina-slides";
|
|
334
|
+
|
|
335
|
+
const engine = new Lumina("#app", { theme: "midnight" });
|
|
336
|
+
|
|
337
|
+
function revealSlideWithDelay(engine, slideIndex, opts = {}) {
|
|
338
|
+
const { delayMs = 500, animate = true, animDuration = 0.5 } = opts;
|
|
339
|
+
const ids = engine.elements(slideIndex);
|
|
340
|
+
if (!ids || !ids.length) return;
|
|
341
|
+
ids.forEach((id, i) => {
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
engine.element(id).show();
|
|
344
|
+
if (animate) {
|
|
345
|
+
engine.element(id).animate({
|
|
346
|
+
from: { opacity: 0, y: 20 },
|
|
347
|
+
to: { opacity: 1, y: 0 },
|
|
348
|
+
duration: animDuration,
|
|
349
|
+
ease: "power2.out",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}, (i + 1) * delayMs);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Must register before load so callbacks run when DOM is ready
|
|
357
|
+
engine.on("ready", () => revealSlideWithDelay(engine, 0, { delayMs: 600, animDuration: 0.5 }));
|
|
358
|
+
engine.on("slideChange", ({ index }) => revealSlideWithDelay(engine, index, { delayMs: 600, animDuration: 0.5 }));
|
|
359
|
+
|
|
360
|
+
engine.load({
|
|
361
|
+
meta: {
|
|
362
|
+
title: "Staggered reveal",
|
|
363
|
+
elementControl: { defaultVisible: false },
|
|
364
|
+
initialElementState: {
|
|
365
|
+
"s0-tag": { visible: false },
|
|
366
|
+
"s0-title": { visible: false },
|
|
367
|
+
"s0-subtitle": { visible: false },
|
|
368
|
+
"s1-header": { visible: false },
|
|
369
|
+
"s1-features-0": { visible: false },
|
|
370
|
+
"s1-features-1": { visible: false },
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
slides: [
|
|
374
|
+
{ type: "statement", title: "Welcome", subtitle: "Each line appears with a delay", tag: "Intro" },
|
|
375
|
+
{ type: "features", title: "Highlights", features: [
|
|
376
|
+
{ title: "First", desc: "Revealed after 600ms", icon: "star" },
|
|
377
|
+
{ title: "Second", desc: "Revealed after 1200ms", icon: "zap" },
|
|
378
|
+
]},
|
|
379
|
+
],
|
|
380
|
+
});
|
|
381
|
+
</script>
|
|
382
|
+
</body>
|
|
383
|
+
</html>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Animation-only (no show/hide), with custom stagger:**
|
|
387
|
+
|
|
388
|
+
```js
|
|
389
|
+
engine.on("ready", () => {
|
|
390
|
+
const ids = engine.elements(0);
|
|
391
|
+
ids.forEach((id, i) => {
|
|
392
|
+
setTimeout(() => {
|
|
393
|
+
engine.element(id).animate({
|
|
394
|
+
from: { opacity: 0, scale: 0.95 },
|
|
395
|
+
to: { opacity: 1, scale: 1 },
|
|
396
|
+
duration: 0.6,
|
|
397
|
+
ease: "back.out(1.2)",
|
|
398
|
+
});
|
|
399
|
+
}, i * 400);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Animation options (engine and deck)
|
|
407
|
+
|
|
408
|
+
- **Engine options:** `options.animation: { enabled?: boolean, type?: 'fade'|'cascade'|'zoom', durationIn?, durationOut? }` when creating `new Lumina(selector, options)`.
|
|
409
|
+
- **Slide transitions** are applied by the engine; you listen to `slideChange` for timing.
|
|
410
|
+
- **Per-element animations** are done with `engine.element(id).animate({ from: { opacity: 0, y: 20 }, to: { opacity: 1, y: 0 }, duration: 0.5 })` from vanilla JS.
|
|
185
411
|
|
|
186
412
|
---
|
|
187
413
|
|
|
188
|
-
##
|
|
414
|
+
## Where to go next
|
|
189
415
|
|
|
190
|
-
- **API
|
|
191
|
-
- **
|
|
192
|
-
- **Docs
|
|
416
|
+
- **API and types:** JSDoc in the code and generated `.d.ts`.
|
|
417
|
+
- **Guide for agents that generate slides:** [AGENTS.md](./AGENTS.md).
|
|
418
|
+
- **Docs and playground:** [lumina-slides](https://pailletjuanpablo.github.io/lumina-slides/).
|