mr-md 1.0.2 → 1.0.3

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/dist/builder.d.ts CHANGED
@@ -149,6 +149,8 @@ export declare class ChapterBuilder {
149
149
  private _rawOptions;
150
150
  constructor(title: string, options?: BuildOptions, callerDir?: string);
151
151
  slug(slug: string): this;
152
+ /** @internal Used by CourseBuilder to push down shared config */
153
+ _inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions): void;
152
154
  description(text: string): this;
153
155
  status(status: "completed" | "active" | "locked"): this;
154
156
  lesson(lessonBuilder: LessonBuilder): this;
@@ -167,4 +169,16 @@ export declare class ChapterBuilder {
167
169
  * .build();
168
170
  */
169
171
  export declare function chapter(title: string, options?: BuildOptions): ChapterBuilder;
172
+ export declare class CourseBuilder {
173
+ private meta;
174
+ private chapterBuilders;
175
+ private options;
176
+ private _rawOptions;
177
+ constructor(title: string, options?: BuildOptions, callerDir?: string);
178
+ slug(slug: string): this;
179
+ description(text: string): this;
180
+ chapter(chapterBuilder: ChapterBuilder): this;
181
+ build(): string;
182
+ }
183
+ export declare function course(title: string, options?: BuildOptions): CourseBuilder;
170
184
  //# sourceMappingURL=builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
package/dist/builder.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { render, renderChapter } from "./renderer/index.js";
3
+ import { render, renderChapter, renderCourse } from "./renderer/index.js";
4
4
  function getCallerDir() {
5
5
  const err = new Error();
6
6
  const stack = err.stack?.split("\n");
@@ -512,6 +512,26 @@ export class ChapterBuilder {
512
512
  this.meta.slug = slug;
513
513
  return this;
514
514
  }
515
+ /** @internal Used by CourseBuilder to push down shared config */
516
+ _inheritOptions(parentOpts, parentRawOpts) {
517
+ this.options = {
518
+ outDir: this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
519
+ contentBase: this._rawOptions.contentBase ??
520
+ parentRawOpts?.contentBase ??
521
+ this.options.contentBase,
522
+ theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
523
+ palette: this._rawOptions.palette ?? parentOpts.palette ?? this.options.palette,
524
+ strict: this._rawOptions.strict ?? parentOpts.strict ?? this.options.strict,
525
+ preset: {
526
+ ...parentOpts.preset,
527
+ ...this._rawOptions.preset,
528
+ ...this.options.preset,
529
+ },
530
+ };
531
+ for (const lb of this.lessonBuilders) {
532
+ lb._inheritOptions(this.options, this._rawOptions);
533
+ }
534
+ }
515
535
  description(text) {
516
536
  this.meta.description = text;
517
537
  return this;
@@ -580,3 +600,67 @@ export class ChapterBuilder {
580
600
  export function chapter(title, options = {}) {
581
601
  return new ChapterBuilder(title, options, getCallerDir());
582
602
  }
603
+ // ─── CourseBuilder ──────────────────────────────────────────────────────────
604
+ export class CourseBuilder {
605
+ meta;
606
+ chapterBuilders = [];
607
+ options;
608
+ _rawOptions;
609
+ constructor(title, options = {}, callerDir) {
610
+ this._rawOptions = options;
611
+ let slug = title
612
+ .toLowerCase()
613
+ .replace(/[^a-z0-9]+/g, "-")
614
+ .replace(/(^-|-$)/g, "");
615
+ if (!slug)
616
+ slug = "course";
617
+ this.meta = { title, slug };
618
+ this.options = {
619
+ outDir: options.outDir ?? "./out",
620
+ contentBase: options.contentBase ?? callerDir ?? ".",
621
+ theme: options.theme ?? "auto",
622
+ palette: options.palette ?? "ink",
623
+ strict: options.strict ?? true,
624
+ preset: {
625
+ layout: "lesson",
626
+ density: "comfortable",
627
+ tone: "scholarly",
628
+ ...options.preset,
629
+ },
630
+ ...options,
631
+ };
632
+ }
633
+ slug(slug) {
634
+ this.meta.slug = slug;
635
+ return this;
636
+ }
637
+ description(text) {
638
+ this.meta.description = text;
639
+ return this;
640
+ }
641
+ chapter(chapterBuilder) {
642
+ chapterBuilder._inheritOptions(this.options, this._rawOptions);
643
+ this.chapterBuilders.push(chapterBuilder);
644
+ return this;
645
+ }
646
+ build() {
647
+ const chapters = [];
648
+ for (const cb of this.chapterBuilders) {
649
+ cb.build();
650
+ chapters.push(cb.toJSON());
651
+ }
652
+ const courseData = { meta: this.meta, chapters };
653
+ const html = renderCourse(courseData, this.options);
654
+ const outDir = path.resolve(this.options.outDir);
655
+ if (!fs.existsSync(outDir))
656
+ fs.mkdirSync(outDir, { recursive: true });
657
+ const outPath = path.join(outDir, `${this.meta.slug}.html`);
658
+ fs.writeFileSync(outPath, html, "utf-8");
659
+ const relPath = path.relative(process.cwd(), outPath);
660
+ console.log(` ✓ Built course (${this.chapterBuilders.length} chapters) → ${relPath}`);
661
+ return outPath;
662
+ }
663
+ }
664
+ export function course(title, options = {}) {
665
+ return new CourseBuilder(title, options, getCallerDir());
666
+ }
@@ -1,5 +1,5 @@
1
1
  function bkSimDoc(js, props, loop, dependencies) {
2
- const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\\n");
2
+ const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\n");
3
3
  return (
4
4
  "<!DOCTYPE html><html><head>" +
5
5
  scriptTags +
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ChapterBuilder, chapter, LessonBuilder, lesson } from "./builder.js";
1
+ export { ChapterBuilder, chapter, CourseBuilder, course, LessonBuilder, lesson } from "./builder.js";
2
2
  export { render } from "./renderer/index.js";
3
3
  export type { AnimationBlock, AnimationOptions, Block, BlockAccent, BlockType, BuildOptions, CalloutBlock, CodeBlock, ColumnItem, ColumnsBlock, ColumnsOptions, HeadingBlock, LatexBlock, LatexOptions, Lesson, LessonMeta, LessonPreset, MarkdownBlock, MediaBlock, MediaKind, MediaOptions, QuizBlock, QuizFile, QuizQuestion, SectionBlock, SimulationBlock, SimulationControl, SimulationOptions, YouTubeBlock, YouTubeOptions, } from "./types.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EACX,cAAc,EACd,gBAAgB,EAChB,KAAK,EACL,WAAW,EACX,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,EACT,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,GACd,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACrG,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EACX,cAAc,EACd,gBAAgB,EAChB,KAAK,EACL,WAAW,EACX,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,EACT,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,GACd,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { ChapterBuilder, chapter, LessonBuilder, lesson } from "./builder.js";
1
+ export { ChapterBuilder, chapter, CourseBuilder, course, LessonBuilder, lesson } from "./builder.js";
2
2
  export { render } from "./renderer/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAuBxC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CA0QxC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/renderer/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAC;AAC/E,OAAO,EACN,WAAW,EAKX,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,YAAY,CAAC;AAG3E,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3C;AACD,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE3C;AAID,iBAAS,WAAW,CACnB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CAuBxC;AAED,iBAAS,gBAAgB,CACxB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,YAAY,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,CA2QxC;AAuID,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
@@ -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 } = mdToHtml(md);
41
- const label = block.title || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Heading");
40
+ const { html, title, headings } = mdToHtml(md);
41
+ const label = block.title || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Heading");
42
42
  const id = `heading-${idx}`;
43
43
  return {
44
44
  html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
@@ -57,8 +57,8 @@ function renderBlockInner(block, idx, options) {
57
57
  }
58
58
  case "section": {
59
59
  const md = resolveContent(block.src, options, "md");
60
- const { html, title } = mdToHtml(md);
61
- const label = block.label || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Section");
60
+ const { html, title, headings } = mdToHtml(md);
61
+ const label = block.label || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Section");
62
62
  const id = `section-${idx}`;
63
63
  return {
64
64
  html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
@@ -210,7 +210,8 @@ function renderBlockInner(block, idx, options) {
210
210
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
211
211
  throw new Error("Quiz file not found or invalid JSON format");
212
212
  }
213
- quiz = JSON.parse(trimmed);
213
+ const parsed = JSON.parse(trimmed);
214
+ quiz = Array.isArray(parsed) ? { questions: parsed } : parsed;
214
215
  }
215
216
  catch (e) {
216
217
  const msg = e instanceof Error ? e.message : String(e);
@@ -265,7 +266,7 @@ function renderQuestion(q, quizId, qi) {
265
266
  }
266
267
  // Wraps a JS string in a minimal iframe document
267
268
  function iframeDoc(js, props, loop, dependencies) {
268
- const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\\n");
269
+ const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\n");
269
270
  const doc = `<!DOCTYPE html><html><head>
270
271
  ${scriptTags}
271
272
  <style>
@@ -1,4 +1,5 @@
1
- import type { BuildOptions, Chapter, Lesson } from "../types.js";
1
+ import type { BuildOptions, Chapter, Course, Lesson } from "../types.js";
2
2
  export declare function render(lesson: Lesson, opts?: BuildOptions): string;
3
3
  export declare function renderChapter(chapter: Chapter, opts?: BuildOptions): string;
4
+ export declare function renderCourse(course: Course, opts?: BuildOptions): string;
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.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,CAqXR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/renderer/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOzE,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,CAqXR;AAID,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,YAAiB,GACrB,MAAM,CAqXR"}
@@ -379,3 +379,369 @@ ${clientScript()}
379
379
  </body>
380
380
  </html>`;
381
381
  }
382
+ // ─── Course Rendering ────────────────────────────────────────────────────────
383
+ export function renderCourse(course, opts = {}) {
384
+ const theme = opts.theme ?? "auto";
385
+ const schemeAttr = theme === "auto" ? "" : `data-theme="${theme}"`;
386
+ const preset = opts.preset ?? {};
387
+ const layout = preset.layout ?? "lesson";
388
+ const density = preset.density ?? "comfortable";
389
+ const tone = preset.tone ?? "scholarly";
390
+ const palette = opts.palette ?? "ink";
391
+ const navHtml = course.chapters
392
+ .map((c) => `<a href="${escAttr(c.meta.slug)}.html" class="bk-nav-item bk-nav-chapter">${escHtml(c.meta.title)}</a>`)
393
+ .join("\n");
394
+ const timelineHtml = `
395
+ <div class="bk-chapter-timeline-wrapper">
396
+ <div class="bk-chapter-timeline">
397
+ ${course.chapters
398
+ .map((chapter, idx) => `
399
+ <a href="${escAttr(chapter.meta.slug)}.html" class="bk-timeline-card bk-status-${chapter.meta.status ?? "unread"}">
400
+ <div class="bk-timeline-node"></div>
401
+ <div class="bk-timeline-content">
402
+ <h3 class="bk-timeline-title" style="view-transition-name: title-${escAttr(chapter.meta.slug)}">${escHtml(chapter.meta.title)}</h3>
403
+ ${chapter.meta.description ? `<p class="bk-timeline-desc">${escHtml(chapter.meta.description)}</p>` : ""}
404
+ <span class="bk-timeline-action">${chapter.meta.status === "completed" ? "Review" : "Start Chapter"} <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="M5 12h14m-7-7 7 7-7 7"/></svg></span>
405
+ </div>
406
+ </a>
407
+ `)
408
+ .join("")}
409
+ </div>
410
+ </div>
411
+ <script>
412
+ if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
413
+ const observer = new IntersectionObserver(
414
+ (entries) => {
415
+ for (const entry of entries) {
416
+ if (entry.isIntersecting) {
417
+ entry.target.classList.add('js-visible');
418
+ entry.target.classList.remove('js-hidden');
419
+ observer.unobserve(entry.target);
420
+ }
421
+ }
422
+ },
423
+ { threshold: 0.1 }
424
+ );
425
+ document.querySelectorAll('.bk-timeline-card').forEach((el) => {
426
+ el.classList.add('js-hidden');
427
+ observer.observe(el);
428
+ });
429
+ }
430
+ </script>`;
431
+ const chapterStyles = `
432
+ .bk-chapter-timeline-wrapper {
433
+ position: relative;
434
+ width: 100%;
435
+ padding: 1rem 0;
436
+ z-index: 1;
437
+ }
438
+
439
+ .bk-chapter-timeline {
440
+ display: flex;
441
+ flex-direction: column;
442
+ gap: 3rem;
443
+ padding: 3rem 0;
444
+ position: relative;
445
+ max-width: 800px;
446
+ margin: 0 auto;
447
+ }
448
+
449
+ /* Minimalist vertical connecting line */
450
+ .bk-chapter-timeline::before {
451
+ content: '';
452
+ position: absolute;
453
+ left: 20px;
454
+ top: 3rem;
455
+ bottom: 3rem;
456
+ width: 1px;
457
+ background: var(--line);
458
+ z-index: 0;
459
+ transform: translateX(-50%);
460
+ }
461
+
462
+ .bk-timeline-card {
463
+ display: flex;
464
+ align-items: stretch;
465
+ gap: 2rem;
466
+ text-decoration: none !important;
467
+ border: none !important;
468
+ color: inherit;
469
+ position: relative;
470
+ z-index: 1;
471
+ opacity: 1;
472
+ transform: none;
473
+ }
474
+
475
+ /* Subtle scroll-driven animations */
476
+ @media (prefers-reduced-motion: no-preference) {
477
+ @supports ((animation-timeline: view()) and (animation-range: entry)) {
478
+ .bk-timeline-card {
479
+ animation-name: slide-fade-in;
480
+ animation-fill-mode: both;
481
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
482
+ animation-timeline: view(block);
483
+ animation-range: entry 5% cover 20%;
484
+ }
485
+ @keyframes slide-fade-in {
486
+ 0% { opacity: 0; transform: translateY(20px); }
487
+ 100% { opacity: 1; transform: translateY(0); }
488
+ }
489
+ }
490
+ }
491
+
492
+ /* Fallback for browsers without animation-timeline support */
493
+ .bk-timeline-card.js-hidden {
494
+ opacity: 0;
495
+ transform: translateY(20px);
496
+ }
497
+ .bk-timeline-card.js-visible {
498
+ opacity: 1;
499
+ transform: translateY(0);
500
+ transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
501
+ }
502
+
503
+ .bk-timeline-node {
504
+ width: 40px;
505
+ height: 40px;
506
+ border-radius: 50%;
507
+ background: var(--paper);
508
+ border: 1px solid var(--accent);
509
+ display: flex;
510
+ align-items: center;
511
+ justify-content: center;
512
+ flex-shrink: 0;
513
+ transition: all 0.3s ease;
514
+ position: relative;
515
+ box-shadow: 0 2px 4px rgba(0,0,0,0.02);
516
+ }
517
+ .bk-timeline-node::after {
518
+ content: '';
519
+ width: 10px;
520
+ height: 10px;
521
+ border-radius: 50%;
522
+ background: var(--accent);
523
+ transition: all 0.3s ease;
524
+ }
525
+ .bk-timeline-card:hover .bk-timeline-node {
526
+ background: var(--accent);
527
+ }
528
+ .bk-timeline-card:hover .bk-timeline-node::after {
529
+ background: var(--paper);
530
+ }
531
+
532
+ .bk-timeline-content {
533
+ background: var(--paper);
534
+ padding: 2rem;
535
+ border-radius: 12px;
536
+ border: 1px solid var(--line);
537
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
538
+ flex: 1;
539
+ transition: all 0.3s ease;
540
+ display: flex;
541
+ flex-direction: column;
542
+ justify-content: center;
543
+ position: relative;
544
+ }
545
+ .bk-timeline-card:hover .bk-timeline-content {
546
+ border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
547
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
548
+ transform: translateY(-2px);
549
+ }
550
+
551
+ .bk-timeline-title {
552
+ margin: 0 0 0.5rem 0;
553
+ font-family: var(--font-display);
554
+ font-size: 1.6rem;
555
+ color: var(--ink);
556
+ width: fit-content;
557
+ transition: color 0.2s ease;
558
+ }
559
+ .bk-timeline-card:hover .bk-timeline-title {
560
+ color: var(--accent);
561
+ }
562
+
563
+ .bk-timeline-desc {
564
+ margin: 0 0 1.5rem 0;
565
+ color: var(--muted);
566
+ line-height: 1.6;
567
+ font-size: 1.05rem;
568
+ }
569
+
570
+ .bk-timeline-action {
571
+ font-weight: 500;
572
+ color: var(--accent);
573
+ display: inline-flex;
574
+ align-items: center;
575
+ gap: 0.4rem;
576
+ font-size: 0.95rem;
577
+ align-self: flex-start;
578
+ transition: all 0.2s ease;
579
+ border-bottom: 1px solid transparent;
580
+ }
581
+ .bk-timeline-card:hover .bk-timeline-action {
582
+ gap: 0.6rem;
583
+ border-bottom-color: var(--accent);
584
+ }
585
+
586
+ .bk-status-unread .bk-timeline-node {
587
+ border: 1px solid var(--line-strong);
588
+ }
589
+ .bk-status-unread .bk-timeline-node::after {
590
+ background: var(--line-strong);
591
+ transform: scale(0.8);
592
+ }
593
+ .bk-status-unread .bk-timeline-content {
594
+ background: transparent;
595
+ box-shadow: none;
596
+ border: 1px solid transparent;
597
+ }
598
+ .bk-status-unread .bk-timeline-title {
599
+ color: var(--muted);
600
+ }
601
+ .bk-status-unread .bk-timeline-desc {
602
+ color: color-mix(in srgb, var(--muted) 80%, transparent);
603
+ }
604
+ .bk-status-unread .bk-timeline-action {
605
+ color: var(--muted);
606
+ }
607
+ .bk-status-unread:hover .bk-timeline-node {
608
+ border-color: var(--ink);
609
+ background: var(--paper);
610
+ }
611
+ .bk-status-unread:hover .bk-timeline-node::after {
612
+ background: var(--ink);
613
+ transform: scale(1);
614
+ }
615
+ .bk-status-unread:hover .bk-timeline-content {
616
+ background: var(--paper);
617
+ border-color: var(--line);
618
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
619
+ }
620
+ .bk-status-unread:hover .bk-timeline-title {
621
+ color: var(--ink);
622
+ }
623
+ .bk-status-unread:hover .bk-timeline-action {
624
+ color: var(--ink);
625
+ border-bottom-color: var(--ink);
626
+ }
627
+
628
+ @media (prefers-color-scheme: dark) {
629
+ .bk-timeline-content {
630
+ background: var(--panel);
631
+ }
632
+ .bk-status-unread .bk-timeline-content {
633
+ background: transparent;
634
+ }
635
+ .bk-status-unread:hover .bk-timeline-content {
636
+ background: var(--panel);
637
+ }
638
+ }
639
+
640
+ @media (max-width: 600px) {
641
+ .bk-chapter-timeline::before {
642
+ left: 16px;
643
+ }
644
+ .bk-timeline-card {
645
+ gap: 1.5rem;
646
+ }
647
+ .bk-timeline-node {
648
+ width: 32px;
649
+ height: 32px;
650
+ }
651
+ .bk-timeline-node::after {
652
+ width: 8px;
653
+ height: 8px;
654
+ }
655
+ .bk-timeline-content {
656
+ padding: 1.5rem;
657
+ border-radius: 10px;
658
+ }
659
+ .bk-timeline-title {
660
+ font-size: 1.4rem;
661
+ }
662
+ }
663
+ `;
664
+ return `<!DOCTYPE html>
665
+ <html lang="en" data-palette="${palette}" ${schemeAttr}>
666
+ <head>
667
+ <meta charset="UTF-8">
668
+ <meta name="viewport" content="width=device-width, initial-scale=1">
669
+ <title>${escHtml(course.meta.title)}</title>
670
+ ${course.meta.description ? `<meta name="description" content="${escHtml(course.meta.description)}">` : ""}
671
+ ${opts.head ?? ""}
672
+ <style>
673
+ ${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
674
+ ${pageCSS()}
675
+ ${chapterStyles}
676
+ </style>
677
+ </head>
678
+ <body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
679
+ <div class="bk-shell">
680
+ <aside class="bk-sidebar">
681
+ <div class="bk-sidebar-inner">
682
+ <div class="bk-sidebar-header">
683
+ <div style="margin-top: 8px;"></div>
684
+ <div class="bk-sidebar-title">${escHtml(course.meta.title)}</div>
685
+ </div>
686
+ <nav class="bk-nav">${navHtml}</nav>
687
+ <div class="bk-sidebar-footer">
688
+ <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">
689
+ <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>
690
+ <span class="bk-sr-only">Display settings</span>
691
+ </button>
692
+ <div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
693
+ <div class="bk-theme-row">
694
+ <span>Theme</span>
695
+ <div class="bk-segmented-control" id="bk-theme-icons">
696
+ <button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
697
+ <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>
698
+ </button>
699
+ <button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
700
+ <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>
701
+ </button>
702
+ <button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : ""}" data-theme="auto" title="System" aria-label="System theme">
703
+ <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>
704
+ </button>
705
+ </div>
706
+ </div>
707
+ <div class="bk-theme-row">
708
+ <span>Palette</span>
709
+ <div class="bk-segmented-control" id="bk-palette-icons">
710
+ <button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
711
+ <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>
712
+ </button>
713
+ <button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
714
+ <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 12"></path></svg>
715
+ </button>
716
+ <button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
717
+ <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>
718
+ </button>
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ </aside>
725
+ <button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
726
+ <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>
727
+ </button>
728
+ <main class="bk-main">
729
+ <button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
730
+ <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>
731
+ </button>
732
+ <article class="bk-content" style="max-width: 1000px; margin: 0 auto;">
733
+ <header class="bk-hero" style="border-bottom: none;">
734
+ <p class="bk-eyebrow">Course</p>
735
+ <h1>${escHtml(course.meta.title)}</h1>
736
+ ${course.meta.description ? `<p class="bk-deck">${escHtml(course.meta.description)}</p>` : ""}
737
+ </header>
738
+ ${timelineHtml}
739
+ </article>
740
+ </main>
741
+ </div>
742
+ <script>
743
+ ${clientScript()}
744
+ </script>
745
+ </body>
746
+ </html>`;
747
+ }
package/dist/types.d.ts CHANGED
@@ -204,4 +204,13 @@ export interface Chapter {
204
204
  meta: ChapterMeta;
205
205
  lessons: Lesson[];
206
206
  }
207
+ export interface CourseMeta {
208
+ title: string;
209
+ slug: string;
210
+ description?: string;
211
+ }
212
+ export interface Course {
213
+ meta: CourseMeta;
214
+ chapters: Chapter[];
215
+ }
207
216
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-md",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Mr Markdown is an opinionated TypeScript SDK for building interactive, single-file learning pages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,8 +22,8 @@
22
22
  "scripts": {
23
23
  "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles dist/client && cp -r src/styles/* dist/styles/ && cp -r src/client/* dist/client/",
24
24
  "prepublishOnly": "bun run build",
25
- "build:example": "bun demo/demo-1/course.ts",
26
- "serve:example": "bunx serve demo/demo-1/out",
25
+ "build:example": "bun demo/src/course.ts",
26
+ "serve:example": "bunx serve demo/out",
27
27
  "typecheck": "bunx tsc --noEmit",
28
28
  "test": "bun test && bun run typecheck",
29
29
  "test:watch": "bun test --watch",
package/src/builder.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { render, renderChapter } from "./renderer/index.js";
3
+ import { render, renderChapter, renderCourse } from "./renderer/index.js";
4
4
  import type {
5
5
  AnimationBlock,
6
6
  AnimationOptions,
@@ -667,6 +667,32 @@ export class ChapterBuilder {
667
667
  return this;
668
668
  }
669
669
 
670
+ /** @internal Used by CourseBuilder to push down shared config */
671
+ _inheritOptions(parentOpts: BuildOptions, parentRawOpts?: BuildOptions) {
672
+ this.options = {
673
+ outDir:
674
+ this._rawOptions.outDir ?? (parentRawOpts?.outDir || parentOpts.outDir) ?? this.options.outDir,
675
+ contentBase:
676
+ this._rawOptions.contentBase ??
677
+ parentRawOpts?.contentBase ??
678
+ this.options.contentBase,
679
+ theme: this._rawOptions.theme ?? parentOpts.theme ?? this.options.theme,
680
+ palette:
681
+ this._rawOptions.palette ?? parentOpts.palette ?? this.options.palette,
682
+ strict:
683
+ this._rawOptions.strict ?? parentOpts.strict ?? this.options.strict,
684
+ preset: {
685
+ ...parentOpts.preset,
686
+ ...this._rawOptions.preset,
687
+ ...this.options.preset,
688
+ },
689
+ };
690
+
691
+ for (const lb of this.lessonBuilders) {
692
+ lb._inheritOptions(this.options, this._rawOptions);
693
+ }
694
+ }
695
+
670
696
  description(text: string): this {
671
697
  this.meta.description = text;
672
698
  return this;
@@ -749,3 +775,83 @@ export function chapter(
749
775
  ): ChapterBuilder {
750
776
  return new ChapterBuilder(title, options, getCallerDir());
751
777
  }
778
+
779
+ // ─── CourseBuilder ──────────────────────────────────────────────────────────
780
+
781
+ export class CourseBuilder {
782
+ private meta: import("./types.js").CourseMeta;
783
+ private chapterBuilders: ChapterBuilder[] = [];
784
+ private options: BuildOptions;
785
+ private _rawOptions: BuildOptions;
786
+
787
+ constructor(title: string, options: BuildOptions = {}, callerDir?: string) {
788
+ this._rawOptions = options;
789
+ let slug = title
790
+ .toLowerCase()
791
+ .replace(/[^a-z0-9]+/g, "-")
792
+ .replace(/(^-|-$)/g, "");
793
+ if (!slug) slug = "course";
794
+
795
+ this.meta = { title, slug };
796
+ this.options = {
797
+ outDir: options.outDir ?? "./out",
798
+ contentBase: options.contentBase ?? callerDir ?? ".",
799
+ theme: options.theme ?? "auto",
800
+ palette: options.palette ?? "ink",
801
+ strict: options.strict ?? true,
802
+ preset: {
803
+ layout: "lesson",
804
+ density: "comfortable",
805
+ tone: "scholarly",
806
+ ...options.preset,
807
+ },
808
+ ...options,
809
+ };
810
+ }
811
+
812
+ slug(slug: string): this {
813
+ this.meta.slug = slug;
814
+ return this;
815
+ }
816
+
817
+ description(text: string): this {
818
+ this.meta.description = text;
819
+ return this;
820
+ }
821
+
822
+ chapter(chapterBuilder: ChapterBuilder): this {
823
+ chapterBuilder._inheritOptions(this.options, this._rawOptions);
824
+ this.chapterBuilders.push(chapterBuilder);
825
+ return this;
826
+ }
827
+
828
+ build(): string {
829
+ const chapters: Chapter[] = [];
830
+ for (const cb of this.chapterBuilders) {
831
+ cb.build();
832
+ chapters.push(cb.toJSON());
833
+ }
834
+
835
+ const courseData: import("./types.js").Course = { meta: this.meta, chapters };
836
+ const html = renderCourse(courseData, this.options);
837
+
838
+ const outDir = path.resolve(this.options.outDir as string);
839
+ if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
840
+
841
+ const outPath = path.join(outDir, `${this.meta.slug}.html`);
842
+ fs.writeFileSync(outPath, html, "utf-8");
843
+
844
+ const relPath = path.relative(process.cwd(), outPath);
845
+ console.log(
846
+ ` ✓ Built course (${this.chapterBuilders.length} chapters) → ${relPath}`,
847
+ );
848
+ return outPath;
849
+ }
850
+ }
851
+
852
+ export function course(
853
+ title: string,
854
+ options: BuildOptions = {},
855
+ ): CourseBuilder {
856
+ return new CourseBuilder(title, options, getCallerDir());
857
+ }
package/src/client/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  function bkSimDoc(js, props, loop, dependencies) {
2
- const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\\n");
2
+ const scriptTags = (dependencies || []).map(url => '<script src="' + url.replace(/"/g, '&quot;') + '"></' + 'script>').join("\n");
3
3
  return (
4
4
  "<!DOCTYPE html><html><head>" +
5
5
  scriptTags +
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { ChapterBuilder, chapter, LessonBuilder, lesson } from "./builder.js";
1
+ export { ChapterBuilder, chapter, CourseBuilder, course, LessonBuilder, lesson } from "./builder.js";
2
2
  export { render } from "./renderer/index.js";
3
3
  export type {
4
4
  AnimationBlock,
@@ -62,8 +62,8 @@ function renderBlockInner(
62
62
  switch (block.type) {
63
63
  case "heading": {
64
64
  const md = resolveContent(block.src, options, "md");
65
- const { html, title } = mdToHtml(md);
66
- const label = block.title || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Heading");
65
+ const { html, title, headings } = mdToHtml(md);
66
+ const label = block.title || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Heading");
67
67
  const id = `heading-${idx}`;
68
68
  return {
69
69
  html: `<section id="${id}" class="bk-section bk-heading">${html}</section>`,
@@ -84,8 +84,8 @@ function renderBlockInner(
84
84
 
85
85
  case "section": {
86
86
  const md = resolveContent(block.src, options, "md");
87
- const { html, title } = mdToHtml(md);
88
- const label = block.label || title || (typeof block.src === "string" && !block.src.includes(".md") ? block.src : "Section");
87
+ const { html, title, headings } = mdToHtml(md);
88
+ const label = block.label || title || headings[0]?.text || (typeof block.src === "string" && !block.src.includes(".md") ? block.src.replace(/^#+\s*/, '') : "Section");
89
89
  const id = `section-${idx}`;
90
90
  return {
91
91
  html: `<section id="${id}" class="bk-section bk-subsection">${html}</section>`,
@@ -288,7 +288,8 @@ function renderBlockInner(
288
288
  if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) {
289
289
  throw new Error("Quiz file not found or invalid JSON format");
290
290
  }
291
- quiz = JSON.parse(trimmed);
291
+ const parsed = JSON.parse(trimmed);
292
+ quiz = Array.isArray(parsed) ? { questions: parsed } : parsed;
292
293
  } catch (e) {
293
294
  const msg = e instanceof Error ? e.message : String(e);
294
295
  if (options.strict !== false) {
@@ -351,7 +352,7 @@ function renderQuestion(q: QuizQuestion, quizId: string, qi: number): string {
351
352
 
352
353
  // Wraps a JS string in a minimal iframe document
353
354
  function iframeDoc(js: string, props: string, loop?: boolean, dependencies?: string[]): string {
354
- const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\\n");
355
+ const scriptTags = (dependencies ?? []).map((url) => `<script src="${escAttr(url)}"></script>`).join("\n");
355
356
  const doc = `<!DOCTYPE html><html><head>
356
357
  ${scriptTags}
357
358
  <style>
@@ -1,4 +1,4 @@
1
- import type { BuildOptions, Chapter, Lesson } from "../types.js";
1
+ import type { BuildOptions, Chapter, Course, Lesson } from "../types.js";
2
2
  import { escAttr, escHtml, renderBlock } from "./blocks.js";
3
3
  import { clientScript, pageCSS, renderPage } from "./html.js"; // Used in renderChapter
4
4
  import type { NavItem } from "./utils.js";
@@ -399,3 +399,383 @@ ${clientScript()}
399
399
  </body>
400
400
  </html>`;
401
401
  }
402
+
403
+ // ─── Course Rendering ────────────────────────────────────────────────────────
404
+
405
+ export function renderCourse(
406
+ course: Course,
407
+ opts: BuildOptions = {},
408
+ ): string {
409
+ const theme = opts.theme ?? "auto";
410
+ const schemeAttr = theme === "auto" ? "" : `data-theme="${theme}"`;
411
+ const preset = opts.preset ?? {};
412
+ const layout = preset.layout ?? "lesson";
413
+ const density = preset.density ?? "comfortable";
414
+ const tone = preset.tone ?? "scholarly";
415
+ const palette = opts.palette ?? "ink";
416
+
417
+ const navHtml = course.chapters
418
+ .map(
419
+ (c) =>
420
+ `<a href="${escAttr(c.meta.slug)}.html" class="bk-nav-item bk-nav-chapter">${escHtml(c.meta.title)}</a>`,
421
+ )
422
+ .join("\n");
423
+
424
+ const timelineHtml = `
425
+ <div class="bk-chapter-timeline-wrapper">
426
+ <div class="bk-chapter-timeline">
427
+ ${course.chapters
428
+ .map(
429
+ (chapter, idx) => `
430
+ <a href="${escAttr(chapter.meta.slug)}.html" class="bk-timeline-card bk-status-${chapter.meta.status ?? "unread"}">
431
+ <div class="bk-timeline-node"></div>
432
+ <div class="bk-timeline-content">
433
+ <h3 class="bk-timeline-title" style="view-transition-name: title-${escAttr(chapter.meta.slug)}">${escHtml(chapter.meta.title)}</h3>
434
+ ${chapter.meta.description ? `<p class="bk-timeline-desc">${escHtml(chapter.meta.description)}</p>` : ""}
435
+ <span class="bk-timeline-action">${chapter.meta.status === "completed" ? "Review" : "Start Chapter"} <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="M5 12h14m-7-7 7 7-7 7"/></svg></span>
436
+ </div>
437
+ </a>
438
+ `,
439
+ )
440
+ .join("")}
441
+ </div>
442
+ </div>
443
+ <script>
444
+ if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
445
+ const observer = new IntersectionObserver(
446
+ (entries) => {
447
+ for (const entry of entries) {
448
+ if (entry.isIntersecting) {
449
+ entry.target.classList.add('js-visible');
450
+ entry.target.classList.remove('js-hidden');
451
+ observer.unobserve(entry.target);
452
+ }
453
+ }
454
+ },
455
+ { threshold: 0.1 }
456
+ );
457
+ document.querySelectorAll('.bk-timeline-card').forEach((el) => {
458
+ el.classList.add('js-hidden');
459
+ observer.observe(el);
460
+ });
461
+ }
462
+ </script>`;
463
+
464
+ const chapterStyles = `
465
+ .bk-chapter-timeline-wrapper {
466
+ position: relative;
467
+ width: 100%;
468
+ padding: 1rem 0;
469
+ z-index: 1;
470
+ }
471
+
472
+ .bk-chapter-timeline {
473
+ display: flex;
474
+ flex-direction: column;
475
+ gap: 3rem;
476
+ padding: 3rem 0;
477
+ position: relative;
478
+ max-width: 800px;
479
+ margin: 0 auto;
480
+ }
481
+
482
+ /* Minimalist vertical connecting line */
483
+ .bk-chapter-timeline::before {
484
+ content: '';
485
+ position: absolute;
486
+ left: 20px;
487
+ top: 3rem;
488
+ bottom: 3rem;
489
+ width: 1px;
490
+ background: var(--line);
491
+ z-index: 0;
492
+ transform: translateX(-50%);
493
+ }
494
+
495
+ .bk-timeline-card {
496
+ display: flex;
497
+ align-items: stretch;
498
+ gap: 2rem;
499
+ text-decoration: none !important;
500
+ border: none !important;
501
+ color: inherit;
502
+ position: relative;
503
+ z-index: 1;
504
+ opacity: 1;
505
+ transform: none;
506
+ }
507
+
508
+ /* Subtle scroll-driven animations */
509
+ @media (prefers-reduced-motion: no-preference) {
510
+ @supports ((animation-timeline: view()) and (animation-range: entry)) {
511
+ .bk-timeline-card {
512
+ animation-name: slide-fade-in;
513
+ animation-fill-mode: both;
514
+ animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
515
+ animation-timeline: view(block);
516
+ animation-range: entry 5% cover 20%;
517
+ }
518
+ @keyframes slide-fade-in {
519
+ 0% { opacity: 0; transform: translateY(20px); }
520
+ 100% { opacity: 1; transform: translateY(0); }
521
+ }
522
+ }
523
+ }
524
+
525
+ /* Fallback for browsers without animation-timeline support */
526
+ .bk-timeline-card.js-hidden {
527
+ opacity: 0;
528
+ transform: translateY(20px);
529
+ }
530
+ .bk-timeline-card.js-visible {
531
+ opacity: 1;
532
+ transform: translateY(0);
533
+ transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
534
+ }
535
+
536
+ .bk-timeline-node {
537
+ width: 40px;
538
+ height: 40px;
539
+ border-radius: 50%;
540
+ background: var(--paper);
541
+ border: 1px solid var(--accent);
542
+ display: flex;
543
+ align-items: center;
544
+ justify-content: center;
545
+ flex-shrink: 0;
546
+ transition: all 0.3s ease;
547
+ position: relative;
548
+ box-shadow: 0 2px 4px rgba(0,0,0,0.02);
549
+ }
550
+ .bk-timeline-node::after {
551
+ content: '';
552
+ width: 10px;
553
+ height: 10px;
554
+ border-radius: 50%;
555
+ background: var(--accent);
556
+ transition: all 0.3s ease;
557
+ }
558
+ .bk-timeline-card:hover .bk-timeline-node {
559
+ background: var(--accent);
560
+ }
561
+ .bk-timeline-card:hover .bk-timeline-node::after {
562
+ background: var(--paper);
563
+ }
564
+
565
+ .bk-timeline-content {
566
+ background: var(--paper);
567
+ padding: 2rem;
568
+ border-radius: 12px;
569
+ border: 1px solid var(--line);
570
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
571
+ flex: 1;
572
+ transition: all 0.3s ease;
573
+ display: flex;
574
+ flex-direction: column;
575
+ justify-content: center;
576
+ position: relative;
577
+ }
578
+ .bk-timeline-card:hover .bk-timeline-content {
579
+ border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
580
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
581
+ transform: translateY(-2px);
582
+ }
583
+
584
+ .bk-timeline-title {
585
+ margin: 0 0 0.5rem 0;
586
+ font-family: var(--font-display);
587
+ font-size: 1.6rem;
588
+ color: var(--ink);
589
+ width: fit-content;
590
+ transition: color 0.2s ease;
591
+ }
592
+ .bk-timeline-card:hover .bk-timeline-title {
593
+ color: var(--accent);
594
+ }
595
+
596
+ .bk-timeline-desc {
597
+ margin: 0 0 1.5rem 0;
598
+ color: var(--muted);
599
+ line-height: 1.6;
600
+ font-size: 1.05rem;
601
+ }
602
+
603
+ .bk-timeline-action {
604
+ font-weight: 500;
605
+ color: var(--accent);
606
+ display: inline-flex;
607
+ align-items: center;
608
+ gap: 0.4rem;
609
+ font-size: 0.95rem;
610
+ align-self: flex-start;
611
+ transition: all 0.2s ease;
612
+ border-bottom: 1px solid transparent;
613
+ }
614
+ .bk-timeline-card:hover .bk-timeline-action {
615
+ gap: 0.6rem;
616
+ border-bottom-color: var(--accent);
617
+ }
618
+
619
+ .bk-status-unread .bk-timeline-node {
620
+ border: 1px solid var(--line-strong);
621
+ }
622
+ .bk-status-unread .bk-timeline-node::after {
623
+ background: var(--line-strong);
624
+ transform: scale(0.8);
625
+ }
626
+ .bk-status-unread .bk-timeline-content {
627
+ background: transparent;
628
+ box-shadow: none;
629
+ border: 1px solid transparent;
630
+ }
631
+ .bk-status-unread .bk-timeline-title {
632
+ color: var(--muted);
633
+ }
634
+ .bk-status-unread .bk-timeline-desc {
635
+ color: color-mix(in srgb, var(--muted) 80%, transparent);
636
+ }
637
+ .bk-status-unread .bk-timeline-action {
638
+ color: var(--muted);
639
+ }
640
+ .bk-status-unread:hover .bk-timeline-node {
641
+ border-color: var(--ink);
642
+ background: var(--paper);
643
+ }
644
+ .bk-status-unread:hover .bk-timeline-node::after {
645
+ background: var(--ink);
646
+ transform: scale(1);
647
+ }
648
+ .bk-status-unread:hover .bk-timeline-content {
649
+ background: var(--paper);
650
+ border-color: var(--line);
651
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.02);
652
+ }
653
+ .bk-status-unread:hover .bk-timeline-title {
654
+ color: var(--ink);
655
+ }
656
+ .bk-status-unread:hover .bk-timeline-action {
657
+ color: var(--ink);
658
+ border-bottom-color: var(--ink);
659
+ }
660
+
661
+ @media (prefers-color-scheme: dark) {
662
+ .bk-timeline-content {
663
+ background: var(--panel);
664
+ }
665
+ .bk-status-unread .bk-timeline-content {
666
+ background: transparent;
667
+ }
668
+ .bk-status-unread:hover .bk-timeline-content {
669
+ background: var(--panel);
670
+ }
671
+ }
672
+
673
+ @media (max-width: 600px) {
674
+ .bk-chapter-timeline::before {
675
+ left: 16px;
676
+ }
677
+ .bk-timeline-card {
678
+ gap: 1.5rem;
679
+ }
680
+ .bk-timeline-node {
681
+ width: 32px;
682
+ height: 32px;
683
+ }
684
+ .bk-timeline-node::after {
685
+ width: 8px;
686
+ height: 8px;
687
+ }
688
+ .bk-timeline-content {
689
+ padding: 1.5rem;
690
+ border-radius: 10px;
691
+ }
692
+ .bk-timeline-title {
693
+ font-size: 1.4rem;
694
+ }
695
+ }
696
+ `;
697
+
698
+ return `<!DOCTYPE html>
699
+ <html lang="en" data-palette="${palette}" ${schemeAttr}>
700
+ <head>
701
+ <meta charset="UTF-8">
702
+ <meta name="viewport" content="width=device-width, initial-scale=1">
703
+ <title>${escHtml(course.meta.title)}</title>
704
+ ${course.meta.description ? `<meta name="description" content="${escHtml(course.meta.description)}">` : ""}
705
+ ${opts.head ?? ""}
706
+ <style>
707
+ ${opts.font ? `:root { --font-sans: ${opts.font}, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }` : ""}
708
+ ${pageCSS()}
709
+ ${chapterStyles}
710
+ </style>
711
+ </head>
712
+ <body class="bk-layout-${layout} bk-density-${density} bk-tone-${tone}">
713
+ <div class="bk-shell">
714
+ <aside class="bk-sidebar">
715
+ <div class="bk-sidebar-inner">
716
+ <div class="bk-sidebar-header">
717
+ <div style="margin-top: 8px;"></div>
718
+ <div class="bk-sidebar-title">${escHtml(course.meta.title)}</div>
719
+ </div>
720
+ <nav class="bk-nav">${navHtml}</nav>
721
+ <div class="bk-sidebar-footer">
722
+ <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">
723
+ <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>
724
+ <span class="bk-sr-only">Display settings</span>
725
+ </button>
726
+ <div class="bk-theme-panel" id="bk-theme-panel" aria-label="Display settings" hidden>
727
+ <div class="bk-theme-row">
728
+ <span>Theme</span>
729
+ <div class="bk-segmented-control" id="bk-theme-icons">
730
+ <button type="button" class="bk-segment-btn ${theme === "light" ? "active" : ""}" data-theme="light" title="Light" aria-label="Light theme">
731
+ <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>
732
+ </button>
733
+ <button type="button" class="bk-segment-btn ${theme === "dark" ? "active" : ""}" data-theme="dark" title="Dark" aria-label="Dark theme">
734
+ <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>
735
+ </button>
736
+ <button type="button" class="bk-segment-btn ${theme === "auto" ? "active" : ""}" data-theme="auto" title="System" aria-label="System theme">
737
+ <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>
738
+ </button>
739
+ </div>
740
+ </div>
741
+ <div class="bk-theme-row">
742
+ <span>Palette</span>
743
+ <div class="bk-segmented-control" id="bk-palette-icons">
744
+ <button type="button" class="bk-segment-btn ${palette === "ink" ? "active" : ""}" data-palette="ink" title="Ink" aria-label="Ink palette">
745
+ <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>
746
+ </button>
747
+ <button type="button" class="bk-segment-btn ${palette === "field" ? "active" : ""}" data-palette="field" title="Field" aria-label="Field palette">
748
+ <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 12"></path></svg>
749
+ </button>
750
+ <button type="button" class="bk-segment-btn ${palette === "ember" ? "active" : ""}" data-palette="ember" title="Ember" aria-label="Ember palette">
751
+ <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>
752
+ </button>
753
+ </div>
754
+ </div>
755
+ </div>
756
+ </div>
757
+ </div>
758
+ </aside>
759
+ <button class="bk-sidebar-collapse-floating" id="bk-sidebar-collapse" aria-label="Collapse sidebar">
760
+ <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>
761
+ </button>
762
+ <main class="bk-main">
763
+ <button class="bk-sidebar-expand" id="bk-sidebar-expand" type="button" aria-label="Expand sidebar">
764
+ <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>
765
+ </button>
766
+ <article class="bk-content" style="max-width: 1000px; margin: 0 auto;">
767
+ <header class="bk-hero" style="border-bottom: none;">
768
+ <p class="bk-eyebrow">Course</p>
769
+ <h1>${escHtml(course.meta.title)}</h1>
770
+ ${course.meta.description ? `<p class="bk-deck">${escHtml(course.meta.description)}</p>` : ""}
771
+ </header>
772
+ ${timelineHtml}
773
+ </article>
774
+ </main>
775
+ </div>
776
+ <script>
777
+ ${clientScript()}
778
+ </script>
779
+ </body>
780
+ </html>`;
781
+ }
package/src/types.ts CHANGED
@@ -286,3 +286,14 @@ export interface Chapter {
286
286
  meta: ChapterMeta;
287
287
  lessons: Lesson[];
288
288
  }
289
+
290
+ export interface CourseMeta {
291
+ title: string;
292
+ slug: string;
293
+ description?: string;
294
+ }
295
+
296
+ export interface Course {
297
+ meta: CourseMeta;
298
+ chapters: Chapter[];
299
+ }