atris 3.30.1 → 3.30.2
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/AGENTS.md +6 -0
- package/atris/AGENTS.md +11 -0
- package/atris/CLAUDE.md +5 -0
- package/atris/atris.md +19 -4
- package/atris/policies/atris-design.md +71 -0
- package/atris/policies/design-seed.md +187 -0
- package/atris/skills/atris/SKILL.md +26 -3
- package/atris/skills/design/SKILL.md +3 -2
- package/atris/skills/loop/SKILL.md +5 -3
- package/atris/team/_template/MEMBER.md +19 -0
- package/atris.md +63 -23
- package/ax +1434 -1698
- package/bin/atris.js +5 -1
- package/commands/agent-spawn.js +2 -2
- package/commands/brain.js +92 -7
- package/commands/brainstorm.js +62 -22
- package/commands/business-sync.js +92 -8
- package/commands/business.js +13 -7
- package/commands/chat-scan.js +102 -0
- package/commands/computer.js +758 -15
- package/commands/deck.js +505 -105
- package/commands/init.js +6 -1
- package/commands/launchpad.js +638 -0
- package/commands/log-sync.js +44 -13
- package/commands/loop-front.js +290 -0
- package/commands/member.js +124 -3
- package/commands/mission.js +653 -30
- package/commands/pull.js +37 -28
- package/commands/pulse.js +11 -8
- package/commands/run.js +79 -39
- package/commands/sync.js +36 -17
- package/commands/task.js +342 -66
- package/commands/xp.js +3 -0
- package/commands/youtube.js +221 -7
- package/decks/README.md +89 -0
- package/decks/archetype-catalog.json +180 -0
- package/decks/atris-antislop-pitch.json +48 -0
- package/decks/atris-archetypes-v2.json +83 -0
- package/decks/atris-one-loop-pitch.json +80 -0
- package/decks/atris-seed-pitch-v3.json +118 -0
- package/decks/atris-seed-pitch-v4-skeleton.json +106 -0
- package/decks/atris-seed-pitch-v5.json +109 -0
- package/decks/atris-seed-pitch-v6.json +137 -0
- package/decks/atris-seed-pitch-v7.json +133 -0
- package/decks/atris-single-shot-proof.json +74 -0
- package/decks/mark-pincus-narrative.json +102 -0
- package/decks/mark-pincus-sourcery.json +94 -0
- package/decks/yash-applied-compute-detailed.json +150 -0
- package/decks/yash-applied-compute-generalist.json +82 -0
- package/decks/yash-applied-compute-narrative.json +54 -0
- package/lib/ax-prefs.js +5 -12
- package/lib/chat-log-scan.js +377 -0
- package/lib/context-gatherer.js +35 -1
- package/lib/deck-compose.js +145 -0
- package/lib/deck-history.js +64 -0
- package/lib/deck-layout.js +169 -0
- package/lib/deck-review.js +431 -0
- package/lib/deck-schema.js +154 -0
- package/lib/file-ops.js +2 -2
- package/lib/functional-owner.js +189 -0
- package/lib/slides-deck.js +512 -58
- package/lib/task-db.js +109 -2
- package/package.json +2 -1
- package/templates/business-starter/team/START_HERE.md +12 -8
- package/utils/auth.js +4 -0
- package/utils/config.js +4 -0
- package/atris/atrisDev.md +0 -717
package/lib/slides-deck.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// stay sentence case, no gradient text, no glassmorphism, one accent hue.
|
|
9
9
|
//
|
|
10
10
|
// Spec shape (see commands/deck.js for the CLI):
|
|
11
|
-
// { theme: 'terminal'|'paper',
|
|
11
|
+
// { theme: 'terminal'|'paper'|'ink'|'noir',
|
|
12
12
|
// brand: { name: 'Sentinel', accent: '.' },
|
|
13
13
|
// slides: [ { type, ...fields } ] }
|
|
14
14
|
// Emphasis: wrap a phrase in **double asterisks** to render it in the accent.
|
|
@@ -29,6 +29,20 @@ const THEMES = {
|
|
|
29
29
|
accent: '#B5572E', accent2: '#9A4723', onAccent: '#FFFFFF',
|
|
30
30
|
sev: ['#B5572E', '#C0883A', '#5F7787'] },
|
|
31
31
|
},
|
|
32
|
+
ink: { // stark light "magazine proof"
|
|
33
|
+
fonts: { display: 'Fraunces', body: 'Outfit', mono: 'Roboto Mono' },
|
|
34
|
+
color: { bg: '#FAFAF8', panel: '#FFFFFF', panelAlt: '#F2F2EE', line: '#D8D8D2',
|
|
35
|
+
ink: '#111110', soft: '#4A4A46', faint: '#7A7A74',
|
|
36
|
+
accent: '#C41E3A', accent2: '#9B1830', onAccent: '#FFFFFF',
|
|
37
|
+
sev: ['#C41E3A', '#B8860B', '#2F4F6F'] },
|
|
38
|
+
},
|
|
39
|
+
noir: { // cool dark "broadcast / podcast"
|
|
40
|
+
fonts: { display: 'Fraunces', body: 'Outfit', mono: 'Roboto Mono' },
|
|
41
|
+
color: { bg: '#0E1117', panel: '#161B22', panelAlt: '#1C2129', line: '#2D333B',
|
|
42
|
+
ink: '#E6EDF3', soft: '#9BA7B4', faint: '#6E7681',
|
|
43
|
+
accent: '#58A6FF', accent2: '#79C0FF', onAccent: '#0E1117',
|
|
44
|
+
sev: ['#58A6FF', '#D2A854', '#3FB950'] },
|
|
45
|
+
},
|
|
32
46
|
};
|
|
33
47
|
const COLOR_ROLES = ['bg', 'panel', 'line', 'ink', 'soft', 'faint', 'accent', 'accent2', 'onAccent'];
|
|
34
48
|
|
|
@@ -62,6 +76,33 @@ function parseEmph(text) {
|
|
|
62
76
|
return { plain, ranges };
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
// ---------- fit estimation (keep dense text on the slide) ----------
|
|
80
|
+
// Slides text boxes do not auto-shrink: text that wraps past the slide edge
|
|
81
|
+
// (H = 405 PT) is clipped. These estimate wrapped height so an archetype can
|
|
82
|
+
// pick the largest font that still fits the vertical space it has.
|
|
83
|
+
function estLines(text, w, size) {
|
|
84
|
+
const cpl = Math.max(1, Math.floor(w / (size * 0.52))); // ~chars per line, proportional
|
|
85
|
+
return String(text == null ? '' : text)
|
|
86
|
+
.split('\n')
|
|
87
|
+
.reduce((acc, line) => acc + Math.max(1, Math.ceil(line.length / cpl)), 0);
|
|
88
|
+
}
|
|
89
|
+
function lineHeight(size, linePct) { return size * 1.15 * (linePct / 100); }
|
|
90
|
+
// Largest size in [minSize, baseSize] whose wrapped text fits maxH.
|
|
91
|
+
function fitSize(text, w, maxH, baseSize, minSize, linePct = 120) {
|
|
92
|
+
for (let s = baseSize; s > minSize; s -= 0.5) {
|
|
93
|
+
if (estLines(text, w, s) * lineHeight(s, linePct) <= maxH) return s;
|
|
94
|
+
}
|
|
95
|
+
return minSize;
|
|
96
|
+
}
|
|
97
|
+
// Smallest fit across several texts sharing one column width (uniform sizing).
|
|
98
|
+
function fitSizeAll(texts, w, maxH, baseSize, minSize, linePct = 120) {
|
|
99
|
+
return Math.min(baseSize, ...texts.map((t) => fitSize(t, w, maxH, baseSize, minSize, linePct)));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Coerce a value to an array so a malformed spec degrades instead of crashing
|
|
103
|
+
// the engine (which is called directly, bypassing the schema gate).
|
|
104
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
105
|
+
|
|
65
106
|
function makeCtx(theme) {
|
|
66
107
|
const C = theme.color, F = theme.fonts, requests = [];
|
|
67
108
|
let uid = 0; const nid = (p) => `${p}_${String(++uid).padStart(4, '0')}`;
|
|
@@ -113,9 +154,9 @@ function makeCtx(theme) {
|
|
|
113
154
|
return id; }
|
|
114
155
|
|
|
115
156
|
// generalized data panel: header + rows + footer
|
|
116
|
-
function panel(slide, x, y, w, data) {
|
|
117
|
-
const rows = (data.rows
|
|
118
|
-
const rowH = 38, headH = data.header ? 30 : 0, footH = data.footer ?
|
|
157
|
+
function panel(slide, x, y, w, data = {}) {
|
|
158
|
+
const rows = (Array.isArray(data.rows) ? data.rows : []).slice(0, 4).map((r) => r || {});
|
|
159
|
+
const rowH = 38, headH = data.header ? 30 : 0, footH = data.footer ? 28 : 0;
|
|
119
160
|
const h = headH + rowH * rows.length + footH;
|
|
120
161
|
fill(shape('ROUND_RECTANGLE', slide, x, y, w, h), C.panel, C.line, 1);
|
|
121
162
|
if (data.header) {
|
|
@@ -125,9 +166,8 @@ function makeCtx(theme) {
|
|
|
125
166
|
}
|
|
126
167
|
rows.forEach((r, i) => {
|
|
127
168
|
const ry = y + headH + i * rowH;
|
|
128
|
-
if (r.active) fill(shape('RECTANGLE', slide, x, ry, 2, rowH), C.accent);
|
|
129
169
|
const sev = C.sev[(r.sev != null ? r.sev : 0) % C.sev.length];
|
|
130
|
-
fill(shape('ELLIPSE', slide, x + 16, ry + rowH / 2 - 3.5, 7, 7), sev);
|
|
170
|
+
fill(shape('ELLIPSE', slide, x + 16, ry + rowH / 2 - 3.5, 7, 7), r.active ? C.accent : sev);
|
|
131
171
|
const nameTxt = sanitize(r.title || '') + (r.sub ? '\n' + sanitize(r.sub) : '');
|
|
132
172
|
const nb = box(slide, x + 30, ry + 6, w - 110, 28, nameTxt, { family: F.body, size: 11, color: C.ink, line: 108 });
|
|
133
173
|
if (r.sub && nb) styleRange(nb, sanitize(r.title || '').length + 1, nameTxt.length, { family: F.body, size: 9, color: C.faint });
|
|
@@ -139,19 +179,37 @@ function makeCtx(theme) {
|
|
|
139
179
|
}
|
|
140
180
|
if (i < rows.length - 1) fill(shape('RECTANGLE', slide, x, ry + rowH, w, 0.75), C.panelAlt);
|
|
141
181
|
});
|
|
142
|
-
if (data.footer) {
|
|
143
|
-
|
|
144
|
-
|
|
182
|
+
if (data.footer) {
|
|
183
|
+
const fy = y + headH + rowH * rows.length;
|
|
184
|
+
box(slide, x + 14, fy + 5, w * 0.55, 16, data.footer.left || '',
|
|
185
|
+
{ family: F.body, size: 9, color: C.faint });
|
|
186
|
+
if (data.footer.right) {
|
|
187
|
+
box(slide, x + w - 108, fy + 5, 94, 16, data.footer.right,
|
|
188
|
+
{ family: F.body, size: 9, color: C.accent2, align: 'END' });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
145
191
|
return h; }
|
|
146
192
|
|
|
147
|
-
function chips(slide, x, y, list
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
193
|
+
function chips(slide, x, y, list, maxW = W - M * 2) {
|
|
194
|
+
let cx = x;
|
|
195
|
+
let cy = y;
|
|
196
|
+
const rowH = 32;
|
|
197
|
+
arr(list).forEach((label) => {
|
|
198
|
+
const t = sanitize(label);
|
|
199
|
+
const w = Math.min(maxW, 16 + t.length * 6.3);
|
|
200
|
+
if (cx > x && cx + w > x + maxW) {
|
|
201
|
+
cx = x;
|
|
202
|
+
cy += rowH;
|
|
203
|
+
}
|
|
204
|
+
fill(shape('ROUND_RECTANGLE', slide, cx, cy, w, 24), C.panel, C.line, 1);
|
|
205
|
+
box(slide, cx, cy, w, 24, t, { family: F.mono, size: 9.5, color: C.faint, align: 'CENTER', vmid: true });
|
|
206
|
+
cx += w + 10;
|
|
207
|
+
});
|
|
208
|
+
return cy + 24;
|
|
209
|
+
}
|
|
152
210
|
|
|
153
211
|
function buttons(slide, y, list) {
|
|
154
|
-
const items = (list
|
|
212
|
+
const items = arr(list).slice(0, 3); const bw = 150, bh = 34, gap = 12;
|
|
155
213
|
const total = items.length * bw + (items.length - 1) * gap; let bx = (W - total) / 2;
|
|
156
214
|
items.forEach((b) => { const primary = b.primary;
|
|
157
215
|
fill(shape('ROUND_RECTANGLE', slide, bx, y, bw, bh), primary ? C.ink : C.bg, primary ? null : C.line, 1);
|
|
@@ -164,74 +222,470 @@ function makeCtx(theme) {
|
|
|
164
222
|
// ---------- slide archetypes ----------
|
|
165
223
|
const ARCHE = {
|
|
166
224
|
title(ctx, slide, s, spec) {
|
|
167
|
-
ctx.wordmark(slide, M, 34, 15, spec.brand);
|
|
168
|
-
ctx.rule(slide, M, 96, 40);
|
|
169
225
|
const hasPanel = !!s.panel;
|
|
170
|
-
ctx.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
226
|
+
ctx.wordmark(slide, M, 28, 14, spec.brand);
|
|
227
|
+
ctx.rule(slide, M, 86, 40);
|
|
228
|
+
const headlineSize = hasPanel ? 34 : 38;
|
|
229
|
+
const headlineW = hasPanel ? 340 : 580;
|
|
230
|
+
const headlineH = hasPanel ? 132 : 150;
|
|
231
|
+
ctx.box(slide, M, 98, headlineW, headlineH, s.headline || s.title || '',
|
|
232
|
+
{ family: ctx.F.display, size: headlineSize, color: ctx.C.ink, line: 96 });
|
|
233
|
+
if (s.sub) {
|
|
234
|
+
ctx.box(slide, M, hasPanel ? 236 : 258, hasPanel ? 330 : 520, 88, s.sub,
|
|
235
|
+
{ family: ctx.F.body, size: 12, color: ctx.C.soft, line: 118 });
|
|
236
|
+
}
|
|
237
|
+
if (hasPanel) ctx.panel(slide, 404, 88, 268, s.panel);
|
|
174
238
|
},
|
|
175
239
|
statement(ctx, slide, s, spec) {
|
|
176
|
-
ctx.wordmark(slide, M,
|
|
177
|
-
ctx.rule(slide, M,
|
|
178
|
-
|
|
179
|
-
|
|
240
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
241
|
+
ctx.rule(slide, M, 138, 40);
|
|
242
|
+
const text = s.text || s.headline || '';
|
|
243
|
+
const textSize = fitSize(text, 580, 120, 38, 26, 96);
|
|
244
|
+
ctx.box(slide, M, 150, 580, 118, text,
|
|
245
|
+
{ family: ctx.F.display, size: textSize, color: ctx.C.ink, line: 96 });
|
|
246
|
+
if (s.sub) {
|
|
247
|
+
const subSize = fitSize(s.sub, 560, 110, 12.5, 10, 120);
|
|
248
|
+
ctx.box(slide, M, 276, 560, 108, s.sub,
|
|
249
|
+
{ family: ctx.F.body, size: subSize, color: ctx.C.soft, line: 120 });
|
|
250
|
+
}
|
|
180
251
|
},
|
|
181
252
|
columns(ctx, slide, s, spec) {
|
|
182
|
-
ctx.wordmark(slide, M,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
253
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
254
|
+
const headingY = 96;
|
|
255
|
+
if (s.heading) {
|
|
256
|
+
ctx.box(slide, M, headingY, 580, 40, s.heading,
|
|
257
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink, line: 110 });
|
|
258
|
+
}
|
|
259
|
+
const cols = arr(s.columns).slice(0, 4);
|
|
260
|
+
const n = cols.length || 1;
|
|
261
|
+
const span = W - M * 2;
|
|
262
|
+
const gap = 14;
|
|
263
|
+
const colW = (span - (n - 1) * gap) / n;
|
|
264
|
+
const cy = s.heading ? 168 : 132;
|
|
265
|
+
// One uniform body size: the largest that fits every column's text in the
|
|
266
|
+
// space down to the slide floor. Narrower columns (more of them) shrink more.
|
|
267
|
+
const bodyMaxH = 388 - (cy + 32);
|
|
268
|
+
const bodySize = fitSizeAll(cols.map((c) => c.b || c.body || ''), colW - 6, bodyMaxH, 11, 8.5, 128);
|
|
269
|
+
cols.forEach((c, i) => {
|
|
270
|
+
const cx = M + i * (colW + gap);
|
|
271
|
+
if (i > 0) ctx.fill(ctx.shape('RECTANGLE', slide, cx - gap / 2, cy, 0.75, 132), ctx.C.line);
|
|
272
|
+
ctx.box(slide, cx, cy, colW - 6, 28, c.h || c.title || '',
|
|
273
|
+
{ family: ctx.F.display, size: 16, color: ctx.C.ink, line: 110 });
|
|
274
|
+
ctx.box(slide, cx, cy + 32, colW - 6, 148, c.b || c.body || '',
|
|
275
|
+
{ family: ctx.F.body, size: bodySize, color: ctx.C.soft, line: 128 });
|
|
276
|
+
});
|
|
190
277
|
},
|
|
191
278
|
panel(ctx, slide, s, spec) {
|
|
192
|
-
ctx.wordmark(slide, M,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
279
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
280
|
+
const headingY = 90;
|
|
281
|
+
const subY = 152;
|
|
282
|
+
if (s.heading) {
|
|
283
|
+
ctx.box(slide, M, headingY, 312, 54, s.heading,
|
|
284
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink, line: 108 });
|
|
285
|
+
}
|
|
286
|
+
if (s.sub) {
|
|
287
|
+
ctx.box(slide, M, subY, 312, 84, s.sub,
|
|
288
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.soft, line: 122 });
|
|
289
|
+
}
|
|
290
|
+
const rows = ((s.panel && s.panel.rows) || []).length;
|
|
291
|
+
const panelH = (s.panel && s.panel.header ? 30 : 0) + 38 * rows + (s.panel && s.panel.footer ? 28 : 0);
|
|
292
|
+
const panelY = Math.max(82, 118 - Math.max(0, panelH - 176) / 2);
|
|
293
|
+
ctx.panel(slide, 370, panelY, 302, s.panel || { rows: [] });
|
|
196
294
|
},
|
|
197
295
|
chips(ctx, slide, s, spec) {
|
|
198
|
-
ctx.wordmark(slide, M,
|
|
199
|
-
if (s.heading)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
296
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
297
|
+
if (s.heading) {
|
|
298
|
+
ctx.box(slide, M, 96, 580, 32, s.heading,
|
|
299
|
+
{ family: ctx.F.display, size: 26, color: ctx.C.ink });
|
|
300
|
+
}
|
|
301
|
+
if (s.sub) {
|
|
302
|
+
ctx.box(slide, M, 136, 520, 48, s.sub,
|
|
303
|
+
{ family: ctx.F.body, size: 12, color: ctx.C.soft, line: 120 });
|
|
304
|
+
}
|
|
305
|
+
const chipBottom = ctx.chips(slide, M, 196, s.chips || []);
|
|
306
|
+
if (s.mono) {
|
|
307
|
+
ctx.box(slide, M, chipBottom + 14, 520, 28, s.mono,
|
|
308
|
+
{ family: ctx.F.mono, size: 11, color: ctx.C.accent2, line: 120 });
|
|
309
|
+
}
|
|
203
310
|
},
|
|
204
311
|
bignumber(ctx, slide, s, spec) {
|
|
205
|
-
ctx.wordmark(slide, M,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
312
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
313
|
+
// pick number, else value; coalesce on null (not falsiness) so a literal 0 renders
|
|
314
|
+
const big = s.number != null ? s.number : (s.value != null ? s.value : '');
|
|
315
|
+
ctx.box(slide, M, 118, W - M * 2, 108, String(big),
|
|
316
|
+
{ family: ctx.F.display, size: 84, color: ctx.C.accent2, line: 96 });
|
|
317
|
+
if (s.label) {
|
|
318
|
+
ctx.box(slide, M, 236, 560, 36, s.label,
|
|
319
|
+
{ family: ctx.F.body, size: 15, color: ctx.C.ink, line: 118 });
|
|
320
|
+
}
|
|
321
|
+
if (s.sub) {
|
|
322
|
+
ctx.box(slide, M, 278, 560, 88, s.sub,
|
|
323
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.soft, line: 122 });
|
|
324
|
+
}
|
|
209
325
|
},
|
|
210
326
|
close(ctx, slide, s, spec) {
|
|
211
|
-
ctx.rule(slide, (W - 48) / 2,
|
|
327
|
+
ctx.rule(slide, (W - 48) / 2, 132, 48);
|
|
212
328
|
const name = (spec.brand && spec.brand.name) || 'Atris';
|
|
213
329
|
const ac = (spec.brand && spec.brand.accent) || '';
|
|
214
|
-
const id = ctx.box(slide, 0,
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
330
|
+
const id = ctx.box(slide, 0, 148, W, 58, name + ac,
|
|
331
|
+
{ family: ctx.F.display, size: 48, color: ctx.C.ink, align: 'CENTER' });
|
|
332
|
+
if (ac && id) {
|
|
333
|
+
ctx.styleRange(id, name.length, name.length + ac.length,
|
|
334
|
+
{ family: ctx.F.display, size: 48, color: ctx.C.accent });
|
|
335
|
+
}
|
|
336
|
+
if (s.tagline) {
|
|
337
|
+
ctx.box(slide, 48, 218, W - 96, 40, s.tagline,
|
|
338
|
+
{ family: ctx.F.body, size: 13.5, color: ctx.C.soft, align: 'CENTER', line: 120 });
|
|
339
|
+
}
|
|
340
|
+
if (s.buttons) ctx.buttons(slide, 278, s.buttons);
|
|
341
|
+
if (s.footer) {
|
|
342
|
+
ctx.box(slide, 0, 352, W, 20, s.footer,
|
|
343
|
+
{ family: ctx.F.body, size: 10, color: ctx.C.faint, align: 'CENTER' });
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
// --- new archetypes (v2) ---
|
|
347
|
+
timeline(ctx, slide, s, spec) {
|
|
348
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
349
|
+
const headingY = 88;
|
|
350
|
+
if (s.heading) {
|
|
351
|
+
ctx.box(slide, M, headingY, 580, 36, s.heading,
|
|
352
|
+
{ family: ctx.F.display, size: 26, color: ctx.C.ink, line: 110 });
|
|
353
|
+
}
|
|
354
|
+
const steps = arr(s.steps).slice(0, 5);
|
|
355
|
+
const n = steps.length || 1;
|
|
356
|
+
const pad = 58;
|
|
357
|
+
const startX = M + pad;
|
|
358
|
+
const endX = W - M - pad;
|
|
359
|
+
const trackW = endX - startX;
|
|
360
|
+
const cy = 210;
|
|
361
|
+
if (n > 1) {
|
|
362
|
+
ctx.fill(ctx.shape('RECTANGLE', slide, startX, cy - 0.5, trackW, 1.5), ctx.C.line);
|
|
363
|
+
}
|
|
364
|
+
steps.forEach((step, i) => {
|
|
365
|
+
const x = n === 1 ? W / 2 : startX + (trackW * i) / (n - 1);
|
|
366
|
+
const active = !!step.active;
|
|
367
|
+
const dotR = active ? 8 : 6;
|
|
368
|
+
ctx.fill(ctx.shape('ELLIPSE', slide, x - dotR, cy - dotR, dotR * 2, dotR * 2),
|
|
369
|
+
active ? ctx.C.accent : ctx.C.panelAlt, active ? ctx.C.accent : ctx.C.line, active ? 0 : 1);
|
|
370
|
+
if (active) {
|
|
371
|
+
ctx.fill(ctx.shape('ELLIPSE', slide, x - 3, cy - 3, 6, 6), ctx.C.onAccent);
|
|
372
|
+
}
|
|
373
|
+
const labelW = 104;
|
|
374
|
+
const labelX = Math.max(M, Math.min(W - M - labelW, x - labelW / 2));
|
|
375
|
+
ctx.box(slide, labelX, cy + 18, labelW, 22, step.label || '',
|
|
376
|
+
{ family: ctx.F.body, size: 11.5, color: active ? ctx.C.ink : ctx.C.soft, align: 'CENTER', line: 110 });
|
|
377
|
+
if (step.sub) {
|
|
378
|
+
ctx.box(slide, labelX, cy + 40, labelW, 36, step.sub,
|
|
379
|
+
{ family: ctx.F.body, size: 9, color: ctx.C.faint, align: 'CENTER', line: 108 });
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
if (s.sub) {
|
|
383
|
+
ctx.box(slide, M, 318, 580, 48, s.sub,
|
|
384
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.soft, line: 122 });
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
versus(ctx, slide, s, spec) {
|
|
388
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
389
|
+
if (s.heading) {
|
|
390
|
+
ctx.box(slide, M, 88, 580, 32, s.heading,
|
|
391
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink });
|
|
392
|
+
}
|
|
393
|
+
const colW = 296;
|
|
394
|
+
const colY = 138;
|
|
395
|
+
const colH = 210;
|
|
396
|
+
const sides = [
|
|
397
|
+
{ data: s.left || {}, x: M, accent: false },
|
|
398
|
+
{ data: s.right || {}, x: M + colW + 32, accent: true },
|
|
399
|
+
];
|
|
400
|
+
sides.forEach(({ data, x, accent }) => {
|
|
401
|
+
ctx.fill(ctx.shape('ROUND_RECTANGLE', slide, x, colY, colW, colH),
|
|
402
|
+
ctx.C.panel, accent ? ctx.C.accent : ctx.C.line, accent ? 1.5 : 1);
|
|
403
|
+
ctx.box(slide, x + 16, colY + 16, colW - 32, 24, data.label || '',
|
|
404
|
+
{ family: ctx.F.display, size: 16, color: accent ? ctx.C.accent2 : ctx.C.soft, line: 110 });
|
|
405
|
+
(Array.isArray(data.items) ? data.items : []).slice(0, 5).forEach((item, i) => {
|
|
406
|
+
const iy = colY + 52 + i * 30;
|
|
407
|
+
ctx.fill(ctx.shape('ELLIPSE', slide, x + 20, iy + 8, 5, 5),
|
|
408
|
+
accent ? ctx.C.accent : ctx.C.faint);
|
|
409
|
+
ctx.box(slide, x + 32, iy, colW - 48, 22, item,
|
|
410
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.ink, line: 118 });
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
metricgrid(ctx, slide, s, spec) {
|
|
415
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
416
|
+
if (s.heading) {
|
|
417
|
+
ctx.box(slide, M, 88, 580, 32, s.heading,
|
|
418
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink });
|
|
419
|
+
}
|
|
420
|
+
const metrics = (Array.isArray(s.metrics) ? s.metrics : []).slice(0, 4).map((m) => m || {});
|
|
421
|
+
const cols = 2;
|
|
422
|
+
const gap = 16;
|
|
423
|
+
const cardW = (W - M * 2 - gap) / cols;
|
|
424
|
+
const cardH = 108;
|
|
425
|
+
const gridY = s.heading ? 138 : 108;
|
|
426
|
+
metrics.forEach((m, i) => {
|
|
427
|
+
const col = i % cols;
|
|
428
|
+
const row = Math.floor(i / cols);
|
|
429
|
+
const x = M + col * (cardW + gap);
|
|
430
|
+
const y = gridY + row * (cardH + gap);
|
|
431
|
+
ctx.fill(ctx.shape('ROUND_RECTANGLE', slide, x, y, cardW, cardH), ctx.C.panel, ctx.C.line, 1);
|
|
432
|
+
ctx.box(slide, x + 16, y + 16, cardW - 32, 42, m.value || '',
|
|
433
|
+
{ family: ctx.F.display, size: 32, color: ctx.C.accent2, line: 96 });
|
|
434
|
+
ctx.box(slide, x + 16, y + 60, cardW - 32, 44, m.label || '',
|
|
435
|
+
{ family: ctx.F.body, size: 10, color: ctx.C.soft, line: 116 });
|
|
436
|
+
});
|
|
437
|
+
},
|
|
438
|
+
receipt(ctx, slide, s, spec) {
|
|
439
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
440
|
+
const headingY = 76;
|
|
441
|
+
if (s.heading) {
|
|
442
|
+
ctx.box(slide, M, headingY, 320, 52, s.heading,
|
|
443
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink, line: 108 });
|
|
444
|
+
}
|
|
445
|
+
if (s.sub) {
|
|
446
|
+
ctx.box(slide, M, headingY + 60, 300, 56, s.sub,
|
|
447
|
+
{ family: ctx.F.body, size: 11, color: ctx.C.soft, line: 120 });
|
|
448
|
+
}
|
|
449
|
+
const rx = 368;
|
|
450
|
+
const ry = 78;
|
|
451
|
+
const rw = 304;
|
|
452
|
+
const fields = arr(s.fields).slice(0, 6);
|
|
453
|
+
const rh = 48 + fields.length * 36 + (s.stamp ? 44 : 16);
|
|
454
|
+
ctx.fill(ctx.shape('ROUND_RECTANGLE', slide, rx, ry, rw, rh), ctx.C.panel, ctx.C.line, 1);
|
|
455
|
+
ctx.box(slide, rx + 16, ry + 12, rw - 32, 18, s.receiptTitle || 'Judgment packet',
|
|
456
|
+
{ family: ctx.F.mono, size: 9, color: ctx.C.faint, line: 110 });
|
|
457
|
+
ctx.fill(ctx.shape('RECTANGLE', slide, rx + 16, ry + 34, rw - 32, 0.75), ctx.C.line);
|
|
458
|
+
fields.forEach((f, i) => {
|
|
459
|
+
const fy = ry + 44 + i * 36;
|
|
460
|
+
ctx.box(slide, rx + 16, fy, 90, 18, f.k || '',
|
|
461
|
+
{ family: ctx.F.mono, size: 9, color: ctx.C.faint, line: 110 });
|
|
462
|
+
ctx.box(slide, rx + 108, fy, rw - 124, 32, f.v || '',
|
|
463
|
+
{ family: ctx.F.body, size: 10.5, color: ctx.C.ink, line: 116 });
|
|
464
|
+
});
|
|
465
|
+
if (s.stamp) {
|
|
466
|
+
const sy = ry + rh - 36;
|
|
467
|
+
ctx.fill(ctx.shape('ROUND_RECTANGLE', slide, rx + 16, sy, rw - 32, 24), ctx.C.panelAlt, ctx.C.accent, 1);
|
|
468
|
+
ctx.box(slide, rx + 16, sy, rw - 32, 24, s.stamp,
|
|
469
|
+
{ family: ctx.F.mono, size: 10, color: ctx.C.accent2, align: 'CENTER', vmid: true });
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
stack(ctx, slide, s, spec) {
|
|
473
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
474
|
+
if (s.heading) {
|
|
475
|
+
ctx.box(slide, M, 88, 580, 32, s.heading,
|
|
476
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink });
|
|
477
|
+
}
|
|
478
|
+
const layers = arr(s.layers).slice(0, 4);
|
|
479
|
+
const cardW = 460;
|
|
480
|
+
const cardH = 56;
|
|
481
|
+
const gap = 10;
|
|
482
|
+
const baseX = (W - cardW) / 2;
|
|
483
|
+
const baseY = s.heading ? 128 : 108;
|
|
484
|
+
layers.forEach((layer, i) => {
|
|
485
|
+
const x = baseX;
|
|
486
|
+
const y = baseY + i * (cardH + gap);
|
|
487
|
+
ctx.fill(ctx.shape('ROUND_RECTANGLE', slide, x, y, cardW, cardH),
|
|
488
|
+
ctx.C.panel, ctx.C.line, 1);
|
|
489
|
+
ctx.box(slide, x + 18, y + 12, cardW - 36, 22, layer.title || '',
|
|
490
|
+
{ family: ctx.F.display, size: 15, color: ctx.C.ink, line: 110 });
|
|
491
|
+
if (layer.sub) {
|
|
492
|
+
ctx.box(slide, x + 18, y + 34, cardW - 36, 20, layer.sub,
|
|
493
|
+
{ family: ctx.F.body, size: 10, color: ctx.C.soft, line: 116 });
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
const totalH = layers.length * cardH + (layers.length - 1) * gap;
|
|
497
|
+
if (s.sub) {
|
|
498
|
+
ctx.box(slide, M, baseY + totalH + 18, W - M * 2, 40, s.sub,
|
|
499
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.soft, align: 'CENTER', line: 122 });
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
quote(ctx, slide, s, spec) {
|
|
503
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
504
|
+
ctx.rule(slide, W / 2 - 24, 118, 48);
|
|
505
|
+
const qText = s.text || '';
|
|
506
|
+
ctx.box(slide, 64, 138, W - 128, 120, qText,
|
|
507
|
+
{ family: ctx.F.display, size: fitSize(qText, W - 128, 120, 28, 16, 118), color: ctx.C.ink, align: 'CENTER', line: 118 });
|
|
508
|
+
const author = [s.author, s.role].filter(Boolean).join(' · ');
|
|
509
|
+
if (author) {
|
|
510
|
+
ctx.box(slide, 64, 278, W - 128, 28, author,
|
|
511
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.faint, align: 'CENTER', line: 118 });
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
hero(ctx, slide, s, spec) {
|
|
515
|
+
ctx.rule(slide, (W - 56) / 2, 108, 56);
|
|
516
|
+
ctx.box(slide, 48, 124, W - 96, 88, s.headline || s.text || '',
|
|
517
|
+
{ family: ctx.F.display, size: 36, color: ctx.C.ink, align: 'CENTER', line: 104 });
|
|
518
|
+
if (s.sub) {
|
|
519
|
+
ctx.box(slide, 72, 220, W - 144, 56, s.sub,
|
|
520
|
+
{ family: ctx.F.body, size: 13, color: ctx.C.soft, align: 'CENTER', line: 122 });
|
|
521
|
+
}
|
|
522
|
+
if (s.mono) {
|
|
523
|
+
const monoSize = s.mono.length > 52 ? 8.5 : 10.5;
|
|
524
|
+
const monoH = s.mono.length > 52 ? 44 : 28;
|
|
525
|
+
ctx.box(slide, 36, 292, W - 72, monoH, s.mono,
|
|
526
|
+
{ family: ctx.F.mono, size: monoSize, color: ctx.C.accent2, align: 'CENTER', line: 118 });
|
|
527
|
+
}
|
|
528
|
+
if (s.footer) {
|
|
529
|
+
ctx.box(slide, 0, 352, W, 20, s.footer,
|
|
530
|
+
{ family: ctx.F.body, size: 10, color: ctx.C.faint, align: 'CENTER' });
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
// --- narrative archetypes (typography-first, no widget boxes) ---
|
|
534
|
+
interstitial(ctx, slide, s) {
|
|
535
|
+
const align = s.align === 'CENTER' ? 'CENTER' : 'START';
|
|
536
|
+
const x = align === 'CENTER' ? M : M;
|
|
537
|
+
const w = W - M * 2;
|
|
538
|
+
if (s.kicker) {
|
|
539
|
+
ctx.box(slide, x, 118, w, 22, s.kicker,
|
|
540
|
+
{ family: ctx.F.mono, size: 10, color: ctx.C.faint, align, line: 110 });
|
|
541
|
+
}
|
|
542
|
+
const txt = s.text || s.headline || '';
|
|
543
|
+
const size = s.size || fitSize(txt, w, 150, 56, 26, 96);
|
|
544
|
+
const textY = s.kicker ? 152 : 128;
|
|
545
|
+
ctx.box(slide, x, textY, w, 150, txt,
|
|
546
|
+
{ family: ctx.F.display, size, color: ctx.C.ink, align, line: 96 });
|
|
547
|
+
if (s.sub) {
|
|
548
|
+
ctx.box(slide, x, textY + 158, Math.min(520, w), 56, s.sub,
|
|
549
|
+
{ family: ctx.F.body, size: 13, color: ctx.C.soft, align, line: 124 });
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
lede(ctx, slide, s, spec) {
|
|
553
|
+
ctx.wordmark(slide, M, 28, 14, spec.brand);
|
|
554
|
+
const head = s.headline || s.title || '';
|
|
555
|
+
ctx.box(slide, M, 92, 640, 128, head,
|
|
556
|
+
{ family: ctx.F.display, size: fitSize(head, 640, 128, 40, 22, 100), color: ctx.C.ink, line: 100 });
|
|
557
|
+
if (s.dek) {
|
|
558
|
+
ctx.box(slide, M, 232, 580, 72, s.dek,
|
|
559
|
+
{ family: ctx.F.body, size: fitSize(s.dek, 580, 72, 14, 10, 128), color: ctx.C.soft, line: 128 });
|
|
560
|
+
}
|
|
561
|
+
if (s.byline) {
|
|
562
|
+
ctx.box(slide, M, 318, 580, 24, s.byline,
|
|
563
|
+
{ family: ctx.F.mono, size: 10, color: ctx.C.faint, line: 118 });
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
prose(ctx, slide, s, spec) {
|
|
567
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
568
|
+
let y = 86;
|
|
569
|
+
if (s.heading) {
|
|
570
|
+
const headSize = s.heading.length > 52 ? 22 : 26;
|
|
571
|
+
const headH = s.heading.length > 52 ? 72 : 52;
|
|
572
|
+
ctx.box(slide, M, y, 580, headH, s.heading,
|
|
573
|
+
{ family: ctx.F.display, size: headSize, color: ctx.C.ink, line: 108 });
|
|
574
|
+
y += headH + 12;
|
|
575
|
+
}
|
|
576
|
+
const paras = (Array.isArray(s.paragraphs) ? s.paragraphs : (s.body ? [s.body] : [])).slice(0, 3);
|
|
577
|
+
const np = paras.length || 1;
|
|
578
|
+
// Distribute paragraphs across the space left to the slide floor, then pick
|
|
579
|
+
// one font size that fits all of them in their slots (3 long paras shrink).
|
|
580
|
+
const slotH = Math.floor((388 - y) / np);
|
|
581
|
+
const size = fitSizeAll(paras, 580, slotH - 10, 14, 10.5, 132);
|
|
582
|
+
paras.forEach((p, i) => {
|
|
583
|
+
ctx.box(slide, M, y + i * slotH, 580, slotH, p,
|
|
584
|
+
{ family: ctx.F.body, size, color: ctx.C.soft, line: 132 });
|
|
585
|
+
});
|
|
586
|
+
},
|
|
587
|
+
bullets(ctx, slide, s, spec) {
|
|
588
|
+
// A real bullet list: accent dot + text, typography only, no card boxes.
|
|
589
|
+
// Font and row height shrink as the list grows so it never runs off-slide.
|
|
590
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
591
|
+
let y = 96;
|
|
592
|
+
if (s.heading) {
|
|
593
|
+
ctx.box(slide, M, y, W - M * 2, 40, s.heading,
|
|
594
|
+
{ family: ctx.F.display, size: 26, color: ctx.C.ink, line: 110 });
|
|
595
|
+
y += 54;
|
|
596
|
+
}
|
|
597
|
+
const items = arr(s.items).slice(0, 7);
|
|
598
|
+
const n = items.length || 1;
|
|
599
|
+
const hasSub = items.some((it) => it && typeof it === 'object' && (it.sub || it.b));
|
|
600
|
+
const bottom = s.sub ? H - 76 : H - 36;
|
|
601
|
+
const rowH = Math.max(hasSub ? 44 : 30, Math.min(hasSub ? 70 : 54, Math.floor((bottom - y) / n)));
|
|
602
|
+
const size = hasSub ? (n > 4 ? 13 : 15) : (n > 5 ? 13.5 : n > 3 ? 15.5 : 18);
|
|
603
|
+
items.forEach((item, i) => {
|
|
604
|
+
const iy = y + i * rowH;
|
|
605
|
+
const isObj = item && typeof item === 'object';
|
|
606
|
+
const txt = isObj ? (item.text || item.h || item.title || '') : item;
|
|
607
|
+
const sub = isObj ? (item.sub || item.b || '') : '';
|
|
608
|
+
ctx.fill(ctx.shape('ELLIPSE', slide, M + 1, iy + size * 0.42, 6, 6), ctx.C.accent);
|
|
609
|
+
ctx.box(slide, M + 22, iy, W - M * 2 - 22, sub ? 24 : rowH, txt,
|
|
610
|
+
{ family: ctx.F.body, size, color: ctx.C.ink, line: 116 });
|
|
611
|
+
if (sub) {
|
|
612
|
+
ctx.box(slide, M + 22, iy + size + 9, W - M * 2 - 22, 24, sub,
|
|
613
|
+
{ family: ctx.F.body, size: size - 3, color: ctx.C.faint, line: 116 });
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
if (s.sub) {
|
|
617
|
+
ctx.box(slide, M, bottom + 6, W - M * 2, 32, s.sub,
|
|
618
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.soft, line: 122 });
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
split(ctx, slide, s, spec) {
|
|
622
|
+
ctx.wordmark(slide, M, 28, 13, spec.brand);
|
|
623
|
+
const leftW = 372;
|
|
624
|
+
const topY = 88;
|
|
625
|
+
if (s.heading) {
|
|
626
|
+
ctx.box(slide, M, topY, leftW, 44, s.heading,
|
|
627
|
+
{ family: ctx.F.display, size: 24, color: ctx.C.ink, line: 108 });
|
|
628
|
+
}
|
|
629
|
+
const bodyY = s.heading ? topY + 52 : topY;
|
|
630
|
+
const body = s.body || s.text || '';
|
|
631
|
+
ctx.box(slide, M, bodyY, leftW, 405 - bodyY - 24, body,
|
|
632
|
+
{ family: ctx.F.body, size: fitSize(body, leftW, 405 - bodyY - 24, 13, 9.5, 132), color: ctx.C.soft, line: 132 });
|
|
633
|
+
const rx = 448;
|
|
634
|
+
if (s.accent) {
|
|
635
|
+
ctx.box(slide, rx, 108, 224, 80, s.accent,
|
|
636
|
+
{ family: ctx.F.display, size: 38, color: ctx.C.accent2, line: 96 });
|
|
637
|
+
}
|
|
638
|
+
if (s.accentSub) {
|
|
639
|
+
ctx.box(slide, rx, 196, 224, 96, s.accentSub,
|
|
640
|
+
{ family: ctx.F.body, size: 11.5, color: ctx.C.faint, line: 122 });
|
|
641
|
+
}
|
|
219
642
|
},
|
|
220
643
|
};
|
|
221
644
|
|
|
645
|
+
// ---------- speaker notes ----------
|
|
646
|
+
// Notes live on a slide's auto-created notesPage, whose body placeholder
|
|
647
|
+
// objectId is only known after the deck exists. So notes are a second pass:
|
|
648
|
+
// GET the presentation, find each slide's notes body, then insert text.
|
|
649
|
+
function findNotesBodyId(apiSlide) {
|
|
650
|
+
const els = (apiSlide
|
|
651
|
+
&& apiSlide.slideProperties
|
|
652
|
+
&& apiSlide.slideProperties.notesPage
|
|
653
|
+
&& apiSlide.slideProperties.notesPage.pageElements) || [];
|
|
654
|
+
const body = els.find((e) => e.shape && e.shape.placeholder && e.shape.placeholder.type === 'BODY');
|
|
655
|
+
return body ? body.objectId : null;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Pure: GET-response slides + the spec's slides -> insertText requests for the
|
|
659
|
+
// notes body of every slide that declares a `notes` string. Slides are matched
|
|
660
|
+
// by index (build creates them in spec order). Notes text is left raw (it is
|
|
661
|
+
// private speaker context, not on-canvas copy, so the AI-tell sanitizer that
|
|
662
|
+
// guards visible text does not apply).
|
|
663
|
+
function notesRequests(apiSlides, specSlides) {
|
|
664
|
+
const reqs = [];
|
|
665
|
+
(apiSlides || []).forEach((s, i) => {
|
|
666
|
+
const note = specSlides && specSlides[i] && specSlides[i].notes;
|
|
667
|
+
if (typeof note !== 'string' || !note) return; // ignore non-string notes (no "[object Object]")
|
|
668
|
+
const bodyId = findNotesBodyId(s);
|
|
669
|
+
if (!bodyId) return;
|
|
670
|
+
reqs.push({ insertText: { objectId: bodyId, text: note } });
|
|
671
|
+
});
|
|
672
|
+
return reqs;
|
|
673
|
+
}
|
|
674
|
+
|
|
222
675
|
// ---------- public: spec -> requests ----------
|
|
223
|
-
function buildDeck(spec
|
|
224
|
-
const
|
|
225
|
-
const theme =
|
|
676
|
+
function buildDeck(spec) {
|
|
677
|
+
const safe = spec && typeof spec === 'object' ? spec : {};
|
|
678
|
+
const theme = THEMES[safe.theme] || THEMES.terminal;
|
|
226
679
|
const ctx = makeCtx(theme);
|
|
227
680
|
const slideIds = [];
|
|
228
|
-
(
|
|
681
|
+
(Array.isArray(safe.slides) ? safe.slides : []).forEach((slide, i) => {
|
|
682
|
+
const s = slide && typeof slide === 'object' ? slide : {};
|
|
229
683
|
const sid = `deck_slide_${i + 1}`; slideIds.push(sid);
|
|
230
684
|
ctx.createSlide(sid);
|
|
231
685
|
ctx.bg(sid, theme.color.bg);
|
|
232
|
-
(ARCHE[s.type] || ARCHE.statement)(ctx, sid, s,
|
|
686
|
+
(ARCHE[s.type] || ARCHE.statement)(ctx, sid, s, safe);
|
|
233
687
|
});
|
|
234
688
|
return { requests: ctx.requests, slideIds };
|
|
235
689
|
}
|
|
236
690
|
|
|
237
|
-
module.exports = { buildDeck, THEMES, ARCHE, sanitize, parseEmph, rgb, COLOR_ROLES };
|
|
691
|
+
module.exports = { buildDeck, THEMES, ARCHE, sanitize, parseEmph, rgb, COLOR_ROLES, fitSize, estLines, findNotesBodyId, notesRequests };
|