mulmocast 2.1.39 → 2.1.40
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/README.md +43 -0
- package/lib/actions/pdf.js +18 -15
- package/lib/slide/blocks.d.ts +5 -0
- package/lib/slide/blocks.js +97 -0
- package/lib/slide/index.d.ts +5 -0
- package/lib/slide/index.js +7 -0
- package/lib/slide/layouts/big_quote.d.ts +2 -0
- package/lib/slide/layouts/big_quote.js +19 -0
- package/lib/slide/layouts/columns.d.ts +2 -0
- package/lib/slide/layouts/columns.js +56 -0
- package/lib/slide/layouts/comparison.d.ts +2 -0
- package/lib/slide/layouts/comparison.js +28 -0
- package/lib/slide/layouts/funnel.d.ts +2 -0
- package/lib/slide/layouts/funnel.js +27 -0
- package/lib/slide/layouts/grid.d.ts +2 -0
- package/lib/slide/layouts/grid.js +43 -0
- package/lib/slide/layouts/index.d.ts +3 -0
- package/lib/slide/layouts/index.js +43 -0
- package/lib/slide/layouts/matrix.d.ts +2 -0
- package/lib/slide/layouts/matrix.js +53 -0
- package/lib/slide/layouts/split.d.ts +2 -0
- package/lib/slide/layouts/split.js +38 -0
- package/lib/slide/layouts/stats.d.ts +2 -0
- package/lib/slide/layouts/stats.js +23 -0
- package/lib/slide/layouts/table.d.ts +2 -0
- package/lib/slide/layouts/table.js +46 -0
- package/lib/slide/layouts/timeline.d.ts +2 -0
- package/lib/slide/layouts/timeline.js +24 -0
- package/lib/slide/layouts/title.d.ts +2 -0
- package/lib/slide/layouts/title.js +17 -0
- package/lib/slide/render.d.ts +3 -0
- package/lib/slide/render.js +29 -0
- package/lib/slide/schema.d.ts +4009 -0
- package/lib/slide/schema.js +330 -0
- package/lib/slide/utils.d.ts +32 -0
- package/lib/slide/utils.js +112 -0
- package/lib/types/schema.d.ts +4487 -38
- package/lib/types/schema.js +11 -0
- package/lib/types/slide.d.ts +4009 -0
- package/lib/types/slide.js +330 -0
- package/lib/utils/context.d.ts +1169 -9
- package/lib/utils/image_plugins/index.js +14 -1
- package/lib/utils/image_plugins/slide.d.ts +5 -0
- package/lib/utils/image_plugins/slide.js +35 -0
- package/package.json +8 -8
- package/scripts/test/golden_age_of_discovery.json +270 -0
- package/scripts/test/test_slide_01.json +105 -0
- package/scripts/test/test_slide_11.json +144 -0
- package/scripts/test/test_slide_12.json +887 -0
- package/scripts/test/test_slide_showcase_corporate.json +497 -0
- package/scripts/test/test_slide_showcase_creative.json +545 -0
- package/scripts/test/test_slide_showcase_minimal.json +501 -0
- package/scripts/test/test_slide_showcase_pop.json +547 -0
- package/scripts/test/test_slide_showcase_warm.json +486 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// ═══════════════════════════════════════════════════════════
|
|
3
|
+
// Foundation: Colors & Typography
|
|
4
|
+
// ═══════════════════════════════════════════════════════════
|
|
5
|
+
/** 6-digit hex without '#' prefix, e.g. "3B82F6" */
|
|
6
|
+
const hexColorSchema = z.string().regex(/^[0-9A-Fa-f]{6}$/);
|
|
7
|
+
/** Semantic accent colors usable on cards, badges, borders, etc. */
|
|
8
|
+
export const accentColorKeySchema = z.enum(["primary", "accent", "success", "warning", "danger", "info", "highlight"]);
|
|
9
|
+
export const slideThemeColorsSchema = z.object({
|
|
10
|
+
bg: hexColorSchema,
|
|
11
|
+
bgCard: hexColorSchema,
|
|
12
|
+
bgCardAlt: hexColorSchema,
|
|
13
|
+
text: hexColorSchema,
|
|
14
|
+
textMuted: hexColorSchema,
|
|
15
|
+
textDim: hexColorSchema,
|
|
16
|
+
primary: hexColorSchema,
|
|
17
|
+
accent: hexColorSchema,
|
|
18
|
+
success: hexColorSchema,
|
|
19
|
+
warning: hexColorSchema,
|
|
20
|
+
danger: hexColorSchema,
|
|
21
|
+
info: hexColorSchema,
|
|
22
|
+
highlight: hexColorSchema,
|
|
23
|
+
});
|
|
24
|
+
export const slideThemeFontsSchema = z.object({
|
|
25
|
+
title: z.string(),
|
|
26
|
+
body: z.string(),
|
|
27
|
+
mono: z.string(),
|
|
28
|
+
});
|
|
29
|
+
export const slideThemeSchema = z.object({
|
|
30
|
+
colors: slideThemeColorsSchema,
|
|
31
|
+
fonts: slideThemeFontsSchema,
|
|
32
|
+
});
|
|
33
|
+
// ═══════════════════════════════════════════════════════════
|
|
34
|
+
// Content Blocks — the atoms of slide content
|
|
35
|
+
// ═══════════════════════════════════════════════════════════
|
|
36
|
+
export const textBlockSchema = z.object({
|
|
37
|
+
type: z.literal("text"),
|
|
38
|
+
value: z.string(),
|
|
39
|
+
align: z.enum(["left", "center", "right"]).optional(),
|
|
40
|
+
bold: z.boolean().optional(),
|
|
41
|
+
dim: z.boolean().optional(),
|
|
42
|
+
fontSize: z.number().optional(),
|
|
43
|
+
color: accentColorKeySchema.optional(),
|
|
44
|
+
});
|
|
45
|
+
export const bulletsBlockSchema = z.object({
|
|
46
|
+
type: z.literal("bullets"),
|
|
47
|
+
items: z.array(z.string()),
|
|
48
|
+
ordered: z.boolean().optional(),
|
|
49
|
+
icon: z.string().optional(),
|
|
50
|
+
});
|
|
51
|
+
export const codeBlockSchema = z.object({
|
|
52
|
+
type: z.literal("code"),
|
|
53
|
+
code: z.string(),
|
|
54
|
+
language: z.string().optional(),
|
|
55
|
+
});
|
|
56
|
+
export const calloutBlockSchema = z.object({
|
|
57
|
+
type: z.literal("callout"),
|
|
58
|
+
text: z.string(),
|
|
59
|
+
label: z.string().optional(),
|
|
60
|
+
color: accentColorKeySchema.optional(),
|
|
61
|
+
style: z.enum(["quote", "info", "warning"]).optional(),
|
|
62
|
+
});
|
|
63
|
+
export const metricBlockSchema = z.object({
|
|
64
|
+
type: z.literal("metric"),
|
|
65
|
+
value: z.string(),
|
|
66
|
+
label: z.string(),
|
|
67
|
+
color: accentColorKeySchema.optional(),
|
|
68
|
+
change: z.string().optional(),
|
|
69
|
+
});
|
|
70
|
+
export const dividerBlockSchema = z.object({
|
|
71
|
+
type: z.literal("divider"),
|
|
72
|
+
color: accentColorKeySchema.optional(),
|
|
73
|
+
});
|
|
74
|
+
export const imageBlockSchema = z.object({
|
|
75
|
+
type: z.literal("image"),
|
|
76
|
+
src: z.string(),
|
|
77
|
+
alt: z.string().optional(),
|
|
78
|
+
fit: z.enum(["contain", "cover"]).optional(),
|
|
79
|
+
});
|
|
80
|
+
export const contentBlockSchema = z.discriminatedUnion("type", [
|
|
81
|
+
textBlockSchema,
|
|
82
|
+
bulletsBlockSchema,
|
|
83
|
+
codeBlockSchema,
|
|
84
|
+
calloutBlockSchema,
|
|
85
|
+
metricBlockSchema,
|
|
86
|
+
dividerBlockSchema,
|
|
87
|
+
imageBlockSchema,
|
|
88
|
+
]);
|
|
89
|
+
// ═══════════════════════════════════════════════════════════
|
|
90
|
+
// Shared Components
|
|
91
|
+
// ═══════════════════════════════════════════════════════════
|
|
92
|
+
/** Bottom-of-slide callout bar */
|
|
93
|
+
export const calloutBarSchema = z.object({
|
|
94
|
+
text: z.string(),
|
|
95
|
+
label: z.string().optional(),
|
|
96
|
+
color: accentColorKeySchema.optional(),
|
|
97
|
+
align: z.enum(["left", "center"]).optional(),
|
|
98
|
+
leftBar: z.boolean().optional(),
|
|
99
|
+
});
|
|
100
|
+
/** Reusable card definition — used by columns, grid */
|
|
101
|
+
export const cardSchema = z.object({
|
|
102
|
+
title: z.string(),
|
|
103
|
+
accentColor: accentColorKeySchema.optional(),
|
|
104
|
+
content: z.array(contentBlockSchema).optional(),
|
|
105
|
+
footer: z.string().optional(),
|
|
106
|
+
label: z.string().optional(),
|
|
107
|
+
num: z.number().optional(),
|
|
108
|
+
icon: z.string().optional(),
|
|
109
|
+
});
|
|
110
|
+
// ═══════════════════════════════════════════════════════════
|
|
111
|
+
// Slide-level styling — orthogonal to layout
|
|
112
|
+
// ═══════════════════════════════════════════════════════════
|
|
113
|
+
export const slideStyleSchema = z.object({
|
|
114
|
+
bgColor: z.string().optional(),
|
|
115
|
+
decorations: z.boolean().optional(),
|
|
116
|
+
bgOpacity: z.number().optional(),
|
|
117
|
+
footer: z.string().optional(),
|
|
118
|
+
});
|
|
119
|
+
/** Common slide properties shared across all layouts */
|
|
120
|
+
const slideBaseFields = {
|
|
121
|
+
accentColor: accentColorKeySchema.optional(),
|
|
122
|
+
style: slideStyleSchema.optional(),
|
|
123
|
+
};
|
|
124
|
+
// ═══════════════════════════════════════════════════════════
|
|
125
|
+
// Layouts
|
|
126
|
+
// ═══════════════════════════════════════════════════════════
|
|
127
|
+
// ─── title ───
|
|
128
|
+
export const titleSlideSchema = z.object({
|
|
129
|
+
layout: z.literal("title"),
|
|
130
|
+
...slideBaseFields,
|
|
131
|
+
title: z.string(),
|
|
132
|
+
subtitle: z.string().optional(),
|
|
133
|
+
author: z.string().optional(),
|
|
134
|
+
note: z.string().optional(),
|
|
135
|
+
});
|
|
136
|
+
// ─── columns ───
|
|
137
|
+
export const columnsSlideSchema = z.object({
|
|
138
|
+
layout: z.literal("columns"),
|
|
139
|
+
...slideBaseFields,
|
|
140
|
+
title: z.string(),
|
|
141
|
+
stepLabel: z.string().optional(),
|
|
142
|
+
subtitle: z.string().optional(),
|
|
143
|
+
columns: z.array(cardSchema),
|
|
144
|
+
showArrows: z.boolean().optional(),
|
|
145
|
+
callout: calloutBarSchema.optional(),
|
|
146
|
+
bottomText: z.string().optional(),
|
|
147
|
+
});
|
|
148
|
+
// ─── comparison ───
|
|
149
|
+
export const comparisonPanelSchema = z.object({
|
|
150
|
+
title: z.string(),
|
|
151
|
+
accentColor: accentColorKeySchema.optional(),
|
|
152
|
+
content: z.array(contentBlockSchema).optional(),
|
|
153
|
+
footer: z.string().optional(),
|
|
154
|
+
});
|
|
155
|
+
export const comparisonSlideSchema = z.object({
|
|
156
|
+
layout: z.literal("comparison"),
|
|
157
|
+
...slideBaseFields,
|
|
158
|
+
title: z.string(),
|
|
159
|
+
stepLabel: z.string().optional(),
|
|
160
|
+
subtitle: z.string().optional(),
|
|
161
|
+
left: comparisonPanelSchema,
|
|
162
|
+
right: comparisonPanelSchema,
|
|
163
|
+
callout: calloutBarSchema.optional(),
|
|
164
|
+
});
|
|
165
|
+
// ─── grid ───
|
|
166
|
+
export const gridItemSchema = z.object({
|
|
167
|
+
title: z.string(),
|
|
168
|
+
description: z.string().optional(),
|
|
169
|
+
accentColor: accentColorKeySchema.optional(),
|
|
170
|
+
num: z.number().optional(),
|
|
171
|
+
icon: z.string().optional(),
|
|
172
|
+
content: z.array(contentBlockSchema).optional(),
|
|
173
|
+
});
|
|
174
|
+
export const gridSlideSchema = z.object({
|
|
175
|
+
layout: z.literal("grid"),
|
|
176
|
+
...slideBaseFields,
|
|
177
|
+
title: z.string(),
|
|
178
|
+
subtitle: z.string().optional(),
|
|
179
|
+
gridColumns: z.number().optional(),
|
|
180
|
+
items: z.array(gridItemSchema),
|
|
181
|
+
footer: z.string().optional(),
|
|
182
|
+
});
|
|
183
|
+
// ─── bigQuote ───
|
|
184
|
+
export const bigQuoteSlideSchema = z.object({
|
|
185
|
+
layout: z.literal("bigQuote"),
|
|
186
|
+
...slideBaseFields,
|
|
187
|
+
quote: z.string(),
|
|
188
|
+
author: z.string().optional(),
|
|
189
|
+
role: z.string().optional(),
|
|
190
|
+
});
|
|
191
|
+
// ─── stats ───
|
|
192
|
+
export const statItemSchema = z.object({
|
|
193
|
+
value: z.string(),
|
|
194
|
+
label: z.string(),
|
|
195
|
+
color: accentColorKeySchema.optional(),
|
|
196
|
+
change: z.string().optional(),
|
|
197
|
+
});
|
|
198
|
+
export const statsSlideSchema = z.object({
|
|
199
|
+
layout: z.literal("stats"),
|
|
200
|
+
...slideBaseFields,
|
|
201
|
+
title: z.string(),
|
|
202
|
+
stepLabel: z.string().optional(),
|
|
203
|
+
subtitle: z.string().optional(),
|
|
204
|
+
stats: z.array(statItemSchema),
|
|
205
|
+
callout: calloutBarSchema.optional(),
|
|
206
|
+
});
|
|
207
|
+
// ─── timeline ───
|
|
208
|
+
export const timelineItemSchema = z.object({
|
|
209
|
+
date: z.string(),
|
|
210
|
+
title: z.string(),
|
|
211
|
+
description: z.string().optional(),
|
|
212
|
+
color: accentColorKeySchema.optional(),
|
|
213
|
+
done: z.boolean().optional(),
|
|
214
|
+
});
|
|
215
|
+
export const timelineSlideSchema = z.object({
|
|
216
|
+
layout: z.literal("timeline"),
|
|
217
|
+
...slideBaseFields,
|
|
218
|
+
title: z.string(),
|
|
219
|
+
stepLabel: z.string().optional(),
|
|
220
|
+
subtitle: z.string().optional(),
|
|
221
|
+
items: z.array(timelineItemSchema),
|
|
222
|
+
});
|
|
223
|
+
// ─── split ───
|
|
224
|
+
export const splitPanelSchema = z.object({
|
|
225
|
+
title: z.string().optional(),
|
|
226
|
+
subtitle: z.string().optional(),
|
|
227
|
+
label: z.string().optional(),
|
|
228
|
+
accentColor: accentColorKeySchema.optional(),
|
|
229
|
+
content: z.array(contentBlockSchema).optional(),
|
|
230
|
+
dark: z.boolean().optional(),
|
|
231
|
+
ratio: z.number().optional(),
|
|
232
|
+
});
|
|
233
|
+
export const splitSlideSchema = z.object({
|
|
234
|
+
layout: z.literal("split"),
|
|
235
|
+
...slideBaseFields,
|
|
236
|
+
left: splitPanelSchema.optional(),
|
|
237
|
+
right: splitPanelSchema.optional(),
|
|
238
|
+
});
|
|
239
|
+
// ─── matrix ───
|
|
240
|
+
export const matrixCellSchema = z.object({
|
|
241
|
+
label: z.string(),
|
|
242
|
+
items: z.array(z.string()).optional(),
|
|
243
|
+
content: z.array(contentBlockSchema).optional(),
|
|
244
|
+
accentColor: accentColorKeySchema.optional(),
|
|
245
|
+
});
|
|
246
|
+
export const matrixSlideSchema = z.object({
|
|
247
|
+
layout: z.literal("matrix"),
|
|
248
|
+
...slideBaseFields,
|
|
249
|
+
title: z.string(),
|
|
250
|
+
stepLabel: z.string().optional(),
|
|
251
|
+
subtitle: z.string().optional(),
|
|
252
|
+
rows: z.number().optional(),
|
|
253
|
+
cols: z.number().optional(),
|
|
254
|
+
xAxis: z
|
|
255
|
+
.object({
|
|
256
|
+
low: z.string().optional(),
|
|
257
|
+
high: z.string().optional(),
|
|
258
|
+
label: z.string().optional(),
|
|
259
|
+
})
|
|
260
|
+
.optional(),
|
|
261
|
+
yAxis: z
|
|
262
|
+
.object({
|
|
263
|
+
low: z.string().optional(),
|
|
264
|
+
high: z.string().optional(),
|
|
265
|
+
label: z.string().optional(),
|
|
266
|
+
})
|
|
267
|
+
.optional(),
|
|
268
|
+
cells: z.array(matrixCellSchema),
|
|
269
|
+
});
|
|
270
|
+
// ─── table ───
|
|
271
|
+
export const tableCellValueSchema = z.union([
|
|
272
|
+
z.string(),
|
|
273
|
+
z.object({
|
|
274
|
+
text: z.string(),
|
|
275
|
+
color: accentColorKeySchema.optional(),
|
|
276
|
+
bold: z.boolean().optional(),
|
|
277
|
+
}),
|
|
278
|
+
]);
|
|
279
|
+
export const tableSlideSchema = z.object({
|
|
280
|
+
layout: z.literal("table"),
|
|
281
|
+
...slideBaseFields,
|
|
282
|
+
title: z.string(),
|
|
283
|
+
stepLabel: z.string().optional(),
|
|
284
|
+
subtitle: z.string().optional(),
|
|
285
|
+
headers: z.array(z.string()),
|
|
286
|
+
rows: z.array(z.array(tableCellValueSchema)),
|
|
287
|
+
rowHeaders: z.boolean().optional(),
|
|
288
|
+
striped: z.boolean().optional(),
|
|
289
|
+
callout: calloutBarSchema.optional(),
|
|
290
|
+
});
|
|
291
|
+
// ─── funnel ───
|
|
292
|
+
export const funnelStageSchema = z.object({
|
|
293
|
+
label: z.string(),
|
|
294
|
+
value: z.string().optional(),
|
|
295
|
+
description: z.string().optional(),
|
|
296
|
+
color: accentColorKeySchema.optional(),
|
|
297
|
+
});
|
|
298
|
+
export const funnelSlideSchema = z.object({
|
|
299
|
+
layout: z.literal("funnel"),
|
|
300
|
+
...slideBaseFields,
|
|
301
|
+
title: z.string(),
|
|
302
|
+
stepLabel: z.string().optional(),
|
|
303
|
+
subtitle: z.string().optional(),
|
|
304
|
+
stages: z.array(funnelStageSchema),
|
|
305
|
+
callout: calloutBarSchema.optional(),
|
|
306
|
+
});
|
|
307
|
+
// ═══════════════════════════════════════════════════════════
|
|
308
|
+
// Slide Union & Media Schema
|
|
309
|
+
// ═══════════════════════════════════════════════════════════
|
|
310
|
+
export const slideLayoutSchema = z.discriminatedUnion("layout", [
|
|
311
|
+
titleSlideSchema,
|
|
312
|
+
columnsSlideSchema,
|
|
313
|
+
comparisonSlideSchema,
|
|
314
|
+
gridSlideSchema,
|
|
315
|
+
bigQuoteSlideSchema,
|
|
316
|
+
statsSlideSchema,
|
|
317
|
+
timelineSlideSchema,
|
|
318
|
+
splitSlideSchema,
|
|
319
|
+
matrixSlideSchema,
|
|
320
|
+
tableSlideSchema,
|
|
321
|
+
funnelSlideSchema,
|
|
322
|
+
]);
|
|
323
|
+
/** Media schema registered in mulmoImageAssetSchema */
|
|
324
|
+
export const mulmoSlideMediaSchema = z
|
|
325
|
+
.object({
|
|
326
|
+
type: z.literal("slide"),
|
|
327
|
+
theme: slideThemeSchema.optional(),
|
|
328
|
+
slide: slideLayoutSchema,
|
|
329
|
+
})
|
|
330
|
+
.strict();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SlideTheme } from "./schema.js";
|
|
2
|
+
/** Escape HTML special characters */
|
|
3
|
+
export declare const escapeHtml: (s: string) => string;
|
|
4
|
+
/** Escape HTML and convert newlines to <br> */
|
|
5
|
+
export declare const nl2br: (s: string) => string;
|
|
6
|
+
/** Sanitize a hex color value (hex digits only) */
|
|
7
|
+
export declare const sanitizeHex: (s: string) => string;
|
|
8
|
+
/** Accent color key → Tailwind class segment: "primary" → "d-primary" */
|
|
9
|
+
export declare const c: (key: string) => string;
|
|
10
|
+
/** Build the Tailwind config JSON string for theme colors and fonts */
|
|
11
|
+
export declare const buildTailwindConfig: (theme: SlideTheme) => string;
|
|
12
|
+
/** Render a numbered circle badge */
|
|
13
|
+
export declare const numBadge: (num: number, colorKey: string) => string;
|
|
14
|
+
/** Render an icon in a square container */
|
|
15
|
+
export declare const iconSquare: (icon: string, colorKey: string) => string;
|
|
16
|
+
/** Render a card wrapper with accent top bar */
|
|
17
|
+
export declare const cardWrap: (accentColor: string, innerHtml: string, extraClass?: string) => string;
|
|
18
|
+
/** Render a callout bar at the bottom of a slide */
|
|
19
|
+
export declare const renderCalloutBar: (obj: {
|
|
20
|
+
text: string;
|
|
21
|
+
label?: string;
|
|
22
|
+
color?: string;
|
|
23
|
+
align?: string;
|
|
24
|
+
leftBar?: boolean;
|
|
25
|
+
}) => string;
|
|
26
|
+
/** Render the common slide header (accent bar + title + subtitle) */
|
|
27
|
+
export declare const slideHeader: (data: {
|
|
28
|
+
accentColor?: string;
|
|
29
|
+
stepLabel?: string;
|
|
30
|
+
title: string;
|
|
31
|
+
subtitle?: string;
|
|
32
|
+
}) => string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Escape HTML special characters */
|
|
2
|
+
export const escapeHtml = (s) => {
|
|
3
|
+
return String(s || "")
|
|
4
|
+
.replace(/&/g, "&")
|
|
5
|
+
.replace(/</g, "<")
|
|
6
|
+
.replace(/>/g, ">")
|
|
7
|
+
.replace(/"/g, """)
|
|
8
|
+
.replace(/'/g, "'");
|
|
9
|
+
};
|
|
10
|
+
/** Escape HTML and convert newlines to <br> */
|
|
11
|
+
export const nl2br = (s) => {
|
|
12
|
+
return escapeHtml(s).replace(/\n/g, "<br>");
|
|
13
|
+
};
|
|
14
|
+
/** Sanitize a value for safe use in CSS class names (alphanumeric + hyphens only) */
|
|
15
|
+
const sanitizeCssClass = (s) => {
|
|
16
|
+
return s.replace(/[^a-zA-Z0-9-]/g, "");
|
|
17
|
+
};
|
|
18
|
+
/** Sanitize a hex color value (hex digits only) */
|
|
19
|
+
export const sanitizeHex = (s) => {
|
|
20
|
+
return s.replace(/[^0-9A-Fa-f]/g, "");
|
|
21
|
+
};
|
|
22
|
+
/** Accent color key → Tailwind class segment: "primary" → "d-primary" */
|
|
23
|
+
export const c = (key) => {
|
|
24
|
+
return `d-${sanitizeCssClass(key)}`;
|
|
25
|
+
};
|
|
26
|
+
const colorKeyMap = {
|
|
27
|
+
bg: "bg",
|
|
28
|
+
bgCard: "card",
|
|
29
|
+
bgCardAlt: "alt",
|
|
30
|
+
text: "text",
|
|
31
|
+
textMuted: "muted",
|
|
32
|
+
textDim: "dim",
|
|
33
|
+
primary: "primary",
|
|
34
|
+
accent: "accent",
|
|
35
|
+
success: "success",
|
|
36
|
+
warning: "warning",
|
|
37
|
+
danger: "danger",
|
|
38
|
+
info: "info",
|
|
39
|
+
highlight: "highlight",
|
|
40
|
+
};
|
|
41
|
+
/** Build the Tailwind config JSON string for theme colors and fonts */
|
|
42
|
+
export const buildTailwindConfig = (theme) => {
|
|
43
|
+
const colorMap = {};
|
|
44
|
+
Object.entries(theme.colors).forEach(([k, v]) => {
|
|
45
|
+
const mapped = colorKeyMap[k];
|
|
46
|
+
if (mapped) {
|
|
47
|
+
colorMap[mapped] = `#${v}`;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return JSON.stringify({
|
|
51
|
+
theme: {
|
|
52
|
+
extend: {
|
|
53
|
+
colors: { d: colorMap },
|
|
54
|
+
fontFamily: {
|
|
55
|
+
title: [theme.fonts.title, "serif"],
|
|
56
|
+
body: [theme.fonts.body, "Arial", "sans-serif"],
|
|
57
|
+
mono: [theme.fonts.mono, "monospace"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
/** Render a numbered circle badge */
|
|
64
|
+
export const numBadge = (num, colorKey) => {
|
|
65
|
+
return `<div class="w-10 h-10 rounded-full bg-${c(colorKey)} flex items-center justify-center shrink-0">
|
|
66
|
+
<span class="text-white font-bold text-sm">${num}</span>
|
|
67
|
+
</div>`;
|
|
68
|
+
};
|
|
69
|
+
/** Render an icon in a square container */
|
|
70
|
+
export const iconSquare = (icon, colorKey) => {
|
|
71
|
+
return `<div class="w-16 h-16 bg-d-alt flex items-center justify-center rounded">
|
|
72
|
+
<span class="text-2xl font-mono font-bold text-${c(colorKey)}">${escapeHtml(icon)}</span>
|
|
73
|
+
</div>`;
|
|
74
|
+
};
|
|
75
|
+
/** Render a card wrapper with accent top bar */
|
|
76
|
+
export const cardWrap = (accentColor, innerHtml, extraClass) => {
|
|
77
|
+
return `<div class="bg-d-card rounded-lg shadow-lg overflow-hidden flex flex-col ${sanitizeCssClass(extraClass || "")}">
|
|
78
|
+
<div class="h-[3px] bg-${c(accentColor)} shrink-0"></div>
|
|
79
|
+
<div class="p-5 flex flex-col flex-1">
|
|
80
|
+
${innerHtml}
|
|
81
|
+
</div>
|
|
82
|
+
</div>`;
|
|
83
|
+
};
|
|
84
|
+
/** Render a callout bar at the bottom of a slide */
|
|
85
|
+
export const renderCalloutBar = (obj) => {
|
|
86
|
+
const color = obj.color || "warning";
|
|
87
|
+
const leftBar = obj.leftBar ? `<div class="w-1 bg-${c(color)} shrink-0"></div>` : "";
|
|
88
|
+
const align = obj.align === "center" ? "text-center" : "";
|
|
89
|
+
const inner = obj.label
|
|
90
|
+
? `<span class="font-bold text-${c(color)}">${escapeHtml(obj.label)}:</span> <span class="text-d-muted">${escapeHtml(obj.text)}</span>`
|
|
91
|
+
: `<span class="text-d-muted">${escapeHtml(obj.text)}</span>`;
|
|
92
|
+
return `<div class="mx-12 bg-d-card rounded flex overflow-hidden ${align}">
|
|
93
|
+
${leftBar}
|
|
94
|
+
<div class="px-4 py-3 text-sm font-body flex-1">${inner}</div>
|
|
95
|
+
</div>`;
|
|
96
|
+
};
|
|
97
|
+
/** Render the common slide header (accent bar + title + subtitle) */
|
|
98
|
+
export const slideHeader = (data) => {
|
|
99
|
+
const accent = data.accentColor || "primary";
|
|
100
|
+
const lines = [];
|
|
101
|
+
lines.push(`<div class="h-[3px] bg-${c(accent)} shrink-0"></div>`);
|
|
102
|
+
lines.push(`<div class="px-12 pt-5 shrink-0">`);
|
|
103
|
+
if (data.stepLabel) {
|
|
104
|
+
lines.push(` <p class="text-sm font-bold text-${c(accent)} font-body">${escapeHtml(data.stepLabel)}</p>`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(` <h2 class="text-[42px] leading-tight font-title font-bold text-d-text">${nl2br(data.title)}</h2>`);
|
|
107
|
+
if (data.subtitle) {
|
|
108
|
+
lines.push(` <p class="text-[15px] text-d-dim mt-2 font-body">${nl2br(data.subtitle)}</p>`);
|
|
109
|
+
}
|
|
110
|
+
lines.push(`</div>`);
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
};
|