yumiamd 0.1.10 → 0.1.14

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
@@ -49,23 +49,35 @@ pnpm add yumiamd
49
49
  Once installed globally or locally, the `yumia` / `yumiamd` commands are available in your terminal:
50
50
 
51
51
  ```bash
52
- # Initialize a new presentation template
53
- yumia init my-presentation
52
+ # Initialize a new presentation template (supports --theme and custom color overrides)
53
+ yumia init my-presentation --theme cyberpunk --primary "#FF2E88"
54
+
55
+ # Start instant live-reload dev server with HTML preview (zero config!)
56
+ yumia dev presentation.yumia.md --open
57
+
58
+ # Compile to native editable PowerPoint (.pptx)
59
+ yumia build presentation.yumia.md --out dist/presentation.pptx
60
+
61
+ # Compile to crisp vector PDF document (.pdf)
62
+ yumia build presentation.yumia.md --format pdf --out dist/presentation.pdf
63
+
64
+ # Compile to standalone interactive HTML5 presentation deck (.html)
65
+ yumia build presentation.yumia.md --format html --out dist/presentation.html
66
+
67
+ # Watch presentation file and recompile automatically on save
68
+ yumia watch presentation.yumia.md --format pdf
54
69
 
55
70
  # Validate markdown syntax and AST structure (supports --json for CI & AI agents)
56
71
  yumia validate presentation.yumia.md --json
57
72
 
58
- # Lint presentation for vertical overflow and layout issues
59
- yumia lint presentation.yumia.md --json
73
+ # Lint presentation for overflow, high density, and layout issues
74
+ yumia lint presentation.yumia.md --strict
60
75
 
61
76
  # Inspect parsed AST and computed layout bounding boxes
62
77
  yumia inspect presentation.yumia.md --layout
63
78
 
64
79
  # Export machine-readable JSON schema for LLMs & AI prompt generation
65
80
  yumia schema
66
-
67
- # Compile to native editable PowerPoint (.pptx)
68
- yumia build presentation.yumia.md --out dist/presentation.pptx
69
81
  ```
70
82
 
71
83
  ---
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-PQYPVPV2.js";
4
+ } from "./chunk-ZOJEPOPI.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -12,4 +12,6 @@ if (result.output) {
12
12
  console.error(result.output);
13
13
  }
14
14
  }
15
- process.exit(result.exitCode);
15
+ if (!result.keepAlive) {
16
+ process.exit(result.exitCode);
17
+ }
@@ -1541,11 +1541,33 @@ var CSS_NAMED_COLORS = {
1541
1541
  function cleanFontFace(fontString) {
1542
1542
  if (!fontString)
1543
1543
  return "Segoe UI";
1544
- const first = fontString.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "") || "";
1545
- if (!first || first === "system-ui" || first === "-apple-system" || first === "sans-serif" || first === "monospace" || first === "serif") {
1546
- return "Segoe UI";
1547
- }
1548
- return first;
1544
+ const fonts = fontString.split(",").map((f) => f.trim().replace(/^['"]|['"]$/g, ""));
1545
+ const safeList = [
1546
+ "Segoe UI",
1547
+ "Arial",
1548
+ "Calibri",
1549
+ "Helvetica",
1550
+ "Verdana",
1551
+ "Tahoma",
1552
+ "Trebuchet MS",
1553
+ "Consolas",
1554
+ "Courier New",
1555
+ "Courier",
1556
+ "Georgia",
1557
+ "Times New Roman"
1558
+ ];
1559
+ for (const font of fonts) {
1560
+ if (safeList.includes(font)) {
1561
+ return font;
1562
+ }
1563
+ }
1564
+ if (fontString.toLowerCase().includes("mono") || fontString.toLowerCase().includes("code")) {
1565
+ return "Consolas";
1566
+ }
1567
+ if (fontString.toLowerCase().includes("serif") && !fontString.toLowerCase().includes("sans")) {
1568
+ return "Georgia";
1569
+ }
1570
+ return "Segoe UI";
1549
1571
  }
1550
1572
  function parseInlineMarkdown(rawText, baseOptions) {
1551
1573
  const chunks = [];
@@ -2393,15 +2415,16 @@ var HtmlRenderer = class {
2393
2415
  right: 24px;
2394
2416
  display: flex;
2395
2417
  align-items: center;
2396
- gap: 12px;
2397
- background: rgba(15, 23, 42, 0.85);
2418
+ gap: 10px;
2419
+ background: rgba(15, 23, 42, 0.88);
2398
2420
  backdrop-filter: blur(12px);
2399
- padding: 8px 16px;
2421
+ padding: 6px 14px;
2400
2422
  border-radius: 30px;
2401
2423
  border: 1px solid rgba(255, 255, 255, 0.15);
2402
2424
  z-index: 1000;
2403
2425
  font-size: 13px;
2404
2426
  color: #e2e8f0;
2427
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
2405
2428
  }
2406
2429
 
2407
2430
  .yumia-btn {
@@ -2409,17 +2432,18 @@ var HtmlRenderer = class {
2409
2432
  border: none;
2410
2433
  color: #f8fafc;
2411
2434
  cursor: pointer;
2412
- padding: 4px 8px;
2435
+ padding: 5px 9px;
2413
2436
  border-radius: 6px;
2414
2437
  font-size: 14px;
2415
2438
  display: flex;
2416
2439
  align-items: center;
2417
2440
  justify-content: center;
2418
- transition: background 0.15s ease;
2441
+ transition: background 0.15s ease, transform 0.1s ease;
2419
2442
  }
2420
2443
 
2421
2444
  .yumia-btn:hover {
2422
2445
  background: rgba(255, 255, 255, 0.15);
2446
+ transform: translateY(-1px);
2423
2447
  }
2424
2448
 
2425
2449
  .yumia-progress-bar {
@@ -2437,47 +2461,291 @@ var HtmlRenderer = class {
2437
2461
  bottom: 0;
2438
2462
  left: 0;
2439
2463
  right: 0;
2440
- max-height: 220px;
2441
- background: rgba(10, 10, 18, 0.95);
2464
+ max-height: 240px;
2465
+ background: rgba(10, 10, 18, 0.96);
2442
2466
  backdrop-filter: blur(16px);
2443
- border-top: 1px solid var(--yumia-primary);
2444
- padding: 16px 24px;
2467
+ border-top: 2px solid var(--yumia-primary);
2468
+ padding: 18px 28px;
2445
2469
  overflow-y: auto;
2446
2470
  display: none;
2447
2471
  z-index: 2000;
2448
- font-size: 14px;
2472
+ font-size: 15px;
2449
2473
  line-height: 1.6;
2450
2474
  color: #cbd5e1;
2475
+ box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
2451
2476
  }
2452
2477
 
2453
2478
  .yumia-notes-drawer.open {
2454
2479
  display: block;
2455
2480
  }
2481
+
2482
+ /* Slide Overview Modal */
2483
+ .yumia-overview-modal {
2484
+ position: fixed;
2485
+ top: 0;
2486
+ left: 0;
2487
+ width: 100vw;
2488
+ height: 100vh;
2489
+ background: rgba(5, 5, 10, 0.94);
2490
+ backdrop-filter: blur(20px);
2491
+ z-index: 3000;
2492
+ display: none;
2493
+ flex-direction: column;
2494
+ padding: 40px;
2495
+ overflow-y: auto;
2496
+ }
2497
+
2498
+ .yumia-overview-modal.open {
2499
+ display: flex;
2500
+ }
2501
+
2502
+ .yumia-overview-header {
2503
+ display: flex;
2504
+ justify-content: space-between;
2505
+ align-items: center;
2506
+ margin-bottom: 30px;
2507
+ color: #fff;
2508
+ }
2509
+
2510
+ .yumia-overview-grid {
2511
+ display: grid;
2512
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2513
+ gap: 24px;
2514
+ width: 100%;
2515
+ }
2516
+
2517
+ .yumia-overview-card {
2518
+ aspect-ratio: var(--yumia-ratio);
2519
+ background-color: var(--yumia-bg);
2520
+ border: 2px solid rgba(255, 255, 255, 0.1);
2521
+ border-radius: 8px;
2522
+ padding: 16px;
2523
+ cursor: pointer;
2524
+ position: relative;
2525
+ overflow: hidden;
2526
+ transform-origin: center;
2527
+ transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
2528
+ display: flex;
2529
+ flex-direction: column;
2530
+ }
2531
+
2532
+ .yumia-overview-card:hover {
2533
+ transform: scale(1.04);
2534
+ border-color: var(--yumia-primary);
2535
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.8), 0 0 15px var(--yumia-primary);
2536
+ }
2537
+
2538
+ .yumia-overview-card.current {
2539
+ border-color: var(--yumia-accent);
2540
+ box-shadow: 0 0 0 2px var(--yumia-accent);
2541
+ }
2542
+
2543
+ .yumia-overview-badge {
2544
+ position: absolute;
2545
+ top: 8px;
2546
+ right: 8px;
2547
+ background: rgba(0, 0, 0, 0.6);
2548
+ color: #fff;
2549
+ font-size: 11px;
2550
+ font-weight: 700;
2551
+ padding: 2px 8px;
2552
+ border-radius: 10px;
2553
+ }
2554
+
2555
+ /* Speaker View Layout (When loaded in speaker mode ?speaker=true) */
2556
+ .speaker-layout {
2557
+ display: grid;
2558
+ grid-template-rows: 60px 1fr;
2559
+ width: 100vw;
2560
+ height: 100vh;
2561
+ background: #09090f;
2562
+ color: #f8fafc;
2563
+ overflow: hidden;
2564
+ }
2565
+
2566
+ .speaker-topbar {
2567
+ display: flex;
2568
+ justify-content: space-between;
2569
+ align-items: center;
2570
+ padding: 0 24px;
2571
+ background: #11111b;
2572
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
2573
+ }
2574
+
2575
+ .speaker-timer-group {
2576
+ display: flex;
2577
+ align-items: center;
2578
+ gap: 16px;
2579
+ font-family: var(--yumia-font-code);
2580
+ font-size: 18px;
2581
+ }
2582
+
2583
+ .speaker-timer-btn {
2584
+ background: rgba(255, 255, 255, 0.1);
2585
+ color: #fff;
2586
+ border: none;
2587
+ padding: 4px 10px;
2588
+ border-radius: 4px;
2589
+ cursor: pointer;
2590
+ font-size: 12px;
2591
+ }
2592
+
2593
+ .speaker-timer-btn:hover {
2594
+ background: var(--yumia-primary);
2595
+ }
2596
+
2597
+ .speaker-main-grid {
2598
+ display: grid;
2599
+ grid-template-columns: 55% 45%;
2600
+ gap: 20px;
2601
+ padding: 20px;
2602
+ height: calc(100vh - 60px);
2603
+ box-sizing: border-box;
2604
+ }
2605
+
2606
+ .speaker-pane {
2607
+ background: #151522;
2608
+ border-radius: 10px;
2609
+ border: 1px solid rgba(255, 255, 255, 0.08);
2610
+ padding: 16px;
2611
+ display: flex;
2612
+ flex-direction: column;
2613
+ overflow: hidden;
2614
+ }
2615
+
2616
+ .speaker-pane-title {
2617
+ font-size: 12px;
2618
+ font-weight: 700;
2619
+ text-transform: uppercase;
2620
+ letter-spacing: 0.05em;
2621
+ color: var(--yumia-muted);
2622
+ margin-bottom: 12px;
2623
+ display: flex;
2624
+ justify-content: space-between;
2625
+ }
2626
+
2627
+ .speaker-preview-box {
2628
+ flex: 1;
2629
+ position: relative;
2630
+ display: flex;
2631
+ align-items: center;
2632
+ justify-content: center;
2633
+ background: #000;
2634
+ border-radius: 8px;
2635
+ overflow: hidden;
2636
+ }
2637
+
2638
+ .speaker-preview-box .yumia-slide-wrapper {
2639
+ display: flex !important;
2640
+ transform: scale(0.6);
2641
+ width: 160% !important;
2642
+ height: 160% !important;
2643
+ }
2644
+
2645
+ .speaker-notes-box {
2646
+ flex: 1;
2647
+ background: #11111a;
2648
+ border-radius: 8px;
2649
+ padding: 16px;
2650
+ font-size: 16px;
2651
+ line-height: 1.7;
2652
+ color: #e2e8f0;
2653
+ overflow-y: auto;
2654
+ }
2456
2655
  </style>
2457
2656
  </head>
2458
2657
  <body>
2459
- <div id="yumia-deck">
2460
- ${slidesHtml}
2658
+ <div id="deck-container">
2659
+ <div id="yumia-deck">
2660
+ ${slidesHtml}
2661
+ </div>
2662
+
2663
+ <div class="yumia-controls" id="deck-controls">
2664
+ <button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
2665
+ <span id="slide-indicator">1 / ${presentation.slides.length}</span>
2666
+ <button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
2667
+ <button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
2668
+ <button class="yumia-btn" id="btn-speaker" title="Speaker View (S)">\u{1F3A4}</button>
2669
+ <button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
2670
+ <button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
2671
+ </div>
2672
+
2673
+ <div id="notes-drawer" class="yumia-notes-drawer"></div>
2674
+
2675
+ <div id="overview-modal" class="yumia-overview-modal">
2676
+ <div class="yumia-overview-header">
2677
+ <h2>Slide Overview</h2>
2678
+ <button class="yumia-btn" id="btn-close-overview" style="font-size: 18px;">\u2715 Close (ESC)</button>
2679
+ </div>
2680
+ <div class="yumia-overview-grid" id="overview-grid"></div>
2681
+ </div>
2461
2682
  </div>
2462
2683
 
2463
- <div class="yumia-controls">
2464
- <button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
2465
- <span id="slide-indicator">1 / ${presentation.slides.length}</span>
2466
- <button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
2467
- <button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
2468
- <button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
2469
- </div>
2470
-
2471
- <div id="notes-drawer" class="yumia-notes-drawer"></div>
2472
-
2473
2684
  <script>
2474
2685
  (function() {
2475
- const slides = document.querySelectorAll('.yumia-slide-wrapper');
2686
+ const isSpeakerMode = new URLSearchParams(window.location.search).get('speaker') === 'true';
2687
+ const slides = Array.from(document.querySelectorAll('.yumia-slide-wrapper'));
2476
2688
  const indicator = document.getElementById('slide-indicator');
2477
2689
  const notesDrawer = document.getElementById('notes-drawer');
2690
+ const overviewModal = document.getElementById('overview-modal');
2691
+ const overviewGrid = document.getElementById('overview-grid');
2478
2692
  let currentIdx = 0;
2479
2693
 
2480
- function updateSlide(newIdx) {
2694
+ // Real-time synchronization channel
2695
+ let syncChannel = null;
2696
+ try {
2697
+ syncChannel = new BroadcastChannel('yumia_presentation_sync');
2698
+ syncChannel.onmessage = function(e) {
2699
+ if (e.data && typeof e.data.index === 'number') {
2700
+ goToSlide(e.data.index, false);
2701
+ }
2702
+ };
2703
+ } catch (err) {
2704
+ // Fallback if BroadcastChannel unavailable
2705
+ }
2706
+
2707
+ function buildOverviewGrid() {
2708
+ if (!overviewGrid) return;
2709
+ overviewGrid.innerHTML = '';
2710
+ slides.forEach((slide, idx) => {
2711
+ const card = document.createElement('div');
2712
+ card.className = 'yumia-overview-card' + (idx === currentIdx ? ' current' : '');
2713
+ const badge = document.createElement('span');
2714
+ badge.className = 'yumia-overview-badge';
2715
+ badge.textContent = (idx + 1);
2716
+
2717
+ const heading = slide.querySelector('h1, h2, h3');
2718
+ const titleText = heading ? heading.textContent : 'Slide ' + (idx + 1);
2719
+
2720
+ const titleDiv = document.createElement('div');
2721
+ titleDiv.style.fontWeight = '600';
2722
+ titleDiv.style.fontSize = '14px';
2723
+ titleDiv.style.color = 'var(--yumia-primary)';
2724
+ titleDiv.textContent = titleText;
2725
+
2726
+ card.appendChild(badge);
2727
+ card.appendChild(titleDiv);
2728
+
2729
+ card.addEventListener('click', () => {
2730
+ goToSlide(idx);
2731
+ toggleOverview(false);
2732
+ });
2733
+ overviewGrid.appendChild(card);
2734
+ });
2735
+ }
2736
+
2737
+ function toggleOverview(force) {
2738
+ if (!overviewModal) return;
2739
+ const isOpen = force !== undefined ? force : !overviewModal.classList.contains('open');
2740
+ if (isOpen) {
2741
+ buildOverviewGrid();
2742
+ overviewModal.classList.add('open');
2743
+ } else {
2744
+ overviewModal.classList.remove('open');
2745
+ }
2746
+ }
2747
+
2748
+ function goToSlide(newIdx, broadcast = true) {
2481
2749
  if (newIdx < 0 || newIdx >= slides.length) return;
2482
2750
  slides[currentIdx]?.classList.remove('active');
2483
2751
  currentIdx = newIdx;
@@ -2490,23 +2758,166 @@ var HtmlRenderer = class {
2490
2758
  const currentSlide = slides[currentIdx];
2491
2759
  const notes = currentSlide ? currentSlide.getAttribute('data-notes') : '';
2492
2760
  if (notesDrawer) {
2493
- notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No notes for this slide.</em>';
2761
+ notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No speaker notes for this slide.</em>';
2494
2762
  }
2495
2763
 
2496
2764
  window.location.hash = '#' + (currentIdx + 1);
2765
+
2766
+ if (broadcast && syncChannel) {
2767
+ syncChannel.postMessage({ index: currentIdx });
2768
+ }
2769
+ }
2770
+
2771
+ function openSpeakerWindow() {
2772
+ const url = new URL(window.location.href);
2773
+ url.searchParams.set('speaker', 'true');
2774
+ window.open(url.toString(), 'yumia_speaker_' + Date.now(), 'width=1180,height=760,menubar=no,toolbar=no');
2775
+ }
2776
+
2777
+ function initSpeakerLayout() {
2778
+ document.body.innerHTML = \`
2779
+ <div class="speaker-layout">
2780
+ <div class="speaker-topbar">
2781
+ <div style="font-weight:700; color:var(--yumia-primary); display:flex; align-items:center; gap:8px;">
2782
+ <span>\u{1F399}\uFE0F YumiaMD Speaker View</span>
2783
+ </div>
2784
+ <div class="speaker-timer-group">
2785
+ <span id="speaker-clock">00:00:00</span>
2786
+ <span style="color:var(--yumia-muted);">|</span>
2787
+ <span id="speaker-timer" style="color:var(--yumia-accent);">00:00</span>
2788
+ <button class="speaker-timer-btn" id="btn-timer-toggle">Pause</button>
2789
+ <button class="speaker-timer-btn" id="btn-timer-reset">Reset</button>
2790
+ </div>
2791
+ <div>
2792
+ <span id="speaker-slide-num" style="font-weight:600;">1 / \${slides.length}</span>
2793
+ </div>
2794
+ </div>
2795
+ <div class="speaker-main-grid">
2796
+ <div class="speaker-pane">
2797
+ <div class="speaker-pane-title">Current Slide</div>
2798
+ <div class="speaker-preview-box" id="speaker-current-box"></div>
2799
+ </div>
2800
+ <div style="display:grid; grid-template-rows: 45% 55%; gap:20px;">
2801
+ <div class="speaker-pane">
2802
+ <div class="speaker-pane-title">Next Slide Preview</div>
2803
+ <div class="speaker-preview-box" id="speaker-next-box"></div>
2804
+ </div>
2805
+ <div class="speaker-pane">
2806
+ <div class="speaker-pane-title">Speaker Notes</div>
2807
+ <div class="speaker-notes-box" id="speaker-notes-content"></div>
2808
+ </div>
2809
+ </div>
2810
+ </div>
2811
+ </div>
2812
+ \`;
2813
+
2814
+ // Live Clock
2815
+ setInterval(() => {
2816
+ const now = new Date();
2817
+ const clockEl = document.getElementById('speaker-clock');
2818
+ if (clockEl) clockEl.textContent = now.toLocaleTimeString();
2819
+ }, 1000);
2820
+
2821
+ // Elapsed Timer
2822
+ let elapsedSeconds = 0;
2823
+ let timerRunning = true;
2824
+ let timerInterval = setInterval(() => {
2825
+ if (!timerRunning) return;
2826
+ elapsedSeconds++;
2827
+ const mins = String(Math.floor(elapsedSeconds / 60)).padStart(2, '0');
2828
+ const secs = String(elapsedSeconds % 60).padStart(2, '0');
2829
+ const timerEl = document.getElementById('speaker-timer');
2830
+ if (timerEl) timerEl.textContent = \`\${mins}:\${secs}\`;
2831
+ }, 1000);
2832
+
2833
+ document.getElementById('btn-timer-toggle')?.addEventListener('click', (e) => {
2834
+ timerRunning = !timerRunning;
2835
+ e.target.textContent = timerRunning ? 'Pause' : 'Start';
2836
+ });
2837
+
2838
+ document.getElementById('btn-timer-reset')?.addEventListener('click', () => {
2839
+ elapsedSeconds = 0;
2840
+ const timerEl = document.getElementById('speaker-timer');
2841
+ if (timerEl) timerEl.textContent = '00:00';
2842
+ });
2843
+
2844
+ function updateSpeakerView(idx) {
2845
+ currentIdx = idx;
2846
+ const numEl = document.getElementById('speaker-slide-num');
2847
+ if (numEl) numEl.textContent = (currentIdx + 1) + ' / ' + slides.length;
2848
+
2849
+ // Render current slide
2850
+ const curBox = document.getElementById('speaker-current-box');
2851
+ if (curBox && slides[currentIdx]) {
2852
+ curBox.innerHTML = slides[currentIdx].outerHTML;
2853
+ curBox.firstElementChild?.classList.add('active');
2854
+ }
2855
+
2856
+ // Render next slide
2857
+ const nextBox = document.getElementById('speaker-next-box');
2858
+ if (nextBox) {
2859
+ if (slides[currentIdx + 1]) {
2860
+ nextBox.innerHTML = slides[currentIdx + 1].outerHTML;
2861
+ nextBox.firstElementChild?.classList.add('active');
2862
+ } else {
2863
+ nextBox.innerHTML = '<div style="color:var(--yumia-muted); font-size:14px;">End of presentation</div>';
2864
+ }
2865
+ }
2866
+
2867
+ // Render notes
2868
+ const notesBox = document.getElementById('speaker-notes-content');
2869
+ if (notesBox && slides[currentIdx]) {
2870
+ const rawNotes = slides[currentIdx].getAttribute('data-notes');
2871
+ notesBox.innerHTML = rawNotes ? rawNotes : '<em style="color:var(--yumia-muted);">No notes provided for this slide.</em>';
2872
+ }
2873
+ }
2874
+
2875
+ window.addEventListener('keydown', (e) => {
2876
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
2877
+ e.preventDefault();
2878
+ if (currentIdx < slides.length - 1) {
2879
+ goToSlide(currentIdx + 1);
2880
+ updateSpeakerView(currentIdx);
2881
+ }
2882
+ } else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
2883
+ e.preventDefault();
2884
+ if (currentIdx > 0) {
2885
+ goToSlide(currentIdx - 1);
2886
+ updateSpeakerView(currentIdx);
2887
+ }
2888
+ }
2889
+ });
2890
+
2891
+ if (syncChannel) {
2892
+ syncChannel.onmessage = (e) => {
2893
+ if (e.data && typeof e.data.index === 'number') {
2894
+ updateSpeakerView(e.data.index);
2895
+ }
2896
+ };
2897
+ }
2898
+
2899
+ const initialHash = parseInt(window.location.hash.replace('#', ''), 10) - 1;
2900
+ updateSpeakerView(isNaN(initialHash) ? 0 : initialHash);
2497
2901
  }
2498
2902
 
2499
2903
  function init() {
2904
+ if (isSpeakerMode) {
2905
+ initSpeakerLayout();
2906
+ return;
2907
+ }
2908
+
2500
2909
  const hash = window.location.hash.replace('#', '');
2501
2910
  const initialIdx = parseInt(hash, 10) - 1;
2502
- updateSlide(isNaN(initialIdx) ? 0 : initialIdx);
2911
+ goToSlide(isNaN(initialIdx) ? 0 : initialIdx, false);
2503
2912
  }
2504
2913
 
2505
2914
  window.deckController = {
2506
2915
  init: init,
2507
- next: function() { updateSlide(currentIdx + 1); },
2508
- prev: function() { updateSlide(currentIdx - 1); },
2916
+ next: function() { goToSlide(currentIdx + 1); },
2917
+ prev: function() { goToSlide(currentIdx - 1); },
2509
2918
  toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
2919
+ toggleOverview: function() { toggleOverview(); },
2920
+ openSpeaker: openSpeakerWindow,
2510
2921
  toggleFs: function() {
2511
2922
  if (!document.fullscreenElement) {
2512
2923
  document.documentElement.requestFullscreen().catch(() => {});
@@ -2519,19 +2930,33 @@ var HtmlRenderer = class {
2519
2930
  document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
2520
2931
  document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
2521
2932
  document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
2933
+ document.getElementById('btn-overview')?.addEventListener('click', window.deckController.toggleOverview);
2934
+ document.getElementById('btn-close-overview')?.addEventListener('click', () => toggleOverview(false));
2935
+ document.getElementById('btn-speaker')?.addEventListener('click', window.deckController.openSpeaker);
2522
2936
  document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
2523
2937
 
2524
2938
  window.addEventListener('keydown', function(e) {
2525
- if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
2939
+ if (overviewModal && overviewModal.classList.contains('open')) {
2940
+ if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
2941
+ toggleOverview(false);
2942
+ }
2943
+ return;
2944
+ }
2945
+
2946
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key.toLowerCase() === 'l') {
2526
2947
  e.preventDefault();
2527
2948
  window.deckController.next();
2528
- } else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
2949
+ } else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp' || e.key.toLowerCase() === 'h') {
2529
2950
  e.preventDefault();
2530
2951
  window.deckController.prev();
2531
2952
  } else if (e.key.toLowerCase() === 'f') {
2532
2953
  window.deckController.toggleFs();
2954
+ } else if (e.key.toLowerCase() === 's') {
2955
+ window.deckController.openSpeaker();
2533
2956
  } else if (e.key.toLowerCase() === 'n') {
2534
2957
  window.deckController.toggleNotes();
2958
+ } else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
2959
+ window.deckController.toggleOverview();
2535
2960
  }
2536
2961
  });
2537
2962
 
@@ -2667,6 +3092,268 @@ var HtmlRenderer = class {
2667
3092
  }
2668
3093
  };
2669
3094
 
3095
+ // ../renderer-pdf/dist/renderer.js
3096
+ import PDFDocument from "pdfkit";
3097
+ var PdfRenderer = class {
3098
+ name = "PdfRenderer";
3099
+ targetFormat = "pdf";
3100
+ async render(presentation, context = {}) {
3101
+ const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
3102
+ const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
3103
+ const theme = context.theme || resolvedTheme || defaultTheme;
3104
+ const is43 = presentation.metadata.aspectRatio === "4:3";
3105
+ const pageWidth = is43 ? 720 : 960;
3106
+ const pageHeight = 540;
3107
+ return new Promise((resolvePromise, rejectPromise) => {
3108
+ try {
3109
+ const doc = new PDFDocument({
3110
+ autoFirstPage: false,
3111
+ margin: 0,
3112
+ info: {
3113
+ Title: presentation.metadata.title || "YumiaMD Presentation",
3114
+ Author: presentation.metadata.author || "YumiaMD",
3115
+ Creator: "YumiaMD Vector PDF Compiler"
3116
+ }
3117
+ });
3118
+ const chunks = [];
3119
+ doc.on("data", (chunk) => chunks.push(chunk));
3120
+ doc.on("end", () => {
3121
+ const buffer = Buffer.concat(chunks);
3122
+ resolvePromise({
3123
+ format: "pdf",
3124
+ data: new Uint8Array(buffer),
3125
+ pageCount: presentation.slides.length,
3126
+ slideCount: presentation.slides.length
3127
+ });
3128
+ });
3129
+ doc.on("error", (err) => rejectPromise(err));
3130
+ const totalSlides = presentation.slides.length;
3131
+ for (let i = 0; i < totalSlides; i++) {
3132
+ const slide = presentation.slides[i];
3133
+ this.renderSlide(doc, slide, i + 1, totalSlides, pageWidth, pageHeight, theme);
3134
+ }
3135
+ doc.end();
3136
+ } catch (err) {
3137
+ rejectPromise(err);
3138
+ }
3139
+ });
3140
+ }
3141
+ renderSlide(doc, slide, slideNum, totalSlides, pageWidth, pageHeight, theme) {
3142
+ doc.addPage({
3143
+ size: [pageWidth, pageHeight],
3144
+ margins: { top: 0, bottom: 0, left: 0, right: 0 }
3145
+ });
3146
+ doc.rect(0, 0, pageWidth, pageHeight).fill(theme.colors.background);
3147
+ const padX = pageWidth * 0.06;
3148
+ const padY = pageHeight * 0.08;
3149
+ const contentWidth = pageWidth - padX * 2;
3150
+ let cursorY = padY;
3151
+ for (const element of slide.elements) {
3152
+ cursorY = this.renderElement(doc, element, padX, cursorY, contentWidth, theme);
3153
+ cursorY += 12;
3154
+ }
3155
+ const progressWidth = slideNum / totalSlides * pageWidth;
3156
+ doc.rect(0, pageHeight - 4, progressWidth, 4).fill(theme.colors.primary);
3157
+ doc.font("Helvetica").fontSize(10).fillColor(theme.colors.muted || "#888888").text(`${slideNum} / ${totalSlides}`, pageWidth - padX - 60, pageHeight - 24, {
3158
+ width: 60,
3159
+ align: "right"
3160
+ });
3161
+ }
3162
+ renderElement(doc, element, x, y, width, theme) {
3163
+ switch (element.type) {
3164
+ case "heading": {
3165
+ const h = element;
3166
+ const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
3167
+ const color = h.level === 1 ? theme.colors.primary : theme.colors.text;
3168
+ doc.font("Helvetica-Bold").fontSize(fontSize).fillColor(color);
3169
+ doc.text(this.stripFormatting(h.text), x, y, { width, lineGap: 4 });
3170
+ const height = doc.heightOfString(this.stripFormatting(h.text), { width });
3171
+ return y + height;
3172
+ }
3173
+ case "paragraph": {
3174
+ const p = element;
3175
+ doc.font("Helvetica").fontSize(14).fillColor(theme.colors.text);
3176
+ doc.text(this.stripFormatting(p.text), x, y, { width, lineGap: 4 });
3177
+ const height = doc.heightOfString(this.stripFormatting(p.text), { width });
3178
+ return y + height;
3179
+ }
3180
+ case "list": {
3181
+ const l = element;
3182
+ let currentY = y;
3183
+ const itemGap = 6;
3184
+ l.items.forEach((item, idx) => {
3185
+ const bullet = l.ordered ? `${idx + 1}.` : "\u2022";
3186
+ doc.font("Helvetica-Bold").fontSize(13).fillColor(theme.colors.primary).text(bullet, x, currentY, { width: 18 });
3187
+ doc.font("Helvetica").fontSize(13).fillColor(theme.colors.text).text(this.stripFormatting(item.text), x + 20, currentY, {
3188
+ width: width - 20,
3189
+ lineGap: 3
3190
+ });
3191
+ const itemHeight = Math.max(18, doc.heightOfString(this.stripFormatting(item.text), { width: width - 20 }));
3192
+ currentY += itemHeight + itemGap;
3193
+ });
3194
+ return currentY;
3195
+ }
3196
+ case "quote": {
3197
+ const q = element;
3198
+ const quoteText = `\u201C${this.stripFormatting(q.text)}\u201D`;
3199
+ const authorText = q.author ? `\u2014 ${this.stripFormatting(q.author)}` : "";
3200
+ doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.muted || "#aaaaaa");
3201
+ const textHeight = doc.heightOfString(quoteText, { width: width - 24 });
3202
+ const authorHeight = authorText ? 18 : 0;
3203
+ const totalHeight = textHeight + authorHeight + 16;
3204
+ doc.roundedRect(x, y, width, totalHeight, 6).fill(theme.colors.surface || "rgba(255,255,255,0.05)");
3205
+ doc.rect(x, y, 4, totalHeight).fill(theme.colors.accent || theme.colors.primary);
3206
+ doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.text).text(quoteText, x + 16, y + 8, { width: width - 28 });
3207
+ if (authorText) {
3208
+ doc.font("Helvetica").fontSize(11).fillColor(theme.colors.muted || "#888888").text(authorText, x + 16, y + 8 + textHeight + 4, { width: width - 28 });
3209
+ }
3210
+ return y + totalHeight;
3211
+ }
3212
+ case "code": {
3213
+ const c = element;
3214
+ const codeText = c.code;
3215
+ doc.font("Courier").fontSize(11);
3216
+ const textHeight = doc.heightOfString(codeText, { width: width - 24 });
3217
+ const boxHeight = textHeight + 20;
3218
+ doc.roundedRect(x, y, width, boxHeight, 6).fill("#0a0a10").strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
3219
+ doc.font("Courier").fontSize(11).fillColor(theme.colors.accent || "#38bdf8").text(codeText, x + 12, y + 10, { width: width - 24 });
3220
+ return y + boxHeight;
3221
+ }
3222
+ case "card": {
3223
+ const card = element;
3224
+ const variantColor = this.getVariantColor(card.variant, theme);
3225
+ const cardPad = 14;
3226
+ let cardCursorY = y + cardPad;
3227
+ if (card.title) {
3228
+ doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, cardCursorY, {
3229
+ width: width - cardPad * 2
3230
+ });
3231
+ cardCursorY += 22;
3232
+ }
3233
+ if (card.elements) {
3234
+ for (const child of card.elements) {
3235
+ cardCursorY = this.renderElement(doc, child, x + cardPad, cardCursorY, width - cardPad * 2, theme);
3236
+ cardCursorY += 8;
3237
+ }
3238
+ }
3239
+ const totalCardHeight = Math.max(70, cardCursorY - y + cardPad);
3240
+ doc.save();
3241
+ doc.roundedRect(x, y, width, totalCardHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
3242
+ doc.roundedRect(x, y, width, totalCardHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
3243
+ doc.restore();
3244
+ let renderTop = y + cardPad;
3245
+ if (card.title) {
3246
+ doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, renderTop, {
3247
+ width: width - cardPad * 2
3248
+ });
3249
+ renderTop += 22;
3250
+ }
3251
+ if (card.elements) {
3252
+ for (const child of card.elements) {
3253
+ renderTop = this.renderElement(doc, child, x + cardPad, renderTop, width - cardPad * 2, theme);
3254
+ renderTop += 8;
3255
+ }
3256
+ }
3257
+ return y + totalCardHeight;
3258
+ }
3259
+ case "metric": {
3260
+ const m = element;
3261
+ const variantColor = this.getVariantColor(m.variant, theme);
3262
+ const boxHeight = 90;
3263
+ doc.roundedRect(x, y, width, boxHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
3264
+ doc.roundedRect(x, y, width, boxHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
3265
+ doc.font("Helvetica-Bold").fontSize(11).fillColor(theme.colors.muted || "#888888").text(m.label.toUpperCase(), x + 16, y + 14, { width: width - 32 });
3266
+ const displayVal = m.unit ? `${m.value} ${m.unit}` : m.value;
3267
+ doc.font("Helvetica-Bold").fontSize(28).fillColor(theme.colors.primary).text(displayVal, x + 16, y + 32, { width: width - 32 });
3268
+ if (m.change) {
3269
+ const changeColor = m.change.startsWith("+") ? "#10b981" : "#ef4444";
3270
+ doc.font("Helvetica-Bold").fontSize(12).fillColor(changeColor).text(m.change, x + width - 90, y + 38, { width: 74, align: "right" });
3271
+ }
3272
+ return y + boxHeight;
3273
+ }
3274
+ case "columns": {
3275
+ const cols = element;
3276
+ const colCount = cols.columns.length;
3277
+ const gap = 16;
3278
+ const availableWidth = width - gap * (colCount - 1);
3279
+ let ratios = Array(colCount).fill(1);
3280
+ if (cols.ratios) {
3281
+ const parsed = cols.ratios.split(":").map((r) => parseFloat(r) || 1);
3282
+ if (parsed.length === colCount)
3283
+ ratios = parsed;
3284
+ }
3285
+ const totalRatio = ratios.reduce((a, b) => a + b, 0);
3286
+ let curX = x;
3287
+ let maxY = y;
3288
+ for (let i = 0; i < colCount; i++) {
3289
+ const col = cols.columns[i];
3290
+ const colWidth = ratios[i] / totalRatio * availableWidth;
3291
+ let colCursorY = y;
3292
+ for (const child of col.elements) {
3293
+ colCursorY = this.renderElement(doc, child, curX, colCursorY, colWidth, theme);
3294
+ colCursorY += 8;
3295
+ }
3296
+ if (colCursorY > maxY)
3297
+ maxY = colCursorY;
3298
+ curX += colWidth + gap;
3299
+ }
3300
+ return maxY;
3301
+ }
3302
+ case "table": {
3303
+ const t = element;
3304
+ const headers = t.headers || [];
3305
+ const rows = t.rows || [];
3306
+ const colCount = Math.max(headers.length, ...rows.map((r) => r.length), 1);
3307
+ const colWidth = width / colCount;
3308
+ const rowHeight = 26;
3309
+ let curY = y;
3310
+ if (headers.length > 0) {
3311
+ doc.rect(x, curY, width, rowHeight).fill(theme.colors.primary);
3312
+ headers.forEach((h, idx) => {
3313
+ doc.font("Helvetica-Bold").fontSize(12).fillColor("#ffffff").text(this.stripFormatting(h), x + idx * colWidth + 6, curY + 6, {
3314
+ width: colWidth - 12
3315
+ });
3316
+ });
3317
+ curY += rowHeight;
3318
+ }
3319
+ rows.forEach((row, rowIdx) => {
3320
+ const isEven = rowIdx % 2 === 0;
3321
+ const rowBg = isEven ? theme.colors.surface || "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.01)";
3322
+ doc.rect(x, curY, width, rowHeight).fill(rowBg);
3323
+ row.forEach((cell, idx) => {
3324
+ doc.font("Helvetica").fontSize(11).fillColor(theme.colors.text).text(this.stripFormatting(cell), x + idx * colWidth + 6, curY + 6, {
3325
+ width: colWidth - 12
3326
+ });
3327
+ });
3328
+ curY += rowHeight;
3329
+ });
3330
+ doc.rect(x, y, width, curY - y).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
3331
+ return curY;
3332
+ }
3333
+ default:
3334
+ return y;
3335
+ }
3336
+ }
3337
+ getVariantColor(variant, theme) {
3338
+ switch (variant) {
3339
+ case "warning":
3340
+ return theme.colors.warning || "#f59e0b";
3341
+ case "info":
3342
+ return theme.colors.info || "#3b82f6";
3343
+ case "success":
3344
+ return theme.colors.success || "#10b981";
3345
+ case "danger":
3346
+ return theme.colors.danger || "#ef4444";
3347
+ case "primary":
3348
+ default:
3349
+ return theme.colors.primary;
3350
+ }
3351
+ }
3352
+ stripFormatting(text) {
3353
+ return text.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/`(.*?)`/g, "$1").replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F900}-\u{1F9FF}\u{1F600}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{2300}-\u{23FF}\u{2B50}]/gu, "").replace(/\u200D/g, "").replace(/\uFE0F/g, "").replace(/[“”]/g, '"').replace(/[‘’]/g, "'").replace(/[—–]/g, "-").trim();
3354
+ }
3355
+ };
3356
+
2670
3357
  // src/dev-server.ts
2671
3358
  import { createServer } from "http";
2672
3359
  import { readFileSync, watch } from "fs";
@@ -2730,6 +3417,14 @@ function startDevServer(filePath, options = {}) {
2730
3417
  });
2731
3418
  server.listen(port, () => {
2732
3419
  const url = `http://localhost:${port}`;
3420
+ if (options.open) {
3421
+ const startCmd = process.platform === "win32" ? `start ${url}` : process.platform === "darwin" ? `open ${url}` : `xdg-open ${url}`;
3422
+ import("child_process").then(({ exec }) => {
3423
+ exec(startCmd, () => {
3424
+ });
3425
+ }).catch(() => {
3426
+ });
3427
+ }
2733
3428
  try {
2734
3429
  watcher = watch(resolvedPath, () => {
2735
3430
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -2766,7 +3461,7 @@ function startDevServer(filePath, options = {}) {
2766
3461
  // src/cli.ts
2767
3462
  import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
2768
3463
  import { basename, dirname, extname, join, resolve as resolve2 } from "path";
2769
- var VERSION = "0.1.10";
3464
+ var VERSION = "0.1.14";
2770
3465
  function printHelp() {
2771
3466
  return `
2772
3467
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -2782,7 +3477,7 @@ Commands:
2782
3477
  lint <file> Analyze presentation for layout overflows and accessibility
2783
3478
  inspect <file> Inspect the AST and geometric layout tree
2784
3479
  schema Output machine-readable JSON schema for AI agents
2785
- build <file> Compile a presentation to PowerPoint (.pptx) or HTML (.html)
3480
+ build <file> Compile a presentation to PowerPoint (.pptx), PDF (.pdf), or HTML (.html)
2786
3481
 
2787
3482
  Theming & Color Options:
2788
3483
  --theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
@@ -2797,7 +3492,7 @@ Server & Compiler Options:
2797
3492
  --open Open default browser automatically in dev mode
2798
3493
  --watch, -w Watch for file changes during compilation
2799
3494
  --out, -o <file> Specify output file path (default: dist/<name>.<format>)
2800
- --format, -f <fmt> Target output format: pptx (default) | html
3495
+ --format, -f <fmt> Target output format: pptx (default) | pdf | html
2801
3496
  --strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
2802
3497
  --json Output results formatted as JSON for CI/CD and AI tools
2803
3498
  --layout Show computed geometric bounding boxes in 'inspect'
@@ -2966,7 +3661,8 @@ Opening slide introducing the presentation deck.
2966
3661
  exitCode: 0,
2967
3662
  output: `\u{1F680} YumiaMD Live Dev Server running at: ${devInstance.url}
2968
3663
  Watching: ${target}
2969
- Hot-reloading active via SSE. Press Ctrl+C to stop.`
3664
+ Hot-reloading active via SSE. Press Ctrl+C to stop.`,
3665
+ keepAlive: true
2970
3666
  };
2971
3667
  } catch (err) {
2972
3668
  const msg = err instanceof Error ? err.message : String(err);
@@ -3120,7 +3816,8 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
3120
3816
  try {
3121
3817
  const resolvedInput = resolve2(process.cwd(), target);
3122
3818
  const isHtml = cliFormat === "html";
3123
- const targetExtension = isHtml ? ".html" : ".pptx";
3819
+ const isPdf = cliFormat === "pdf";
3820
+ const targetExtension = isHtml ? ".html" : isPdf ? ".pdf" : ".pptx";
3124
3821
  let outputPath = "";
3125
3822
  const outFlagIndex = args.findIndex((a) => a === "--out" || a === "-o");
3126
3823
  if (outFlagIndex !== -1 && args[outFlagIndex + 1]) {
@@ -3149,6 +3846,14 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
3149
3846
  });
3150
3847
  writeFileSync(outputPath, result2.html, "utf-8");
3151
3848
  return { slideCount: result2.slideCount, format: result2.format };
3849
+ } else if (isPdf) {
3850
+ const pdfRenderer = new PdfRenderer();
3851
+ const result2 = await compiler.compile(source, pdfRenderer, {
3852
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
3853
+ });
3854
+ const buffer = result2.data instanceof Uint8Array ? Buffer.from(result2.data) : Buffer.from(new Uint8Array(result2.data));
3855
+ writeFileSync(outputPath, buffer);
3856
+ return { slideCount: result2.slideCount, format: result2.format };
3152
3857
  } else {
3153
3858
  const pptxRenderer = new PptxRenderer();
3154
3859
  const result2 = await compiler.compile(source, pptxRenderer, {
@@ -3186,9 +3891,11 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
3186
3891
  };
3187
3892
  }
3188
3893
  const watchMsg = command === "watch" || isWatch ? " [Watching for changes...]" : "";
3894
+ const formatDesc = isHtml ? "interactive HTML slides" : isPdf ? "vector PDF slides" : "native editable slides";
3189
3895
  return {
3190
3896
  exitCode: 0,
3191
- output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${isHtml ? "interactive HTML slides" : "native editable slides"})${watchMsg}`
3897
+ output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${formatDesc})${watchMsg}`,
3898
+ keepAlive: command === "watch" || isWatch
3192
3899
  };
3193
3900
  } catch (err) {
3194
3901
  const msg = err instanceof Error ? err.message : String(err);
@@ -3243,6 +3950,7 @@ export {
3243
3950
  parseInlineMarkdown,
3244
3951
  PptxRenderer,
3245
3952
  HtmlRenderer,
3953
+ PdfRenderer,
3246
3954
  startDevServer,
3247
3955
  VERSION,
3248
3956
  printHelp,
package/dist/index.d.ts CHANGED
@@ -7,12 +7,14 @@ export * from '@yumiamd/layout';
7
7
  export * from '@yumiamd/renderer';
8
8
  export * from '@yumiamd/renderer-pptx';
9
9
  export * from '@yumiamd/renderer-html';
10
+ export * from '@yumiamd/renderer-pdf';
10
11
 
11
- declare const VERSION = "0.1.10";
12
+ declare const VERSION = "0.1.14";
12
13
  declare function printHelp(): string;
13
14
  declare function runCli(argv: string[]): Promise<{
14
15
  exitCode: number;
15
16
  output: string;
17
+ keepAlive?: boolean;
16
18
  }>;
17
19
 
18
20
  interface DevServerOptions {
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  DefaultLayoutEngine,
4
4
  DefaultYumiaParser,
5
5
  HtmlRenderer,
6
+ PdfRenderer,
6
7
  PptxRenderer,
7
8
  THEME_REGISTRY,
8
9
  VERSION,
@@ -38,12 +39,13 @@ import {
38
39
  runCli,
39
40
  startDevServer,
40
41
  terminalTheme
41
- } from "./chunk-PQYPVPV2.js";
42
+ } from "./chunk-ZOJEPOPI.js";
42
43
  export {
43
44
  DEFAULT_VIEWPORT,
44
45
  DefaultLayoutEngine,
45
46
  DefaultYumiaParser,
46
47
  HtmlRenderer,
48
+ PdfRenderer,
47
49
  PptxRenderer,
48
50
  THEME_REGISTRY,
49
51
  VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yumiamd",
3
- "version": "0.1.10",
3
+ "version": "0.1.14",
4
4
  "description": "Command line interface and compiler for YumiaMD presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,18 +16,20 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
+ "pdfkit": "^0.20.2",
19
20
  "pptxgenjs": "^4.0.1"
20
21
  },
21
22
  "devDependencies": {
22
23
  "tsup": "^8.5.1",
23
- "@yumiamd/ast": "0.1.10",
24
- "@yumiamd/layout": "0.1.10",
25
- "@yumiamd/core": "0.1.10",
26
- "@yumiamd/parser": "0.1.10",
27
- "@yumiamd/renderer": "0.1.10",
28
- "@yumiamd/renderer-pptx": "0.1.10",
29
- "@yumiamd/renderer-html": "0.1.10",
30
- "@yumiamd/theme": "0.1.10"
24
+ "@yumiamd/ast": "0.1.14",
25
+ "@yumiamd/parser": "0.1.14",
26
+ "@yumiamd/core": "0.1.14",
27
+ "@yumiamd/layout": "0.1.14",
28
+ "@yumiamd/renderer": "0.1.14",
29
+ "@yumiamd/renderer-pptx": "0.1.14",
30
+ "@yumiamd/renderer-html": "0.1.14",
31
+ "@yumiamd/renderer-pdf": "0.1.14",
32
+ "@yumiamd/theme": "0.1.14"
31
33
  },
32
34
  "keywords": [
33
35
  "yumia",
@@ -53,7 +55,7 @@
53
55
  "LICENSE"
54
56
  ],
55
57
  "scripts": {
56
- "build": "tsup src/bin.ts src/index.ts --format esm --dts --clean --external pptxgenjs",
58
+ "build": "tsup src/bin.ts src/index.ts --format esm --dts --clean --external pptxgenjs,pdfkit",
57
59
  "typecheck": "tsc --noEmit"
58
60
  }
59
61
  }