yumiamd 0.1.15 → 0.1.17

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 CHANGED
@@ -16,6 +16,9 @@ You can run YumiaMD immediately without installing anything via `npx`:
16
16
  # Initialize a starter presentation
17
17
  npx yumiamd init my-deck
18
18
 
19
+ # Launch instant dev server with live-reload
20
+ npx yumiamd dev presentation.yumia.md --open
21
+
19
22
  # Compile presentation directly to an editable PowerPoint (.pptx)
20
23
  npx yumiamd build presentation.yumia.md --out presentation.pptx
21
24
  ```
@@ -64,6 +67,9 @@ yumia build presentation.yumia.md --format pdf --out dist/presentation.pdf
64
67
  # Compile to standalone interactive HTML5 presentation deck (.html)
65
68
  yumia build presentation.yumia.md --format html --out dist/presentation.html
66
69
 
70
+ # Deploy presentation deck (static, GitHub Pages, or Vercel)
71
+ yumia deploy presentation.yumia.md --provider gh-pages --out public
72
+
67
73
  # Watch presentation file and recompile automatically on save
68
74
  yumia watch presentation.yumia.md --format pdf
69
75
 
@@ -96,6 +102,9 @@ author: Biagio Scaglia
96
102
 
97
103
  Deterministic compilation from Markdown directly to native PowerPoint slides.
98
104
 
105
+ :::badge text="v0.1.15" variant="primary" :::
106
+ :::badge text="Multi-Target" variant="success" :::
107
+
99
108
  :::notes
100
109
  Opening slide introducing the core architectural vision.
101
110
  :::
@@ -128,14 +137,26 @@ Opening slide introducing the core architectural vision.
128
137
 
129
138
  ---
130
139
 
131
- # Feature Comparison
140
+ # Native Charts & Data Visuals
141
+
142
+ :::chart type="bar" title="Execution Speed" labels="Parser, Layout, PDF, PPTX" data="1200, 850, 430, 680"
143
+
144
+ ---
145
+
146
+ # Roadmap Timeline
147
+
148
+ :::timeline layout="horizontal"
149
+
150
+ - [Phase 1] AST & Compiler Core
151
+ - [Phase 2] Multi-Format Renderers
152
+ - [Phase 3] Rich Directives & Charts
153
+ - [Phase 4] Cloud Deployments
154
+ :::
155
+
156
+ :::step
132
157
 
133
- | Feature | YumiaMD | Legacy HTML Deck Tools |
134
- | :----------------------- | :------------------------------- | :----------------------------------- |
135
- | **Output Type** | Native OpenXML Shapes & Text | Flat rasterized images / screenshots |
136
- | **Full Editability** | ✅ 100% Editable in PowerPoint | ❌ Read-only image slides |
137
- | **Deterministic Layout** | ✅ Pixel-exact bounding boxes | ❌ Browser rendering variance |
138
- | **AI / Agent Tooling** | ✅ Machine-readable schema & CLI | ❌ Complex DOM scraping |
158
+ - 🚀 Seamless deployment via `yumia deploy`
159
+ :::
139
160
  ```
140
161
 
141
162
  ---
@@ -171,8 +192,10 @@ const schema = compiler.getSchema();
171
192
 
172
193
  ## ✨ Key Features
173
194
 
174
- - 🎯 **Native Object Principle**: Slides compiled to PowerPoint are **NOT** rasterized screenshot images. Headings, bullet points, cards, tables, and speaker notes are generated as **100% native vector PowerPoint shapes, tables & textboxes** that you can click, re-format, and edit in Microsoft PowerPoint or Google Slides.
175
- - 📊 **Native Tables & Multicolumn**: Full support for Markdown tables and semantic responsive multi-column layouts with customizable ratio splits (`ratios="50:50"` or `"30:70"`).
195
+ - 🎯 **Native Object Principle**: Slides compiled to PowerPoint are **NOT** rasterized screenshot images. Headings, bullet points, cards, tables, charts, badges, and speaker notes are generated as **100% native vector PowerPoint shapes, tables & textboxes** that you can click, re-format, and edit in Microsoft PowerPoint or Google Slides.
196
+ - 📊 **Native Charts & Diagrams**: Full support for `:::chart` (bar, line, pie, doughnut) and `:::mermaid` diagrams across HTML, PDF, and PowerPoint.
197
+ - ⏳ **Timelines, Compare & Steps**: Rich layout directives including `:::timeline`, `:::compare`, and click-to-reveal `:::step` animations.
198
+ - 🚀 **One-Command Cloud Deploy**: Instantly deploy decks to GitHub Pages, Vercel, or static web servers with `yumia deploy`.
176
199
  - 📐 **Deterministic Layout Engine**: Exact coordinate calculations for stack, columns, cards, and automatic overflow detection.
177
200
  - 🎨 **Semantic Design Tokens**: Built-in themes with typography scales, color palettes, and contrast-safe themes.
178
201
  - 🤖 **AI-Friendly Format**: Clean Markdown syntax designed for LLM prompts and agentic workflows, complete with `yumia schema` and `--json` CLI diagnostics.
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-7A5BHABM.js";
4
+ } from "./chunk-IAEXC5SG.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -81,6 +81,45 @@ function createTable(rows, headers) {
81
81
  ...headers ? { headers } : {}
82
82
  };
83
83
  }
84
+ function createChart(chartType, labels, series, title) {
85
+ return {
86
+ type: "chart",
87
+ chartType,
88
+ labels,
89
+ series,
90
+ ...title ? { title } : {}
91
+ };
92
+ }
93
+ function createMermaid(code, chartType) {
94
+ return {
95
+ type: "mermaid",
96
+ code,
97
+ ...chartType ? { chartType } : {}
98
+ };
99
+ }
100
+ function createTimeline(items, layout = "horizontal") {
101
+ return {
102
+ type: "timeline",
103
+ items,
104
+ layout
105
+ };
106
+ }
107
+ function createCompare(left, right, leftTitle, rightTitle) {
108
+ return {
109
+ type: "compare",
110
+ left,
111
+ right,
112
+ ...leftTitle ? { leftTitle } : {},
113
+ ...rightTitle ? { rightTitle } : {}
114
+ };
115
+ }
116
+ function createBadge(text, variant) {
117
+ return {
118
+ type: "badge",
119
+ text,
120
+ ...variant ? { variant } : {}
121
+ };
122
+ }
84
123
  function createGroup(elements, direction = "row", gap) {
85
124
  return {
86
125
  type: "group",
@@ -402,6 +441,32 @@ var DefaultYumiaParser = class {
402
441
  i++;
403
442
  continue;
404
443
  }
444
+ if (directiveHeader.startsWith("badge")) {
445
+ const badgeArg = directiveHeader.slice(5).trim();
446
+ const variantMatch = badgeArg.match(/variant=['"](.*?)['"]/);
447
+ const textMatch = badgeArg.match(/text=['"](.*?)['"]/);
448
+ const variant = variantMatch ? variantMatch[1] : void 0;
449
+ let text = textMatch ? textMatch[1] : badgeArg.replace(/variant=['"].*?['"]/, "").trim();
450
+ text = text.replace(/^['"](.*)['"]$/, "$1");
451
+ const el = createBadge(text, variant);
452
+ el.loc = {
453
+ start: { line: currentLineNum, column: 1 },
454
+ end: { line: currentLineNum, column: rawLine.length + 1 }
455
+ };
456
+ elements.push(el);
457
+ i++;
458
+ continue;
459
+ }
460
+ if (directiveHeader.startsWith("chart") && (directiveHeader.includes("data=") || directiveHeader.includes("labels="))) {
461
+ const el = this.parseChartDirective(directiveHeader, []);
462
+ el.loc = {
463
+ start: { line: currentLineNum, column: 1 },
464
+ end: { line: currentLineNum, column: rawLine.length + 1 }
465
+ };
466
+ elements.push(el);
467
+ i++;
468
+ continue;
469
+ }
405
470
  const [directiveName, ...args] = directiveHeader.split(" ");
406
471
  const directiveArg = args.join(" ").trim();
407
472
  const blockLines = [];
@@ -410,7 +475,9 @@ var DefaultYumiaParser = class {
410
475
  let closed = false;
411
476
  while (i < lines.length) {
412
477
  const innerLine = lines[i]?.trim() ?? "";
413
- const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
478
+ const isInlineClosed = innerLine.startsWith(":::") && innerLine.endsWith(":::") && innerLine.length > 5;
479
+ const isSeparator = innerLine === ":::vs" || innerLine.startsWith(":::vs ");
480
+ const isSingleLine = isInlineClosed || isSeparator || innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout") || innerLine.startsWith(":::badge") && (innerLine.includes("text=") || innerLine.includes("variant=")) || innerLine.startsWith(":::chart") && innerLine.includes("data=");
414
481
  if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
415
482
  nestedCount++;
416
483
  } else if (innerLine === ":::") {
@@ -473,6 +540,41 @@ var DefaultYumiaParser = class {
473
540
  end: { line: baseLine + i - 1, column: 1 }
474
541
  };
475
542
  elements.push(el);
543
+ } else if (directiveName === "mermaid") {
544
+ const code = blockLines.join("\n").trim();
545
+ const el = createMermaid(code);
546
+ el.loc = {
547
+ start: { line: directiveStartLine, column: 1 },
548
+ end: { line: baseLine + i - 1, column: 1 }
549
+ };
550
+ elements.push(el);
551
+ } else if (directiveName === "chart") {
552
+ const el = this.parseChartDirective(directiveArg, blockLines);
553
+ el.loc = {
554
+ start: { line: directiveStartLine, column: 1 },
555
+ end: { line: baseLine + i - 1, column: 1 }
556
+ };
557
+ elements.push(el);
558
+ } else if (directiveName === "timeline") {
559
+ const el = this.parseTimelineBlock(directiveArg, blockLines);
560
+ el.loc = {
561
+ start: { line: directiveStartLine, column: 1 },
562
+ end: { line: baseLine + i - 1, column: 1 }
563
+ };
564
+ elements.push(el);
565
+ } else if (directiveName === "compare") {
566
+ const el = this.parseCompareBlock(directiveArg, blockLines, blockBaseLine);
567
+ el.loc = {
568
+ start: { line: directiveStartLine, column: 1 },
569
+ end: { line: baseLine + i - 1, column: 1 }
570
+ };
571
+ elements.push(el);
572
+ } else if (directiveName === "step") {
573
+ const { elements: stepElements } = this.parseLines(blockLines, blockBaseLine);
574
+ for (const sEl of stepElements) {
575
+ sEl.step = 1;
576
+ elements.push(sEl);
577
+ }
476
578
  }
477
579
  continue;
478
580
  }
@@ -595,7 +697,9 @@ var DefaultYumiaParser = class {
595
697
  let nestedCount = 1;
596
698
  while (i < lines.length) {
597
699
  const innerLine = lines[i]?.trim() ?? "";
598
- const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
700
+ const isInlineClosed = innerLine.startsWith(":::") && innerLine.endsWith(":::") && innerLine.length > 5;
701
+ const isSeparator = innerLine === ":::vs" || innerLine.startsWith(":::vs ");
702
+ const isSingleLine = isInlineClosed || isSeparator || innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout") || innerLine.startsWith(":::badge") && (innerLine.includes("text=") || innerLine.includes("variant=")) || innerLine.startsWith(":::chart") && innerLine.includes("data=");
599
703
  if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
600
704
  nestedCount++;
601
705
  } else if (innerLine === ":::") {
@@ -621,6 +725,140 @@ var DefaultYumiaParser = class {
621
725
  }
622
726
  return columns;
623
727
  }
728
+ parseChartDirective(headerArg, blockLines) {
729
+ const typeMatch = headerArg.match(/type=['"](.*?)['"]/);
730
+ const titleMatch = headerArg.match(/title=['"](.*?)['"]/);
731
+ const labelsMatch = headerArg.match(/labels=['"](.*?)['"]/);
732
+ const dataMatch = headerArg.match(/data=['"](.*?)['"]/);
733
+ const chartType = typeMatch ? typeMatch[1] : "bar";
734
+ const title = titleMatch ? titleMatch[1] : void 0;
735
+ let labels = [];
736
+ const series = [];
737
+ if (labelsMatch && labelsMatch[1]) {
738
+ labels = labelsMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
739
+ }
740
+ if (dataMatch && dataMatch[1]) {
741
+ const rawData = dataMatch[1].trim();
742
+ if (rawData.includes(":")) {
743
+ const parts = rawData.split(",").map((p) => p.trim());
744
+ const values = [];
745
+ const extractedLabels = [];
746
+ for (const part of parts) {
747
+ const [k, v] = part.split(":").map((s) => s.trim());
748
+ if (k && v !== void 0) {
749
+ extractedLabels.push(k);
750
+ values.push(parseFloat(v) || 0);
751
+ }
752
+ }
753
+ if (labels.length === 0)
754
+ labels = extractedLabels;
755
+ series.push({ name: title || "Data", values });
756
+ } else {
757
+ const values = rawData.split(",").map((v) => parseFloat(v.trim()) || 0);
758
+ series.push({ name: title || "Data", values });
759
+ }
760
+ }
761
+ for (const line of blockLines) {
762
+ const trimmed = line.trim();
763
+ if (!trimmed || trimmed.startsWith("#"))
764
+ continue;
765
+ if (trimmed.toLowerCase().startsWith("labels:")) {
766
+ labels = trimmed.slice(7).split(",").map((s) => s.trim()).filter(Boolean);
767
+ } else if (trimmed.toLowerCase().startsWith("series:")) {
768
+ const seriesContent = trimmed.slice(7).trim();
769
+ const sMatch = seriesContent.match(/^(.*?)\s*\[(.*?)\]$/);
770
+ if (sMatch && sMatch[1] && sMatch[2]) {
771
+ const sName = sMatch[1].trim();
772
+ const sValues = sMatch[2].split(",").map((v) => parseFloat(v.trim()) || 0);
773
+ series.push({ name: sName, values: sValues });
774
+ }
775
+ } else if (trimmed.includes("|")) {
776
+ const parts = trimmed.split("|").map((s) => s.trim());
777
+ if (parts.length >= 2) {
778
+ const [l, valStr] = parts;
779
+ if (l && valStr) {
780
+ labels.push(l);
781
+ const val = parseFloat(valStr) || 0;
782
+ if (series.length === 0)
783
+ series.push({ name: "Data", values: [] });
784
+ series[0]?.values.push(val);
785
+ }
786
+ }
787
+ }
788
+ }
789
+ if (labels.length === 0) {
790
+ const maxLen = Math.max(0, ...series.map((s) => s.values.length));
791
+ labels = Array.from({ length: maxLen }, (_, i) => `Item ${i + 1}`);
792
+ }
793
+ return createChart(chartType, labels, series, title);
794
+ }
795
+ parseTimelineBlock(headerArg, blockLines) {
796
+ const layoutMatch = headerArg.match(/layout=['"](.*?)['"]/);
797
+ const layout = layoutMatch && layoutMatch[1] === "vertical" ? "vertical" : "horizontal";
798
+ const items = [];
799
+ for (const line of blockLines) {
800
+ const trimmed = line.trim();
801
+ if (!trimmed)
802
+ continue;
803
+ const bracketMatch = trimmed.match(/^[-*]?\s*\[(.*?)\]\s*(.*?)(?::\s*(.*))?$/);
804
+ if (bracketMatch && bracketMatch[1] && bracketMatch[2]) {
805
+ items.push({
806
+ date: bracketMatch[1].trim(),
807
+ title: bracketMatch[2].trim(),
808
+ description: bracketMatch[3] ? bracketMatch[3].trim() : void 0
809
+ });
810
+ continue;
811
+ }
812
+ if (trimmed.includes("|")) {
813
+ const parts = trimmed.split("|").map((s) => s.trim());
814
+ if (parts.length >= 2) {
815
+ items.push({
816
+ date: parts[0],
817
+ title: parts[1] || "",
818
+ description: parts[2] || void 0
819
+ });
820
+ continue;
821
+ }
822
+ }
823
+ items.push({
824
+ title: trimmed.replace(/^[-*]\s*/, "")
825
+ });
826
+ }
827
+ return createTimeline(items, layout);
828
+ }
829
+ parseCompareBlock(headerArg, blockLines, baseLine) {
830
+ const leftMatch = headerArg.match(/left=['"](.*?)['"]/);
831
+ const rightMatch = headerArg.match(/right=['"](.*?)['"]/);
832
+ const leftTitle = leftMatch ? leftMatch[1] : void 0;
833
+ const rightTitle = rightMatch ? rightMatch[1] : void 0;
834
+ let leftLines = [];
835
+ let rightLines = [];
836
+ let currentSide = "left";
837
+ for (const line of blockLines) {
838
+ const trimmed = line.trim();
839
+ if (trimmed === ":::vs" || trimmed === ":::right" || trimmed === "---") {
840
+ currentSide = "right";
841
+ continue;
842
+ }
843
+ if (trimmed === ":::left") {
844
+ currentSide = "left";
845
+ continue;
846
+ }
847
+ if (currentSide === "left") {
848
+ leftLines.push(line);
849
+ } else {
850
+ rightLines.push(line);
851
+ }
852
+ }
853
+ if (rightLines.length === 0 && leftLines.length > 1) {
854
+ const half = Math.ceil(leftLines.length / 2);
855
+ rightLines = leftLines.slice(half);
856
+ leftLines = leftLines.slice(0, half);
857
+ }
858
+ const { elements: leftElements } = this.parseLines(leftLines, baseLine);
859
+ const { elements: rightElements } = this.parseLines(rightLines, baseLine + leftLines.length);
860
+ return createCompare(leftElements, rightElements, leftTitle, rightTitle);
861
+ }
624
862
  };
625
863
  function parseYumia(source, options) {
626
864
  const parser = new DefaultYumiaParser();
@@ -2050,6 +2288,21 @@ var PptxRenderer = class {
2050
2288
  case "columns":
2051
2289
  this.renderColumns(pptxSlide, pptx, node, scaleX, scaleY, theme);
2052
2290
  break;
2291
+ case "badge":
2292
+ this.renderBadge(pptxSlide, pptx, element, rect, theme);
2293
+ break;
2294
+ case "chart":
2295
+ this.renderChart(pptxSlide, pptx, element, rect, theme);
2296
+ break;
2297
+ case "timeline":
2298
+ this.renderTimeline(pptxSlide, pptx, element, rect, theme);
2299
+ break;
2300
+ case "compare":
2301
+ this.renderCompare(pptxSlide, pptx, node, scaleX, scaleY, theme);
2302
+ break;
2303
+ case "mermaid":
2304
+ this.renderMermaid(pptxSlide, pptx, element, rect, theme);
2305
+ break;
2053
2306
  default:
2054
2307
  break;
2055
2308
  }
@@ -2358,6 +2611,216 @@ var PptxRenderer = class {
2358
2611
  valign: "middle"
2359
2612
  });
2360
2613
  }
2614
+ renderBadge(pptxSlide, pptx, badge, rect, theme) {
2615
+ const colorKey = badge.variant || "primary";
2616
+ const badgeColor = this.cleanHexColor(theme.colors[colorKey] || theme.colors.primary);
2617
+ const badgeW = Math.min(2.5, Math.max(1, badge.text.length * 0.12 + 0.4));
2618
+ const badgeH = Math.min(0.38, rect.h);
2619
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2620
+ x: rect.x,
2621
+ y: rect.y,
2622
+ w: badgeW,
2623
+ h: badgeH,
2624
+ fill: { color: this.cleanHexColor(theme.colors.surface) },
2625
+ line: { color: badgeColor, width: 1.5 },
2626
+ rectRadius: 0.15
2627
+ });
2628
+ pptxSlide.addText(badge.text.toUpperCase(), {
2629
+ x: rect.x,
2630
+ y: rect.y,
2631
+ w: badgeW,
2632
+ h: badgeH,
2633
+ fontSize: 10,
2634
+ bold: true,
2635
+ color: badgeColor,
2636
+ fontFace: cleanFontFace(theme.typography.headingFont),
2637
+ align: "center",
2638
+ valign: "middle"
2639
+ });
2640
+ }
2641
+ renderChart(pptxSlide, pptx, chart, rect, theme) {
2642
+ const series = chart.series || [];
2643
+ const labels = chart.labels || [];
2644
+ const chartData = series.map((s) => ({
2645
+ name: s.name || "Data",
2646
+ labels,
2647
+ values: s.values
2648
+ }));
2649
+ let pptxChartType = pptx.ChartType.bar;
2650
+ if (chart.chartType === "line")
2651
+ pptxChartType = pptx.ChartType.line;
2652
+ if (chart.chartType === "pie")
2653
+ pptxChartType = pptx.ChartType.pie;
2654
+ if (chart.chartType === "doughnut")
2655
+ pptxChartType = pptx.ChartType.doughnut;
2656
+ const chartColors = [
2657
+ this.cleanHexColor(theme.colors.primary),
2658
+ this.cleanHexColor(theme.colors.accent),
2659
+ this.cleanHexColor(theme.colors.secondary),
2660
+ this.cleanHexColor(theme.colors.success),
2661
+ this.cleanHexColor(theme.colors.warning)
2662
+ ];
2663
+ try {
2664
+ pptxSlide.addChart(pptxChartType, chartData, {
2665
+ x: rect.x,
2666
+ y: rect.y,
2667
+ w: rect.w,
2668
+ h: rect.h,
2669
+ showTitle: Boolean(chart.title),
2670
+ title: chart.title || "",
2671
+ titleColor: this.cleanHexColor(theme.colors.text),
2672
+ titleFontFace: cleanFontFace(theme.typography.headingFont),
2673
+ showLegend: true,
2674
+ legendPos: "b",
2675
+ legendColor: this.cleanHexColor(theme.colors.muted || theme.colors.text),
2676
+ chartColors
2677
+ });
2678
+ } catch {
2679
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2680
+ x: rect.x,
2681
+ y: rect.y,
2682
+ w: rect.w,
2683
+ h: rect.h,
2684
+ fill: { color: this.cleanHexColor(theme.colors.surface) },
2685
+ line: { color: this.cleanHexColor(theme.colors.border), width: 1 }
2686
+ });
2687
+ pptxSlide.addText(`[Chart: ${chart.title || chart.chartType}]`, {
2688
+ x: rect.x,
2689
+ y: rect.y,
2690
+ w: rect.w,
2691
+ h: rect.h,
2692
+ color: this.cleanHexColor(theme.colors.primary),
2693
+ align: "center",
2694
+ valign: "middle"
2695
+ });
2696
+ }
2697
+ }
2698
+ renderTimeline(pptxSlide, pptx, timeline, rect, theme) {
2699
+ const items = timeline.items || [];
2700
+ if (items.length === 0)
2701
+ return;
2702
+ const itemW = rect.w / items.length;
2703
+ const lineY = rect.y + 0.25;
2704
+ pptxSlide.addShape(pptx.ShapeType.rect, {
2705
+ x: rect.x + 0.2,
2706
+ y: lineY,
2707
+ w: rect.w - 0.4,
2708
+ h: 0.03,
2709
+ fill: { color: this.cleanHexColor(theme.colors.border) }
2710
+ });
2711
+ items.forEach((item, idx) => {
2712
+ const itemX = rect.x + idx * itemW;
2713
+ const dotX = itemX + itemW / 2 - 0.1;
2714
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2715
+ x: dotX,
2716
+ y: lineY - 0.08,
2717
+ w: 0.2,
2718
+ h: 0.2,
2719
+ fill: { color: this.cleanHexColor(theme.colors.primary) },
2720
+ line: { color: this.cleanHexColor(theme.colors.background), width: 2 },
2721
+ rectRadius: 0.1
2722
+ });
2723
+ if (item.date) {
2724
+ pptxSlide.addText(item.date, {
2725
+ x: itemX,
2726
+ y: lineY + 0.18,
2727
+ w: itemW,
2728
+ h: 0.25,
2729
+ fontSize: 11,
2730
+ bold: true,
2731
+ color: this.cleanHexColor(theme.colors.accent || theme.colors.primary),
2732
+ fontFace: cleanFontFace(theme.typography.codeFont),
2733
+ align: "center"
2734
+ });
2735
+ }
2736
+ const descText = item.description ? `
2737
+ ${item.description}` : "";
2738
+ pptxSlide.addText(`${item.title}${descText}`, {
2739
+ x: itemX + 0.05,
2740
+ y: lineY + 0.45,
2741
+ w: itemW - 0.1,
2742
+ h: rect.h - 0.55,
2743
+ fontSize: 12,
2744
+ color: this.cleanHexColor(theme.colors.text),
2745
+ fontFace: cleanFontFace(theme.typography.bodyFont),
2746
+ align: "center",
2747
+ valign: "top"
2748
+ });
2749
+ });
2750
+ }
2751
+ renderCompare(pptxSlide, pptx, node, scaleX, scaleY, theme) {
2752
+ const { element, bounds } = node;
2753
+ const compare = element;
2754
+ const rect = this.toInches(bounds, scaleX, scaleY);
2755
+ const colW = (rect.w - 0.4) / 2;
2756
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2757
+ x: rect.x,
2758
+ y: rect.y,
2759
+ w: colW,
2760
+ h: rect.h,
2761
+ fill: { color: this.cleanHexColor(theme.colors.surface) },
2762
+ line: { color: this.cleanHexColor(theme.colors.border), width: 1 },
2763
+ rectRadius: 0.1
2764
+ });
2765
+ if (compare.leftTitle) {
2766
+ pptxSlide.addText(compare.leftTitle, {
2767
+ x: rect.x + 0.15,
2768
+ y: rect.y + 0.15,
2769
+ w: colW - 0.3,
2770
+ h: 0.4,
2771
+ fontSize: 14,
2772
+ bold: true,
2773
+ color: this.cleanHexColor(theme.colors.primary),
2774
+ fontFace: cleanFontFace(theme.typography.headingFont)
2775
+ });
2776
+ }
2777
+ const rightX = rect.x + colW + 0.4;
2778
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2779
+ x: rightX,
2780
+ y: rect.y,
2781
+ w: colW,
2782
+ h: rect.h,
2783
+ fill: { color: this.cleanHexColor(theme.colors.surface) },
2784
+ line: { color: this.cleanHexColor(theme.colors.border), width: 1 },
2785
+ rectRadius: 0.1
2786
+ });
2787
+ if (compare.rightTitle) {
2788
+ pptxSlide.addText(compare.rightTitle, {
2789
+ x: rightX + 0.15,
2790
+ y: rect.y + 0.15,
2791
+ w: colW - 0.3,
2792
+ h: 0.4,
2793
+ fontSize: 14,
2794
+ bold: true,
2795
+ color: this.cleanHexColor(theme.colors.primary),
2796
+ fontFace: cleanFontFace(theme.typography.headingFont)
2797
+ });
2798
+ }
2799
+ }
2800
+ renderMermaid(pptxSlide, pptx, mermaid, rect, theme) {
2801
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
2802
+ x: rect.x,
2803
+ y: rect.y,
2804
+ w: rect.w,
2805
+ h: rect.h,
2806
+ fill: { color: this.cleanHexColor(theme.colors.surface) },
2807
+ line: { color: this.cleanHexColor(theme.colors.primary), width: 1 },
2808
+ rectRadius: 0.1
2809
+ });
2810
+ pptxSlide.addText(`[Diagram: Mermaid]
2811
+
2812
+ ${mermaid.code}`, {
2813
+ x: rect.x + 0.2,
2814
+ y: rect.y + 0.2,
2815
+ w: rect.w - 0.4,
2816
+ h: rect.h - 0.4,
2817
+ fontSize: 12,
2818
+ color: this.cleanHexColor(theme.colors.text),
2819
+ fontFace: cleanFontFace(theme.typography.codeFont),
2820
+ align: "center",
2821
+ valign: "middle"
2822
+ });
2823
+ }
2361
2824
  toInches(bounds, scaleX, scaleY) {
2362
2825
  return {
2363
2826
  x: Math.max(0, bounds.x * scaleX),
@@ -2460,7 +2923,43 @@ var HtmlRenderer = class {
2460
2923
  <title>${this.escapeHtml(title)}</title>
2461
2924
  <link rel="preconnect" href="https://fonts.googleapis.com">
2462
2925
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2463
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&family=Outfit:wght@600;700;800&display=swap" rel="stylesheet">
2926
+ <script type="module">
2927
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
2928
+ try {
2929
+ mermaid.initialize({
2930
+ startOnLoad: false,
2931
+ theme: 'dark',
2932
+ securityLevel: 'loose',
2933
+ themeVariables: {
2934
+ darkMode: true,
2935
+ background: '${theme.colors.background}',
2936
+ primaryColor: '${theme.colors.primary}',
2937
+ primaryTextColor: '#ffffff',
2938
+ primaryBorderColor: '${theme.colors.primary}',
2939
+ lineColor: '${theme.colors.accent || theme.colors.primary}',
2940
+ secondaryColor: '${theme.colors.surface}',
2941
+ tertiaryColor: '${theme.colors.surface}'
2942
+ }
2943
+ });
2944
+ window.mermaid = mermaid;
2945
+ window.renderMermaidInSlide = async function(slideEl) {
2946
+ if (!slideEl || !window.mermaid) return;
2947
+ const nodes = Array.from(slideEl.querySelectorAll('.mermaid:not([data-processed="true"])'));
2948
+ if (nodes.length > 0) {
2949
+ try {
2950
+ await window.mermaid.run({ nodes: nodes });
2951
+ } catch (e) {
2952
+ console.warn('Mermaid render notice:', e);
2953
+ }
2954
+ }
2955
+ };
2956
+ // Auto-render visible slide if present
2957
+ const cur = document.querySelector('.yumia-slide-wrapper.active');
2958
+ if (cur) window.renderMermaidInSlide(cur);
2959
+ } catch (err) {
2960
+ console.warn('Mermaid load notice:', err);
2961
+ }
2962
+ </script>
2464
2963
  <style>
2465
2964
  :root {
2466
2965
  --yumia-bg: ${theme.colors.background};
@@ -2843,6 +3342,194 @@ var HtmlRenderer = class {
2843
3342
  transition: width 0.25s ease;
2844
3343
  }
2845
3344
 
3345
+ /* Badge Directive */
3346
+ .yumia-badge {
3347
+ display: inline-flex;
3348
+ align-items: center;
3349
+ padding: 3px 10px;
3350
+ border-radius: 999px;
3351
+ font-size: 0.8rem;
3352
+ font-weight: 700;
3353
+ letter-spacing: 0.04em;
3354
+ text-transform: uppercase;
3355
+ background: rgba(255, 255, 255, 0.1);
3356
+ color: var(--yumia-text);
3357
+ border: 1px solid var(--yumia-border);
3358
+ margin-bottom: 0.5rem;
3359
+ }
3360
+ .yumia-badge.variant-primary { background: rgba(0, 240, 255, 0.15); color: var(--yumia-primary); border-color: var(--yumia-primary); }
3361
+ .yumia-badge.variant-success { background: rgba(16, 185, 129, 0.15); color: var(--yumia-success); border-color: var(--yumia-success); }
3362
+ .yumia-badge.variant-warning { background: rgba(245, 158, 11, 0.15); color: var(--yumia-warning); border-color: var(--yumia-warning); }
3363
+ .yumia-badge.variant-danger { background: rgba(239, 68, 68, 0.15); color: var(--yumia-danger); border-color: var(--yumia-danger); }
3364
+ .yumia-badge.variant-info { background: rgba(59, 130, 246, 0.15); color: var(--yumia-info); border-color: var(--yumia-info); }
3365
+ .yumia-badge.variant-accent { background: rgba(255, 46, 136, 0.15); color: var(--yumia-accent); border-color: var(--yumia-accent); }
3366
+
3367
+ /* Timeline Directive */
3368
+ .yumia-timeline {
3369
+ display: flex;
3370
+ gap: 16px;
3371
+ margin: 1.2rem 0;
3372
+ width: 100%;
3373
+ }
3374
+ .yumia-timeline.layout-horizontal {
3375
+ flex-direction: row;
3376
+ justify-content: space-between;
3377
+ position: relative;
3378
+ }
3379
+ .yumia-timeline.layout-horizontal::before {
3380
+ content: '';
3381
+ position: absolute;
3382
+ top: 18px;
3383
+ left: 20px;
3384
+ right: 20px;
3385
+ height: 2px;
3386
+ background: var(--yumia-border);
3387
+ z-index: 0;
3388
+ }
3389
+ .yumia-timeline-item {
3390
+ position: relative;
3391
+ z-index: 1;
3392
+ display: flex;
3393
+ flex-direction: column;
3394
+ align-items: center;
3395
+ text-align: center;
3396
+ flex: 1;
3397
+ }
3398
+ .yumia-timeline-dot {
3399
+ width: 14px;
3400
+ height: 14px;
3401
+ border-radius: 50%;
3402
+ background: var(--yumia-primary);
3403
+ border: 3px solid var(--yumia-bg);
3404
+ box-shadow: 0 0 10px var(--yumia-primary);
3405
+ margin-bottom: 10px;
3406
+ }
3407
+ .yumia-timeline-date {
3408
+ font-size: 0.85rem;
3409
+ font-weight: 700;
3410
+ color: var(--yumia-accent);
3411
+ margin-bottom: 4px;
3412
+ font-family: var(--yumia-font-code);
3413
+ }
3414
+ .yumia-timeline-title {
3415
+ font-size: 1.05rem;
3416
+ font-weight: 600;
3417
+ color: var(--yumia-text);
3418
+ margin-bottom: 4px;
3419
+ }
3420
+ .yumia-timeline-desc {
3421
+ font-size: 0.85rem;
3422
+ color: var(--yumia-muted);
3423
+ line-height: 1.4;
3424
+ }
3425
+
3426
+ /* Compare Directive */
3427
+ .yumia-compare {
3428
+ display: grid;
3429
+ grid-template-columns: 1fr auto 1fr;
3430
+ gap: 20px;
3431
+ align-items: stretch;
3432
+ margin: 1.2rem 0;
3433
+ }
3434
+ .yumia-compare-col {
3435
+ background: var(--yumia-surface);
3436
+ border: 1px solid var(--yumia-border);
3437
+ border-radius: var(--yumia-radius-card);
3438
+ padding: 1.5rem;
3439
+ display: flex;
3440
+ flex-direction: column;
3441
+ }
3442
+ .yumia-compare-title {
3443
+ font-size: 1.2rem;
3444
+ font-weight: 700;
3445
+ color: var(--yumia-primary);
3446
+ margin-bottom: 1rem;
3447
+ padding-bottom: 0.5rem;
3448
+ border-bottom: 1px solid var(--yumia-divider);
3449
+ }
3450
+ .yumia-compare-divider {
3451
+ display: flex;
3452
+ align-items: center;
3453
+ justify-content: center;
3454
+ font-weight: 800;
3455
+ font-size: 0.9rem;
3456
+ color: var(--yumia-muted);
3457
+ background: rgba(255, 255, 255, 0.05);
3458
+ border-radius: 50%;
3459
+ width: 36px;
3460
+ height: 36px;
3461
+ align-self: center;
3462
+ border: 1px solid var(--yumia-border);
3463
+ }
3464
+
3465
+ /* Native SVG Chart Container */
3466
+ .yumia-chart-container {
3467
+ background: var(--yumia-surface);
3468
+ border: 1px solid var(--yumia-border);
3469
+ border-radius: var(--yumia-radius-card);
3470
+ padding: 1.25rem;
3471
+ margin: 1rem 0;
3472
+ display: flex;
3473
+ flex-direction: column;
3474
+ align-items: center;
3475
+ width: 100%;
3476
+ }
3477
+ .yumia-chart-title {
3478
+ font-size: 1.1rem;
3479
+ font-weight: 700;
3480
+ color: var(--yumia-text);
3481
+ margin-bottom: 0.75rem;
3482
+ align-self: flex-start;
3483
+ }
3484
+
3485
+ /* Mermaid Container */
3486
+ .mermaid-container {
3487
+ background: rgba(10, 10, 18, 0.6);
3488
+ border: 1.5px solid var(--yumia-border);
3489
+ border-radius: var(--yumia-radius-card);
3490
+ padding: 1.5rem;
3491
+ margin: 1rem 0;
3492
+ display: flex;
3493
+ justify-content: center;
3494
+ align-items: center;
3495
+ overflow: auto;
3496
+ width: 100%;
3497
+ min-height: 240px;
3498
+ }
3499
+
3500
+ .mermaid-container pre.mermaid,
3501
+ .mermaid-container .mermaid {
3502
+ background: transparent !important;
3503
+ border: none !important;
3504
+ padding: 0 !important;
3505
+ margin: 0 !important;
3506
+ font-size: 1rem;
3507
+ color: #fff;
3508
+ display: flex;
3509
+ justify-content: center;
3510
+ align-items: center;
3511
+ width: 100%;
3512
+ }
3513
+
3514
+ .mermaid-container svg {
3515
+ max-width: 100%;
3516
+ height: auto;
3517
+ max-height: 420px;
3518
+ }
3519
+
3520
+ /* Progressive Reveal (Step Builds) */
3521
+ .yumia-slide-wrapper [data-step] {
3522
+ transition: opacity 0.25s ease, transform 0.25s ease;
3523
+ }
3524
+ .yumia-slide-wrapper:not(.step-active) [data-step] {
3525
+ opacity: 0.2;
3526
+ transform: translateY(4px);
3527
+ }
3528
+ .yumia-slide-wrapper.step-active [data-step] {
3529
+ opacity: 1;
3530
+ transform: translateY(0);
3531
+ }
3532
+
2846
3533
  /* Notes Drawer */
2847
3534
  .yumia-notes-drawer {
2848
3535
  position: fixed;
@@ -3154,6 +3841,12 @@ var HtmlRenderer = class {
3154
3841
  if (broadcast && syncChannel) {
3155
3842
  syncChannel.postMessage({ index: currentIdx });
3156
3843
  }
3844
+
3845
+ setTimeout(() => {
3846
+ if (window.renderMermaidInSlide && slides[currentIdx]) {
3847
+ window.renderMermaidInSlide(slides[currentIdx]);
3848
+ }
3849
+ }, 50);
3157
3850
  }
3158
3851
 
3159
3852
  function openSpeakerWindow() {
@@ -3258,6 +3951,13 @@ var HtmlRenderer = class {
3258
3951
  const rawNotes = slides[currentIdx].getAttribute('data-notes');
3259
3952
  notesBox.innerHTML = rawNotes ? rawNotes : '<em style="color:var(--yumia-muted);">No notes provided for this slide.</em>';
3260
3953
  }
3954
+
3955
+ setTimeout(() => {
3956
+ if (window.renderMermaidInSlide) {
3957
+ if (curBox) window.renderMermaidInSlide(curBox);
3958
+ if (nextBox) window.renderMermaidInSlide(nextBox);
3959
+ }
3960
+ }, 50);
3261
3961
  }
3262
3962
 
3263
3963
  window.addEventListener('keydown', (e) => {
@@ -3467,10 +4167,174 @@ var HtmlRenderer = class {
3467
4167
  }).join("\n");
3468
4168
  return `<div class="yumia-columns" style="grid-template-columns: ${ratioTemplate};">${colsHtml}</div>`;
3469
4169
  }
4170
+ case "badge": {
4171
+ return this.renderBadge(element);
4172
+ }
4173
+ case "mermaid": {
4174
+ const m = element;
4175
+ return `
4176
+ <div class="mermaid-container">
4177
+ <pre class="mermaid">${this.escapeHtml(m.code)}</pre>
4178
+ </div>`;
4179
+ }
4180
+ case "chart": {
4181
+ return this.renderChart(element, theme);
4182
+ }
4183
+ case "timeline": {
4184
+ return this.renderTimeline(element);
4185
+ }
4186
+ case "compare": {
4187
+ return this.renderCompare(element, theme);
4188
+ }
3470
4189
  default:
3471
4190
  return "";
3472
4191
  }
3473
4192
  }
4193
+ renderBadge(b) {
4194
+ const variant = b.variant || "default";
4195
+ return `<span class="yumia-badge variant-${variant}">${this.escapeHtml(b.text)}</span>`;
4196
+ }
4197
+ renderTimeline(t) {
4198
+ const layout = t.layout || "horizontal";
4199
+ const itemsHtml = t.items.map((item) => {
4200
+ const dateHtml = item.date ? `<div class="yumia-timeline-date">${this.escapeHtml(item.date)}</div>` : "";
4201
+ const descHtml = item.description ? `<div class="yumia-timeline-desc">${this.formatInline(item.description)}</div>` : "";
4202
+ return `
4203
+ <div class="yumia-timeline-item">
4204
+ <div class="yumia-timeline-dot"></div>
4205
+ ${dateHtml}
4206
+ <div class="yumia-timeline-title">${this.formatInline(item.title)}</div>
4207
+ ${descHtml}
4208
+ </div>`;
4209
+ }).join("\n");
4210
+ return `<div class="yumia-timeline layout-${layout}">${itemsHtml}</div>`;
4211
+ }
4212
+ renderCompare(c, theme) {
4213
+ const leftTitle = c.leftTitle ? `<div class="yumia-compare-title">${this.escapeHtml(c.leftTitle)}</div>` : "";
4214
+ const rightTitle = c.rightTitle ? `<div class="yumia-compare-title">${this.escapeHtml(c.rightTitle)}</div>` : "";
4215
+ const leftInner = c.left.map((el) => this.renderElement(el, theme)).join("\n");
4216
+ const rightInner = c.right.map((el) => this.renderElement(el, theme)).join("\n");
4217
+ return `
4218
+ <div class="yumia-compare">
4219
+ <div class="yumia-compare-col left">
4220
+ ${leftTitle}
4221
+ ${leftInner}
4222
+ </div>
4223
+ <div class="yumia-compare-divider">VS</div>
4224
+ <div class="yumia-compare-col right">
4225
+ ${rightTitle}
4226
+ ${rightInner}
4227
+ </div>
4228
+ </div>`;
4229
+ }
4230
+ renderChart(c, theme) {
4231
+ const titleHtml = c.title ? `<div class="yumia-chart-title">${this.escapeHtml(c.title)}</div>` : "";
4232
+ const labels = c.labels || [];
4233
+ const series = c.series || [];
4234
+ const colors = [
4235
+ theme.colors.primary || "#00F0FF",
4236
+ theme.colors.accent || "#FF2E88",
4237
+ theme.colors.secondary || "#7B2CBF",
4238
+ theme.colors.success || "#10B981",
4239
+ theme.colors.warning || "#F59E0B"
4240
+ ];
4241
+ if (c.chartType === "line") {
4242
+ const allValues = series.flatMap((s) => s.values);
4243
+ const maxVal2 = Math.max(...allValues, 1);
4244
+ const width2 = 500;
4245
+ const height2 = 180;
4246
+ const padding2 = 35;
4247
+ const plotW2 = width2 - padding2 * 2;
4248
+ const plotH2 = height2 - padding2 * 2;
4249
+ let pathsHtml = "";
4250
+ series.forEach((s, sIdx) => {
4251
+ const sColor = s.color || colors[sIdx % colors.length];
4252
+ const pts = s.values.map((v, i) => {
4253
+ const x = padding2 + i / Math.max(s.values.length - 1, 1) * plotW2;
4254
+ const y = height2 - padding2 - v / maxVal2 * plotH2;
4255
+ return `${x},${y}`;
4256
+ });
4257
+ const pointsStr = pts.join(" ");
4258
+ const circles = pts.map((pt) => `<circle cx="${pt.split(",")[0]}" cy="${pt.split(",")[1]}" r="4" fill="${sColor}" />`).join("");
4259
+ pathsHtml += `
4260
+ <polyline fill="none" stroke="${sColor}" stroke-width="3" stroke-linecap="round" points="${pointsStr}" />
4261
+ ${circles}`;
4262
+ });
4263
+ const labelTexts = labels.map((l, i) => {
4264
+ const x = padding2 + i / Math.max(labels.length - 1, 1) * plotW2;
4265
+ return `<text x="${x}" y="${height2 - 10}" text-anchor="middle" fill="${theme.colors.muted || "#94a3b8"}" font-size="11" font-family="sans-serif">${this.escapeHtml(l)}</text>`;
4266
+ }).join("");
4267
+ return `
4268
+ <div class="yumia-chart-container">
4269
+ ${titleHtml}
4270
+ <svg viewBox="0 0 ${width2} ${height2}" style="width:100%; max-height:220px;">
4271
+ <line x1="${padding2}" y1="${height2 - padding2}" x2="${width2 - padding2}" y2="${height2 - padding2}" stroke="rgba(255,255,255,0.15)" stroke-width="1" />
4272
+ ${pathsHtml}
4273
+ ${labelTexts}
4274
+ </svg>
4275
+ </div>`;
4276
+ }
4277
+ if (c.chartType === "pie" || c.chartType === "doughnut") {
4278
+ const values2 = series[0]?.values || [];
4279
+ const total = values2.reduce((a, b) => a + b, 0) || 1;
4280
+ let cumulativePercent = 0;
4281
+ const radius = 60;
4282
+ const cx = 100;
4283
+ const cy = 100;
4284
+ const strokeWidth = c.chartType === "doughnut" ? 24 : 60;
4285
+ const circ = 2 * Math.PI * radius;
4286
+ const slices = values2.map((val, i) => {
4287
+ const percent = val / total;
4288
+ const strokeDasharray = `${percent * circ} ${circ}`;
4289
+ const strokeDashoffset = -cumulativePercent * circ;
4290
+ cumulativePercent += percent;
4291
+ const sColor = colors[i % colors.length];
4292
+ return `<circle cx="${cx}" cy="${cy}" r="${radius}" fill="none" stroke="${sColor}" stroke-width="${strokeWidth}" stroke-dasharray="${strokeDasharray}" stroke-dashoffset="${strokeDashoffset}" />`;
4293
+ }).join("");
4294
+ const legend = labels.map((l, i) => {
4295
+ const sColor = colors[i % colors.length];
4296
+ const pct = Math.round((values2[i] || 0) / total * 100);
4297
+ return `<div style="display:flex; align-items:center; gap:8px; font-size:12px; margin:4px 0;"><span style="width:10px; height:10px; border-radius:50%; background:${sColor};"></span><span style="color:var(--yumia-text);">${this.escapeHtml(l)} (${pct}%)</span></div>`;
4298
+ }).join("");
4299
+ return `
4300
+ <div class="yumia-chart-container">
4301
+ ${titleHtml}
4302
+ <div style="display:flex; align-items:center; justify-content:center; gap:28px; width:100%;">
4303
+ <svg viewBox="0 0 200 200" style="width:160px; height:160px; transform: rotate(-90deg);">
4304
+ ${slices}
4305
+ </svg>
4306
+ <div style="display:flex; flex-direction:column;">${legend}</div>
4307
+ </div>
4308
+ </div>`;
4309
+ }
4310
+ const values = series[0]?.values || [];
4311
+ const maxVal = Math.max(...values, 1);
4312
+ const width = 500;
4313
+ const height = 180;
4314
+ const padding = 35;
4315
+ const plotW = width - padding * 2;
4316
+ const plotH = height - padding * 2;
4317
+ const barWidth = Math.min(48, Math.max(16, plotW / Math.max(values.length, 1) * 0.6));
4318
+ const bars = values.map((val, i) => {
4319
+ const x = padding + (i + 0.5) * (plotW / Math.max(values.length, 1)) - barWidth / 2;
4320
+ const barH = val / maxVal * plotH;
4321
+ const y = height - padding - barH;
4322
+ const color = colors[i % colors.length];
4323
+ const label = labels[i] || "";
4324
+ return `
4325
+ <rect x="${x}" y="${y}" width="${barWidth}" height="${barH}" rx="4" fill="${color}" opacity="0.9" />
4326
+ <text x="${x + barWidth / 2}" y="${y - 6}" text-anchor="middle" fill="${color}" font-size="11" font-weight="600" font-family="sans-serif">${val}</text>
4327
+ <text x="${x + barWidth / 2}" y="${height - 12}" text-anchor="middle" fill="${theme.colors.muted || "#94a3b8"}" font-size="11" font-family="sans-serif">${this.escapeHtml(label)}</text>`;
4328
+ }).join("");
4329
+ return `
4330
+ <div class="yumia-chart-container">
4331
+ ${titleHtml}
4332
+ <svg viewBox="0 0 ${width} ${height}" style="width:100%; max-height:220px;">
4333
+ <line x1="${padding}" y1="${height - padding}" x2="${width - padding}" y2="${height - padding}" stroke="rgba(255,255,255,0.15)" stroke-width="1" />
4334
+ ${bars}
4335
+ </svg>
4336
+ </div>`;
4337
+ }
3474
4338
  formatInline(text) {
3475
4339
  const escaped = this.escapeHtml(text);
3476
4340
  return escaped.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/`(.*?)`/g, "<code>$1</code>");
@@ -3718,6 +4582,117 @@ var PdfRenderer = class {
3718
4582
  doc.rect(x, y, width, curY - y).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
3719
4583
  return curY;
3720
4584
  }
4585
+ case "badge": {
4586
+ const b = element;
4587
+ const variantColor = this.getVariantColor(b.variant, theme);
4588
+ const text = this.stripFormatting(b.text).toUpperCase();
4589
+ const badgeW = Math.min(180, Math.max(60, text.length * 7 + 20));
4590
+ const badgeH = 22;
4591
+ doc.roundedRect(x, y, badgeW, badgeH, 11).fill(theme.colors.surface || "rgba(255,255,255,0.08)");
4592
+ doc.roundedRect(x, y, badgeW, badgeH, 11).lineWidth(1).strokeColor(variantColor).stroke();
4593
+ doc.font("Helvetica-Bold").fontSize(10).fillColor(variantColor).text(text, x, y + 6, { width: badgeW, align: "center" });
4594
+ return y + badgeH + 6;
4595
+ }
4596
+ case "timeline": {
4597
+ const t = element;
4598
+ const items = t.items || [];
4599
+ if (items.length === 0)
4600
+ return y;
4601
+ const itemW = (width - (items.length - 1) * 12) / items.length;
4602
+ const lineY = y + 14;
4603
+ doc.moveTo(x + 10, lineY).lineTo(x + width - 10, lineY).lineWidth(2).strokeColor(theme.colors.border || "rgba(255,255,255,0.2)").stroke();
4604
+ let maxItemH = 60;
4605
+ items.forEach((item, idx) => {
4606
+ const itemX = x + idx * (itemW + 12);
4607
+ const dotX = itemX + itemW / 2;
4608
+ doc.circle(dotX, lineY, 6).fill(theme.colors.primary);
4609
+ let curItemY = lineY + 12;
4610
+ if (item.date) {
4611
+ doc.font("Helvetica-Bold").fontSize(10).fillColor(theme.colors.accent || theme.colors.primary).text(this.stripFormatting(item.date), itemX, curItemY, {
4612
+ width: itemW,
4613
+ align: "center"
4614
+ });
4615
+ curItemY += 14;
4616
+ }
4617
+ doc.font("Helvetica-Bold").fontSize(12).fillColor(theme.colors.text).text(this.stripFormatting(item.title), itemX, curItemY, {
4618
+ width: itemW,
4619
+ align: "center"
4620
+ });
4621
+ curItemY += 16;
4622
+ if (item.description) {
4623
+ doc.font("Helvetica").fontSize(9).fillColor(theme.colors.muted || "#888888").text(this.stripFormatting(item.description), itemX, curItemY, {
4624
+ width: itemW,
4625
+ align: "center"
4626
+ });
4627
+ curItemY += 24;
4628
+ }
4629
+ if (curItemY - y > maxItemH)
4630
+ maxItemH = curItemY - y;
4631
+ });
4632
+ return y + maxItemH + 8;
4633
+ }
4634
+ case "compare": {
4635
+ const c = element;
4636
+ const colW = (width - 24) / 2;
4637
+ let leftY = y + 12;
4638
+ let rightY = y + 12;
4639
+ if (c.leftTitle) {
4640
+ doc.font("Helvetica-Bold").fontSize(14).fillColor(theme.colors.primary).text(this.stripFormatting(c.leftTitle), x + 12, leftY, { width: colW - 24 });
4641
+ leftY += 22;
4642
+ }
4643
+ for (const el of c.left) {
4644
+ leftY = this.renderElement(doc, el, x + 12, leftY, colW - 24, theme) + 6;
4645
+ }
4646
+ const rightX = x + colW + 24;
4647
+ if (c.rightTitle) {
4648
+ doc.font("Helvetica-Bold").fontSize(14).fillColor(theme.colors.primary).text(this.stripFormatting(c.rightTitle), rightX + 12, rightY, { width: colW - 24 });
4649
+ rightY += 22;
4650
+ }
4651
+ for (const el of c.right) {
4652
+ rightY = this.renderElement(doc, el, rightX + 12, rightY, colW - 24, theme) + 6;
4653
+ }
4654
+ const totalH = Math.max(leftY - y, rightY - y, 60) + 12;
4655
+ doc.roundedRect(x, y, colW, totalH, 8).strokeColor(theme.colors.border || "rgba(255,255,255,0.15)").stroke();
4656
+ doc.roundedRect(rightX, y, colW, totalH, 8).strokeColor(theme.colors.border || "rgba(255,255,255,0.15)").stroke();
4657
+ return y + totalH + 8;
4658
+ }
4659
+ case "chart": {
4660
+ const ch = element;
4661
+ const boxH = 130;
4662
+ doc.roundedRect(x, y, width, boxH, 8).fill(theme.colors.surface || "rgba(255,255,255,0.04)");
4663
+ doc.roundedRect(x, y, width, boxH, 8).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
4664
+ let topY = y + 10;
4665
+ if (ch.title) {
4666
+ doc.font("Helvetica-Bold").fontSize(13).fillColor(theme.colors.text).text(this.stripFormatting(ch.title), x + 14, topY, { width: width - 28 });
4667
+ topY += 20;
4668
+ }
4669
+ const values = ch.series[0]?.values || [];
4670
+ const maxVal = Math.max(...values, 1);
4671
+ const plotH = boxH - (topY - y) - 28;
4672
+ const barW = Math.min(40, (width - 40) / Math.max(values.length, 1) * 0.6);
4673
+ values.forEach((val, idx) => {
4674
+ const barX = x + 24 + idx * ((width - 48) / Math.max(values.length, 1));
4675
+ const h = val / maxVal * plotH;
4676
+ const barY = topY + plotH - h;
4677
+ doc.rect(barX, barY, barW, h).fill(theme.colors.primary);
4678
+ doc.font("Helvetica").fontSize(9).fillColor(theme.colors.text).text(String(val), barX, barY - 12, { width: barW, align: "center" });
4679
+ if (ch.labels[idx]) {
4680
+ doc.font("Helvetica").fontSize(9).fillColor(theme.colors.muted || "#888888").text(this.stripFormatting(ch.labels[idx]), barX - 10, topY + plotH + 4, {
4681
+ width: barW + 20,
4682
+ align: "center"
4683
+ });
4684
+ }
4685
+ });
4686
+ return y + boxH + 8;
4687
+ }
4688
+ case "mermaid": {
4689
+ const m = element;
4690
+ const boxH = 90;
4691
+ doc.roundedRect(x, y, width, boxH, 8).fill(theme.colors.surface || "rgba(255,255,255,0.04)");
4692
+ doc.roundedRect(x, y, width, boxH, 8).strokeColor(theme.colors.primary).stroke();
4693
+ doc.font("Courier").fontSize(11).fillColor(theme.colors.text).text(this.stripFormatting(m.code), x + 12, y + 12, { width: width - 24 });
4694
+ return y + boxH + 8;
4695
+ }
3721
4696
  default:
3722
4697
  return y;
3723
4698
  }
@@ -3849,7 +4824,7 @@ function startDevServer(filePath, options = {}) {
3849
4824
  // src/cli.ts
3850
4825
  import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
3851
4826
  import { basename, dirname, extname, join, resolve as resolve2 } from "path";
3852
- var VERSION = "0.1.15";
4827
+ var VERSION = "0.1.17";
3853
4828
  function printHelp() {
3854
4829
  return `
3855
4830
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -3866,6 +4841,7 @@ Commands:
3866
4841
  inspect <file> Inspect the AST and geometric layout tree
3867
4842
  schema Output machine-readable JSON schema for AI agents
3868
4843
  build <file> Compile a presentation to PowerPoint (.pptx), PDF (.pdf), or HTML (.html)
4844
+ deploy <file> Export and deploy presentation to static site, GitHub Pages, or Vercel
3869
4845
 
3870
4846
  Theming & Color Options:
3871
4847
  --theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
@@ -4196,6 +5172,68 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
4196
5172
  };
4197
5173
  }
4198
5174
  }
5175
+ if (command === "deploy") {
5176
+ if (!target) {
5177
+ const msg = "Error: Please specify a presentation file to deploy (e.g. 'yumia deploy presentation.yumia.md')";
5178
+ return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
5179
+ }
5180
+ try {
5181
+ const resolvedInput = resolve2(process.cwd(), target);
5182
+ const source = readFileSync2(resolvedInput, "utf-8");
5183
+ const provider = (getFlagValue(args, ["--provider"]) || "static").toLowerCase();
5184
+ const outDir = resolve2(process.cwd(), getFlagValue(args, ["--out", "-o"]) || "dist-site");
5185
+ mkdirSync(outDir, { recursive: true });
5186
+ const compiler = new YumiaCompiler();
5187
+ const htmlRenderer = new HtmlRenderer();
5188
+ const result = await compiler.compile(source, htmlRenderer);
5189
+ const indexPath = join(outDir, "index.html");
5190
+ writeFileSync(indexPath, result.html, "utf-8");
5191
+ if (provider === "gh-pages" || provider === "github") {
5192
+ writeFileSync(join(outDir, ".nojekyll"), "", "utf-8");
5193
+ } else if (provider === "vercel") {
5194
+ const vercelConfig = {
5195
+ version: 2,
5196
+ cleanUrls: true,
5197
+ routes: [{ src: "/(.*)", dest: "/index.html" }]
5198
+ };
5199
+ writeFileSync(join(outDir, "vercel.json"), JSON.stringify(vercelConfig, null, 2), "utf-8");
5200
+ }
5201
+ if (isJson) {
5202
+ return {
5203
+ exitCode: 0,
5204
+ output: JSON.stringify(
5205
+ {
5206
+ success: true,
5207
+ provider,
5208
+ outputDirectory: outDir,
5209
+ indexPath,
5210
+ slideCount: result.slideCount
5211
+ },
5212
+ null,
5213
+ 2
5214
+ )
5215
+ };
5216
+ }
5217
+ return {
5218
+ exitCode: 0,
5219
+ output: [
5220
+ `\u{1F680} YumiaMD Presentation Deployment Ready!`,
5221
+ `\u2713 Standalone interactive deck compiled (${result.slideCount} slides)`,
5222
+ `\u2713 Target Directory: ${outDir}`,
5223
+ `\u2713 Provider Profile: ${provider}`,
5224
+ `\u2713 Entrypoint: ${indexPath}`,
5225
+ ``,
5226
+ provider === "gh-pages" ? `To publish to GitHub Pages: push '${outDir}' contents to 'gh-pages' branch.` : `To deploy to Vercel/Netlify/S3: run 'vercel ${outDir}' or host files from '${outDir}'.`
5227
+ ].join("\n")
5228
+ };
5229
+ } catch (err) {
5230
+ const msg = err instanceof Error ? err.message : String(err);
5231
+ if (isJson) {
5232
+ return { exitCode: 1, output: JSON.stringify({ success: false, error: msg }) };
5233
+ }
5234
+ return { exitCode: 1, output: `\u2717 Deployment build failed: ${msg}` };
5235
+ }
5236
+ }
4199
5237
  if (command === "build" || command === "watch") {
4200
5238
  if (!target) {
4201
5239
  const msg = `Error: Please specify a presentation file to ${command}.`;
@@ -4314,6 +5352,11 @@ export {
4314
5352
  createCode,
4315
5353
  createQuote,
4316
5354
  createTable,
5355
+ createChart,
5356
+ createMermaid,
5357
+ createTimeline,
5358
+ createCompare,
5359
+ createBadge,
4317
5360
  createGroup,
4318
5361
  createColumn,
4319
5362
  createColumns,
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export * from '@yumiamd/renderer-pptx';
9
9
  export * from '@yumiamd/renderer-html';
10
10
  export * from '@yumiamd/renderer-pdf';
11
11
 
12
- declare const VERSION = "0.1.15";
12
+ declare const VERSION = "0.1.17";
13
13
  declare function printHelp(): string;
14
14
  declare function runCli(argv: string[]): Promise<{
15
15
  exitCode: number;
package/dist/index.js CHANGED
@@ -13,15 +13,19 @@ import {
13
13
  academicTheme,
14
14
  cleanFontFace,
15
15
  corporateTheme,
16
+ createBadge,
16
17
  createCard,
18
+ createChart,
17
19
  createCode,
18
20
  createColumn,
19
21
  createColumns,
22
+ createCompare,
20
23
  createGroup,
21
24
  createHeading,
22
25
  createImage,
23
26
  createLayoutDirective,
24
27
  createList,
28
+ createMermaid,
25
29
  createMetric,
26
30
  createParagraph,
27
31
  createPresentation,
@@ -29,6 +33,7 @@ import {
29
33
  createSlide,
30
34
  createTable,
31
35
  createTheme,
36
+ createTimeline,
32
37
  cyberpunkTheme,
33
38
  defaultTheme,
34
39
  minimalTheme,
@@ -39,7 +44,7 @@ import {
39
44
  runCli,
40
45
  startDevServer,
41
46
  terminalTheme
42
- } from "./chunk-7A5BHABM.js";
47
+ } from "./chunk-IAEXC5SG.js";
43
48
  export {
44
49
  DEFAULT_VIEWPORT,
45
50
  DefaultLayoutEngine,
@@ -55,15 +60,19 @@ export {
55
60
  academicTheme,
56
61
  cleanFontFace,
57
62
  corporateTheme,
63
+ createBadge,
58
64
  createCard,
65
+ createChart,
59
66
  createCode,
60
67
  createColumn,
61
68
  createColumns,
69
+ createCompare,
62
70
  createGroup,
63
71
  createHeading,
64
72
  createImage,
65
73
  createLayoutDirective,
66
74
  createList,
75
+ createMermaid,
67
76
  createMetric,
68
77
  createParagraph,
69
78
  createPresentation,
@@ -71,6 +80,7 @@ export {
71
80
  createSlide,
72
81
  createTable,
73
82
  createTheme,
83
+ createTimeline,
74
84
  cyberpunkTheme,
75
85
  defaultTheme,
76
86
  minimalTheme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yumiamd",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Command line interface and compiler for YumiaMD presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,15 +21,15 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "tsup": "^8.5.1",
24
- "@yumiamd/ast": "0.1.15",
25
- "@yumiamd/parser": "0.1.15",
26
- "@yumiamd/core": "0.1.15",
27
- "@yumiamd/renderer-html": "0.1.15",
28
- "@yumiamd/renderer": "0.1.15",
29
- "@yumiamd/layout": "0.1.15",
30
- "@yumiamd/renderer-pptx": "0.1.15",
31
- "@yumiamd/theme": "0.1.15",
32
- "@yumiamd/renderer-pdf": "0.1.15"
24
+ "@yumiamd/ast": "0.1.17",
25
+ "@yumiamd/core": "0.1.17",
26
+ "@yumiamd/renderer": "0.1.17",
27
+ "@yumiamd/parser": "0.1.17",
28
+ "@yumiamd/renderer-html": "0.1.17",
29
+ "@yumiamd/theme": "0.1.17",
30
+ "@yumiamd/layout": "0.1.17",
31
+ "@yumiamd/renderer-pptx": "0.1.17",
32
+ "@yumiamd/renderer-pdf": "0.1.17"
33
33
  },
34
34
  "keywords": [
35
35
  "yumia",