mr-md 1.0.4 → 2.0.0-beta
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 +10 -5
- package/dist/builder.d.ts +6 -20
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +38 -97
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/dev.js +92 -0
- package/dist/cli/generate.d.ts +2 -0
- package/dist/cli/generate.d.ts.map +1 -0
- package/dist/cli/generate.js +171 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +89 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +27 -0
- package/dist/client/app.js +282 -107
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/renderer/blocks.d.ts.map +1 -1
- package/dist/renderer/blocks.js +88 -16
- package/dist/renderer/html-neo.d.ts +7 -0
- package/dist/renderer/html-neo.d.ts.map +1 -0
- package/dist/renderer/html-neo.js +173 -0
- package/dist/renderer/html.d.ts.map +1 -1
- package/dist/renderer/html.js +36 -7
- package/dist/renderer/index-neo.d.ts +4 -0
- package/dist/renderer/index-neo.d.ts.map +1 -0
- package/dist/renderer/index-neo.js +469 -0
- package/dist/renderer/index.d.ts +1 -2
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +29 -379
- package/dist/renderer/markdown.d.ts +1 -1
- package/dist/renderer/markdown.d.ts.map +1 -1
- package/dist/renderer/markdown.js +3 -3
- package/dist/renderer/utils.d.ts +1 -1
- package/dist/renderer/utils.d.ts.map +1 -1
- package/dist/renderer/utils.js +41 -34
- package/dist/styles/theme-neo.css +1369 -0
- package/dist/styles/theme.css +412 -127
- package/dist/types.d.ts +8 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/builder.ts +49 -125
- package/src/cli/dev.ts +102 -0
- package/src/cli/generate.ts +191 -0
- package/src/cli/init.ts +97 -0
- package/src/cli.ts +29 -0
- package/src/client/app.js +282 -107
- package/src/index.ts +1 -1
- package/src/renderer/blocks.ts +89 -15
- package/src/renderer/html.ts +36 -7
- package/src/renderer/index.ts +30 -394
- package/src/renderer/markdown.ts +3 -2
- package/src/renderer/utils.ts +43 -36
- package/src/styles/theme.css +412 -127
- package/src/types.ts +8 -12
package/dist/renderer/blocks.js
CHANGED
|
@@ -37,8 +37,8 @@ function renderBlockInner(block, idx, options) {
|
|
|
37
37
|
switch (block.type) {
|
|
38
38
|
case "heading": {
|
|
39
39
|
const md = resolveContent(block.src, options, "md");
|
|
40
|
-
const { html, title
|
|
41
|
-
const label = block.title || title ||
|
|
40
|
+
const { html, title } = mdToHtml(md);
|
|
41
|
+
const label = block.title || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Heading");
|
|
42
42
|
const id = `heading-${idx}`;
|
|
43
43
|
return {
|
|
44
44
|
html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
|
|
@@ -51,14 +51,14 @@ function renderBlockInner(block, idx, options) {
|
|
|
51
51
|
const navItems = headings.map(h => ({
|
|
52
52
|
id: h.id,
|
|
53
53
|
label: h.text,
|
|
54
|
-
kind: h.level ===
|
|
54
|
+
kind: h.level === 1 ? "heading" : "section"
|
|
55
55
|
}));
|
|
56
56
|
return { html: `<div class="bk-markdown">${html}</div>`, navItems: navItems.length > 0 ? navItems : undefined };
|
|
57
57
|
}
|
|
58
58
|
case "section": {
|
|
59
59
|
const md = resolveContent(block.src, options, "md");
|
|
60
|
-
const { html, title
|
|
61
|
-
const label = block.label || title ||
|
|
60
|
+
const { html, title } = mdToHtml(md);
|
|
61
|
+
const label = block.label || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Section");
|
|
62
62
|
const id = `section-${idx}`;
|
|
63
63
|
return {
|
|
64
64
|
html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
|
|
@@ -115,6 +115,8 @@ function renderBlockInner(block, idx, options) {
|
|
|
115
115
|
const propsJson = escapeScriptJson(block.props ?? {});
|
|
116
116
|
const simSrc = resolveContent(block.src, options, "js");
|
|
117
117
|
const simConfig = { js: simSrc, loop: false, dependencies: block.dependencies };
|
|
118
|
+
const id = `sim-${idx}`;
|
|
119
|
+
const label = block.label || "Interactive Simulation";
|
|
118
120
|
return {
|
|
119
121
|
html: blockChrome(block.controls === "observe" ? "Simulation" : "Interactive Lab", block.label, block.caption, `${renderSimulationControls(block)}
|
|
120
122
|
<div class="bk-embed-frame bk-embed-interactive">
|
|
@@ -123,21 +125,24 @@ function renderBlockInner(block, idx, options) {
|
|
|
123
125
|
</div>
|
|
124
126
|
<iframe srcdoc="${iframeDoc(simSrc, propsJson, false, block.dependencies)}"
|
|
125
127
|
sandbox="allow-scripts"
|
|
128
|
+
loading="lazy"
|
|
126
129
|
style="width:100%;height:100%;border:none;display:block;">
|
|
127
130
|
</iframe>
|
|
128
131
|
</div>
|
|
129
|
-
<script type="application/json" class="bk-sim-config">${escapeScriptJson(simConfig)}</script>`, block.accent ?? "blue"),
|
|
132
|
+
<script type="application/json" class="bk-sim-config">${escapeScriptJson(simConfig)}</script>`, block.accent ?? "blue", true, id),
|
|
133
|
+
navItems: [{ id, label, kind: "simulation" }],
|
|
130
134
|
};
|
|
131
135
|
}
|
|
132
136
|
case "animation": {
|
|
133
137
|
const animSrc = resolveContent(block.src, options, "js");
|
|
134
138
|
return {
|
|
135
|
-
html: blockChrome("Animation", block.label, block.caption, `<div class="bk-embed-frame bk-embed-interactive">
|
|
139
|
+
html: blockChrome("Animation", block.label, block.caption, `<div class="bk-embed-frame bk-embed-interactive" data-is-animation="true">
|
|
136
140
|
<div class="bk-embed-overlay" tabindex="0" role="button" aria-label="Activate interactive animation">
|
|
137
141
|
<span class="bk-embed-overlay-text">Click to interact</span>
|
|
138
142
|
</div>
|
|
139
143
|
<iframe srcdoc="${iframeDoc(animSrc, "{}", block.loop)}"
|
|
140
144
|
sandbox="allow-scripts"
|
|
145
|
+
loading="lazy"
|
|
141
146
|
style="width:100%;height:100%;border:none;display:block;">
|
|
142
147
|
</iframe>
|
|
143
148
|
</div>`, block.accent ?? "neutral"),
|
|
@@ -210,8 +215,7 @@ function renderBlockInner(block, idx, options) {
|
|
|
210
215
|
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
|
|
211
216
|
throw new Error("Quiz file not found or invalid JSON format");
|
|
212
217
|
}
|
|
213
|
-
|
|
214
|
-
quiz = Array.isArray(parsed) ? { questions: parsed } : parsed;
|
|
218
|
+
quiz = JSON.parse(trimmed);
|
|
215
219
|
}
|
|
216
220
|
catch (e) {
|
|
217
221
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -266,7 +270,7 @@ function renderQuestion(q, quizId, qi) {
|
|
|
266
270
|
}
|
|
267
271
|
// Wraps a JS string in a minimal iframe document
|
|
268
272
|
function iframeDoc(js, props, loop, dependencies) {
|
|
269
|
-
const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("
|
|
273
|
+
const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\\n");
|
|
270
274
|
const doc = `<!DOCTYPE html><html><head>
|
|
271
275
|
${scriptTags}
|
|
272
276
|
<style>
|
|
@@ -280,6 +284,26 @@ ${scriptTags}
|
|
|
280
284
|
window.__simProps=${props};
|
|
281
285
|
window.__loop=${loop ?? false};
|
|
282
286
|
window.bkSetupCalled = false;
|
|
287
|
+
window.__bkTheme = { colors: {}, theme: "light", palette: "ink", ui: "standard" };
|
|
288
|
+
window.bkColor = function(name) { return window.__bkTheme.colors[name] || "#000000"; };
|
|
289
|
+
window.bkUi = function() { return window.__bkTheme.ui; };
|
|
290
|
+
window.bkThemeMode = function() {
|
|
291
|
+
const rootTheme = window.__bkTheme.theme;
|
|
292
|
+
if (rootTheme === "auto") {
|
|
293
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? "dark" : "light";
|
|
294
|
+
}
|
|
295
|
+
return rootTheme;
|
|
296
|
+
};
|
|
297
|
+
window.addEventListener("message", function(e) {
|
|
298
|
+
if (e.data && e.data.type === "bk:theme-sync") {
|
|
299
|
+
window.__bkTheme = e.data.state;
|
|
300
|
+
window.dispatchEvent(new CustomEvent("bk:theme-changed", { detail: window.__bkTheme }));
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
if (window.parent && window.parent !== window) {
|
|
304
|
+
window.parent.postMessage({ type: "bk:request-theme" }, "*");
|
|
305
|
+
}
|
|
306
|
+
|
|
283
307
|
window.bkCanvasPoint = function(event, canvas) {
|
|
284
308
|
const c = canvas || event.currentTarget || event.target;
|
|
285
309
|
const rect = c.getBoundingClientRect();
|
|
@@ -330,19 +354,67 @@ window.bkSetup = function(requestedW, requestedH, loopFn) {
|
|
|
330
354
|
if (!canvas) return;
|
|
331
355
|
const ctx = canvas.getContext("2d");
|
|
332
356
|
|
|
357
|
+
let loopId = null;
|
|
358
|
+
let cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
359
|
+
|
|
333
360
|
function loop() {
|
|
334
|
-
const fit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
335
361
|
if (window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
336
362
|
ctx.save();
|
|
337
|
-
ctx.scale(
|
|
363
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
338
364
|
|
|
339
|
-
loopFn(ctx,
|
|
365
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
340
366
|
|
|
341
367
|
ctx.restore();
|
|
342
368
|
}
|
|
343
|
-
|
|
369
|
+
if (window.__loop) {
|
|
370
|
+
loopId = requestAnimationFrame(loop);
|
|
371
|
+
} else {
|
|
372
|
+
loopId = null;
|
|
373
|
+
}
|
|
344
374
|
}
|
|
345
|
-
|
|
375
|
+
|
|
376
|
+
function initDraw() {
|
|
377
|
+
if (window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
378
|
+
cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
379
|
+
loop();
|
|
380
|
+
} else {
|
|
381
|
+
requestAnimationFrame(initDraw);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
initDraw();
|
|
385
|
+
|
|
386
|
+
window.addEventListener("resize", () => {
|
|
387
|
+
cachedFit = window.bkFitCanvas(canvas, requestedW, requestedH);
|
|
388
|
+
if (!window.__loop && window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
389
|
+
if (!loopId) {
|
|
390
|
+
ctx.save();
|
|
391
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
392
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
393
|
+
ctx.restore();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
window.addEventListener("bk:theme-changed", () => {
|
|
399
|
+
if (!window.__loop && window.innerWidth >= 32 && window.innerHeight >= 32) {
|
|
400
|
+
if (!loopId) {
|
|
401
|
+
ctx.save();
|
|
402
|
+
ctx.scale(cachedFit.scale, cachedFit.scale);
|
|
403
|
+
loopFn(ctx, cachedFit.width, cachedFit.height);
|
|
404
|
+
ctx.restore();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
window.addEventListener("message", (event) => {
|
|
410
|
+
if (!event.data) return;
|
|
411
|
+
if (event.data.type === "bk:play") {
|
|
412
|
+
window.__loop = true;
|
|
413
|
+
if (!loopId) loopId = requestAnimationFrame(loop);
|
|
414
|
+
} else if (event.data.type === "bk:pause") {
|
|
415
|
+
window.__loop = false; // will stop at next frame
|
|
416
|
+
}
|
|
417
|
+
});
|
|
346
418
|
};
|
|
347
419
|
|
|
348
420
|
window.addEventListener("message", (event) => {
|
|
@@ -359,9 +431,9 @@ try {
|
|
|
359
431
|
if (!window.bkSetupCalled) {
|
|
360
432
|
function fallbackScale() {
|
|
361
433
|
window.bkFitCanvas(document.getElementById("c"), 800, 500, { bitmap: false });
|
|
362
|
-
requestAnimationFrame(fallbackScale);
|
|
363
434
|
}
|
|
364
435
|
fallbackScale();
|
|
436
|
+
window.addEventListener("resize", fallbackScale);
|
|
365
437
|
}
|
|
366
438
|
</script>
|
|
367
439
|
</body></html>`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BuildOptions, Lesson } from "../types.js";
|
|
2
|
+
import type { NavItem } from "./utils.js";
|
|
3
|
+
declare function renderNavItem(item: NavItem): string;
|
|
4
|
+
declare function renderPage(lesson: Lesson, navItems: NavItem[], bodyHtml: string, opts: BuildOptions): string;
|
|
5
|
+
declare function copyCoreAssets(outDir: string): void;
|
|
6
|
+
export { copyCoreAssets, renderNavItem, renderPage };
|
|
7
|
+
//# sourceMappingURL=html-neo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-neo.d.ts","sourceRoot":"","sources":["../../src/renderer/html-neo.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAU5C;AAsBD,iBAAS,UAAU,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GAChB,MAAM,CA0HR;AAID,iBAAS,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAa5C;AAED,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
import { escHtml } from "./blocks.js";
|
|
7
|
+
// ─── Page shell ───────────────────────────────────────────────────────────────
|
|
8
|
+
function renderNavItem(item) {
|
|
9
|
+
const kindClass = item.kind === "heading"
|
|
10
|
+
? "bk-nav-heading"
|
|
11
|
+
: item.kind === "quiz"
|
|
12
|
+
? "bk-nav-quiz"
|
|
13
|
+
: item.kind === "simulation"
|
|
14
|
+
? "bk-nav-sim"
|
|
15
|
+
: "bk-nav-sub";
|
|
16
|
+
return `<a href="#${item.id}" class="bk-nav-item ${kindClass}" data-id="${item.id}">${escHtml(item.label)}</a>`;
|
|
17
|
+
}
|
|
18
|
+
function renderEndNav(lesson) {
|
|
19
|
+
const { prevSlug, prevTitle, nextSlug, nextTitle } = lesson.meta;
|
|
20
|
+
if (!prevSlug && !nextSlug)
|
|
21
|
+
return "";
|
|
22
|
+
return `<nav class="bk-end-nav" aria-label="Lesson navigation" style="grid-template-columns: repeat(2, minmax(0, 1fr));">
|
|
23
|
+
${prevSlug ? `
|
|
24
|
+
<a class="bk-end-link bk-end-link--prev" href="${prevSlug}.html">
|
|
25
|
+
<span>Previous Lesson</span>
|
|
26
|
+
<strong>${escHtml(prevTitle || "Previous")}</strong>
|
|
27
|
+
</a>
|
|
28
|
+
` : `<div class="bk-end-link" style="visibility:hidden"></div>`}
|
|
29
|
+
${nextSlug ? `
|
|
30
|
+
<a class="bk-end-link bk-end-link--next" href="${nextSlug}.html">
|
|
31
|
+
<span>Next Lesson</span>
|
|
32
|
+
<strong>${escHtml(nextTitle || "Next")}</strong>
|
|
33
|
+
</a>
|
|
34
|
+
` : `<div class="bk-end-link" style="visibility:hidden"></div>`}
|
|
35
|
+
</nav>`;
|
|
36
|
+
}
|
|
37
|
+
function renderPage(lesson, navItems, bodyHtml, opts) {
|
|
38
|
+
const theme = opts.theme ?? "auto";
|
|
39
|
+
const schemeAttr = `data-theme="${theme}"`;
|
|
40
|
+
const preset = opts.preset ?? {};
|
|
41
|
+
const layout = preset.layout ?? "lesson";
|
|
42
|
+
const density = preset.density ?? "comfortable";
|
|
43
|
+
const tone = preset.tone ?? "scholarly";
|
|
44
|
+
const palette = opts.palette ?? "ink";
|
|
45
|
+
const navHtml = navItems.map(renderNavItem).join("\n");
|
|
46
|
+
const endNavHtml = renderEndNav(lesson);
|
|
47
|
+
return `<!DOCTYPE html>
|
|
48
|
+
<html lang="en" data-palette="${palette}" ${schemeAttr}>
|
|
49
|
+
<head>
|
|
50
|
+
<meta charset="UTF-8">
|
|
51
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
52
|
+
<title>${escHtml(lesson.meta.title)}</title>
|
|
53
|
+
${lesson.meta.description ? `<meta name="description" content="${escHtml(lesson.meta.description)}">` : ""}
|
|
54
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
55
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
56
|
+
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
|
|
57
|
+
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet">
|
|
58
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.47/dist/katex.min.css">
|
|
59
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/styles/github-dark.min.css">
|
|
60
|
+
${opts.head ?? ""}
|
|
61
|
+
<link rel="stylesheet" href="assets/theme.css">
|
|
62
|
+
<style>
|
|
63
|
+
${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
|
|
64
|
+
</style>
|
|
65
|
+
<script type="speculationrules">
|
|
66
|
+
{
|
|
67
|
+
"prefetch": [{
|
|
68
|
+
"where": { "href_matches": "/*" },
|
|
69
|
+
"eagerness": "eager"
|
|
70
|
+
}],
|
|
71
|
+
"prerender": [{
|
|
72
|
+
"where": { "href_matches": "/*" },
|
|
73
|
+
"eagerness": "moderate"
|
|
74
|
+
}]
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
<link rel="expect" href="#bk-content" blocking="render">
|
|
78
|
+
<script blocking="render">
|
|
79
|
+
const savedTheme = localStorage.getItem("bk-theme");
|
|
80
|
+
const savedPalette = localStorage.getItem("bk-palette");
|
|
81
|
+
if (savedTheme) document.documentElement.setAttribute("data-theme", savedTheme);
|
|
82
|
+
if (savedPalette) {
|
|
83
|
+
const normalizedPalette = savedPalette === "green" ? "field" : savedPalette;
|
|
84
|
+
document.documentElement.setAttribute("data-palette", normalizedPalette);
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
</head>
|
|
88
|
+
<body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
|
|
89
|
+
<div class="bk-shell">
|
|
90
|
+
<aside class="bk-sidebar">
|
|
91
|
+
<div class="bk-sidebar-inner">
|
|
92
|
+
<div class="bk-sidebar-header">
|
|
93
|
+
${lesson.meta.parentSlug ? `<div style="margin-top: 8px;"><a href="${lesson.meta.parentSlug}.html" class="bk-back-link" aria-label="Back to Chapter" style="margin-bottom: 12px;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>Back to Chapter</a></div>` : `<div style="margin-top: 8px;"></div>`}
|
|
94
|
+
<div class="bk-sidebar-title">${escHtml(lesson.meta.title)}</div>
|
|
95
|
+
${lesson.meta.author ? `<div class="bk-sidebar-author">By ${escHtml(lesson.meta.author)}</div>` : ""}
|
|
96
|
+
${lesson.meta.tags?.length ? `<div class="bk-tag-row">${lesson.meta.tags.map((tag) => `<span>${escHtml(tag)}</span>`).join("")}</div>` : ""}
|
|
97
|
+
</div>
|
|
98
|
+
<nav class="bk-nav">${navHtml}</nav>
|
|
99
|
+
<div class="bk-sidebar-footer">
|
|
100
|
+
<button class="bk-icon-btn bk-settings-button" id="bk-settings-button" type="button" aria-expanded="false" aria-controls="bk-theme-panel" title="Display settings">
|
|
101
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
|
|
102
|
+
<span class="bk-sr-only">Display settings</span>
|
|
103
|
+
</button>
|
|
104
|
+
<div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
|
|
105
|
+
<div class="bk-theme-row">
|
|
106
|
+
<span>Theme</span>
|
|
107
|
+
<div class="bk-segmented-control" id="bk-theme-icons">
|
|
108
|
+
<button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
|
|
109
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
110
|
+
</button>
|
|
111
|
+
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : (!theme ? "active" : "")}" data-theme="auto" title="System" aria-label="System theme">
|
|
112
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
113
|
+
</button>
|
|
114
|
+
<button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
|
|
115
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="bk-theme-row">
|
|
120
|
+
<span>Palette</span>
|
|
121
|
+
<div class="bk-segmented-control" id="bk-palette-icons">
|
|
122
|
+
<button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
|
|
123
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path></svg>
|
|
124
|
+
</button>
|
|
125
|
+
<button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
|
|
126
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10Z"></path><path d="M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"></path></svg>
|
|
127
|
+
</button>
|
|
128
|
+
<button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
|
|
129
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.5 14.5A2.5 2.5 0 0011 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 11-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 002.5 2.5z"></path></svg>
|
|
130
|
+
</button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
</aside>
|
|
137
|
+
<button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
|
|
138
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
|
139
|
+
</button>
|
|
140
|
+
<main class="bk-main">
|
|
141
|
+
<button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
|
|
142
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
|
|
143
|
+
</button>
|
|
144
|
+
<article class="bk-content" id="bk-content">
|
|
145
|
+
<header class="bk-hero">
|
|
146
|
+
<p class="bk-eyebrow">Interactive Lesson</p>
|
|
147
|
+
<h1 style="view-transition-name: title-${lesson.meta.slug}">${escHtml(lesson.meta.title)}</h1>
|
|
148
|
+
${lesson.meta.description ? `<p class="bk-deck">${escHtml(lesson.meta.description)}</p>` : ""}
|
|
149
|
+
</header>
|
|
150
|
+
${bodyHtml}
|
|
151
|
+
${endNavHtml}
|
|
152
|
+
</article>
|
|
153
|
+
</main>
|
|
154
|
+
</div>
|
|
155
|
+
<script src="assets/app.js" defer></script>
|
|
156
|
+
</body>
|
|
157
|
+
</html>`;
|
|
158
|
+
}
|
|
159
|
+
// ─── Core Assets ────────────────────────────────────────────────────────────────
|
|
160
|
+
function copyCoreAssets(outDir) {
|
|
161
|
+
const assetsDir = path.join(outDir, "assets");
|
|
162
|
+
if (!fs.existsSync(assetsDir))
|
|
163
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
164
|
+
const cssPath = path.join(__dirname, "../styles/theme.css");
|
|
165
|
+
if (fs.existsSync(cssPath)) {
|
|
166
|
+
fs.copyFileSync(cssPath, path.join(assetsDir, "theme.css"));
|
|
167
|
+
}
|
|
168
|
+
const jsPath = path.join(__dirname, "../client/app.js");
|
|
169
|
+
if (fs.existsSync(jsPath)) {
|
|
170
|
+
fs.copyFileSync(jsPath, path.join(assetsDir, "app.js"));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
export { copyCoreAssets, renderNavItem, renderPage };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/renderer/html.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/renderer/html.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,iBAAS,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAU5C;AAsBD,iBAAS,UAAU,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,YAAY,GAChB,MAAM,CA6HR;AAID,iBAAS,OAAO,IAAI,MAAM,CAKzB;AAID,iBAAS,YAAY,IAAI,MAAM,CAE9B;AAED,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/renderer/html.js
CHANGED
|
@@ -10,7 +10,9 @@ function renderNavItem(item) {
|
|
|
10
10
|
? "bk-nav-heading"
|
|
11
11
|
: item.kind === "quiz"
|
|
12
12
|
? "bk-nav-quiz"
|
|
13
|
-
: "
|
|
13
|
+
: item.kind === "simulation"
|
|
14
|
+
? "bk-nav-sim"
|
|
15
|
+
: "bk-nav-sub";
|
|
14
16
|
return `<a href="#${item.id}" class="bk-nav-item ${kindClass}" data-id="${item.id}">${escHtml(item.label)}</a>`;
|
|
15
17
|
}
|
|
16
18
|
function renderEndNav(lesson) {
|
|
@@ -33,23 +35,36 @@ function renderEndNav(lesson) {
|
|
|
33
35
|
</nav>`;
|
|
34
36
|
}
|
|
35
37
|
function renderPage(lesson, navItems, bodyHtml, opts) {
|
|
36
|
-
const theme = opts.theme ?? "
|
|
37
|
-
const schemeAttr =
|
|
38
|
+
const theme = opts.theme ?? "light";
|
|
39
|
+
const schemeAttr = `data-theme="${theme}"`;
|
|
38
40
|
const preset = opts.preset ?? {};
|
|
39
41
|
const layout = preset.layout ?? "lesson";
|
|
40
42
|
const density = preset.density ?? "comfortable";
|
|
41
43
|
const tone = preset.tone ?? "scholarly";
|
|
42
44
|
const palette = opts.palette ?? "ink";
|
|
45
|
+
const ui = opts.ui ?? "standard";
|
|
43
46
|
const navHtml = navItems.map(renderNavItem).join("\n");
|
|
44
47
|
const endNavHtml = renderEndNav(lesson);
|
|
45
48
|
return `<!DOCTYPE html>
|
|
46
|
-
<html lang="en" data-palette="${palette}" ${schemeAttr}>
|
|
49
|
+
<html lang="en" data-palette="${palette}" data-ui="${ui}" ${schemeAttr}>
|
|
47
50
|
<head>
|
|
51
|
+
<script>
|
|
52
|
+
(function() {
|
|
53
|
+
var t = localStorage.getItem("bk-theme");
|
|
54
|
+
var p = localStorage.getItem("bk-palette");
|
|
55
|
+
var u = localStorage.getItem("bk-ui");
|
|
56
|
+
var root = document.documentElement;
|
|
57
|
+
if (t) root.setAttribute("data-theme", t);
|
|
58
|
+
if (p) root.setAttribute("data-palette", p === "green" ? "field" : p);
|
|
59
|
+
if (u) root.setAttribute("data-ui", u);
|
|
60
|
+
})();
|
|
61
|
+
</script>
|
|
48
62
|
<meta charset="UTF-8">
|
|
49
63
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
50
64
|
<title>${escHtml(lesson.meta.title)}</title>
|
|
51
65
|
${lesson.meta.description ? `<meta name="description" content="${escHtml(lesson.meta.description)}">` : ""}
|
|
52
66
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.47/dist/katex.min.css">
|
|
67
|
+
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,650;9..144,760&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&family=Archivo:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Syne:wght@600;700;800&family=Playfair+Display:ital,wght@0,400..700;1,400..700&family=Lora:ital,wght@0,400..700;1,400..700&display=swap" rel="stylesheet">
|
|
53
68
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/styles/github-dark.min.css">
|
|
54
69
|
${opts.head ?? ""}
|
|
55
70
|
<style>
|
|
@@ -80,12 +95,12 @@ ${pageCSS()}
|
|
|
80
95
|
<button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
|
|
81
96
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
|
|
82
97
|
</button>
|
|
98
|
+
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : (!theme ? "active" : "")}" data-theme="auto" title="System" aria-label="System theme">
|
|
99
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
100
|
+
</button>
|
|
83
101
|
<button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
|
|
84
102
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
|
|
85
103
|
</button>
|
|
86
|
-
<button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : ""}" data-theme="auto" title="System" aria-label="System theme">
|
|
87
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
|
|
88
|
-
</button>
|
|
89
104
|
</div>
|
|
90
105
|
</div>
|
|
91
106
|
<div class="bk-theme-row">
|
|
@@ -102,6 +117,20 @@ ${pageCSS()}
|
|
|
102
117
|
</button>
|
|
103
118
|
</div>
|
|
104
119
|
</div>
|
|
120
|
+
<div class="bk-theme-row">
|
|
121
|
+
<span>UI</span>
|
|
122
|
+
<div class="bk-segmented-control" id="bk-ui-icons">
|
|
123
|
+
<button type="button" class="bk-segment-btn ${ui === 'standard' ? 'active' : ''}" data-ui="standard" title="Standard" aria-label="Standard UI">
|
|
124
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="4" ry="4"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
|
|
125
|
+
</button>
|
|
126
|
+
<button type="button" class="bk-segment-btn ${ui === 'neo' ? 'active' : ''}" data-ui="neo" title="Neo Brutalist" aria-label="Neo UI">
|
|
127
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><rect x="3" y="3" width="18" height="18"></rect><path d="M3 10h18"></path><path d="M10 10v11"></path></svg>
|
|
128
|
+
</button>
|
|
129
|
+
<button type="button" class="bk-segment-btn ${ui === 'playful' ? 'active' : ''}" data-ui="playful" title="Playful" aria-label="Playful UI">
|
|
130
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="6" ry="6"></rect><circle cx="8.5" cy="8.5" r="1.5" fill="currentColor"></circle><circle cx="15.5" cy="15.5" r="1.5" fill="currentColor"></circle></svg>
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
105
134
|
</div>
|
|
106
135
|
</div>
|
|
107
136
|
</div>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BuildOptions, Chapter, Lesson } from "../types.js";
|
|
2
|
+
export declare function render(lesson: Lesson, opts?: BuildOptions): string;
|
|
3
|
+
export declare function renderChapter(chapter: Chapter, opts?: BuildOptions): string;
|
|
4
|
+
//# sourceMappingURL=index-neo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-neo.d.ts","sourceRoot":"","sources":["../../src/renderer/index-neo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOjE,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,MAAM,CAatE;AAID,wBAAgB,aAAa,CAC5B,OAAO,EAAE,OAAO,EAChB,IAAI,GAAE,YAAiB,GACrB,MAAM,CA6cR"}
|