yumiamd 0.1.9 → 0.1.13

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.
@@ -2017,10 +2017,1421 @@ var PptxRenderer = class {
2017
2017
  }
2018
2018
  };
2019
2019
 
2020
+ // ../renderer-html/dist/renderer.js
2021
+ var HtmlRenderer = class {
2022
+ name = "HtmlRenderer";
2023
+ targetFormat = "html";
2024
+ async render(presentation, context = {}) {
2025
+ const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
2026
+ const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
2027
+ const theme = context.theme || resolvedTheme || defaultTheme;
2028
+ const title = presentation.metadata.title || "YumiaMD Presentation";
2029
+ const aspectRatio = presentation.metadata.aspectRatio || "16:9";
2030
+ const is43 = aspectRatio === "4:3";
2031
+ const ratioAspect = is43 ? "4 / 3" : "16 / 9";
2032
+ const options = context.options || {};
2033
+ const liveReloadScript = options.liveReload ? `
2034
+ <!-- YumiaMD Live Reload -->
2035
+ <script>
2036
+ (function() {
2037
+ const port = ${options.liveReloadPort || 3e3};
2038
+ const evtSource = new EventSource('/__yumia_live_reload');
2039
+ evtSource.onmessage = function(event) {
2040
+ if (event.data === 'reload') {
2041
+ const currentHash = window.location.hash;
2042
+ fetch(window.location.href)
2043
+ .then(res => res.text())
2044
+ .then(html => {
2045
+ const parser = new DOMParser();
2046
+ const doc = parser.parseFromString(html, 'text/html');
2047
+ const newDeck = doc.getElementById('yumia-deck');
2048
+ const oldDeck = document.getElementById('yumia-deck');
2049
+ if (newDeck && oldDeck) {
2050
+ oldDeck.innerHTML = newDeck.innerHTML;
2051
+ window.deckController.init();
2052
+ if (currentHash) window.location.hash = currentHash;
2053
+ } else {
2054
+ window.location.reload();
2055
+ }
2056
+ })
2057
+ .catch(() => window.location.reload());
2058
+ }
2059
+ };
2060
+ })();
2061
+ </script>
2062
+ ` : "";
2063
+ const slidesHtml = presentation.slides.map((slide, idx) => this.renderSlide(slide, idx + 1, presentation.slides.length, theme)).join("\n");
2064
+ const html = `<!DOCTYPE html>
2065
+ <html lang="en">
2066
+ <head>
2067
+ <meta charset="UTF-8">
2068
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2069
+ <title>${this.escapeHtml(title)}</title>
2070
+ <link rel="preconnect" href="https://fonts.googleapis.com">
2071
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
2072
+ <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">
2073
+ <style>
2074
+ :root {
2075
+ --yumia-bg: ${theme.colors.background};
2076
+ --yumia-surface: ${theme.colors.surface};
2077
+ --yumia-text: ${theme.colors.text};
2078
+ --yumia-muted: ${theme.colors.muted || "#94a3b8"};
2079
+ --yumia-primary: ${theme.colors.primary};
2080
+ --yumia-secondary: ${theme.colors.secondary || theme.colors.primary};
2081
+ --yumia-accent: ${theme.colors.accent || theme.colors.primary};
2082
+ --yumia-border: ${theme.colors.border || "rgba(255,255,255,0.1)"};
2083
+ --yumia-success: ${theme.colors.success || "#10b981"};
2084
+ --yumia-warning: ${theme.colors.warning || "#f59e0b"};
2085
+ --yumia-danger: ${theme.colors.danger || "#ef4444"};
2086
+ --yumia-info: ${theme.colors.info || "#3b82f6"};
2087
+ --yumia-font-heading: ${theme.typography.headingFont};
2088
+ --yumia-font-body: ${theme.typography.bodyFont};
2089
+ --yumia-font-code: ${theme.typography.codeFont || "monospace"};
2090
+ --yumia-ratio: ${ratioAspect};
2091
+ }
2092
+
2093
+ * {
2094
+ box-sizing: border-box;
2095
+ margin: 0;
2096
+ padding: 0;
2097
+ }
2098
+
2099
+ body {
2100
+ background-color: #050508;
2101
+ color: var(--yumia-text);
2102
+ font-family: var(--yumia-font-body);
2103
+ overflow: hidden;
2104
+ width: 100vw;
2105
+ height: 100vh;
2106
+ display: flex;
2107
+ align-items: center;
2108
+ justify-content: center;
2109
+ user-select: none;
2110
+ }
2111
+
2112
+ #yumia-deck {
2113
+ position: relative;
2114
+ width: 100%;
2115
+ height: 100%;
2116
+ display: flex;
2117
+ align-items: center;
2118
+ justify-content: center;
2119
+ }
2120
+
2121
+ .yumia-slide-wrapper {
2122
+ position: relative;
2123
+ width: min(94vw, calc(94vh * (${is43 ? "4 / 3" : "16 / 9"})));
2124
+ height: min(calc(94vw / (${is43 ? "4 / 3" : "16 / 9"})), 94vh);
2125
+ aspect-ratio: var(--yumia-ratio);
2126
+ background-color: var(--yumia-bg);
2127
+ border-radius: 12px;
2128
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7), 0 0 0 1px var(--yumia-border);
2129
+ overflow: hidden;
2130
+ display: none;
2131
+ flex-direction: column;
2132
+ padding: 4.5% 5.5%;
2133
+ animation: fadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1);
2134
+ }
2135
+
2136
+ .yumia-slide-wrapper.active {
2137
+ display: flex;
2138
+ }
2139
+
2140
+ @keyframes fadeIn {
2141
+ from { opacity: 0; transform: scale(0.985); }
2142
+ to { opacity: 1; transform: scale(1); }
2143
+ }
2144
+
2145
+ /* Headings */
2146
+ h1, h2, h3, h4 {
2147
+ font-family: var(--yumia-font-heading);
2148
+ font-weight: 700;
2149
+ line-height: 1.15;
2150
+ margin-bottom: 0.7em;
2151
+ letter-spacing: -0.02em;
2152
+ }
2153
+
2154
+ h1 {
2155
+ font-size: clamp(2rem, 3.8vw, 3.4rem);
2156
+ color: var(--yumia-primary);
2157
+ }
2158
+
2159
+ h2 {
2160
+ font-size: clamp(1.6rem, 2.8vw, 2.5rem);
2161
+ color: var(--yumia-text);
2162
+ }
2163
+
2164
+ h3 {
2165
+ font-size: clamp(1.3rem, 2.2vw, 1.9rem);
2166
+ color: var(--yumia-text);
2167
+ }
2168
+
2169
+ h4 {
2170
+ font-size: clamp(1.1rem, 1.7vw, 1.4rem);
2171
+ color: var(--yumia-muted);
2172
+ }
2173
+
2174
+ /* Paragraphs */
2175
+ p {
2176
+ font-size: clamp(1rem, 1.4vw, 1.25rem);
2177
+ line-height: 1.6;
2178
+ color: var(--yumia-text);
2179
+ margin-bottom: 0.8em;
2180
+ }
2181
+
2182
+ p strong, li strong {
2183
+ color: var(--yumia-primary);
2184
+ font-weight: 700;
2185
+ }
2186
+
2187
+ p em, li em {
2188
+ font-style: italic;
2189
+ color: var(--yumia-secondary);
2190
+ }
2191
+
2192
+ p code, li code {
2193
+ font-family: var(--yumia-font-code);
2194
+ background: rgba(255, 255, 255, 0.08);
2195
+ padding: 0.15em 0.4em;
2196
+ border-radius: 4px;
2197
+ font-size: 0.9em;
2198
+ color: var(--yumia-accent);
2199
+ }
2200
+
2201
+ /* Lists */
2202
+ ul, ol {
2203
+ font-size: clamp(1rem, 1.35vw, 1.2rem);
2204
+ line-height: 1.65;
2205
+ margin-bottom: 1em;
2206
+ padding-left: 1.5em;
2207
+ }
2208
+
2209
+ li {
2210
+ margin-bottom: 0.5em;
2211
+ color: var(--yumia-text);
2212
+ }
2213
+
2214
+ li::marker {
2215
+ color: var(--yumia-primary);
2216
+ }
2217
+
2218
+ /* Columns */
2219
+ .yumia-columns {
2220
+ display: grid;
2221
+ gap: 1.5rem;
2222
+ width: 100%;
2223
+ margin: 0.8rem 0;
2224
+ align-items: stretch;
2225
+ }
2226
+
2227
+ .yumia-column {
2228
+ display: flex;
2229
+ flex-direction: column;
2230
+ gap: 0.8rem;
2231
+ }
2232
+
2233
+ /* Cards */
2234
+ .yumia-card {
2235
+ background: var(--yumia-surface);
2236
+ border: 1.5px solid var(--yumia-border);
2237
+ border-radius: 12px;
2238
+ padding: 1.25rem 1.5rem;
2239
+ display: flex;
2240
+ flex-direction: column;
2241
+ gap: 0.6rem;
2242
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
2243
+ }
2244
+
2245
+ .yumia-card[data-variant="primary"] {
2246
+ border-color: var(--yumia-primary);
2247
+ }
2248
+ .yumia-card[data-variant="primary"] .yumia-card-title {
2249
+ color: var(--yumia-primary);
2250
+ }
2251
+
2252
+ .yumia-card[data-variant="warning"] {
2253
+ border-color: var(--yumia-warning);
2254
+ }
2255
+ .yumia-card[data-variant="warning"] .yumia-card-title {
2256
+ color: var(--yumia-warning);
2257
+ }
2258
+
2259
+ .yumia-card[data-variant="success"] {
2260
+ border-color: var(--yumia-success);
2261
+ }
2262
+ .yumia-card[data-variant="success"] .yumia-card-title {
2263
+ color: var(--yumia-success);
2264
+ }
2265
+
2266
+ .yumia-card[data-variant="info"] {
2267
+ border-color: var(--yumia-info);
2268
+ }
2269
+ .yumia-card[data-variant="info"] .yumia-card-title {
2270
+ color: var(--yumia-info);
2271
+ }
2272
+
2273
+ .yumia-card-title {
2274
+ font-family: var(--yumia-font-heading);
2275
+ font-size: 1.25rem;
2276
+ font-weight: 700;
2277
+ color: var(--yumia-primary);
2278
+ margin-bottom: 0.3rem;
2279
+ }
2280
+
2281
+ /* Metrics */
2282
+ .yumia-metric {
2283
+ background: var(--yumia-surface);
2284
+ border: 1.5px solid var(--yumia-border);
2285
+ border-radius: 12px;
2286
+ padding: 1rem 1.2rem;
2287
+ display: flex;
2288
+ flex-direction: column;
2289
+ align-items: center;
2290
+ justify-content: center;
2291
+ text-align: center;
2292
+ gap: 0.25rem;
2293
+ }
2294
+
2295
+ .yumia-metric[data-variant="primary"] { border-color: var(--yumia-primary); }
2296
+ .yumia-metric[data-variant="primary"] .yumia-metric-value { color: var(--yumia-primary); }
2297
+ .yumia-metric[data-variant="success"] { border-color: var(--yumia-success); }
2298
+ .yumia-metric[data-variant="success"] .yumia-metric-value { color: var(--yumia-success); }
2299
+ .yumia-metric[data-variant="info"] { border-color: var(--yumia-info); }
2300
+ .yumia-metric[data-variant="info"] .yumia-metric-value { color: var(--yumia-info); }
2301
+ .yumia-metric[data-variant="warning"] { border-color: var(--yumia-warning); }
2302
+ .yumia-metric[data-variant="warning"] .yumia-metric-value { color: var(--yumia-warning); }
2303
+ .yumia-metric[data-variant="danger"] { border-color: var(--yumia-danger); }
2304
+ .yumia-metric[data-variant="danger"] .yumia-metric-value { color: var(--yumia-danger); }
2305
+
2306
+ .yumia-metric-label {
2307
+ font-size: 0.75rem;
2308
+ font-weight: 700;
2309
+ text-transform: uppercase;
2310
+ letter-spacing: 0.08em;
2311
+ color: var(--yumia-muted);
2312
+ }
2313
+
2314
+ .yumia-metric-value {
2315
+ font-family: var(--yumia-font-heading);
2316
+ font-size: clamp(1.8rem, 3.2vw, 2.6rem);
2317
+ font-weight: 800;
2318
+ line-height: 1.1;
2319
+ color: var(--yumia-primary);
2320
+ }
2321
+
2322
+ .yumia-metric-change {
2323
+ font-size: 0.85rem;
2324
+ font-weight: 600;
2325
+ }
2326
+ .yumia-metric-change.positive { color: var(--yumia-success); }
2327
+ .yumia-metric-change.negative { color: var(--yumia-danger); }
2328
+
2329
+ /* Tables */
2330
+ table {
2331
+ width: 100%;
2332
+ border-collapse: collapse;
2333
+ margin: 1rem 0;
2334
+ font-size: clamp(0.9rem, 1.15vw, 1.05rem);
2335
+ border: 1px solid var(--yumia-border);
2336
+ border-radius: 8px;
2337
+ overflow: hidden;
2338
+ }
2339
+
2340
+ th {
2341
+ background: var(--yumia-primary);
2342
+ color: #ffffff;
2343
+ font-weight: 700;
2344
+ padding: 0.75rem 1rem;
2345
+ text-align: left;
2346
+ }
2347
+
2348
+ td {
2349
+ padding: 0.7rem 1rem;
2350
+ border-top: 1px solid var(--yumia-border);
2351
+ color: var(--yumia-text);
2352
+ background: var(--yumia-surface);
2353
+ }
2354
+
2355
+ tr:nth-child(even) td {
2356
+ background: rgba(255, 255, 255, 0.03);
2357
+ }
2358
+
2359
+ /* Code Blocks */
2360
+ pre {
2361
+ background: #0a0a10;
2362
+ border: 1px solid var(--yumia-border);
2363
+ border-radius: 8px;
2364
+ padding: 1rem 1.25rem;
2365
+ overflow-x: auto;
2366
+ margin: 0.8rem 0;
2367
+ }
2368
+
2369
+ pre code {
2370
+ font-family: var(--yumia-font-code);
2371
+ font-size: 0.95rem;
2372
+ color: #00F0FF;
2373
+ background: none;
2374
+ padding: 0;
2375
+ }
2376
+
2377
+ /* Blockquotes */
2378
+ blockquote {
2379
+ border-left: 4px solid var(--yumia-accent);
2380
+ padding: 0.6rem 1.2rem;
2381
+ margin: 1rem 0;
2382
+ font-style: italic;
2383
+ color: var(--yumia-muted);
2384
+ font-size: 1.1rem;
2385
+ background: rgba(255, 255, 255, 0.02);
2386
+ border-radius: 0 8px 8px 0;
2387
+ }
2388
+
2389
+ /* Controls Overlay */
2390
+ .yumia-controls {
2391
+ position: fixed;
2392
+ bottom: 20px;
2393
+ right: 24px;
2394
+ display: flex;
2395
+ align-items: center;
2396
+ gap: 10px;
2397
+ background: rgba(15, 23, 42, 0.88);
2398
+ backdrop-filter: blur(12px);
2399
+ padding: 6px 14px;
2400
+ border-radius: 30px;
2401
+ border: 1px solid rgba(255, 255, 255, 0.15);
2402
+ z-index: 1000;
2403
+ font-size: 13px;
2404
+ color: #e2e8f0;
2405
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
2406
+ }
2407
+
2408
+ .yumia-btn {
2409
+ background: none;
2410
+ border: none;
2411
+ color: #f8fafc;
2412
+ cursor: pointer;
2413
+ padding: 5px 9px;
2414
+ border-radius: 6px;
2415
+ font-size: 14px;
2416
+ display: flex;
2417
+ align-items: center;
2418
+ justify-content: center;
2419
+ transition: background 0.15s ease, transform 0.1s ease;
2420
+ }
2421
+
2422
+ .yumia-btn:hover {
2423
+ background: rgba(255, 255, 255, 0.15);
2424
+ transform: translateY(-1px);
2425
+ }
2426
+
2427
+ .yumia-progress-bar {
2428
+ position: absolute;
2429
+ bottom: 0;
2430
+ left: 0;
2431
+ height: 4px;
2432
+ background: var(--yumia-primary);
2433
+ transition: width 0.25s ease;
2434
+ }
2435
+
2436
+ /* Notes Drawer */
2437
+ .yumia-notes-drawer {
2438
+ position: fixed;
2439
+ bottom: 0;
2440
+ left: 0;
2441
+ right: 0;
2442
+ max-height: 240px;
2443
+ background: rgba(10, 10, 18, 0.96);
2444
+ backdrop-filter: blur(16px);
2445
+ border-top: 2px solid var(--yumia-primary);
2446
+ padding: 18px 28px;
2447
+ overflow-y: auto;
2448
+ display: none;
2449
+ z-index: 2000;
2450
+ font-size: 15px;
2451
+ line-height: 1.6;
2452
+ color: #cbd5e1;
2453
+ box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.5);
2454
+ }
2455
+
2456
+ .yumia-notes-drawer.open {
2457
+ display: block;
2458
+ }
2459
+
2460
+ /* Slide Overview Modal */
2461
+ .yumia-overview-modal {
2462
+ position: fixed;
2463
+ top: 0;
2464
+ left: 0;
2465
+ width: 100vw;
2466
+ height: 100vh;
2467
+ background: rgba(5, 5, 10, 0.94);
2468
+ backdrop-filter: blur(20px);
2469
+ z-index: 3000;
2470
+ display: none;
2471
+ flex-direction: column;
2472
+ padding: 40px;
2473
+ overflow-y: auto;
2474
+ }
2475
+
2476
+ .yumia-overview-modal.open {
2477
+ display: flex;
2478
+ }
2479
+
2480
+ .yumia-overview-header {
2481
+ display: flex;
2482
+ justify-content: space-between;
2483
+ align-items: center;
2484
+ margin-bottom: 30px;
2485
+ color: #fff;
2486
+ }
2487
+
2488
+ .yumia-overview-grid {
2489
+ display: grid;
2490
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2491
+ gap: 24px;
2492
+ width: 100%;
2493
+ }
2494
+
2495
+ .yumia-overview-card {
2496
+ aspect-ratio: var(--yumia-ratio);
2497
+ background-color: var(--yumia-bg);
2498
+ border: 2px solid rgba(255, 255, 255, 0.1);
2499
+ border-radius: 8px;
2500
+ padding: 16px;
2501
+ cursor: pointer;
2502
+ position: relative;
2503
+ overflow: hidden;
2504
+ transform-origin: center;
2505
+ transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
2506
+ display: flex;
2507
+ flex-direction: column;
2508
+ }
2509
+
2510
+ .yumia-overview-card:hover {
2511
+ transform: scale(1.04);
2512
+ border-color: var(--yumia-primary);
2513
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.8), 0 0 15px var(--yumia-primary);
2514
+ }
2515
+
2516
+ .yumia-overview-card.current {
2517
+ border-color: var(--yumia-accent);
2518
+ box-shadow: 0 0 0 2px var(--yumia-accent);
2519
+ }
2520
+
2521
+ .yumia-overview-badge {
2522
+ position: absolute;
2523
+ top: 8px;
2524
+ right: 8px;
2525
+ background: rgba(0, 0, 0, 0.6);
2526
+ color: #fff;
2527
+ font-size: 11px;
2528
+ font-weight: 700;
2529
+ padding: 2px 8px;
2530
+ border-radius: 10px;
2531
+ }
2532
+
2533
+ /* Speaker View Layout (When loaded in speaker mode ?speaker=true) */
2534
+ .speaker-layout {
2535
+ display: grid;
2536
+ grid-template-rows: 60px 1fr;
2537
+ width: 100vw;
2538
+ height: 100vh;
2539
+ background: #09090f;
2540
+ color: #f8fafc;
2541
+ overflow: hidden;
2542
+ }
2543
+
2544
+ .speaker-topbar {
2545
+ display: flex;
2546
+ justify-content: space-between;
2547
+ align-items: center;
2548
+ padding: 0 24px;
2549
+ background: #11111b;
2550
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
2551
+ }
2552
+
2553
+ .speaker-timer-group {
2554
+ display: flex;
2555
+ align-items: center;
2556
+ gap: 16px;
2557
+ font-family: var(--yumia-font-code);
2558
+ font-size: 18px;
2559
+ }
2560
+
2561
+ .speaker-timer-btn {
2562
+ background: rgba(255, 255, 255, 0.1);
2563
+ color: #fff;
2564
+ border: none;
2565
+ padding: 4px 10px;
2566
+ border-radius: 4px;
2567
+ cursor: pointer;
2568
+ font-size: 12px;
2569
+ }
2570
+
2571
+ .speaker-timer-btn:hover {
2572
+ background: var(--yumia-primary);
2573
+ }
2574
+
2575
+ .speaker-main-grid {
2576
+ display: grid;
2577
+ grid-template-columns: 55% 45%;
2578
+ gap: 20px;
2579
+ padding: 20px;
2580
+ height: calc(100vh - 60px);
2581
+ box-sizing: border-box;
2582
+ }
2583
+
2584
+ .speaker-pane {
2585
+ background: #151522;
2586
+ border-radius: 10px;
2587
+ border: 1px solid rgba(255, 255, 255, 0.08);
2588
+ padding: 16px;
2589
+ display: flex;
2590
+ flex-direction: column;
2591
+ overflow: hidden;
2592
+ }
2593
+
2594
+ .speaker-pane-title {
2595
+ font-size: 12px;
2596
+ font-weight: 700;
2597
+ text-transform: uppercase;
2598
+ letter-spacing: 0.05em;
2599
+ color: var(--yumia-muted);
2600
+ margin-bottom: 12px;
2601
+ display: flex;
2602
+ justify-content: space-between;
2603
+ }
2604
+
2605
+ .speaker-preview-box {
2606
+ flex: 1;
2607
+ position: relative;
2608
+ display: flex;
2609
+ align-items: center;
2610
+ justify-content: center;
2611
+ background: #000;
2612
+ border-radius: 8px;
2613
+ overflow: hidden;
2614
+ }
2615
+
2616
+ .speaker-preview-box .yumia-slide-wrapper {
2617
+ display: flex !important;
2618
+ transform: scale(0.6);
2619
+ width: 160% !important;
2620
+ height: 160% !important;
2621
+ }
2622
+
2623
+ .speaker-notes-box {
2624
+ flex: 1;
2625
+ background: #11111a;
2626
+ border-radius: 8px;
2627
+ padding: 16px;
2628
+ font-size: 16px;
2629
+ line-height: 1.7;
2630
+ color: #e2e8f0;
2631
+ overflow-y: auto;
2632
+ }
2633
+ </style>
2634
+ </head>
2635
+ <body>
2636
+ <div id="deck-container">
2637
+ <div id="yumia-deck">
2638
+ ${slidesHtml}
2639
+ </div>
2640
+
2641
+ <div class="yumia-controls" id="deck-controls">
2642
+ <button class="yumia-btn" id="btn-prev" title="Previous Slide (\u2190)">\u25C0</button>
2643
+ <span id="slide-indicator">1 / ${presentation.slides.length}</span>
2644
+ <button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
2645
+ <button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
2646
+ <button class="yumia-btn" id="btn-speaker" title="Speaker View (S)">\u{1F3A4}</button>
2647
+ <button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
2648
+ <button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
2649
+ </div>
2650
+
2651
+ <div id="notes-drawer" class="yumia-notes-drawer"></div>
2652
+
2653
+ <div id="overview-modal" class="yumia-overview-modal">
2654
+ <div class="yumia-overview-header">
2655
+ <h2>Slide Overview</h2>
2656
+ <button class="yumia-btn" id="btn-close-overview" style="font-size: 18px;">\u2715 Close (ESC)</button>
2657
+ </div>
2658
+ <div class="yumia-overview-grid" id="overview-grid"></div>
2659
+ </div>
2660
+ </div>
2661
+
2662
+ <script>
2663
+ (function() {
2664
+ const isSpeakerMode = new URLSearchParams(window.location.search).get('speaker') === 'true';
2665
+ const slides = Array.from(document.querySelectorAll('.yumia-slide-wrapper'));
2666
+ const indicator = document.getElementById('slide-indicator');
2667
+ const notesDrawer = document.getElementById('notes-drawer');
2668
+ const overviewModal = document.getElementById('overview-modal');
2669
+ const overviewGrid = document.getElementById('overview-grid');
2670
+ let currentIdx = 0;
2671
+
2672
+ // Real-time synchronization channel
2673
+ let syncChannel = null;
2674
+ try {
2675
+ syncChannel = new BroadcastChannel('yumia_presentation_sync');
2676
+ syncChannel.onmessage = function(e) {
2677
+ if (e.data && typeof e.data.index === 'number') {
2678
+ goToSlide(e.data.index, false);
2679
+ }
2680
+ };
2681
+ } catch (err) {
2682
+ // Fallback if BroadcastChannel unavailable
2683
+ }
2684
+
2685
+ function buildOverviewGrid() {
2686
+ if (!overviewGrid) return;
2687
+ overviewGrid.innerHTML = '';
2688
+ slides.forEach((slide, idx) => {
2689
+ const card = document.createElement('div');
2690
+ card.className = 'yumia-overview-card' + (idx === currentIdx ? ' current' : '');
2691
+ const badge = document.createElement('span');
2692
+ badge.className = 'yumia-overview-badge';
2693
+ badge.textContent = (idx + 1);
2694
+
2695
+ const heading = slide.querySelector('h1, h2, h3');
2696
+ const titleText = heading ? heading.textContent : 'Slide ' + (idx + 1);
2697
+
2698
+ const titleDiv = document.createElement('div');
2699
+ titleDiv.style.fontWeight = '600';
2700
+ titleDiv.style.fontSize = '14px';
2701
+ titleDiv.style.color = 'var(--yumia-primary)';
2702
+ titleDiv.textContent = titleText;
2703
+
2704
+ card.appendChild(badge);
2705
+ card.appendChild(titleDiv);
2706
+
2707
+ card.addEventListener('click', () => {
2708
+ goToSlide(idx);
2709
+ toggleOverview(false);
2710
+ });
2711
+ overviewGrid.appendChild(card);
2712
+ });
2713
+ }
2714
+
2715
+ function toggleOverview(force) {
2716
+ if (!overviewModal) return;
2717
+ const isOpen = force !== undefined ? force : !overviewModal.classList.contains('open');
2718
+ if (isOpen) {
2719
+ buildOverviewGrid();
2720
+ overviewModal.classList.add('open');
2721
+ } else {
2722
+ overviewModal.classList.remove('open');
2723
+ }
2724
+ }
2725
+
2726
+ function goToSlide(newIdx, broadcast = true) {
2727
+ if (newIdx < 0 || newIdx >= slides.length) return;
2728
+ slides[currentIdx]?.classList.remove('active');
2729
+ currentIdx = newIdx;
2730
+ slides[currentIdx]?.classList.add('active');
2731
+
2732
+ if (indicator) {
2733
+ indicator.textContent = (currentIdx + 1) + ' / ' + slides.length;
2734
+ }
2735
+
2736
+ const currentSlide = slides[currentIdx];
2737
+ const notes = currentSlide ? currentSlide.getAttribute('data-notes') : '';
2738
+ if (notesDrawer) {
2739
+ notesDrawer.innerHTML = notes ? '<strong>Speaker Notes:</strong><br>' + notes : '<em>No speaker notes for this slide.</em>';
2740
+ }
2741
+
2742
+ window.location.hash = '#' + (currentIdx + 1);
2743
+
2744
+ if (broadcast && syncChannel) {
2745
+ syncChannel.postMessage({ index: currentIdx });
2746
+ }
2747
+ }
2748
+
2749
+ function openSpeakerWindow() {
2750
+ const url = new URL(window.location.href);
2751
+ url.searchParams.set('speaker', 'true');
2752
+ window.open(url.toString(), 'yumia_speaker_' + Date.now(), 'width=1180,height=760,menubar=no,toolbar=no');
2753
+ }
2754
+
2755
+ function initSpeakerLayout() {
2756
+ document.body.innerHTML = \`
2757
+ <div class="speaker-layout">
2758
+ <div class="speaker-topbar">
2759
+ <div style="font-weight:700; color:var(--yumia-primary); display:flex; align-items:center; gap:8px;">
2760
+ <span>\u{1F399}\uFE0F YumiaMD Speaker View</span>
2761
+ </div>
2762
+ <div class="speaker-timer-group">
2763
+ <span id="speaker-clock">00:00:00</span>
2764
+ <span style="color:var(--yumia-muted);">|</span>
2765
+ <span id="speaker-timer" style="color:var(--yumia-accent);">00:00</span>
2766
+ <button class="speaker-timer-btn" id="btn-timer-toggle">Pause</button>
2767
+ <button class="speaker-timer-btn" id="btn-timer-reset">Reset</button>
2768
+ </div>
2769
+ <div>
2770
+ <span id="speaker-slide-num" style="font-weight:600;">1 / \${slides.length}</span>
2771
+ </div>
2772
+ </div>
2773
+ <div class="speaker-main-grid">
2774
+ <div class="speaker-pane">
2775
+ <div class="speaker-pane-title">Current Slide</div>
2776
+ <div class="speaker-preview-box" id="speaker-current-box"></div>
2777
+ </div>
2778
+ <div style="display:grid; grid-template-rows: 45% 55%; gap:20px;">
2779
+ <div class="speaker-pane">
2780
+ <div class="speaker-pane-title">Next Slide Preview</div>
2781
+ <div class="speaker-preview-box" id="speaker-next-box"></div>
2782
+ </div>
2783
+ <div class="speaker-pane">
2784
+ <div class="speaker-pane-title">Speaker Notes</div>
2785
+ <div class="speaker-notes-box" id="speaker-notes-content"></div>
2786
+ </div>
2787
+ </div>
2788
+ </div>
2789
+ </div>
2790
+ \`;
2791
+
2792
+ // Live Clock
2793
+ setInterval(() => {
2794
+ const now = new Date();
2795
+ const clockEl = document.getElementById('speaker-clock');
2796
+ if (clockEl) clockEl.textContent = now.toLocaleTimeString();
2797
+ }, 1000);
2798
+
2799
+ // Elapsed Timer
2800
+ let elapsedSeconds = 0;
2801
+ let timerRunning = true;
2802
+ let timerInterval = setInterval(() => {
2803
+ if (!timerRunning) return;
2804
+ elapsedSeconds++;
2805
+ const mins = String(Math.floor(elapsedSeconds / 60)).padStart(2, '0');
2806
+ const secs = String(elapsedSeconds % 60).padStart(2, '0');
2807
+ const timerEl = document.getElementById('speaker-timer');
2808
+ if (timerEl) timerEl.textContent = \`\${mins}:\${secs}\`;
2809
+ }, 1000);
2810
+
2811
+ document.getElementById('btn-timer-toggle')?.addEventListener('click', (e) => {
2812
+ timerRunning = !timerRunning;
2813
+ e.target.textContent = timerRunning ? 'Pause' : 'Start';
2814
+ });
2815
+
2816
+ document.getElementById('btn-timer-reset')?.addEventListener('click', () => {
2817
+ elapsedSeconds = 0;
2818
+ const timerEl = document.getElementById('speaker-timer');
2819
+ if (timerEl) timerEl.textContent = '00:00';
2820
+ });
2821
+
2822
+ function updateSpeakerView(idx) {
2823
+ currentIdx = idx;
2824
+ const numEl = document.getElementById('speaker-slide-num');
2825
+ if (numEl) numEl.textContent = (currentIdx + 1) + ' / ' + slides.length;
2826
+
2827
+ // Render current slide
2828
+ const curBox = document.getElementById('speaker-current-box');
2829
+ if (curBox && slides[currentIdx]) {
2830
+ curBox.innerHTML = slides[currentIdx].outerHTML;
2831
+ curBox.firstElementChild?.classList.add('active');
2832
+ }
2833
+
2834
+ // Render next slide
2835
+ const nextBox = document.getElementById('speaker-next-box');
2836
+ if (nextBox) {
2837
+ if (slides[currentIdx + 1]) {
2838
+ nextBox.innerHTML = slides[currentIdx + 1].outerHTML;
2839
+ nextBox.firstElementChild?.classList.add('active');
2840
+ } else {
2841
+ nextBox.innerHTML = '<div style="color:var(--yumia-muted); font-size:14px;">End of presentation</div>';
2842
+ }
2843
+ }
2844
+
2845
+ // Render notes
2846
+ const notesBox = document.getElementById('speaker-notes-content');
2847
+ if (notesBox && slides[currentIdx]) {
2848
+ const rawNotes = slides[currentIdx].getAttribute('data-notes');
2849
+ notesBox.innerHTML = rawNotes ? rawNotes : '<em style="color:var(--yumia-muted);">No notes provided for this slide.</em>';
2850
+ }
2851
+ }
2852
+
2853
+ window.addEventListener('keydown', (e) => {
2854
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
2855
+ e.preventDefault();
2856
+ if (currentIdx < slides.length - 1) {
2857
+ goToSlide(currentIdx + 1);
2858
+ updateSpeakerView(currentIdx);
2859
+ }
2860
+ } else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp') {
2861
+ e.preventDefault();
2862
+ if (currentIdx > 0) {
2863
+ goToSlide(currentIdx - 1);
2864
+ updateSpeakerView(currentIdx);
2865
+ }
2866
+ }
2867
+ });
2868
+
2869
+ if (syncChannel) {
2870
+ syncChannel.onmessage = (e) => {
2871
+ if (e.data && typeof e.data.index === 'number') {
2872
+ updateSpeakerView(e.data.index);
2873
+ }
2874
+ };
2875
+ }
2876
+
2877
+ const initialHash = parseInt(window.location.hash.replace('#', ''), 10) - 1;
2878
+ updateSpeakerView(isNaN(initialHash) ? 0 : initialHash);
2879
+ }
2880
+
2881
+ function init() {
2882
+ if (isSpeakerMode) {
2883
+ initSpeakerLayout();
2884
+ return;
2885
+ }
2886
+
2887
+ const hash = window.location.hash.replace('#', '');
2888
+ const initialIdx = parseInt(hash, 10) - 1;
2889
+ goToSlide(isNaN(initialIdx) ? 0 : initialIdx, false);
2890
+ }
2891
+
2892
+ window.deckController = {
2893
+ init: init,
2894
+ next: function() { goToSlide(currentIdx + 1); },
2895
+ prev: function() { goToSlide(currentIdx - 1); },
2896
+ toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
2897
+ toggleOverview: function() { toggleOverview(); },
2898
+ openSpeaker: openSpeakerWindow,
2899
+ toggleFs: function() {
2900
+ if (!document.fullscreenElement) {
2901
+ document.documentElement.requestFullscreen().catch(() => {});
2902
+ } else {
2903
+ document.exitFullscreen().catch(() => {});
2904
+ }
2905
+ }
2906
+ };
2907
+
2908
+ document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
2909
+ document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
2910
+ document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
2911
+ document.getElementById('btn-overview')?.addEventListener('click', window.deckController.toggleOverview);
2912
+ document.getElementById('btn-close-overview')?.addEventListener('click', () => toggleOverview(false));
2913
+ document.getElementById('btn-speaker')?.addEventListener('click', window.deckController.openSpeaker);
2914
+ document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
2915
+
2916
+ window.addEventListener('keydown', function(e) {
2917
+ if (overviewModal && overviewModal.classList.contains('open')) {
2918
+ if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
2919
+ toggleOverview(false);
2920
+ }
2921
+ return;
2922
+ }
2923
+
2924
+ if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key.toLowerCase() === 'l') {
2925
+ e.preventDefault();
2926
+ window.deckController.next();
2927
+ } else if (e.key === 'ArrowLeft' || e.key === 'Backspace' || e.key === 'PageUp' || e.key.toLowerCase() === 'h') {
2928
+ e.preventDefault();
2929
+ window.deckController.prev();
2930
+ } else if (e.key.toLowerCase() === 'f') {
2931
+ window.deckController.toggleFs();
2932
+ } else if (e.key.toLowerCase() === 's') {
2933
+ window.deckController.openSpeaker();
2934
+ } else if (e.key.toLowerCase() === 'n') {
2935
+ window.deckController.toggleNotes();
2936
+ } else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
2937
+ window.deckController.toggleOverview();
2938
+ }
2939
+ });
2940
+
2941
+ // Touch swipe support
2942
+ let touchStartX = 0;
2943
+ window.addEventListener('touchstart', function(e) {
2944
+ touchStartX = e.changedTouches[0]?.screenX || 0;
2945
+ });
2946
+ window.addEventListener('touchend', function(e) {
2947
+ const touchEndX = e.changedTouches[0]?.screenX || 0;
2948
+ if (touchStartX - touchEndX > 50) window.deckController.next();
2949
+ if (touchEndX - touchStartX > 50) window.deckController.prev();
2950
+ });
2951
+
2952
+ init();
2953
+ })();
2954
+ </script>
2955
+ ${liveReloadScript}
2956
+ </body>
2957
+ </html>`;
2958
+ return {
2959
+ format: "html",
2960
+ html,
2961
+ slideCount: presentation.slides.length
2962
+ };
2963
+ }
2964
+ renderSlide(slide, slideNum, totalSlides, theme) {
2965
+ const activeClass = slideNum === 1 ? "active" : "";
2966
+ const notesAttr = slide.notes ? this.escapeHtml(slide.notes.replace(/\n/g, "<br>")) : "";
2967
+ const progressPercent = Math.round(slideNum / totalSlides * 100);
2968
+ const elementsHtml = slide.elements.map((el) => this.renderElement(el, theme)).join("\n");
2969
+ return `
2970
+ <div class="yumia-slide-wrapper ${activeClass}" id="slide-${slideNum}" data-notes="${notesAttr}">
2971
+ ${elementsHtml}
2972
+ <div class="yumia-progress-bar" style="width: ${progressPercent}%;"></div>
2973
+ </div>`;
2974
+ }
2975
+ renderElement(element, theme) {
2976
+ switch (element.type) {
2977
+ case "heading": {
2978
+ const h = element;
2979
+ const tag = `h${Math.min(4, Math.max(1, h.level))}`;
2980
+ return `<${tag}>${this.formatInline(h.text)}</${tag}>`;
2981
+ }
2982
+ case "paragraph": {
2983
+ const p = element;
2984
+ return `<p>${this.formatInline(p.text)}</p>`;
2985
+ }
2986
+ case "list": {
2987
+ const l = element;
2988
+ const tag = l.ordered ? "ol" : "ul";
2989
+ const items = l.items.map((i) => `<li>${this.formatInline(i.text)}</li>`).join("\n");
2990
+ return `<${tag}>${items}</${tag}>`;
2991
+ }
2992
+ case "code": {
2993
+ const c = element;
2994
+ const langClass = c.language ? `class="language-${c.language}"` : "";
2995
+ return `<pre><code ${langClass}>${this.escapeHtml(c.code)}</code></pre>`;
2996
+ }
2997
+ case "quote": {
2998
+ const q = element;
2999
+ const author = q.author ? `<br><small>\u2014 ${this.escapeHtml(q.author)}</small>` : "";
3000
+ return `<blockquote>\u201C${this.formatInline(q.text)}\u201D${author}</blockquote>`;
3001
+ }
3002
+ case "table": {
3003
+ const t = element;
3004
+ let html = "<table>";
3005
+ if (t.headers && t.headers.length > 0) {
3006
+ html += "<thead><tr>";
3007
+ html += t.headers.map((h) => `<th>${this.formatInline(h.replace(/\*\*/g, ""))}</th>`).join("");
3008
+ html += "</tr></thead>";
3009
+ }
3010
+ if (t.rows) {
3011
+ html += "<tbody>";
3012
+ for (const row of t.rows) {
3013
+ html += "<tr>";
3014
+ html += row.map((c) => `<td>${this.formatInline(c.replace(/\*\*/g, ""))}</td>`).join("");
3015
+ html += "</tr>";
3016
+ }
3017
+ html += "</tbody>";
3018
+ }
3019
+ html += "</table>";
3020
+ return html;
3021
+ }
3022
+ case "image": {
3023
+ const img = element;
3024
+ const alt = img.alt ? `alt="${this.escapeHtml(img.alt)}"` : "";
3025
+ return `<img src="${this.escapeHtml(img.src)}" ${alt} style="max-width: 100%; border-radius: 8px;">`;
3026
+ }
3027
+ case "metric": {
3028
+ const m = element;
3029
+ const variant = m.variant || "primary";
3030
+ const displayVal = m.unit ? `${m.value} ${m.unit}` : m.value;
3031
+ const changeHtml = m.change ? `<span class="yumia-metric-change ${m.change.startsWith("+") ? "positive" : "negative"}">${this.escapeHtml(m.change)}</span>` : "";
3032
+ return `
3033
+ <div class="yumia-metric" data-variant="${variant}">
3034
+ <span class="yumia-metric-label">${this.escapeHtml(m.label)}</span>
3035
+ <span class="yumia-metric-value">${this.escapeHtml(displayVal)}</span>
3036
+ ${changeHtml}
3037
+ </div>`;
3038
+ }
3039
+ case "card": {
3040
+ const card = element;
3041
+ const variant = card.variant || "default";
3042
+ const titleHtml = card.title ? `<div class="yumia-card-title">${this.escapeHtml(card.title)}</div>` : "";
3043
+ const innerHtml = card.elements ? card.elements.map((child) => this.renderElement(child, theme)).join("\n") : "";
3044
+ return `
3045
+ <div class="yumia-card" data-variant="${variant}">
3046
+ ${titleHtml}
3047
+ ${innerHtml}
3048
+ </div>`;
3049
+ }
3050
+ case "columns": {
3051
+ const cols = element;
3052
+ const colCount = cols.columns.length;
3053
+ const ratioTemplate = cols.ratios ? cols.ratios.split(":").map((r) => `${r}fr`).join(" ") : `repeat(${colCount}, 1fr)`;
3054
+ const colsHtml = cols.columns.map((col) => {
3055
+ const inner = col.elements.map((child) => this.renderElement(child, theme)).join("\n");
3056
+ return `<div class="yumia-column">${inner}</div>`;
3057
+ }).join("\n");
3058
+ return `<div class="yumia-columns" style="grid-template-columns: ${ratioTemplate};">${colsHtml}</div>`;
3059
+ }
3060
+ default:
3061
+ return "";
3062
+ }
3063
+ }
3064
+ formatInline(text) {
3065
+ const escaped = this.escapeHtml(text);
3066
+ return escaped.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/`(.*?)`/g, "<code>$1</code>");
3067
+ }
3068
+ escapeHtml(str) {
3069
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
3070
+ }
3071
+ };
3072
+
3073
+ // ../renderer-pdf/dist/renderer.js
3074
+ import PDFDocument from "pdfkit";
3075
+ var PdfRenderer = class {
3076
+ name = "PdfRenderer";
3077
+ targetFormat = "pdf";
3078
+ async render(presentation, context = {}) {
3079
+ const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
3080
+ const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
3081
+ const theme = context.theme || resolvedTheme || defaultTheme;
3082
+ const is43 = presentation.metadata.aspectRatio === "4:3";
3083
+ const pageWidth = is43 ? 720 : 960;
3084
+ const pageHeight = 540;
3085
+ return new Promise((resolvePromise, rejectPromise) => {
3086
+ try {
3087
+ const doc = new PDFDocument({
3088
+ autoFirstPage: false,
3089
+ margin: 0,
3090
+ info: {
3091
+ Title: presentation.metadata.title || "YumiaMD Presentation",
3092
+ Author: presentation.metadata.author || "YumiaMD",
3093
+ Creator: "YumiaMD Vector PDF Compiler"
3094
+ }
3095
+ });
3096
+ const chunks = [];
3097
+ doc.on("data", (chunk) => chunks.push(chunk));
3098
+ doc.on("end", () => {
3099
+ const buffer = Buffer.concat(chunks);
3100
+ resolvePromise({
3101
+ format: "pdf",
3102
+ data: new Uint8Array(buffer),
3103
+ pageCount: presentation.slides.length,
3104
+ slideCount: presentation.slides.length
3105
+ });
3106
+ });
3107
+ doc.on("error", (err) => rejectPromise(err));
3108
+ const totalSlides = presentation.slides.length;
3109
+ for (let i = 0; i < totalSlides; i++) {
3110
+ const slide = presentation.slides[i];
3111
+ this.renderSlide(doc, slide, i + 1, totalSlides, pageWidth, pageHeight, theme);
3112
+ }
3113
+ doc.end();
3114
+ } catch (err) {
3115
+ rejectPromise(err);
3116
+ }
3117
+ });
3118
+ }
3119
+ renderSlide(doc, slide, slideNum, totalSlides, pageWidth, pageHeight, theme) {
3120
+ doc.addPage({
3121
+ size: [pageWidth, pageHeight],
3122
+ margins: { top: 0, bottom: 0, left: 0, right: 0 }
3123
+ });
3124
+ doc.rect(0, 0, pageWidth, pageHeight).fill(theme.colors.background);
3125
+ const padX = pageWidth * 0.06;
3126
+ const padY = pageHeight * 0.08;
3127
+ const contentWidth = pageWidth - padX * 2;
3128
+ let cursorY = padY;
3129
+ for (const element of slide.elements) {
3130
+ cursorY = this.renderElement(doc, element, padX, cursorY, contentWidth, theme);
3131
+ cursorY += 12;
3132
+ }
3133
+ const progressWidth = slideNum / totalSlides * pageWidth;
3134
+ doc.rect(0, pageHeight - 4, progressWidth, 4).fill(theme.colors.primary);
3135
+ doc.font("Helvetica").fontSize(10).fillColor(theme.colors.muted || "#888888").text(`${slideNum} / ${totalSlides}`, pageWidth - padX - 60, pageHeight - 24, {
3136
+ width: 60,
3137
+ align: "right"
3138
+ });
3139
+ }
3140
+ renderElement(doc, element, x, y, width, theme) {
3141
+ switch (element.type) {
3142
+ case "heading": {
3143
+ const h = element;
3144
+ const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
3145
+ const color = h.level === 1 ? theme.colors.primary : theme.colors.text;
3146
+ doc.font("Helvetica-Bold").fontSize(fontSize).fillColor(color);
3147
+ doc.text(this.stripFormatting(h.text), x, y, { width, lineGap: 4 });
3148
+ const height = doc.heightOfString(this.stripFormatting(h.text), { width });
3149
+ return y + height;
3150
+ }
3151
+ case "paragraph": {
3152
+ const p = element;
3153
+ doc.font("Helvetica").fontSize(14).fillColor(theme.colors.text);
3154
+ doc.text(this.stripFormatting(p.text), x, y, { width, lineGap: 4 });
3155
+ const height = doc.heightOfString(this.stripFormatting(p.text), { width });
3156
+ return y + height;
3157
+ }
3158
+ case "list": {
3159
+ const l = element;
3160
+ let currentY = y;
3161
+ const itemGap = 6;
3162
+ l.items.forEach((item, idx) => {
3163
+ const bullet = l.ordered ? `${idx + 1}.` : "\u2022";
3164
+ doc.font("Helvetica-Bold").fontSize(13).fillColor(theme.colors.primary).text(bullet, x, currentY, { width: 18 });
3165
+ doc.font("Helvetica").fontSize(13).fillColor(theme.colors.text).text(this.stripFormatting(item.text), x + 20, currentY, {
3166
+ width: width - 20,
3167
+ lineGap: 3
3168
+ });
3169
+ const itemHeight = Math.max(18, doc.heightOfString(this.stripFormatting(item.text), { width: width - 20 }));
3170
+ currentY += itemHeight + itemGap;
3171
+ });
3172
+ return currentY;
3173
+ }
3174
+ case "quote": {
3175
+ const q = element;
3176
+ const quoteText = `\u201C${this.stripFormatting(q.text)}\u201D`;
3177
+ const authorText = q.author ? `\u2014 ${this.stripFormatting(q.author)}` : "";
3178
+ doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.muted || "#aaaaaa");
3179
+ const textHeight = doc.heightOfString(quoteText, { width: width - 24 });
3180
+ const authorHeight = authorText ? 18 : 0;
3181
+ const totalHeight = textHeight + authorHeight + 16;
3182
+ doc.roundedRect(x, y, width, totalHeight, 6).fill(theme.colors.surface || "rgba(255,255,255,0.05)");
3183
+ doc.rect(x, y, 4, totalHeight).fill(theme.colors.accent || theme.colors.primary);
3184
+ doc.font("Helvetica-Oblique").fontSize(13).fillColor(theme.colors.text).text(quoteText, x + 16, y + 8, { width: width - 28 });
3185
+ if (authorText) {
3186
+ doc.font("Helvetica").fontSize(11).fillColor(theme.colors.muted || "#888888").text(authorText, x + 16, y + 8 + textHeight + 4, { width: width - 28 });
3187
+ }
3188
+ return y + totalHeight;
3189
+ }
3190
+ case "code": {
3191
+ const c = element;
3192
+ const codeText = c.code;
3193
+ doc.font("Courier").fontSize(11);
3194
+ const textHeight = doc.heightOfString(codeText, { width: width - 24 });
3195
+ const boxHeight = textHeight + 20;
3196
+ doc.roundedRect(x, y, width, boxHeight, 6).fill("#0a0a10").strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
3197
+ doc.font("Courier").fontSize(11).fillColor(theme.colors.accent || "#38bdf8").text(codeText, x + 12, y + 10, { width: width - 24 });
3198
+ return y + boxHeight;
3199
+ }
3200
+ case "card": {
3201
+ const card = element;
3202
+ const variantColor = this.getVariantColor(card.variant, theme);
3203
+ const cardPad = 14;
3204
+ let cardCursorY = y + cardPad;
3205
+ if (card.title) {
3206
+ doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, cardCursorY, {
3207
+ width: width - cardPad * 2
3208
+ });
3209
+ cardCursorY += 22;
3210
+ }
3211
+ if (card.elements) {
3212
+ for (const child of card.elements) {
3213
+ cardCursorY = this.renderElement(doc, child, x + cardPad, cardCursorY, width - cardPad * 2, theme);
3214
+ cardCursorY += 8;
3215
+ }
3216
+ }
3217
+ const totalCardHeight = Math.max(70, cardCursorY - y + cardPad);
3218
+ doc.save();
3219
+ doc.roundedRect(x, y, width, totalCardHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
3220
+ doc.roundedRect(x, y, width, totalCardHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
3221
+ doc.restore();
3222
+ let renderTop = y + cardPad;
3223
+ if (card.title) {
3224
+ doc.font("Helvetica-Bold").fontSize(15).fillColor(variantColor).text(this.stripFormatting(card.title), x + cardPad, renderTop, {
3225
+ width: width - cardPad * 2
3226
+ });
3227
+ renderTop += 22;
3228
+ }
3229
+ if (card.elements) {
3230
+ for (const child of card.elements) {
3231
+ renderTop = this.renderElement(doc, child, x + cardPad, renderTop, width - cardPad * 2, theme);
3232
+ renderTop += 8;
3233
+ }
3234
+ }
3235
+ return y + totalCardHeight;
3236
+ }
3237
+ case "metric": {
3238
+ const m = element;
3239
+ const variantColor = this.getVariantColor(m.variant, theme);
3240
+ const boxHeight = 90;
3241
+ doc.roundedRect(x, y, width, boxHeight, 8).fill(theme.colors.surface || "rgba(255,255,255,0.06)");
3242
+ doc.roundedRect(x, y, width, boxHeight, 8).lineWidth(1.5).strokeColor(variantColor).stroke();
3243
+ doc.font("Helvetica-Bold").fontSize(11).fillColor(theme.colors.muted || "#888888").text(m.label.toUpperCase(), x + 16, y + 14, { width: width - 32 });
3244
+ const displayVal = m.unit ? `${m.value} ${m.unit}` : m.value;
3245
+ doc.font("Helvetica-Bold").fontSize(28).fillColor(theme.colors.primary).text(displayVal, x + 16, y + 32, { width: width - 32 });
3246
+ if (m.change) {
3247
+ const changeColor = m.change.startsWith("+") ? "#10b981" : "#ef4444";
3248
+ doc.font("Helvetica-Bold").fontSize(12).fillColor(changeColor).text(m.change, x + width - 90, y + 38, { width: 74, align: "right" });
3249
+ }
3250
+ return y + boxHeight;
3251
+ }
3252
+ case "columns": {
3253
+ const cols = element;
3254
+ const colCount = cols.columns.length;
3255
+ const gap = 16;
3256
+ const availableWidth = width - gap * (colCount - 1);
3257
+ let ratios = Array(colCount).fill(1);
3258
+ if (cols.ratios) {
3259
+ const parsed = cols.ratios.split(":").map((r) => parseFloat(r) || 1);
3260
+ if (parsed.length === colCount)
3261
+ ratios = parsed;
3262
+ }
3263
+ const totalRatio = ratios.reduce((a, b) => a + b, 0);
3264
+ let curX = x;
3265
+ let maxY = y;
3266
+ for (let i = 0; i < colCount; i++) {
3267
+ const col = cols.columns[i];
3268
+ const colWidth = ratios[i] / totalRatio * availableWidth;
3269
+ let colCursorY = y;
3270
+ for (const child of col.elements) {
3271
+ colCursorY = this.renderElement(doc, child, curX, colCursorY, colWidth, theme);
3272
+ colCursorY += 8;
3273
+ }
3274
+ if (colCursorY > maxY)
3275
+ maxY = colCursorY;
3276
+ curX += colWidth + gap;
3277
+ }
3278
+ return maxY;
3279
+ }
3280
+ case "table": {
3281
+ const t = element;
3282
+ const headers = t.headers || [];
3283
+ const rows = t.rows || [];
3284
+ const colCount = Math.max(headers.length, ...rows.map((r) => r.length), 1);
3285
+ const colWidth = width / colCount;
3286
+ const rowHeight = 26;
3287
+ let curY = y;
3288
+ if (headers.length > 0) {
3289
+ doc.rect(x, curY, width, rowHeight).fill(theme.colors.primary);
3290
+ headers.forEach((h, idx) => {
3291
+ doc.font("Helvetica-Bold").fontSize(12).fillColor("#ffffff").text(this.stripFormatting(h), x + idx * colWidth + 6, curY + 6, {
3292
+ width: colWidth - 12
3293
+ });
3294
+ });
3295
+ curY += rowHeight;
3296
+ }
3297
+ rows.forEach((row, rowIdx) => {
3298
+ const isEven = rowIdx % 2 === 0;
3299
+ const rowBg = isEven ? theme.colors.surface || "rgba(255,255,255,0.04)" : "rgba(255,255,255,0.01)";
3300
+ doc.rect(x, curY, width, rowHeight).fill(rowBg);
3301
+ row.forEach((cell, idx) => {
3302
+ doc.font("Helvetica").fontSize(11).fillColor(theme.colors.text).text(this.stripFormatting(cell), x + idx * colWidth + 6, curY + 6, {
3303
+ width: colWidth - 12
3304
+ });
3305
+ });
3306
+ curY += rowHeight;
3307
+ });
3308
+ doc.rect(x, y, width, curY - y).strokeColor(theme.colors.border || "rgba(255,255,255,0.1)").stroke();
3309
+ return curY;
3310
+ }
3311
+ default:
3312
+ return y;
3313
+ }
3314
+ }
3315
+ getVariantColor(variant, theme) {
3316
+ switch (variant) {
3317
+ case "warning":
3318
+ return theme.colors.warning || "#f59e0b";
3319
+ case "info":
3320
+ return theme.colors.info || "#3b82f6";
3321
+ case "success":
3322
+ return theme.colors.success || "#10b981";
3323
+ case "danger":
3324
+ return theme.colors.danger || "#ef4444";
3325
+ case "primary":
3326
+ default:
3327
+ return theme.colors.primary;
3328
+ }
3329
+ }
3330
+ stripFormatting(text) {
3331
+ return text.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\*(.*?)\*/g, "$1").replace(/`(.*?)`/g, "$1");
3332
+ }
3333
+ };
3334
+
3335
+ // src/dev-server.ts
3336
+ import { createServer } from "http";
3337
+ import { readFileSync, watch } from "fs";
3338
+ import { resolve } from "path";
3339
+ function startDevServer(filePath, options = {}) {
3340
+ return new Promise((resolvePromise, rejectPromise) => {
3341
+ const port = options.port || 3e3;
3342
+ const resolvedPath = resolve(process.cwd(), filePath);
3343
+ const compiler = new YumiaCompiler();
3344
+ const renderer = new HtmlRenderer();
3345
+ const sseClients = /* @__PURE__ */ new Set();
3346
+ let watcher = null;
3347
+ let debounceTimer = null;
3348
+ async function getCompiledHtml() {
3349
+ try {
3350
+ const source = readFileSync(resolvedPath, "utf-8");
3351
+ const output = await compiler.compile(source, renderer, {
3352
+ renderContext: {
3353
+ options: {
3354
+ liveReload: true,
3355
+ liveReloadPort: port
3356
+ }
3357
+ }
3358
+ });
3359
+ return output.html;
3360
+ } catch (err) {
3361
+ const errMsg = err instanceof Error ? err.message : String(err);
3362
+ return `<!DOCTYPE html><html><head><title>YumiaMD Compile Error</title><style>body{background:#0b0b12;color:#ef4444;font-family:monospace;padding:40px;}</style></head><body><h1>Compile Error</h1><pre>${errMsg}</pre></body></html>`;
3363
+ }
3364
+ }
3365
+ const server = createServer(async (req, res) => {
3366
+ const url = req.url || "/";
3367
+ if (url === "/__yumia_live_reload") {
3368
+ res.writeHead(200, {
3369
+ "Content-Type": "text/event-stream",
3370
+ "Cache-Control": "no-cache",
3371
+ Connection: "keep-alive",
3372
+ "Access-Control-Allow-Origin": "*"
3373
+ });
3374
+ res.write("data: connected\n\n");
3375
+ sseClients.add(res);
3376
+ req.on("close", () => {
3377
+ sseClients.delete(res);
3378
+ });
3379
+ return;
3380
+ }
3381
+ if (url === "/" || url.startsWith("/#")) {
3382
+ const html = await getCompiledHtml();
3383
+ res.writeHead(200, {
3384
+ "Content-Type": "text/html; charset=utf-8",
3385
+ "Cache-Control": "no-store"
3386
+ });
3387
+ res.end(html);
3388
+ return;
3389
+ }
3390
+ res.writeHead(404, { "Content-Type": "text/plain" });
3391
+ res.end("Not Found");
3392
+ });
3393
+ server.on("error", (err) => {
3394
+ rejectPromise(err);
3395
+ });
3396
+ server.listen(port, () => {
3397
+ const url = `http://localhost:${port}`;
3398
+ try {
3399
+ watcher = watch(resolvedPath, () => {
3400
+ if (debounceTimer) clearTimeout(debounceTimer);
3401
+ debounceTimer = setTimeout(() => {
3402
+ for (const client of sseClients) {
3403
+ try {
3404
+ client.write("data: reload\n\n");
3405
+ } catch {
3406
+ sseClients.delete(client);
3407
+ }
3408
+ }
3409
+ }, 40);
3410
+ });
3411
+ } catch (watchErr) {
3412
+ console.warn(`[Yumia Dev] Warning: could not watch file: ${watchErr}`);
3413
+ }
3414
+ resolvePromise({
3415
+ server,
3416
+ port,
3417
+ url,
3418
+ close: () => new Promise((resClose) => {
3419
+ if (watcher) watcher.close();
3420
+ for (const client of sseClients) {
3421
+ client.end();
3422
+ }
3423
+ sseClients.clear();
3424
+ server.close(() => resClose());
3425
+ })
3426
+ });
3427
+ });
3428
+ });
3429
+ }
3430
+
2020
3431
  // src/cli.ts
2021
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
2022
- import { basename, dirname, extname, join, resolve } from "path";
2023
- var VERSION = "0.1.9";
3432
+ import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
3433
+ import { basename, dirname, extname, join, resolve as resolve2 } from "path";
3434
+ var VERSION = "0.1.13";
2024
3435
  function printHelp() {
2025
3436
  return `
2026
3437
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -2030,11 +3441,13 @@ Usage:
2030
3441
 
2031
3442
  Commands:
2032
3443
  init [name] Scaffold a new YumiaMD presentation project
3444
+ dev <file> Start live-reload dev server with instant HTML preview
3445
+ watch <file> Watch presentation file and recompile automatically on save
2033
3446
  validate <file> Validate a .yumia.md presentation syntax and structure
2034
3447
  lint <file> Analyze presentation for layout overflows and accessibility
2035
3448
  inspect <file> Inspect the AST and geometric layout tree
2036
3449
  schema Output machine-readable JSON schema for AI agents
2037
- build <file> Compile a presentation to editable PowerPoint (.pptx)
3450
+ build <file> Compile a presentation to PowerPoint (.pptx), PDF (.pdf), or HTML (.html)
2038
3451
 
2039
3452
  Theming & Color Options:
2040
3453
  --theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
@@ -2044,9 +3457,12 @@ Theming & Color Options:
2044
3457
  --text Hex text color (e.g. "#FFFFFF" or "#0F172A")
2045
3458
  --accent Hex accent bar & highlight color
2046
3459
 
2047
- Compiler & Output Options:
2048
- --out, -o <file> Specify output file path (default: dist/<name>.pptx)
2049
- --format, -f <fmt> Target output format: pptx (default)
3460
+ Server & Compiler Options:
3461
+ --port <num> Port for live dev server (default: 3000)
3462
+ --open Open default browser automatically in dev mode
3463
+ --watch, -w Watch for file changes during compilation
3464
+ --out, -o <file> Specify output file path (default: dist/<name>.<format>)
3465
+ --format, -f <fmt> Target output format: pptx (default) | pdf | html
2050
3466
  --strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
2051
3467
  --json Output results formatted as JSON for CI/CD and AI tools
2052
3468
  --layout Show computed geometric bounding boxes in 'inspect'
@@ -2079,6 +3495,7 @@ async function runCli(argv) {
2079
3495
  }
2080
3496
  const isJson = args.includes("--json");
2081
3497
  const isStrict = args.includes("--strict");
3498
+ const isWatch = args.includes("--watch") || args.includes("-w");
2082
3499
  const nonFlagArgs = args.filter((a) => !a.startsWith("-"));
2083
3500
  const command = nonFlagArgs[0];
2084
3501
  const target = nonFlagArgs[1];
@@ -2088,6 +3505,7 @@ async function runCli(argv) {
2088
3505
  const cliSecondary = getFlagValue(args, ["--secondary"]);
2089
3506
  const cliText = getFlagValue(args, ["--text"]);
2090
3507
  const cliAccent = getFlagValue(args, ["--accent"]);
3508
+ const cliFormat = (getFlagValue(args, ["--format", "-f"]) || "pptx").toLowerCase();
2091
3509
  if (command === "schema") {
2092
3510
  const compiler = new YumiaCompiler();
2093
3511
  return {
@@ -2175,7 +3593,7 @@ Opening slide introducing the presentation deck.
2175
3593
  } catch (err) {
2176
3594
  const msg = err instanceof Error ? err.message : String(err);
2177
3595
  if (isJson) {
2178
- return { exitCode: 1, output: JSON.stringify({ success: false, error: msg }, null, 2) };
3596
+ return { exitCode: 1, output: JSON.stringify({ success: false, error: msg }) };
2179
3597
  }
2180
3598
  return {
2181
3599
  exitCode: 1,
@@ -2183,13 +3601,56 @@ Opening slide introducing the presentation deck.
2183
3601
  };
2184
3602
  }
2185
3603
  }
3604
+ if (command === "dev") {
3605
+ if (!target) {
3606
+ const msg = "Error: Please specify a presentation file to preview in dev server.";
3607
+ return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
3608
+ }
3609
+ try {
3610
+ const resolvedInput = resolve2(process.cwd(), target);
3611
+ const portStr = getFlagValue(args, ["--port"]);
3612
+ const port = portStr ? parseInt(portStr, 10) : 3e3;
3613
+ const open = args.includes("--open");
3614
+ const devInstance = await startDevServer(resolvedInput, { port, open });
3615
+ if (isJson) {
3616
+ return {
3617
+ exitCode: 0,
3618
+ output: JSON.stringify(
3619
+ {
3620
+ success: true,
3621
+ url: devInstance.url,
3622
+ port: devInstance.port,
3623
+ file: resolvedInput
3624
+ },
3625
+ null,
3626
+ 2
3627
+ )
3628
+ };
3629
+ }
3630
+ return {
3631
+ exitCode: 0,
3632
+ output: `\u{1F680} YumiaMD Live Dev Server running at: ${devInstance.url}
3633
+ Watching: ${target}
3634
+ Hot-reloading active via SSE. Press Ctrl+C to stop.`
3635
+ };
3636
+ } catch (err) {
3637
+ const msg = err instanceof Error ? err.message : String(err);
3638
+ if (isJson) {
3639
+ return { exitCode: 1, output: JSON.stringify({ success: false, error: msg }) };
3640
+ }
3641
+ return {
3642
+ exitCode: 1,
3643
+ output: `\u2717 Dev server failed: ${msg}`
3644
+ };
3645
+ }
3646
+ }
2186
3647
  if (command === "validate") {
2187
3648
  if (!target) {
2188
3649
  const msg = "Error: Please specify a file to validate.";
2189
3650
  return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
2190
3651
  }
2191
3652
  try {
2192
- const source = readFileSync(target, "utf-8");
3653
+ const source = readFileSync2(target, "utf-8");
2193
3654
  const compiler = new YumiaCompiler();
2194
3655
  const validation = compiler.validate(source);
2195
3656
  if (isJson) {
@@ -2231,7 +3692,7 @@ ${errorLines.join("\n")}`
2231
3692
  return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
2232
3693
  }
2233
3694
  try {
2234
- const source = readFileSync(target, "utf-8");
3695
+ const source = readFileSync2(target, "utf-8");
2235
3696
  const compiler = new YumiaCompiler();
2236
3697
  const report = compiler.lint(source, { strict: isStrict });
2237
3698
  if (isJson) {
@@ -2291,7 +3752,7 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
2291
3752
  return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
2292
3753
  }
2293
3754
  try {
2294
- const source = readFileSync(target, "utf-8");
3755
+ const source = readFileSync2(target, "utf-8");
2295
3756
  const presentation = parseYumia(source);
2296
3757
  if (args.includes("--layout")) {
2297
3758
  const engine = new DefaultLayoutEngine();
@@ -2316,25 +3777,26 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
2316
3777
  };
2317
3778
  }
2318
3779
  }
2319
- if (command === "build") {
3780
+ if (command === "build" || command === "watch") {
2320
3781
  if (!target) {
2321
- const msg = "Error: Please specify a presentation file to build.";
3782
+ const msg = `Error: Please specify a presentation file to ${command}.`;
2322
3783
  return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
2323
3784
  }
2324
3785
  try {
2325
- const resolvedInput = resolve(process.cwd(), target);
2326
- const source = readFileSync(resolvedInput, "utf-8");
3786
+ const resolvedInput = resolve2(process.cwd(), target);
3787
+ const isHtml = cliFormat === "html";
3788
+ const isPdf = cliFormat === "pdf";
3789
+ const targetExtension = isHtml ? ".html" : isPdf ? ".pdf" : ".pptx";
2327
3790
  let outputPath = "";
2328
3791
  const outFlagIndex = args.findIndex((a) => a === "--out" || a === "-o");
2329
3792
  if (outFlagIndex !== -1 && args[outFlagIndex + 1]) {
2330
- outputPath = resolve(process.cwd(), args[outFlagIndex + 1]);
3793
+ outputPath = resolve2(process.cwd(), args[outFlagIndex + 1]);
2331
3794
  } else {
2332
3795
  const fileBase = basename(resolvedInput, extname(resolvedInput)).replace(/\.yumia$/, "");
2333
- outputPath = join(dirname(resolvedInput), "dist", `${fileBase}.pptx`);
3796
+ outputPath = join(dirname(resolvedInput), "dist", `${fileBase}${targetExtension}`);
2334
3797
  }
2335
3798
  mkdirSync(dirname(outputPath), { recursive: true });
2336
3799
  const compiler = new YumiaCompiler();
2337
- const renderer = new PptxRenderer();
2338
3800
  const cliColorOverrides = {};
2339
3801
  if (cliBg) cliColorOverrides.background = cliBg;
2340
3802
  if (cliPrimary) cliColorOverrides.primary = cliPrimary;
@@ -2344,11 +3806,42 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
2344
3806
  const renderTheme = cliTheme || Object.keys(cliColorOverrides).length > 0 ? resolveTheme(cliTheme || "default", {
2345
3807
  ...Object.keys(cliColorOverrides).length > 0 ? { colors: cliColorOverrides } : {}
2346
3808
  }) : void 0;
2347
- const result = await compiler.compile(source, renderer, {
2348
- ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
2349
- });
2350
- const buffer = result.data instanceof Uint8Array ? Buffer.from(result.data) : Buffer.from(new Uint8Array(result.data));
2351
- writeFileSync(outputPath, buffer);
3809
+ const compileFile = async () => {
3810
+ const source = readFileSync2(resolvedInput, "utf-8");
3811
+ if (isHtml) {
3812
+ const htmlRenderer = new HtmlRenderer();
3813
+ const result2 = await compiler.compile(source, htmlRenderer, {
3814
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
3815
+ });
3816
+ writeFileSync(outputPath, result2.html, "utf-8");
3817
+ return { slideCount: result2.slideCount, format: result2.format };
3818
+ } else if (isPdf) {
3819
+ const pdfRenderer = new PdfRenderer();
3820
+ const result2 = await compiler.compile(source, pdfRenderer, {
3821
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
3822
+ });
3823
+ const buffer = result2.data instanceof Uint8Array ? Buffer.from(result2.data) : Buffer.from(new Uint8Array(result2.data));
3824
+ writeFileSync(outputPath, buffer);
3825
+ return { slideCount: result2.slideCount, format: result2.format };
3826
+ } else {
3827
+ const pptxRenderer = new PptxRenderer();
3828
+ const result2 = await compiler.compile(source, pptxRenderer, {
3829
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
3830
+ });
3831
+ const buffer = result2.data instanceof Uint8Array ? Buffer.from(result2.data) : Buffer.from(new Uint8Array(result2.data));
3832
+ writeFileSync(outputPath, buffer);
3833
+ return { slideCount: result2.slideCount, format: result2.format };
3834
+ }
3835
+ };
3836
+ const result = await compileFile();
3837
+ if (command === "watch" || isWatch) {
3838
+ fsWatch(resolvedInput, async () => {
3839
+ try {
3840
+ await compileFile();
3841
+ } catch {
3842
+ }
3843
+ });
3844
+ }
2352
3845
  if (isJson) {
2353
3846
  return {
2354
3847
  exitCode: 0,
@@ -2358,16 +3851,19 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
2358
3851
  source: target,
2359
3852
  output: outputPath,
2360
3853
  slideCount: result.slideCount,
2361
- format: result.format
3854
+ format: result.format,
3855
+ watching: command === "watch" || isWatch
2362
3856
  },
2363
3857
  null,
2364
3858
  2
2365
3859
  )
2366
3860
  };
2367
3861
  }
3862
+ const watchMsg = command === "watch" || isWatch ? " [Watching for changes...]" : "";
3863
+ const formatDesc = isHtml ? "interactive HTML slides" : isPdf ? "vector PDF slides" : "native editable slides";
2368
3864
  return {
2369
3865
  exitCode: 0,
2370
- output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} native editable slides)`
3866
+ output: `\u2713 Successfully compiled '${target}' \u2794 '${outputPath}' (${result.slideCount} ${formatDesc})${watchMsg}`
2371
3867
  };
2372
3868
  } catch (err) {
2373
3869
  const msg = err instanceof Error ? err.message : String(err);
@@ -2421,6 +3917,9 @@ export {
2421
3917
  cleanFontFace,
2422
3918
  parseInlineMarkdown,
2423
3919
  PptxRenderer,
3920
+ HtmlRenderer,
3921
+ PdfRenderer,
3922
+ startDevServer,
2424
3923
  VERSION,
2425
3924
  printHelp,
2426
3925
  runCli