@thanh01.pmt/presentation-kit 0.2.17 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/ai/index.cjs +33 -28
- package/dist/ai/index.cjs.map +1 -1
- package/dist/ai/index.d.cts +4 -3
- package/dist/ai/index.d.ts +4 -3
- package/dist/ai/index.js +3 -2
- package/dist/ai/index.js.map +1 -1
- package/dist/assets/fonts/NOTICE.md +25 -0
- package/dist/assets/fonts/SOURCES.json +47 -0
- package/dist/assets/fonts/fira-code.woff2 +0 -0
- package/dist/assets/fonts/inter.woff2 +0 -0
- package/dist/assets/fonts/jetbrains-mono.woff2 +0 -0
- package/dist/assets/fonts/playfair-display.woff2 +0 -0
- package/dist/assets/fonts/plus-jakarta-sans.woff2 +0 -0
- package/dist/assets/fonts/space-grotesk.woff2 +0 -0
- package/dist/chunk-3Q4RMZVQ.cjs +867 -0
- package/dist/chunk-3Q4RMZVQ.cjs.map +1 -0
- package/dist/chunk-HSVXH7DD.cjs +1624 -0
- package/dist/chunk-HSVXH7DD.cjs.map +1 -0
- package/dist/{chunk-NYLDRVHZ.js → chunk-I5RG2L3M.js} +4 -78
- package/dist/chunk-I5RG2L3M.js.map +1 -0
- package/dist/{chunk-7QHDWDSB.js → chunk-INAOTYLK.cjs} +437 -849
- package/dist/chunk-INAOTYLK.cjs.map +1 -0
- package/dist/{chunk-TGI6YSZP.cjs → chunk-JBGGQGSQ.cjs} +4 -80
- package/dist/chunk-JBGGQGSQ.cjs.map +1 -0
- package/dist/{chunk-JNTRKKJX.cjs → chunk-OCP2TAY2.js} +421 -868
- package/dist/chunk-OCP2TAY2.js.map +1 -0
- package/dist/chunk-V6X7D44H.js +854 -0
- package/dist/chunk-V6X7D44H.js.map +1 -0
- package/dist/chunk-VRXEXUHR.js +1610 -0
- package/dist/chunk-VRXEXUHR.js.map +1 -0
- package/dist/{click-css-Bcc1CkTN.d.cts → click-css-C8VciwZ6.d.cts} +1 -1
- package/dist/{click-css-B2s9B_uP.d.ts → click-css-COIiFo1I.d.ts} +1 -1
- package/dist/index.cjs +143 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -6
- package/dist/index.d.ts +62 -6
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +117 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +36 -0
- package/dist/node/index.d.ts +36 -0
- package/dist/node/index.js +105 -0
- package/dist/node/index.js.map +1 -0
- package/dist/{presets-D_hfPJX9.d.ts → presets-DPRGtpO-.d.ts} +1 -1
- package/dist/{presets-BYvgAAkJ.d.cts → presets-MIi91wZg.d.cts} +1 -1
- package/dist/react/index.cjs +17 -16
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +4 -3
- package/dist/react/index.d.ts +4 -3
- package/dist/react/index.js +4 -3
- package/dist/react/index.js.map +1 -1
- package/dist/skills/index.cjs +16 -12
- package/dist/skills/index.d.cts +36 -20
- package/dist/skills/index.d.ts +36 -20
- package/dist/skills/index.js +1 -1
- package/dist/{types-C3d04zzP.d.cts → types-CvxCxWus.d.cts} +1 -67
- package/dist/{types-C3d04zzP.d.ts → types-CvxCxWus.d.ts} +1 -67
- package/dist/types-k_eae8UV.d.cts +74 -0
- package/dist/types-k_eae8UV.d.ts +74 -0
- package/package.json +8 -2
- package/dist/chunk-6R6L6KSC.cjs +0 -601
- package/dist/chunk-6R6L6KSC.cjs.map +0 -1
- package/dist/chunk-7QHDWDSB.js.map +0 -1
- package/dist/chunk-BHJXQCVE.js +0 -588
- package/dist/chunk-BHJXQCVE.js.map +0 -1
- package/dist/chunk-JNTRKKJX.cjs.map +0 -1
- package/dist/chunk-NYLDRVHZ.js.map +0 -1
- package/dist/chunk-TGI6YSZP.cjs.map +0 -1
|
@@ -1,858 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
${yamlStr.split("\n").join("\n ")}
|
|
38
|
-
-->`;
|
|
39
|
-
}
|
|
40
|
-
function splitMarkdownSlides(markdown) {
|
|
41
|
-
const lines = markdown.split("\n");
|
|
42
|
-
let frontmatter = null;
|
|
43
|
-
const slideBuffers = [];
|
|
44
|
-
let currentBuffer = [];
|
|
45
|
-
let inCodeFence = false;
|
|
46
|
-
let lineIdx = 0;
|
|
47
|
-
if (lines.length > 0 && lines[0].trim() === "---") {
|
|
48
|
-
const fmBuffer = [lines[0]];
|
|
49
|
-
lineIdx = 1;
|
|
50
|
-
while (lineIdx < lines.length) {
|
|
51
|
-
const line = lines[lineIdx];
|
|
52
|
-
fmBuffer.push(line);
|
|
53
|
-
if (line.trim() === "---") {
|
|
54
|
-
frontmatter = fmBuffer.join("\n");
|
|
55
|
-
lineIdx++;
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
lineIdx++;
|
|
59
|
-
}
|
|
1
|
+
// src/plugins/shiki.ts
|
|
2
|
+
var highlighterPromise = null;
|
|
3
|
+
var highlighterFailed = false;
|
|
4
|
+
var SUPPORTED_LANGS = [
|
|
5
|
+
"javascript",
|
|
6
|
+
"typescript",
|
|
7
|
+
"python",
|
|
8
|
+
"cpp",
|
|
9
|
+
"c",
|
|
10
|
+
"java",
|
|
11
|
+
"html",
|
|
12
|
+
"css",
|
|
13
|
+
"json",
|
|
14
|
+
"yaml",
|
|
15
|
+
"bash",
|
|
16
|
+
"shell",
|
|
17
|
+
"sql",
|
|
18
|
+
"go",
|
|
19
|
+
"rust",
|
|
20
|
+
"swift",
|
|
21
|
+
"kotlin",
|
|
22
|
+
"ruby",
|
|
23
|
+
"php",
|
|
24
|
+
"xml",
|
|
25
|
+
"markdown",
|
|
26
|
+
"dockerfile",
|
|
27
|
+
"toml",
|
|
28
|
+
"ini",
|
|
29
|
+
"graphql",
|
|
30
|
+
"prisma",
|
|
31
|
+
"solidity"
|
|
32
|
+
];
|
|
33
|
+
async function getHighlighter() {
|
|
34
|
+
if (highlighterFailed) {
|
|
35
|
+
highlighterPromise = null;
|
|
36
|
+
highlighterFailed = false;
|
|
60
37
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
currentBuffer.push(line);
|
|
38
|
+
if (!highlighterPromise) {
|
|
39
|
+
highlighterPromise = import('shiki').then(
|
|
40
|
+
(mod) => mod.createHighlighter({
|
|
41
|
+
themes: ["vitesse-dark", "vitesse-light"],
|
|
42
|
+
langs: [...SUPPORTED_LANGS]
|
|
43
|
+
})
|
|
44
|
+
).catch((err) => {
|
|
45
|
+
highlighterPromise = null;
|
|
46
|
+
highlighterFailed = true;
|
|
47
|
+
throw new Error(`[presentation-kit] Failed to load Shiki highlighter: ${err}`);
|
|
48
|
+
});
|
|
74
49
|
}
|
|
75
|
-
|
|
76
|
-
const slides = slideBuffers.map((b) => b.join("\n"));
|
|
77
|
-
return { frontmatter, slides };
|
|
50
|
+
return highlighterPromise;
|
|
78
51
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
if (slideIndex < 0 || slideIndex >= slides.length) {
|
|
82
|
-
return markdown;
|
|
83
|
-
}
|
|
84
|
-
let slideContent = slides[slideIndex];
|
|
85
|
-
const layoutCommentRegex = /<!--\s*layout:\s*[\s\S]*?-->/i;
|
|
86
|
-
const newComment = layout && layout.boxes && layout.boxes.length > 0 ? serializeLayoutToComment(layout) : "";
|
|
87
|
-
if (layoutCommentRegex.test(slideContent)) {
|
|
88
|
-
if (newComment) {
|
|
89
|
-
slideContent = slideContent.replace(layoutCommentRegex, newComment);
|
|
90
|
-
} else {
|
|
91
|
-
slideContent = slideContent.replace(layoutCommentRegex, "").trim();
|
|
92
|
-
}
|
|
93
|
-
} else if (newComment) {
|
|
94
|
-
const trimmed = slideContent.trimStart();
|
|
95
|
-
slideContent = `${newComment}
|
|
96
|
-
|
|
97
|
-
${trimmed}`;
|
|
98
|
-
}
|
|
99
|
-
slides[slideIndex] = slideContent;
|
|
100
|
-
const joinedSlides = slides.join("\n---\n");
|
|
101
|
-
if (frontmatter) {
|
|
102
|
-
return `${frontmatter}
|
|
103
|
-
|
|
104
|
-
${joinedSlides}`;
|
|
105
|
-
}
|
|
106
|
-
return joinedSlides;
|
|
52
|
+
function decodeHtmlEntities(html) {
|
|
53
|
+
return html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(/ /g, " ").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code))).replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
107
54
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return markdown;
|
|
112
|
-
}
|
|
113
|
-
const cleanOld = oldText.trim();
|
|
114
|
-
const cleanNew = newText.trim();
|
|
115
|
-
if (!cleanOld || cleanOld === cleanNew) return markdown;
|
|
116
|
-
let slideContent = slides[slideIndex];
|
|
117
|
-
if (slideContent.includes(cleanOld)) {
|
|
118
|
-
slideContent = slideContent.replace(cleanOld, cleanNew);
|
|
119
|
-
} else {
|
|
120
|
-
const lines = slideContent.split("\n");
|
|
121
|
-
let replaced = false;
|
|
122
|
-
for (let i = 0; i < lines.length; i++) {
|
|
123
|
-
const line = lines[i];
|
|
124
|
-
const stripped = line.replace(/^[#\s\-\*\>]+/, "").replace(/[*_`]/g, "").trim();
|
|
125
|
-
if (stripped === cleanOld) {
|
|
126
|
-
const prefixMatch = line.match(/^([#\s\-\*\>]+)/);
|
|
127
|
-
const prefix = prefixMatch ? prefixMatch[1] : "";
|
|
128
|
-
lines[i] = `${prefix}${cleanNew}`;
|
|
129
|
-
replaced = true;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (replaced) {
|
|
134
|
-
slideContent = lines.join("\n");
|
|
135
|
-
} else {
|
|
136
|
-
const escaped = cleanOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
137
|
-
const regex = new RegExp(escaped, "i");
|
|
138
|
-
if (regex.test(slideContent)) {
|
|
139
|
-
slideContent = slideContent.replace(regex, cleanNew);
|
|
140
|
-
} else {
|
|
141
|
-
const words = cleanOld.split(/\s+/).filter((w) => w.length > 2);
|
|
142
|
-
if (words.length > 0) {
|
|
143
|
-
const pattern = words.map((w) => w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\s\\S]*?");
|
|
144
|
-
const fuzzyRegex = new RegExp(pattern, "i");
|
|
145
|
-
if (fuzzyRegex.test(slideContent)) {
|
|
146
|
-
slideContent = slideContent.replace(fuzzyRegex, cleanNew);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
slides[slideIndex] = slideContent;
|
|
153
|
-
const joinedSlides = slides.join("\n---\n");
|
|
154
|
-
if (frontmatter) {
|
|
155
|
-
return `${frontmatter}
|
|
156
|
-
|
|
157
|
-
${joinedSlides}`;
|
|
158
|
-
}
|
|
159
|
-
return joinedSlides;
|
|
55
|
+
function getShikiTheme(theme) {
|
|
56
|
+
if (theme === "light") return "vitesse-light";
|
|
57
|
+
return "vitesse-dark";
|
|
160
58
|
}
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const stripped = line.replace(/^[#\s\-\*\>]+/, "").replace(/[*_`]/g, "").trim();
|
|
174
|
-
if (stripped === cleanContent || line.includes(cleanContent)) {
|
|
175
|
-
removed = true;
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
});
|
|
180
|
-
if (removed) {
|
|
181
|
-
slideContent = updatedLines.join("\n");
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const layoutCommentRegex = /<!--\s*layout:\s*([\s\S]*?)-->/i;
|
|
185
|
-
const match = slideContent.match(layoutCommentRegex);
|
|
186
|
-
if (match && match[1]) {
|
|
187
|
-
try {
|
|
188
|
-
const parsed = yaml__default.default.load(match[1]);
|
|
189
|
-
if (parsed && Array.isArray(parsed.boxes)) {
|
|
190
|
-
const remainingBoxes = parsed.boxes.filter((b) => {
|
|
191
|
-
if (options.boxId && b.id === options.boxId) return false;
|
|
192
|
-
if (options.target && b.target === options.target) return false;
|
|
193
|
-
return true;
|
|
194
|
-
});
|
|
195
|
-
if (remainingBoxes.length > 0) {
|
|
196
|
-
const yamlStr = yaml__default.default.dump({ boxes: remainingBoxes }, { indent: 2, lineWidth: -1, noRefs: true }).trim();
|
|
197
|
-
slideContent = slideContent.replace(layoutCommentRegex, `<!-- layout:
|
|
198
|
-
${yamlStr.split("\n").join("\n ")}
|
|
199
|
-
-->`);
|
|
200
|
-
} else {
|
|
201
|
-
slideContent = slideContent.replace(layoutCommentRegex, "").trim();
|
|
202
|
-
}
|
|
59
|
+
async function highlightCodeBlocks(html, theme = "dark") {
|
|
60
|
+
if (!html.includes("<pre><code")) return html;
|
|
61
|
+
const highlighter = await getHighlighter();
|
|
62
|
+
const shikiTheme = getShikiTheme(theme);
|
|
63
|
+
return html.replace(
|
|
64
|
+
/<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
|
|
65
|
+
(_, lang, code) => {
|
|
66
|
+
const decoded = decodeHtmlEntities(code);
|
|
67
|
+
try {
|
|
68
|
+
return highlighter.codeToHtml(decoded, { lang, theme: shikiTheme });
|
|
69
|
+
} catch {
|
|
70
|
+
return `<pre><code class="language-${lang}">${code}</code></pre>`;
|
|
203
71
|
}
|
|
204
|
-
} catch {
|
|
205
72
|
}
|
|
206
|
-
|
|
207
|
-
slides[slideIndex] = slideContent;
|
|
208
|
-
const joinedSlides = slides.join("\n---\n");
|
|
209
|
-
if (frontmatter) {
|
|
210
|
-
return `${frontmatter}
|
|
211
|
-
|
|
212
|
-
${joinedSlides}`;
|
|
213
|
-
}
|
|
214
|
-
return joinedSlides;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// src/core/layout/presets.ts
|
|
218
|
-
var SLIDE_LAYOUT_PRESETS = [
|
|
219
|
-
{
|
|
220
|
-
id: "title-hero",
|
|
221
|
-
name: "Title & Speaker Cover",
|
|
222
|
-
category: "cover",
|
|
223
|
-
description: "High-impact opening slide with large title, subtitle, and speaker credentials.",
|
|
224
|
-
layout: {
|
|
225
|
-
boxes: [
|
|
226
|
-
{
|
|
227
|
-
id: "title",
|
|
228
|
-
target: "h1",
|
|
229
|
-
pos: [100, 180, 1080, 140],
|
|
230
|
-
fontSize: "3rem",
|
|
231
|
-
textAlign: "center"
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
id: "subtitle",
|
|
235
|
-
target: "h3",
|
|
236
|
-
pos: [100, 350, 1080, 70],
|
|
237
|
-
fontSize: "1.5rem",
|
|
238
|
-
textAlign: "center"
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
id: "speaker-meta",
|
|
242
|
-
target: "p",
|
|
243
|
-
pos: [100, 460, 1080, 80],
|
|
244
|
-
fontSize: "1.1rem",
|
|
245
|
-
textAlign: "center"
|
|
246
|
-
}
|
|
247
|
-
]
|
|
248
|
-
},
|
|
249
|
-
markdownSnippet: `<!-- layout:
|
|
250
|
-
boxes:
|
|
251
|
-
- id: title
|
|
252
|
-
target: h1
|
|
253
|
-
pos: [100, 180, 1080, 140]
|
|
254
|
-
fontSize: 3rem
|
|
255
|
-
textAlign: center
|
|
256
|
-
- id: subtitle
|
|
257
|
-
target: h3
|
|
258
|
-
pos: [100, 350, 1080, 70]
|
|
259
|
-
fontSize: 1.5rem
|
|
260
|
-
textAlign: center
|
|
261
|
-
- id: speaker-meta
|
|
262
|
-
target: p
|
|
263
|
-
pos: [100, 460, 1080, 80]
|
|
264
|
-
fontSize: 1.1rem
|
|
265
|
-
textAlign: center
|
|
266
|
-
-->
|
|
267
|
-
|
|
268
|
-
# \u{1F680} Engineering Scalable Cloud Systems
|
|
269
|
-
### Principles, Bottlenecks, and Real-World Architecture Patterns
|
|
270
|
-
|
|
271
|
-
**Presenter:** Senior Systems Architect | **Duration:** 45 mins | **Level:** Intermediate
|
|
272
|
-
|
|
273
|
-
<!--
|
|
274
|
-
Presenter Notes:
|
|
275
|
-
- Welcome the audience and outline today's primary engineering goals.
|
|
276
|
-
- Spark curiosity with an opening hook about scaling under high concurrency.
|
|
277
|
-
-->`
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
id: "two-column-split",
|
|
281
|
-
name: "Two-Column Comparison",
|
|
282
|
-
category: "split",
|
|
283
|
-
description: "Balanced side-by-side comparison for legacy vs modern patterns or pros vs cons.",
|
|
284
|
-
layout: {
|
|
285
|
-
boxes: [
|
|
286
|
-
{
|
|
287
|
-
id: "heading",
|
|
288
|
-
target: "h2",
|
|
289
|
-
pos: [80, 50, 1120, 70],
|
|
290
|
-
fontSize: "2rem"
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
id: "col-left",
|
|
294
|
-
target: ".columns-2 > div:first-child",
|
|
295
|
-
pos: [80, 150, 530, 510]
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
id: "col-right",
|
|
299
|
-
target: ".columns-2 > div:last-child",
|
|
300
|
-
pos: [670, 150, 530, 510]
|
|
301
|
-
}
|
|
302
|
-
]
|
|
303
|
-
},
|
|
304
|
-
markdownSnippet: `<!-- layout:
|
|
305
|
-
boxes:
|
|
306
|
-
- id: heading
|
|
307
|
-
target: h2
|
|
308
|
-
pos: [80, 50, 1120, 70]
|
|
309
|
-
fontSize: 2rem
|
|
310
|
-
- id: col-left
|
|
311
|
-
target: '.columns-2 > div:first-child'
|
|
312
|
-
pos: [80, 150, 530, 510]
|
|
313
|
-
- id: col-right
|
|
314
|
-
target: '.columns-2 > div:last-child'
|
|
315
|
-
pos: [670, 150, 530, 510]
|
|
316
|
-
-->
|
|
317
|
-
|
|
318
|
-
## \u2696\uFE0F Monolithic Polling vs Event-Driven Architecture
|
|
319
|
-
|
|
320
|
-
<div class="columns-2">
|
|
321
|
-
<div>
|
|
322
|
-
|
|
323
|
-
### \u{1F534} Legacy Polling
|
|
324
|
-
- Heavy database CPU overhead on idle cycles
|
|
325
|
-
- High network latency between updates
|
|
326
|
-
- Prone to cascading timeouts during spikes
|
|
327
|
-
|
|
328
|
-
</div>
|
|
329
|
-
<div>
|
|
330
|
-
|
|
331
|
-
### \u{1F7E2} Event-Driven Streams
|
|
332
|
-
- Real-time event propagation via webhooks
|
|
333
|
-
- Zero idle compute waste with serverless consumers
|
|
334
|
-
- Automatic backpressure and queue isolation
|
|
335
|
-
|
|
336
|
-
</div>
|
|
337
|
-
</div>
|
|
338
|
-
|
|
339
|
-
<!--
|
|
340
|
-
Presenter Notes:
|
|
341
|
-
- Emphasize the core failure mode of periodic polling under sudden load spikes.
|
|
342
|
-
- Ask the room if anyone has experienced database lockups caused by polling loops.
|
|
343
|
-
-->`
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
id: "code-explainer",
|
|
347
|
-
name: "Code Walkthrough & Analysis",
|
|
348
|
-
category: "code",
|
|
349
|
-
description: "Syntax-highlighted code block on the left with step-by-step key annotations on the right.",
|
|
350
|
-
layout: {
|
|
351
|
-
boxes: [
|
|
352
|
-
{
|
|
353
|
-
id: "heading",
|
|
354
|
-
target: "h2",
|
|
355
|
-
pos: [80, 45, 1120, 65],
|
|
356
|
-
fontSize: "1.9rem"
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
id: "code-box",
|
|
360
|
-
target: ".columns-2 > div:first-child",
|
|
361
|
-
pos: [80, 135, 630, 535]
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
id: "notes-box",
|
|
365
|
-
target: ".columns-2 > div:last-child",
|
|
366
|
-
pos: [740, 135, 460, 535]
|
|
367
|
-
}
|
|
368
|
-
]
|
|
369
|
-
},
|
|
370
|
-
markdownSnippet: `<!-- layout:
|
|
371
|
-
boxes:
|
|
372
|
-
- id: heading
|
|
373
|
-
target: h2
|
|
374
|
-
pos: [80, 45, 1120, 65]
|
|
375
|
-
fontSize: 1.9rem
|
|
376
|
-
- id: code-box
|
|
377
|
-
target: '.columns-2 > div:first-child'
|
|
378
|
-
pos: [80, 135, 630, 535]
|
|
379
|
-
- id: notes-box
|
|
380
|
-
target: '.columns-2 > div:last-child'
|
|
381
|
-
pos: [740, 135, 460, 535]
|
|
382
|
-
-->
|
|
383
|
-
|
|
384
|
-
## \u{1F4BB} Robust Async Transaction Pipeline
|
|
385
|
-
|
|
386
|
-
<div class="columns-2">
|
|
387
|
-
<div>
|
|
388
|
-
|
|
389
|
-
\`\`\`ts
|
|
390
|
-
// Process payment with idempotency guarantee
|
|
391
|
-
export async function processPayment(order: Order) {
|
|
392
|
-
const isValid = await validateOrder(order);
|
|
393
|
-
if (!isValid) throw new Error("ValidationFailed");
|
|
394
|
-
|
|
395
|
-
const receipt = await chargeCard(order);
|
|
396
|
-
return sendConfirmation(receipt);
|
|
397
|
-
}
|
|
398
|
-
\`\`\`
|
|
399
|
-
|
|
400
|
-
</div>
|
|
401
|
-
<div>
|
|
402
|
-
|
|
403
|
-
### \u{1F50D} Key Engineering Invariants:
|
|
404
|
-
1. **Early Guard Clause:** Validates payload immediately at line 3 before triggering external calls.
|
|
405
|
-
2. **Deterministic Sequence:** Enforces strict order of operations using native \`await\`.
|
|
406
|
-
3. **Audit Trail:** Returns immutable payment receipt upon successful execution.
|
|
407
|
-
|
|
408
|
-
</div>
|
|
409
|
-
</div>
|
|
410
|
-
|
|
411
|
-
<!--
|
|
412
|
-
Presenter Notes:
|
|
413
|
-
- Walk through lines 1 to 7 sequentially.
|
|
414
|
-
- Cold-call check: "What happens if line 6 throws a network timeout?"
|
|
415
|
-
- Cognitive scaffolding: Highlight the importance of idempotency keys in payment APIs.
|
|
416
|
-
-->`
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
id: "metrics-3-card",
|
|
420
|
-
name: "3-Metric Key Results Grid",
|
|
421
|
-
category: "metrics",
|
|
422
|
-
description: "Three high-visibility KPI stat cards for benchmarks, impact, and success criteria.",
|
|
423
|
-
layout: {
|
|
424
|
-
boxes: [
|
|
425
|
-
{
|
|
426
|
-
id: "heading",
|
|
427
|
-
target: "h2",
|
|
428
|
-
pos: [80, 50, 1120, 70],
|
|
429
|
-
fontSize: "2rem",
|
|
430
|
-
textAlign: "center"
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
id: "metric-1",
|
|
434
|
-
target: ".columns-3 > div:nth-child(1)",
|
|
435
|
-
pos: [80, 165, 340, 485]
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
id: "metric-2",
|
|
439
|
-
target: ".columns-3 > div:nth-child(2)",
|
|
440
|
-
pos: [470, 165, 340, 485]
|
|
441
|
-
},
|
|
442
|
-
{
|
|
443
|
-
id: "metric-3",
|
|
444
|
-
target: ".columns-3 > div:nth-child(3)",
|
|
445
|
-
pos: [860, 165, 340, 485]
|
|
446
|
-
}
|
|
447
|
-
]
|
|
448
|
-
},
|
|
449
|
-
markdownSnippet: `<!-- layout:
|
|
450
|
-
boxes:
|
|
451
|
-
- id: heading
|
|
452
|
-
target: h2
|
|
453
|
-
pos: [80, 50, 1120, 70]
|
|
454
|
-
fontSize: 2rem
|
|
455
|
-
textAlign: center
|
|
456
|
-
- id: metric-1
|
|
457
|
-
target: '.columns-3 > div:nth-child(1)'
|
|
458
|
-
pos: [80, 165, 340, 485]
|
|
459
|
-
- id: metric-2
|
|
460
|
-
target: '.columns-3 > div:nth-child(2)'
|
|
461
|
-
pos: [470, 165, 340, 485]
|
|
462
|
-
- id: metric-3
|
|
463
|
-
target: '.columns-3 > div:nth-child(3)'
|
|
464
|
-
pos: [860, 165, 340, 485]
|
|
465
|
-
-->
|
|
466
|
-
|
|
467
|
-
## \u{1F4CA} Performance & Reliability Benchmarks
|
|
468
|
-
|
|
469
|
-
<div class="columns-3">
|
|
470
|
-
<div>
|
|
471
|
-
|
|
472
|
-
# \u26A1 99.99%
|
|
473
|
-
### SLA Availability
|
|
474
|
-
High-availability multi-region cluster with automated failover routing.
|
|
475
|
-
|
|
476
|
-
</div>
|
|
477
|
-
<div>
|
|
478
|
-
|
|
479
|
-
# \u{1F680} 15ms
|
|
480
|
-
### P99 Latency
|
|
481
|
-
Sub-20ms roundtrip response time via distributed edge caching.
|
|
482
|
-
|
|
483
|
-
</div>
|
|
484
|
-
<div>
|
|
485
|
-
|
|
486
|
-
# \u{1F6E1}\uFE0F Zero
|
|
487
|
-
### Unhandled Breaches
|
|
488
|
-
Full SOC2 Type II compliance and end-to-end payload encryption.
|
|
489
|
-
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
|
|
493
|
-
<!--
|
|
494
|
-
Presenter Notes:
|
|
495
|
-
- Highlight the 15ms P99 latency target as the primary technical milestone.
|
|
496
|
-
- Connect these metrics directly to the architecture decisions discussed next.
|
|
497
|
-
-->`
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
id: "process-flow",
|
|
501
|
-
name: "Architecture & Process Flow",
|
|
502
|
-
category: "diagram",
|
|
503
|
-
description: "Multi-stage workflow with responsive Mermaid diagram and phase summaries.",
|
|
504
|
-
layout: {
|
|
505
|
-
boxes: [
|
|
506
|
-
{
|
|
507
|
-
id: "heading",
|
|
508
|
-
target: "h2",
|
|
509
|
-
pos: [80, 45, 1120, 65],
|
|
510
|
-
fontSize: "1.9rem"
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
id: "diagram-box",
|
|
514
|
-
target: "pre.mermaid, .mermaid",
|
|
515
|
-
pos: [80, 130, 1120, 310]
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
id: "summary-box",
|
|
519
|
-
target: "ul, p",
|
|
520
|
-
pos: [80, 465, 1120, 205]
|
|
521
|
-
}
|
|
522
|
-
]
|
|
523
|
-
},
|
|
524
|
-
markdownSnippet: `<!-- layout:
|
|
525
|
-
boxes:
|
|
526
|
-
- id: heading
|
|
527
|
-
target: h2
|
|
528
|
-
pos: [80, 45, 1120, 65]
|
|
529
|
-
fontSize: 1.9rem
|
|
530
|
-
- id: diagram-box
|
|
531
|
-
target: 'pre.mermaid, .mermaid'
|
|
532
|
-
pos: [80, 130, 1120, 310]
|
|
533
|
-
- id: summary-box
|
|
534
|
-
target: 'ul, p'
|
|
535
|
-
pos: [80, 465, 1120, 205]
|
|
536
|
-
-->
|
|
537
|
-
|
|
538
|
-
## \u{1F504} End-to-End Data Ingestion Pipeline
|
|
539
|
-
|
|
540
|
-
\`\`\`mermaid
|
|
541
|
-
graph LR
|
|
542
|
-
A[1. Client Ingress] --> B[2. API Gateway]
|
|
543
|
-
B --> C[3. Auth Verifier]
|
|
544
|
-
C --> D[4. Distributed DB]
|
|
545
|
-
style A fill:#0284c7,stroke:#38bdf8,stroke-width:2px,color:#fff
|
|
546
|
-
style B fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#fff
|
|
547
|
-
style C fill:#059669,stroke:#34d399,stroke-width:2px,color:#fff
|
|
548
|
-
style D fill:#7c3aed,stroke:#a78bfa,stroke-width:2px,color:#fff
|
|
549
|
-
\`\`\`
|
|
550
|
-
|
|
551
|
-
- **Ingress & Gateway:** Rate-limits traffic and applies reverse-proxy load balancing.
|
|
552
|
-
- **Verification & Storage:** Decodes cryptographic JWT tokens before transactional write commit.
|
|
553
|
-
|
|
554
|
-
<!--
|
|
555
|
-
Presenter Notes:
|
|
556
|
-
- Trace each hop across the diagram from left to right.
|
|
557
|
-
- Ask: "Where should rate limiting be applied to prevent DDoS attacks?"
|
|
558
|
-
-->`
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
id: "quote-highlight",
|
|
562
|
-
name: "Core Principle & Quote Highlight",
|
|
563
|
-
category: "quote",
|
|
564
|
-
description: "Prominent quote callout for fundamental engineering rules or memorable mantras.",
|
|
565
|
-
layout: {
|
|
566
|
-
boxes: [
|
|
567
|
-
{
|
|
568
|
-
id: "heading",
|
|
569
|
-
target: "h2",
|
|
570
|
-
pos: [100, 100, 1080, 80],
|
|
571
|
-
fontSize: "2.2rem",
|
|
572
|
-
textAlign: "center"
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
id: "quote-box",
|
|
576
|
-
target: "blockquote",
|
|
577
|
-
pos: [140, 230, 1e3, 360],
|
|
578
|
-
fontSize: "1.4rem"
|
|
579
|
-
}
|
|
580
|
-
]
|
|
581
|
-
},
|
|
582
|
-
markdownSnippet: `<!-- layout:
|
|
583
|
-
boxes:
|
|
584
|
-
- id: heading
|
|
585
|
-
target: h2
|
|
586
|
-
pos: [100, 100, 1080, 80]
|
|
587
|
-
fontSize: 2.2rem
|
|
588
|
-
textAlign: center
|
|
589
|
-
- id: quote-box
|
|
590
|
-
target: blockquote
|
|
591
|
-
pos: [140, 230, 1000, 360]
|
|
592
|
-
fontSize: 1.4rem
|
|
593
|
-
-->
|
|
594
|
-
|
|
595
|
-
## \u{1F4A1} Foundational Architecture Principle
|
|
596
|
-
|
|
597
|
-
> "Simplicity is prerequisite for reliability. A well-engineered distributed system is one where a new engineer can understand the request flow within their first hour."
|
|
598
|
-
>
|
|
599
|
-
> \u2014 **Edsger W. Dijkstra**
|
|
600
|
-
|
|
601
|
-
<!--
|
|
602
|
-
Presenter Notes:
|
|
603
|
-
- Pause for 5 seconds of silence to let the quote sink in.
|
|
604
|
-
- Ask: "How do our current architectural choices reflect this principle?"
|
|
605
|
-
-->`
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
id: "quiz-checkpoint",
|
|
609
|
-
name: "Interactive Formative Checkpoint",
|
|
610
|
-
category: "assessment",
|
|
611
|
-
description: "Interactive multiple-choice diagnostic checkpoint with A/B/C/D voting options.",
|
|
612
|
-
layout: {
|
|
613
|
-
boxes: [
|
|
614
|
-
{
|
|
615
|
-
id: "heading",
|
|
616
|
-
target: "h2",
|
|
617
|
-
pos: [80, 45, 1120, 65],
|
|
618
|
-
fontSize: "1.9rem"
|
|
619
|
-
},
|
|
620
|
-
{
|
|
621
|
-
id: "question-callout",
|
|
622
|
-
target: "blockquote",
|
|
623
|
-
pos: [80, 130, 1120, 110],
|
|
624
|
-
fontSize: "1.2rem"
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
id: "options-list",
|
|
628
|
-
target: "ul",
|
|
629
|
-
pos: [80, 260, 1120, 390],
|
|
630
|
-
fontSize: "1.15rem"
|
|
631
|
-
}
|
|
632
|
-
]
|
|
633
|
-
},
|
|
634
|
-
markdownSnippet: `<!-- layout:
|
|
635
|
-
boxes:
|
|
636
|
-
- id: heading
|
|
637
|
-
target: h2
|
|
638
|
-
pos: [80, 45, 1120, 65]
|
|
639
|
-
fontSize: 1.9rem
|
|
640
|
-
- id: question-callout
|
|
641
|
-
target: blockquote
|
|
642
|
-
pos: [80, 130, 1120, 110]
|
|
643
|
-
fontSize: 1.2rem
|
|
644
|
-
- id: options-list
|
|
645
|
-
target: ul
|
|
646
|
-
pos: [80, 260, 1120, 390]
|
|
647
|
-
fontSize: 1.15rem
|
|
648
|
-
-->
|
|
649
|
-
|
|
650
|
-
## \u{1F3AF} Formative Checkpoint: Verify Your Understanding
|
|
651
|
-
|
|
652
|
-
> **Question:** In an event-driven architecture, which pattern guarantees that messages are never permanently lost if a downstream consumer crashes?
|
|
653
|
-
|
|
654
|
-
- **A.** Direct HTTP POST call with a short 2-second timeout
|
|
655
|
-
- **B.** Persistent Message Broker with Dead-Letter Queue (DLQ) & retry policy
|
|
656
|
-
- **C.** In-memory client RAM cache without disk persistence
|
|
657
|
-
- **D.** Discarding failed packets and prompting the user to re-submit
|
|
658
|
-
|
|
659
|
-
<!--
|
|
660
|
-
Presenter Notes:
|
|
661
|
-
- Give students 30 seconds to vote on option A, B, C, or D.
|
|
662
|
-
- Correct answer: B.
|
|
663
|
-
- Explain why options A and C introduce catastrophic data loss in production.
|
|
664
|
-
-->`
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
id: "tiered-practice-3cards",
|
|
668
|
-
name: "Tiered Differentiation (Bronze / Silver / Gold)",
|
|
669
|
-
category: "content",
|
|
670
|
-
description: "Three progressive competency tiers allowing self-paced learning acceleration.",
|
|
671
|
-
layout: {
|
|
672
|
-
boxes: [
|
|
673
|
-
{
|
|
674
|
-
id: "heading",
|
|
675
|
-
target: "h2",
|
|
676
|
-
pos: [80, 45, 1120, 65],
|
|
677
|
-
fontSize: "1.9rem"
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
id: "tier-bronze",
|
|
681
|
-
target: ".columns-3 > div:nth-child(1)",
|
|
682
|
-
pos: [80, 140, 350, 520]
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
id: "tier-silver",
|
|
686
|
-
target: ".columns-3 > div:nth-child(2)",
|
|
687
|
-
pos: [465, 140, 350, 520]
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
id: "tier-gold",
|
|
691
|
-
target: ".columns-3 > div:nth-child(3)",
|
|
692
|
-
pos: [850, 140, 350, 520]
|
|
693
|
-
}
|
|
694
|
-
]
|
|
695
|
-
},
|
|
696
|
-
markdownSnippet: `<!-- layout:
|
|
697
|
-
boxes:
|
|
698
|
-
- id: heading
|
|
699
|
-
target: h2
|
|
700
|
-
pos: [80, 45, 1120, 65]
|
|
701
|
-
fontSize: 1.9rem
|
|
702
|
-
- id: tier-bronze
|
|
703
|
-
target: '.columns-3 > div:nth-child(1)'
|
|
704
|
-
pos: [80, 140, 350, 520]
|
|
705
|
-
- id: tier-silver
|
|
706
|
-
target: '.columns-3 > div:nth-child(2)'
|
|
707
|
-
pos: [465, 140, 350, 520]
|
|
708
|
-
- id: tier-gold
|
|
709
|
-
target: '.columns-3 > div:nth-child(3)'
|
|
710
|
-
pos: [850, 140, 350, 520]
|
|
711
|
-
-->
|
|
712
|
-
|
|
713
|
-
## \u{1F6E0}\uFE0F Hands-On Challenge: 3-Tier Differentiation
|
|
714
|
-
|
|
715
|
-
<div class="columns-3">
|
|
716
|
-
<div>
|
|
717
|
-
|
|
718
|
-
### \u{1F949} Bronze Tier
|
|
719
|
-
- Implement basic handler logic
|
|
720
|
-
- Pass 3 baseline unit tests
|
|
721
|
-
- *Goal: Foundational mastery*
|
|
722
|
-
|
|
723
|
-
</div>
|
|
724
|
-
<div>
|
|
725
|
-
|
|
726
|
-
### \u{1F948} Silver Tier
|
|
727
|
-
- Add boundary error handling
|
|
728
|
-
- Enforce O(N) memory complexity
|
|
729
|
-
- *Goal: Production-ready code*
|
|
730
|
-
|
|
731
|
-
</div>
|
|
732
|
-
<div>
|
|
733
|
-
|
|
734
|
-
### \u{1F947} Gold Tier
|
|
735
|
-
- Design distributed retry queue
|
|
736
|
-
- Add 95% automated test coverage
|
|
737
|
-
- *Goal: Architectural leadership*
|
|
738
|
-
|
|
739
|
-
</div>
|
|
740
|
-
</div>
|
|
741
|
-
|
|
742
|
-
<!--
|
|
743
|
-
Presenter Notes:
|
|
744
|
-
- All learners begin at Bronze to establish baseline competency.
|
|
745
|
-
- Accelerate self-directed learners to Silver and Gold as they complete each tier.
|
|
746
|
-
-->`
|
|
747
|
-
},
|
|
748
|
-
{
|
|
749
|
-
id: "agenda-timeline",
|
|
750
|
-
name: "Session Agenda & Milestones",
|
|
751
|
-
category: "content",
|
|
752
|
-
description: "Structured time-budgeted agenda mapping stages to measurable deliverables.",
|
|
753
|
-
layout: {
|
|
754
|
-
boxes: [
|
|
755
|
-
{
|
|
756
|
-
id: "heading",
|
|
757
|
-
target: "h2",
|
|
758
|
-
pos: [80, 45, 1120, 65],
|
|
759
|
-
fontSize: "1.9rem"
|
|
760
|
-
},
|
|
761
|
-
{
|
|
762
|
-
id: "table-box",
|
|
763
|
-
target: "table",
|
|
764
|
-
pos: [80, 140, 1120, 520]
|
|
765
|
-
}
|
|
766
|
-
]
|
|
767
|
-
},
|
|
768
|
-
markdownSnippet: `<!-- layout:
|
|
769
|
-
boxes:
|
|
770
|
-
- id: heading
|
|
771
|
-
target: h2
|
|
772
|
-
pos: [80, 45, 1120, 65]
|
|
773
|
-
fontSize: 1.9rem
|
|
774
|
-
- id: table-box
|
|
775
|
-
target: table
|
|
776
|
-
pos: [80, 140, 1120, 520]
|
|
777
|
-
-->
|
|
778
|
-
|
|
779
|
-
## \u{1F5FA}\uFE0F Milestone Roadmap & Time Budget
|
|
780
|
-
|
|
781
|
-
| Allocated Time | Session Stage | Core Learning Focus | Deliverable Milestone |
|
|
782
|
-
|:---:|---|---|---|
|
|
783
|
-
| **05 mins** | Warm-Up | Real-World Scenario & Context | Identify system bottleneck |
|
|
784
|
-
| **15 mins** | Concept Discovery | Core Architectural Patterns | Deconstruct message queue models |
|
|
785
|
-
| **20 mins** | Hands-On Lab | Tiered Code Implementation | Complete Bronze + Silver tasks |
|
|
786
|
-
| **05 mins** | Wrap-Up | Formative Checkpoint & Debrief | Consolidate key principles |
|
|
787
|
-
|
|
788
|
-
<!--
|
|
789
|
-
Presenter Notes:
|
|
790
|
-
- Walk through the time budget so students understand expectations and pacing.
|
|
791
|
-
-->`
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
id: "takeaways-summary",
|
|
795
|
-
name: "Key Takeaways & Wrap-up",
|
|
796
|
-
category: "content",
|
|
797
|
-
description: "Three memorable synthesis takeaways with actionable next steps.",
|
|
798
|
-
layout: {
|
|
799
|
-
boxes: [
|
|
800
|
-
{
|
|
801
|
-
id: "heading",
|
|
802
|
-
target: "h2",
|
|
803
|
-
pos: [80, 50, 1120, 70],
|
|
804
|
-
fontSize: "2rem"
|
|
805
|
-
},
|
|
806
|
-
{
|
|
807
|
-
id: "list-box",
|
|
808
|
-
target: "ol",
|
|
809
|
-
pos: [80, 150, 1120, 340],
|
|
810
|
-
fontSize: "1.25rem"
|
|
811
|
-
},
|
|
812
|
-
{
|
|
813
|
-
id: "callout-next",
|
|
814
|
-
target: "blockquote",
|
|
815
|
-
pos: [80, 520, 1120, 140],
|
|
816
|
-
fontSize: "1.1rem"
|
|
817
|
-
}
|
|
818
|
-
]
|
|
819
|
-
},
|
|
820
|
-
markdownSnippet: `<!-- layout:
|
|
821
|
-
boxes:
|
|
822
|
-
- id: heading
|
|
823
|
-
target: h2
|
|
824
|
-
pos: [80, 50, 1120, 70]
|
|
825
|
-
fontSize: 2rem
|
|
826
|
-
- id: list-box
|
|
827
|
-
target: ol
|
|
828
|
-
pos: [80, 150, 1120, 340]
|
|
829
|
-
fontSize: 1.25rem
|
|
830
|
-
- id: callout-next
|
|
831
|
-
target: blockquote
|
|
832
|
-
pos: [80, 520, 1120, 140]
|
|
833
|
-
fontSize: 1.1rem
|
|
834
|
-
-->
|
|
835
|
-
|
|
836
|
-
## \u{1F3C1} Key Takeaways & Action Items
|
|
837
|
-
|
|
838
|
-
1. **Decouple Before Scaling:** Separate producer rate from consumer processing capacity.
|
|
839
|
-
2. **Design for Failure:** Always configure retries, backoff intervals, and dead-letter queues.
|
|
840
|
-
3. **Measure End-to-End:** Track P99 latency and error rates across all distributed boundaries.
|
|
841
|
-
|
|
842
|
-
> \u{1F4DD} **Next Action Item:** Complete the Silver Tier refactoring challenge before our next peer-review session.
|
|
843
|
-
|
|
844
|
-
<!--
|
|
845
|
-
Presenter Notes:
|
|
846
|
-
- Deliver a crisp 2-minute wrap-up reinforcing the 3 core takeaways.
|
|
847
|
-
- Acknowledge strong peer collaboration during the hands-on lab.
|
|
848
|
-
-->`
|
|
849
|
-
}
|
|
850
|
-
];
|
|
851
|
-
function getSlideLayoutPresetById(id) {
|
|
852
|
-
return SLIDE_LAYOUT_PRESETS.find((p) => p.id === id);
|
|
853
|
-
}
|
|
854
|
-
function getSlideLayoutPresetsByCategory(category) {
|
|
855
|
-
return SLIDE_LAYOUT_PRESETS.filter((p) => p.category === category);
|
|
73
|
+
);
|
|
856
74
|
}
|
|
857
75
|
|
|
858
76
|
// src/html-engine/themes.ts
|
|
@@ -1280,6 +498,133 @@ var HTML_SLIDE_PRESETS = {
|
|
|
1280
498
|
}
|
|
1281
499
|
};
|
|
1282
500
|
|
|
501
|
+
// src/html-engine/utilities.ts
|
|
502
|
+
var FONT_SIZES = [
|
|
503
|
+
// class, var, fallback px, line-height
|
|
504
|
+
["xs", "--font-size-xs", "14px", "1.4"],
|
|
505
|
+
["sm", "--font-size-sm", "16px", "1.45"],
|
|
506
|
+
["base", "--font-size-base", "18px", "1.5"],
|
|
507
|
+
["lg", "--font-size-lg", "20px", "1.5"],
|
|
508
|
+
["xl", "--font-size-xl", "22px", "1.5"],
|
|
509
|
+
["2xl", "--font-size-2xl", "26px", "1.4"],
|
|
510
|
+
["3xl", "--font-size-3xl", "32px", "1.3"],
|
|
511
|
+
["4xl", "--font-size-4xl", "38px", "1.25"],
|
|
512
|
+
["6xl", "--font-size-6xl", "58px", "1.15"],
|
|
513
|
+
["8xl", "--font-size-8xl", "96px", "1.05"]
|
|
514
|
+
];
|
|
515
|
+
var SPACING = {
|
|
516
|
+
"0.5": "0.125rem",
|
|
517
|
+
"1": "0.25rem",
|
|
518
|
+
"1.5": "0.375rem",
|
|
519
|
+
"2": "0.5rem",
|
|
520
|
+
"2.5": "0.625rem",
|
|
521
|
+
"3": "0.75rem",
|
|
522
|
+
"4": "1rem",
|
|
523
|
+
"5": "1.25rem",
|
|
524
|
+
"6": "1.5rem",
|
|
525
|
+
"7": "1.75rem",
|
|
526
|
+
"8": "2rem",
|
|
527
|
+
"10": "2.5rem",
|
|
528
|
+
"12": "3rem",
|
|
529
|
+
"16": "4rem",
|
|
530
|
+
"20": "5rem",
|
|
531
|
+
"24": "6rem"
|
|
532
|
+
};
|
|
533
|
+
function sp(value) {
|
|
534
|
+
return `calc(${SPACING[value]} * var(--scale-spacing-eff, 1))`;
|
|
535
|
+
}
|
|
536
|
+
function fontSizeRules() {
|
|
537
|
+
return FONT_SIZES.map(
|
|
538
|
+
([cls, cssVar, fallback, lh]) => `.text-${cls}{font-size:var(${cssVar}, ${fallback});line-height:${lh}}`
|
|
539
|
+
).join("\n");
|
|
540
|
+
}
|
|
541
|
+
function spacingRules() {
|
|
542
|
+
const out = [];
|
|
543
|
+
for (const value of Object.keys(SPACING)) {
|
|
544
|
+
out.push(`.p-${value}{padding:${sp(value)}}`);
|
|
545
|
+
out.push(`.px-${value}{padding-left:${sp(value)};padding-right:${sp(value)}}`);
|
|
546
|
+
out.push(`.py-${value}{padding-top:${sp(value)};padding-bottom:${sp(value)}}`);
|
|
547
|
+
out.push(`.pt-${value}{padding-top:${sp(value)}}`);
|
|
548
|
+
out.push(`.pb-${value}{padding-bottom:${sp(value)}}`);
|
|
549
|
+
out.push(`.mt-${value}{margin-top:${sp(value)}}`);
|
|
550
|
+
out.push(`.mb-${value}{margin-bottom:${sp(value)}}`);
|
|
551
|
+
out.push(`.my-${value}{margin-top:${sp(value)};margin-bottom:${sp(value)}`);
|
|
552
|
+
out.push(`.gap-${value}{gap:${sp(value)}}`);
|
|
553
|
+
}
|
|
554
|
+
return out.join("\n");
|
|
555
|
+
}
|
|
556
|
+
function sizeRules() {
|
|
557
|
+
const out = [];
|
|
558
|
+
for (const value of Object.keys(SPACING)) {
|
|
559
|
+
out.push(`.w-${value}{width:${sp(value)}}`);
|
|
560
|
+
out.push(`.h-${value}{height:${sp(value)}}`);
|
|
561
|
+
}
|
|
562
|
+
return out.join("\n");
|
|
563
|
+
}
|
|
564
|
+
var SLIDE_UTILITY_CSS = `
|
|
565
|
+
/* preflight \u2014 the presets were authored against Tailwind's reset (no default
|
|
566
|
+
margins, no list markers, border-box everywhere), so it has to come along. */
|
|
567
|
+
.slide-page *,.slide-page *::before,.slide-page *::after{box-sizing:border-box;border-width:0;border-style:solid}
|
|
568
|
+
.slide-page h1,.slide-page h2,.slide-page h3,.slide-page h4,.slide-page h5,.slide-page h6,
|
|
569
|
+
.slide-page p,.slide-page ul,.slide-page ol,.slide-page li,.slide-page figure,.slide-page blockquote,
|
|
570
|
+
.slide-page pre,.slide-page dl,.slide-page dd{margin:0;padding:0}
|
|
571
|
+
.slide-page ul,.slide-page ol{list-style:none}
|
|
572
|
+
.slide-page img,.slide-page svg,.slide-page video{display:block;max-width:100%}
|
|
573
|
+
.slide-page button{font:inherit;background:none;border:0;cursor:pointer;color:inherit}
|
|
574
|
+
.slide-page table{border-collapse:collapse}
|
|
575
|
+
.slide-page a{color:inherit;text-decoration:inherit}
|
|
576
|
+
/* layout */
|
|
577
|
+
.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.inline-block{display:inline-block}
|
|
578
|
+
.flex-col{flex-direction:column}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink-0{flex-shrink:0}
|
|
579
|
+
.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
580
|
+
.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
581
|
+
.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}
|
|
582
|
+
.col-span-5{grid-column:span 5/span 5}.col-span-7{grid-column:span 7/span 7}
|
|
583
|
+
.items-start{align-items:flex-start}.items-center{align-items:center}.items-stretch{align-items:stretch}
|
|
584
|
+
.justify-center{justify-content:center}.justify-between{justify-content:space-between}
|
|
585
|
+
.relative{position:relative}.overflow-hidden{overflow:hidden}.z-10{z-index:10}
|
|
586
|
+
.h-full{height:100%}.w-full{width:100%}.min-h-0{min-height:0}.my-auto{margin-top:auto;margin-bottom:auto}
|
|
587
|
+
.text-center{text-align:center}
|
|
588
|
+
/* typography */
|
|
589
|
+
.font-medium{font-weight:500}.font-semibold{font-weight:600}.font-bold{font-weight:700}
|
|
590
|
+
.font-extrabold{font-weight:800}.font-black{font-weight:900}.font-mono{font-family:var(--font-code)}
|
|
591
|
+
.tracking-tighter{letter-spacing:-0.05em}.tracking-tight{letter-spacing:-0.025em}
|
|
592
|
+
.tracking-wider{letter-spacing:0.05em}.tracking-widest{letter-spacing:0.1em}
|
|
593
|
+
.uppercase{text-transform:uppercase}
|
|
594
|
+
.leading-tight{line-height:1.25}.leading-snug{line-height:1.375}.leading-relaxed{line-height:1.625}
|
|
595
|
+
.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}
|
|
596
|
+
${fontSizeRules()}
|
|
597
|
+
/* spacing (scaled by the density editor) */
|
|
598
|
+
${spacingRules()}
|
|
599
|
+
${sizeRules()}
|
|
600
|
+
.space-y-3 > * + *{margin-top:${sp("3")}}
|
|
601
|
+
.space-y-4 > * + *{margin-top:${sp("4")}}
|
|
602
|
+
.space-y-6 > * + *{margin-top:${sp("6")}}
|
|
603
|
+
/* surfaces & color (theme-variable driven) */
|
|
604
|
+
.rounded{border-radius:0.25rem}.rounded-xl{border-radius:0.75rem}.rounded-full{border-radius:9999px}
|
|
605
|
+
.border{border-width:1px;border-style:solid;border-color:var(--border)}
|
|
606
|
+
.border-t{border-top-width:1px;border-top-style:solid;border-top-color:var(--border)}
|
|
607
|
+
.border-b{border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:var(--border)}
|
|
608
|
+
.border-current{border-color:currentColor}
|
|
609
|
+
.shadow{box-shadow:0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.08)}
|
|
610
|
+
.shadow-sm{box-shadow:0 1px 2px rgba(0,0,0,.08)}
|
|
611
|
+
.shadow-lg{box-shadow:0 10px 15px -3px rgba(0,0,0,.15),0 4px 6px -4px rgba(0,0,0,.1)}
|
|
612
|
+
.opacity-60{opacity:.6}.opacity-65{opacity:.65}.opacity-75{opacity:.75}
|
|
613
|
+
.opacity-80{opacity:.8}.opacity-85{opacity:.85}.opacity-90{opacity:.9}
|
|
614
|
+
.bg-red-500\\/70{background-color:rgba(239,68,68,.7)}
|
|
615
|
+
.bg-yellow-500\\/70{background-color:rgba(234,179,8,.7)}
|
|
616
|
+
.bg-green-500\\/70{background-color:rgba(34,197,94,.7)}
|
|
617
|
+
/* theme color utilities (previously provided by tailwind.config extend) */
|
|
618
|
+
.text-primary{color:var(--primary)}.text-muted{color:var(--text-muted)}.text-themeText{color:var(--text)}
|
|
619
|
+
.bg-themeBg{background-color:var(--bg)}.bg-themeBgSecondary{background-color:var(--bg-secondary)}
|
|
620
|
+
.bg-themePrimary{background-color:var(--primary)}.border-themeBorder{border-color:var(--border)}
|
|
621
|
+
`.trim();
|
|
622
|
+
function listUtilityClasses() {
|
|
623
|
+
return Array.from(SLIDE_UTILITY_CSS.matchAll(/\.([a-zA-Z0-9\\/_.-]+)(?=[{,: >])/g)).map(
|
|
624
|
+
(m) => m[1].replace(/\\\//g, "/")
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
1283
628
|
// src/html-engine/shell.ts
|
|
1284
629
|
function generateHtmlShell(options) {
|
|
1285
630
|
const { title, theme, slidesHtml, slideCount, customHeadTags = "", deckId = "" } = options;
|
|
@@ -1291,12 +636,16 @@ function generateHtmlShell(options) {
|
|
|
1291
636
|
<title>${title}</title>
|
|
1292
637
|
|
|
1293
638
|
<!-- Fonts -->
|
|
639
|
+
<!-- DECK_FONT_BLOCK_START -->${options?.fontFaceCss ? `
|
|
640
|
+
<style>
|
|
641
|
+
${options.fontFaceCss}
|
|
642
|
+
</style>` : `
|
|
1294
643
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
1295
644
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
1296
|
-
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@500;700;800&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet"
|
|
645
|
+
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@500;700;800&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet">`}<!-- DECK_FONT_BLOCK_END -->
|
|
1297
646
|
|
|
1298
|
-
<!-- Core Presentation Styling & 1920x1080 Scaled
|
|
1299
|
-
<
|
|
647
|
+
<!-- Core Presentation Styling & 1920x1080 Scaled Layout Utilities (inlined) -->
|
|
648
|
+
<style>${SLIDE_UTILITY_CSS}</style>
|
|
1300
649
|
<script>
|
|
1301
650
|
window.tailwind = window.tailwind || {};
|
|
1302
651
|
tailwind.config = {
|
|
@@ -1446,6 +795,59 @@ function generateHtmlShell(options) {
|
|
|
1446
795
|
opacity: 1;
|
|
1447
796
|
}
|
|
1448
797
|
|
|
798
|
+
/* \u2500\u2500 Bespoke (authored) slides \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
799
|
+
The frontend-slides mode hands the model the whole slide surface, so the
|
|
800
|
+
shell owns only what must stay invariant: full-stage sizing, border-box
|
|
801
|
+
maths, theme type defaults, and one orchestrated entrance. */
|
|
802
|
+
.bespoke-slide {
|
|
803
|
+
position: relative;
|
|
804
|
+
width: 100%;
|
|
805
|
+
height: 100%;
|
|
806
|
+
overflow: hidden;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.bespoke-slide,
|
|
810
|
+
.bespoke-slide *,
|
|
811
|
+
.bespoke-slide *::before,
|
|
812
|
+
.bespoke-slide *::after {
|
|
813
|
+
box-sizing: border-box;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.bespoke-slide h1,
|
|
817
|
+
.bespoke-slide h2 {
|
|
818
|
+
font-family: var(--font-display);
|
|
819
|
+
letter-spacing: -0.02em;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
.bespoke-slide img,
|
|
823
|
+
.bespoke-slide svg {
|
|
824
|
+
max-width: 100%;
|
|
825
|
+
height: auto;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.slide-page.active .bespoke-slide > * {
|
|
829
|
+
animation: bespokeReveal 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
.slide-page.active .bespoke-slide > *:nth-child(2) { animation-delay: 0.08s; }
|
|
833
|
+
.slide-page.active .bespoke-slide > *:nth-child(3) { animation-delay: 0.16s; }
|
|
834
|
+
.slide-page.active .bespoke-slide > *:nth-child(4) { animation-delay: 0.24s; }
|
|
835
|
+
.slide-page.active .bespoke-slide > *:nth-child(5) { animation-delay: 0.32s; }
|
|
836
|
+
.slide-page.active .bespoke-slide > *:nth-child(n + 6) { animation-delay: 0.4s; }
|
|
837
|
+
|
|
838
|
+
@keyframes bespokeReveal {
|
|
839
|
+
from { opacity: 0; transform: translateY(14px); }
|
|
840
|
+
to { opacity: 1; transform: none; }
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
@media (prefers-reduced-motion: reduce) {
|
|
844
|
+
.slide-page.active .bespoke-slide > * {
|
|
845
|
+
animation: none;
|
|
846
|
+
opacity: 1;
|
|
847
|
+
transform: none;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
1449
851
|
/* Top Progress Bar */
|
|
1450
852
|
#progress-bar {
|
|
1451
853
|
position: absolute;
|
|
@@ -3588,15 +2990,152 @@ main();`;
|
|
|
3588
2990
|
return slots;
|
|
3589
2991
|
}
|
|
3590
2992
|
|
|
2993
|
+
// src/html-engine/sanitize.ts
|
|
2994
|
+
var CONTAINER_TAGS = ["script", "iframe", "object", "embed", "applet", "form", "button", "textarea", "select", "template"];
|
|
2995
|
+
var VOID_TAGS = ["link", "meta", "base", "input"];
|
|
2996
|
+
var CONTAINER_ELEMENT_RE = new RegExp(
|
|
2997
|
+
`<(${CONTAINER_TAGS.join("|")})\\b[\\s\\S]*?<\\/\\1\\s*>`,
|
|
2998
|
+
"gi"
|
|
2999
|
+
);
|
|
3000
|
+
var STRAY_TAG_RE = new RegExp(`<\\/?(?:${[...CONTAINER_TAGS, ...VOID_TAGS].join("|")})\\b[^>]*>`, "gi");
|
|
3001
|
+
var EVENT_ATTR_RE = /\s+on[a-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
|
|
3002
|
+
var EXTERNAL_URL_RE = /^(?:https?:)?\/\//i;
|
|
3003
|
+
var CSS_URL_RE = /url\(\s*(?:"([^"]*)"|'([^']*)'|([^)'"\s]*))\s*\)/gi;
|
|
3004
|
+
var CSS_IMPORT_RE = /@import[^;]+;/gi;
|
|
3005
|
+
function isOffBoxUrl(url) {
|
|
3006
|
+
const value = url.trim();
|
|
3007
|
+
if (!value) return false;
|
|
3008
|
+
return EXTERNAL_URL_RE.test(value) || /^javascript:/i.test(value) || /^data:(?!image\/)/i.test(value);
|
|
3009
|
+
}
|
|
3010
|
+
function sanitizeSlideHtml(raw) {
|
|
3011
|
+
const removed = [];
|
|
3012
|
+
let html = String(raw ?? "");
|
|
3013
|
+
html = html.replace(CONTAINER_ELEMENT_RE, (_match, tag) => {
|
|
3014
|
+
removed.push(`<${String(tag).toLowerCase()}>`);
|
|
3015
|
+
return "";
|
|
3016
|
+
});
|
|
3017
|
+
html = html.replace(STRAY_TAG_RE, (match) => {
|
|
3018
|
+
const tag = (match.match(/^<\/?\s*([a-z]+)/i) || [])[1]?.toLowerCase();
|
|
3019
|
+
if (tag) removed.push(`<${tag}>`);
|
|
3020
|
+
return "";
|
|
3021
|
+
});
|
|
3022
|
+
const beforeEvents = html.length;
|
|
3023
|
+
html = html.replace(EVENT_ATTR_RE, "");
|
|
3024
|
+
if (html.length !== beforeEvents) removed.push("inline event handler");
|
|
3025
|
+
html = html.replace(CSS_URL_RE, (match, dq, sq, bare) => {
|
|
3026
|
+
if (!isOffBoxUrl(String(dq ?? sq ?? bare ?? ""))) return match;
|
|
3027
|
+
removed.push("external url()");
|
|
3028
|
+
return "none";
|
|
3029
|
+
});
|
|
3030
|
+
const beforeImports = html.length;
|
|
3031
|
+
html = html.replace(CSS_IMPORT_RE, "");
|
|
3032
|
+
if (html.length !== beforeImports) removed.push("@import");
|
|
3033
|
+
html = html.replace(/\s+(href|src|srcset|poster)\s*=\s*("([^"]*)"|'([^']*)')/gi, (match, attr, _q, dq, sq) => {
|
|
3034
|
+
const value = String(dq ?? sq ?? "").trim();
|
|
3035
|
+
if (!isOffBoxUrl(value)) return match;
|
|
3036
|
+
removed.push(`${attr}="${value.slice(0, 60)}"`);
|
|
3037
|
+
return "";
|
|
3038
|
+
});
|
|
3039
|
+
return { html: html.trim(), removed: Array.from(new Set(removed)) };
|
|
3040
|
+
}
|
|
3041
|
+
function validateBespokeSlideHtml(raw, contract = {}) {
|
|
3042
|
+
const maxChars = contract.maxChars ?? 16e3;
|
|
3043
|
+
const minChars = contract.minChars ?? 120;
|
|
3044
|
+
const issues = [];
|
|
3045
|
+
const html = String(raw ?? "").trim();
|
|
3046
|
+
if (!html) return { valid: false, issues: ["slide html is empty"] };
|
|
3047
|
+
if (html.length > maxChars) issues.push(`slide html exceeds ${maxChars} chars (${html.length})`);
|
|
3048
|
+
if (html.replace(/<[^>]*>/g, "").replace(/\s+/g, "").length < minChars) {
|
|
3049
|
+
issues.push("slide has too little text content to teach anything");
|
|
3050
|
+
}
|
|
3051
|
+
if (!/<h[12]\b/i.test(html)) {
|
|
3052
|
+
issues.push("slide has no <h1>/<h2> \u2014 typographic hierarchy is required");
|
|
3053
|
+
}
|
|
3054
|
+
const { html: clean, removed } = sanitizeSlideHtml(html);
|
|
3055
|
+
if (removed.length > 0) {
|
|
3056
|
+
issues.push(`slide is not self-contained (stripped: ${removed.join(", ")})`);
|
|
3057
|
+
}
|
|
3058
|
+
if (/<script\b/i.test(clean)) issues.push("slide still contains a script tag after sanitizing");
|
|
3059
|
+
if (/\b(todo|tbd|lorem ipsum|<code>)\b/i.test(clean) && !/<pre\b/i.test(clean)) {
|
|
3060
|
+
issues.push("slide contains placeholder text");
|
|
3061
|
+
}
|
|
3062
|
+
if (/<html\b|<body\b|<section\b[^>]*class="[^"]*slide-page/i.test(clean)) {
|
|
3063
|
+
issues.push("slide markup carries deck-level structure (html/body/section.slide-page)");
|
|
3064
|
+
}
|
|
3065
|
+
return { valid: issues.length === 0, issues };
|
|
3066
|
+
}
|
|
3067
|
+
function buildBespokeSlideMarkup(raw) {
|
|
3068
|
+
const { html } = sanitizeSlideHtml(raw);
|
|
3069
|
+
return `<div class="bespoke-slide">${html}</div>`;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3591
3072
|
// src/html-engine/compiler.ts
|
|
3073
|
+
var SHIKI_LANGS = /* @__PURE__ */ new Set([
|
|
3074
|
+
"javascript",
|
|
3075
|
+
"typescript",
|
|
3076
|
+
"python",
|
|
3077
|
+
"cpp",
|
|
3078
|
+
"c",
|
|
3079
|
+
"java",
|
|
3080
|
+
"html",
|
|
3081
|
+
"css",
|
|
3082
|
+
"json",
|
|
3083
|
+
"yaml",
|
|
3084
|
+
"bash",
|
|
3085
|
+
"shell",
|
|
3086
|
+
"sql",
|
|
3087
|
+
"go",
|
|
3088
|
+
"rust",
|
|
3089
|
+
"swift",
|
|
3090
|
+
"kotlin",
|
|
3091
|
+
"ruby",
|
|
3092
|
+
"php",
|
|
3093
|
+
"xml",
|
|
3094
|
+
"markdown",
|
|
3095
|
+
"dockerfile",
|
|
3096
|
+
"toml",
|
|
3097
|
+
"ini",
|
|
3098
|
+
"graphql",
|
|
3099
|
+
"prisma",
|
|
3100
|
+
"solidity"
|
|
3101
|
+
]);
|
|
3102
|
+
function normalizeShikiLang(lang) {
|
|
3103
|
+
if (!lang) return null;
|
|
3104
|
+
const l = String(lang).toLowerCase().trim();
|
|
3105
|
+
if (SHIKI_LANGS.has(l)) return l;
|
|
3106
|
+
if (l === "js") return "javascript";
|
|
3107
|
+
if (l === "ts") return "typescript";
|
|
3108
|
+
if (l === "py") return "python";
|
|
3109
|
+
if (l === "sh") return "bash";
|
|
3110
|
+
if (l === "yml") return "yaml";
|
|
3111
|
+
if (l === "c++") return "cpp";
|
|
3112
|
+
return null;
|
|
3113
|
+
}
|
|
3114
|
+
function decodeHtmlEntities2(html) {
|
|
3115
|
+
return html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'");
|
|
3116
|
+
}
|
|
3592
3117
|
function compileHtmlDeck(deckData, options) {
|
|
3593
3118
|
const theme = options?.themeOverride || resolveHtmlTheme(deckData.theme);
|
|
3594
3119
|
const slides = deckData.slides || [];
|
|
3595
3120
|
const slideCount = Math.max(slides.length, 1);
|
|
3596
3121
|
const renderedSlides = slides.map((slide, index) => {
|
|
3122
|
+
if (typeof slide.html === "string" && slide.html.trim()) {
|
|
3123
|
+
const notesAttr2 = slide.notes ? ` data-notes="${encodeURIComponent(slide.notes)}"` : "";
|
|
3124
|
+
const customClass2 = slide.customClass ? ` ${slide.customClass}` : "";
|
|
3125
|
+
return `
|
|
3126
|
+
<!-- Slide ${index + 1}: authored html -->
|
|
3127
|
+
<section class="slide-page${customClass2}" data-index="${index}" data-layout="bespoke"${notesAttr2}>
|
|
3128
|
+
${buildBespokeSlideMarkup(slide.html)}
|
|
3129
|
+
</section>
|
|
3130
|
+
`;
|
|
3131
|
+
}
|
|
3597
3132
|
const preset = HTML_SLIDE_PRESETS[slide.layoutId] || HTML_SLIDE_PRESETS["hero-cover"];
|
|
3598
3133
|
const normalizedSlots = normalizeSlideSlots(slide);
|
|
3599
|
-
|
|
3134
|
+
let innerHtml = preset.template(normalizedSlots, theme);
|
|
3135
|
+
if (normalizedSlots.code != null && String(normalizedSlots.code).trim()) {
|
|
3136
|
+
const lang = normalizeShikiLang(normalizedSlots.language) || "typescript";
|
|
3137
|
+
innerHtml = innerHtml.replace("<code>", `<code class="language-${lang}">`);
|
|
3138
|
+
}
|
|
3600
3139
|
const notesAttr = slide.notes ? ` data-notes="${encodeURIComponent(slide.notes)}"` : "";
|
|
3601
3140
|
const customClass = slide.customClass ? ` ${slide.customClass}` : "";
|
|
3602
3141
|
return `
|
|
@@ -3622,20 +3161,34 @@ function compileHtmlDeck(deckData, options) {
|
|
|
3622
3161
|
title: deckData.title
|
|
3623
3162
|
};
|
|
3624
3163
|
}
|
|
3164
|
+
async function highlightDeckCodeBlocks(html, scheme = "dark") {
|
|
3165
|
+
if (!html.includes('<code class="language-')) return html;
|
|
3166
|
+
const highlighter = await getHighlighter();
|
|
3167
|
+
const shikiTheme = scheme === "light" ? "vitesse-light" : "vitesse-dark";
|
|
3168
|
+
return html.replace(
|
|
3169
|
+
/<pre([^>]*)><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
|
|
3170
|
+
(full, preAttrs, lang, escapedCode) => {
|
|
3171
|
+
try {
|
|
3172
|
+
const decoded = decodeHtmlEntities2(escapedCode);
|
|
3173
|
+
return highlighter.codeToHtml(decoded, { lang, theme: shikiTheme }).replace("<pre", `<pre${preAttrs}`);
|
|
3174
|
+
} catch {
|
|
3175
|
+
return full;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
);
|
|
3179
|
+
}
|
|
3180
|
+
async function compileHtmlDeckAsync(deckData, options) {
|
|
3181
|
+
const result = compileHtmlDeck(deckData, options);
|
|
3182
|
+
if (options?.highlight === false) return result;
|
|
3183
|
+
try {
|
|
3184
|
+
const html = await highlightDeckCodeBlocks(result.html, result.theme.scheme);
|
|
3185
|
+
return { ...result, html };
|
|
3186
|
+
} catch (err) {
|
|
3187
|
+
console.warn("[html-engine] Shiki highlighting unavailable, using plain escaped code:", err);
|
|
3188
|
+
return result;
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3625
3191
|
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
exports.compileHtmlDeck = compileHtmlDeck;
|
|
3630
|
-
exports.generateHtmlShell = generateHtmlShell;
|
|
3631
|
-
exports.getSlideLayoutPresetById = getSlideLayoutPresetById;
|
|
3632
|
-
exports.getSlideLayoutPresetsByCategory = getSlideLayoutPresetsByCategory;
|
|
3633
|
-
exports.normalizeSlideSlots = normalizeSlideSlots;
|
|
3634
|
-
exports.removeSlideElementFromMarkdown = removeSlideElementFromMarkdown;
|
|
3635
|
-
exports.resolveHtmlTheme = resolveHtmlTheme;
|
|
3636
|
-
exports.serializeLayoutToComment = serializeLayoutToComment;
|
|
3637
|
-
exports.splitMarkdownSlides = splitMarkdownSlides;
|
|
3638
|
-
exports.updateSlideLayoutInMarkdown = updateSlideLayoutInMarkdown;
|
|
3639
|
-
exports.updateSlideTextInMarkdown = updateSlideTextInMarkdown;
|
|
3640
|
-
//# sourceMappingURL=chunk-JNTRKKJX.cjs.map
|
|
3641
|
-
//# sourceMappingURL=chunk-JNTRKKJX.cjs.map
|
|
3192
|
+
export { HTML_SLIDE_PRESETS, HTML_THEMES, SLIDE_UTILITY_CSS, buildBespokeSlideMarkup, compileHtmlDeck, compileHtmlDeckAsync, generateHtmlShell, getHighlighter, highlightCodeBlocks, highlightDeckCodeBlocks, listUtilityClasses, normalizeSlideSlots, resolveHtmlTheme, sanitizeSlideHtml, validateBespokeSlideHtml };
|
|
3193
|
+
//# sourceMappingURL=chunk-OCP2TAY2.js.map
|
|
3194
|
+
//# sourceMappingURL=chunk-OCP2TAY2.js.map
|