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.
- package/README.md +19 -7
- package/dist/bin.js +1 -1
- package/dist/{chunk-F3CX7UG6.js → chunk-6GEDN7OB.js} +1524 -25
- package/dist/index.d.ts +17 -2
- package/dist/index.js +7 -1
- package/package.json +12 -9
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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.
|
|
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
|
|
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
|
-
|
|
2048
|
-
--
|
|
2049
|
-
--
|
|
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 }
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
2326
|
-
const
|
|
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 =
|
|
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}
|
|
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
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
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}
|
|
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
|