yumiamd 0.1.22 → 0.1.24
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 +54 -86
- package/dist/bin.js +1 -1
- package/dist/{chunk-KNQTEIHN.js → chunk-FKFINZOL.js} +727 -26
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# YumiaMD (`yumiamd`)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> Author
|
|
3
|
+
> **The Design Compiler for Presentations & Visual Documents.**
|
|
4
|
+
> Author decks with high-level design intent and compile directly to **100% native, editable PowerPoint (.pptx)** presentations, crisp vector PDFs, and interactive HTML5 slides.
|
|
5
5
|
|
|
6
6
|
[](https://www.npmjs.com/package/yumiamd)
|
|
7
7
|
[](https://github.com/biagio-scaglia/Yumia-MD/blob/main/LICENSE)
|
|
@@ -10,15 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
## ⚡ Quick Start (Zero Install)
|
|
12
12
|
|
|
13
|
-
You can run
|
|
13
|
+
You can run Yumia immediately without installing anything via `npx`:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
# Initialize a starter presentation
|
|
17
|
-
npx yumiamd init my-deck
|
|
16
|
+
# Initialize a starter presentation with custom theme
|
|
17
|
+
npx yumiamd init my-deck --theme corporate
|
|
18
18
|
|
|
19
|
-
# Launch instant dev server with live-reload
|
|
19
|
+
# Launch instant dev server with live-reload and Visual Inspector
|
|
20
20
|
npx yumiamd dev presentation.yumia.md --open
|
|
21
21
|
|
|
22
|
+
# Run automated design audit & visual quality score
|
|
23
|
+
npx yumiamd check presentation.yumia.md --optimize
|
|
24
|
+
|
|
22
25
|
# Compile presentation directly to an editable PowerPoint (.pptx)
|
|
23
26
|
npx yumiamd build presentation.yumia.md --out presentation.pptx
|
|
24
27
|
```
|
|
@@ -47,7 +50,7 @@ pnpm add yumiamd
|
|
|
47
50
|
|
|
48
51
|
---
|
|
49
52
|
|
|
50
|
-
## 🚀 CLI Commands &
|
|
53
|
+
## 🚀 CLI Commands & Design Tooling
|
|
51
54
|
|
|
52
55
|
Once installed globally or locally, the `yumia` / `yumiamd` commands are available in your terminal:
|
|
53
56
|
|
|
@@ -55,9 +58,12 @@ Once installed globally or locally, the `yumia` / `yumiamd` commands are availab
|
|
|
55
58
|
# Initialize a new presentation template (supports --theme and custom color overrides)
|
|
56
59
|
yumia init my-presentation --theme cyberpunk --primary "#FF2E88"
|
|
57
60
|
|
|
58
|
-
# Start instant live-reload dev server with HTML preview (zero config!)
|
|
61
|
+
# Start instant live-reload dev server with HTML preview & Visual Inspector (zero config!)
|
|
59
62
|
yumia dev presentation.yumia.md --open
|
|
60
63
|
|
|
64
|
+
# Run design audit, check WCAG contrast, density & compute Visual Quality Score (0-100)
|
|
65
|
+
yumia check presentation.yumia.md --optimize
|
|
66
|
+
|
|
61
67
|
# Compile to native editable PowerPoint (.pptx)
|
|
62
68
|
yumia build presentation.yumia.md --out dist/presentation.pptx
|
|
63
69
|
|
|
@@ -93,115 +99,77 @@ yumia schema
|
|
|
93
99
|
```markdown
|
|
94
100
|
---
|
|
95
101
|
title: System Architecture & Capabilities
|
|
96
|
-
theme:
|
|
102
|
+
theme: corporate
|
|
97
103
|
aspectRatio: '16:9'
|
|
98
|
-
author:
|
|
104
|
+
author: Engineering Team
|
|
99
105
|
---
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Deterministic compilation from Markdown directly to native PowerPoint slides.
|
|
104
|
-
|
|
105
|
-
:::badge text="v0.1.15" variant="primary" :::
|
|
106
|
-
:::badge text="Multi-Target" variant="success" :::
|
|
107
|
+
<!-- Slide 1: Hero Cover -->
|
|
107
108
|
|
|
108
|
-
:::
|
|
109
|
-
Opening slide introducing the core architectural vision.
|
|
109
|
+
:::hero title="Next-Generation Cloud Architecture" subtitle="High-throughput distributed compute platform" badge="v2.4 Enterprise" align="center"
|
|
110
110
|
:::
|
|
111
111
|
|
|
112
112
|
---
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
<!-- Slide 2: Primitives & Visual Intent -->
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
:::column
|
|
119
|
-
:::card Core Engine
|
|
120
|
-
|
|
121
|
-
- 100% Deterministic placement
|
|
122
|
-
- Zero runtime dependencies
|
|
123
|
-
- TypeScript-first architecture
|
|
124
|
-
:::
|
|
125
|
-
:::
|
|
126
|
-
|
|
127
|
-
:::column
|
|
128
|
-
:::card Native PowerPoint Export
|
|
129
|
-
|
|
130
|
-
- Headings, cards, shapes & notes
|
|
131
|
-
- Fully editable OpenXML objects
|
|
132
|
-
- Compatible with PowerPoint & Google Slides
|
|
133
|
-
:::
|
|
134
|
-
:::
|
|
116
|
+
# Core Infrastructure Breakdown
|
|
135
117
|
|
|
118
|
+
:::callout variant="info" title="Zero Downtime Rollout"
|
|
119
|
+
All worker nodes are upgraded progressively with active health probing.
|
|
136
120
|
:::
|
|
137
121
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
# Native Charts & Data Visuals
|
|
141
|
-
|
|
142
|
-
:::chart type="bar" title="Execution Speed" labels="Parser, Layout, PDF, PPTX" data="1200, 850, 430, 680"
|
|
122
|
+
:::grid columns=3 gap=20
|
|
123
|
+
:::card "Edge Routing" variant="primary"
|
|
143
124
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
125
|
+
- Global Anycast DNS
|
|
126
|
+
- Sub-5ms SSL Termination
|
|
127
|
+
:::
|
|
147
128
|
|
|
148
|
-
:::
|
|
129
|
+
:::card "Stateful Compute" variant="success"
|
|
149
130
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
- [Phase 3] Rich Directives & Charts
|
|
153
|
-
- [Phase 4] Cloud Deployments
|
|
131
|
+
- Autonomous Actor Runtime
|
|
132
|
+
- Distributed Raft Consensus
|
|
154
133
|
:::
|
|
155
134
|
|
|
156
|
-
:::
|
|
135
|
+
:::card "Vector Store" variant="accent"
|
|
157
136
|
|
|
158
|
-
-
|
|
137
|
+
- Multi-index HNSW Search
|
|
138
|
+
- 10M query/sec throughput
|
|
139
|
+
:::
|
|
159
140
|
:::
|
|
160
|
-
```
|
|
161
141
|
|
|
162
142
|
---
|
|
163
143
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
You can also use `yumiamd` programmatically inside your Node.js or TypeScript backend, CLI tools, and AI pipelines:
|
|
144
|
+
<!-- Slide 3: Key Performance Metrics -->
|
|
167
145
|
|
|
168
|
-
|
|
169
|
-
import { compile, parse, YumiaCompiler } from 'yumiamd';
|
|
170
|
-
import fs from 'node:fs';
|
|
146
|
+
# Operational Performance
|
|
171
147
|
|
|
172
|
-
|
|
148
|
+
:::grid columns=3 gap=20
|
|
149
|
+
:::metric value="99.999%" label="Service SLA" change="+0.009%" variant="success" trend="up"
|
|
150
|
+
:::
|
|
151
|
+
:::metric value="1.2ms" label="p99 Latency" change="-35%" variant="primary" trend="down"
|
|
152
|
+
:::
|
|
153
|
+
:::metric value="14.2M" label="Active Invocations" change="+120%" variant="accent" trend="up"
|
|
154
|
+
:::
|
|
155
|
+
:::
|
|
173
156
|
|
|
174
|
-
|
|
175
|
-
const compiler = new YumiaCompiler();
|
|
176
|
-
const validation = compiler.validate(markdown);
|
|
177
|
-
console.log(`Valid: ${validation.valid}, Slides: ${validation.slideCount}`);
|
|
157
|
+
---
|
|
178
158
|
|
|
179
|
-
|
|
180
|
-
const { buffer, errors } = await compile(markdown, { format: 'pptx' });
|
|
159
|
+
<!-- Slide 4: Data Visualization -->
|
|
181
160
|
|
|
182
|
-
|
|
183
|
-
fs.writeFileSync('output.pptx', buffer);
|
|
184
|
-
console.log('✅ Presentation generated successfully!');
|
|
185
|
-
}
|
|
161
|
+
# Global Throughput & Adoption
|
|
186
162
|
|
|
187
|
-
|
|
188
|
-
|
|
163
|
+
:::chart type="bar" title="Monthly API Invocations (Millions)" labels="Jan,Feb,Mar,Apr,May,Jun" data="4.2,6.8,9.1,11.5,13.2,14.2"
|
|
164
|
+
:::
|
|
189
165
|
```
|
|
190
166
|
|
|
191
167
|
---
|
|
192
168
|
|
|
193
|
-
##
|
|
194
|
-
|
|
195
|
-
- 🎯 **Native Object Principle**: Slides compiled to PowerPoint are **NOT** rasterized screenshot images. Headings, bullet points, cards, tables, charts, badges, and speaker notes are generated as **100% native vector PowerPoint shapes, tables & textboxes** that you can click, re-format, and edit in Microsoft PowerPoint or Google Slides.
|
|
196
|
-
- 📊 **Native Charts & Diagrams**: Full support for `:::chart` (bar, line, pie, doughnut) and `:::mermaid` diagrams across HTML, PDF, and PowerPoint.
|
|
197
|
-
- ⏳ **Timelines, Compare & Steps**: Rich layout directives including `:::timeline`, `:::compare`, and click-to-reveal `:::step` animations.
|
|
198
|
-
- 🚀 **One-Command Cloud Deploy**: Instantly deploy decks to GitHub Pages, Vercel, or static web servers with `yumia deploy`.
|
|
199
|
-
- 📐 **Deterministic Layout Engine**: Exact coordinate calculations for stack, columns, cards, and automatic overflow detection.
|
|
200
|
-
- 🎨 **Semantic Design Tokens**: Built-in themes with typography scales, color palettes, and contrast-safe themes.
|
|
201
|
-
- 🤖 **AI-Friendly Format**: Clean Markdown syntax designed for LLM prompts and agentic workflows, complete with `yumia schema` and `--json` CLI diagnostics.
|
|
202
|
-
|
|
203
|
-
---
|
|
169
|
+
## 🔍 Visual Inspector & Speaker View
|
|
204
170
|
|
|
205
|
-
|
|
171
|
+
When previewing in HTML5 (`yumia dev` or `--format html`):
|
|
206
172
|
|
|
207
|
-
|
|
173
|
+
- **Visual Inspector**: Press `I` or `Alt+Click` on any element to view computed design tokens, variants, bounding boxes, and source lines.
|
|
174
|
+
- **Dual-Window Speaker View**: Press `S` to open an synchronized second screen with speaker notes, elapsed timer, and next-slide preview.
|
|
175
|
+
- **Overview Mode**: Press `O` or `Esc` to view slide grid thumbnails.
|
package/dist/bin.js
CHANGED
|
@@ -31,12 +31,13 @@ function createList(items, ordered = false) {
|
|
|
31
31
|
items: normalizedItems
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
function createImage(src, alt, caption) {
|
|
34
|
+
function createImage(src, alt, caption, options) {
|
|
35
35
|
return {
|
|
36
36
|
type: "image",
|
|
37
37
|
src,
|
|
38
38
|
...alt ? { alt } : {},
|
|
39
|
-
...caption ? { caption } : {}
|
|
39
|
+
...caption ? { caption } : {},
|
|
40
|
+
...options || {}
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
function createCard(elements, title, variant) {
|
|
@@ -214,6 +215,24 @@ function createSlot(name, elements) {
|
|
|
214
215
|
...elements ? { elements } : {}
|
|
215
216
|
};
|
|
216
217
|
}
|
|
218
|
+
function createHero(title, subtitle, elements, options) {
|
|
219
|
+
return {
|
|
220
|
+
type: "hero",
|
|
221
|
+
title,
|
|
222
|
+
...subtitle ? { subtitle } : {},
|
|
223
|
+
...elements ? { elements } : {},
|
|
224
|
+
...options || {}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function createCallout(text, severity = "info", title, icon) {
|
|
228
|
+
return {
|
|
229
|
+
type: "callout",
|
|
230
|
+
text,
|
|
231
|
+
severity,
|
|
232
|
+
...title ? { title } : {},
|
|
233
|
+
...icon ? { icon } : {}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
217
236
|
|
|
218
237
|
// ../parser/dist/parser.js
|
|
219
238
|
function parseHighlightLines(highlightStr) {
|
|
@@ -677,6 +696,36 @@ var DefaultYumiaParser = class {
|
|
|
677
696
|
const blockBaseLine = directiveStartLine + 1;
|
|
678
697
|
if (directiveName === "notes") {
|
|
679
698
|
notes = blockLines.map((l) => l.trim()).filter(Boolean).join("\n");
|
|
699
|
+
} else if (directiveName === "hero") {
|
|
700
|
+
const subMatch = directiveArg.match(/subtitle=['"](.*?)['"]/);
|
|
701
|
+
const tagMatch = directiveArg.match(/tagline=['"](.*?)['"]/);
|
|
702
|
+
const alignMatch = directiveArg.match(/align=['"](.*?)['"]/);
|
|
703
|
+
const emphMatch = directiveArg.match(/emphasis=['"](.*?)['"]/);
|
|
704
|
+
const densMatch = directiveArg.match(/density=['"](.*?)['"]/);
|
|
705
|
+
const title = directiveArg.replace(/(?:subtitle|tagline|align|emphasis|density)=['"].*?['"]/g, "").trim().replace(/^['"](.*)['"]$/, "$1") || "Hero";
|
|
706
|
+
const { elements: heroElements } = this.parseLines(blockLines, blockBaseLine);
|
|
707
|
+
const el = createHero(title, subMatch ? subMatch[1] : void 0, heroElements.length > 0 ? heroElements : void 0, {
|
|
708
|
+
tagline: tagMatch ? tagMatch[1] : void 0,
|
|
709
|
+
align: alignMatch ? alignMatch[1] : void 0,
|
|
710
|
+
emphasis: emphMatch ? emphMatch[1] : void 0,
|
|
711
|
+
density: densMatch ? densMatch[1] : void 0
|
|
712
|
+
});
|
|
713
|
+
el.loc = {
|
|
714
|
+
start: { line: directiveStartLine, column: 1 },
|
|
715
|
+
end: { line: baseLine + i - 1, column: 1 }
|
|
716
|
+
};
|
|
717
|
+
elements.push(el);
|
|
718
|
+
} else if (directiveName === "callout") {
|
|
719
|
+
const titleMatch = directiveArg.match(/title=['"](.*?)['"]/);
|
|
720
|
+
const sevMatch = directiveArg.match(/(?:severity|variant)=['"](.*?)['"]/);
|
|
721
|
+
const iconMatch = directiveArg.match(/icon=['"](.*?)['"]/);
|
|
722
|
+
const calloutText = blockLines.length > 0 ? blockLines.map((l) => l.trim()).join(" ").trim() : directiveArg.replace(/(?:title|severity|variant|icon)=['"].*?['"]/g, "").trim().replace(/^['"](.*)['"]$/, "$1") || "Note";
|
|
723
|
+
const el = createCallout(calloutText, sevMatch ? sevMatch[1] : "info", titleMatch ? titleMatch[1] : void 0, iconMatch ? iconMatch[1] : void 0);
|
|
724
|
+
el.loc = {
|
|
725
|
+
start: { line: directiveStartLine, column: 1 },
|
|
726
|
+
end: { line: baseLine + i - 1, column: 1 }
|
|
727
|
+
};
|
|
728
|
+
elements.push(el);
|
|
680
729
|
} else if (directiveName === "card") {
|
|
681
730
|
let cardTitle;
|
|
682
731
|
let cardVariant;
|
|
@@ -1331,13 +1380,70 @@ var NativeYumiaParser = class {
|
|
|
1331
1380
|
const tok = tokens[idx];
|
|
1332
1381
|
const baseIndent = tok.indent;
|
|
1333
1382
|
switch (tok.command) {
|
|
1383
|
+
case "hero": {
|
|
1384
|
+
const titleMatch = tok.args.match(/^(?:title=)?["']([^"']+)["']/);
|
|
1385
|
+
const subMatch = tok.args.match(/\bsubtitle=["']([^"']+)["']/);
|
|
1386
|
+
const tagMatch = tok.args.match(/\btagline=["']([^"']+)["']/);
|
|
1387
|
+
const alignMatch = tok.args.match(/\balign=["']?([^"'\s]+)["']?/);
|
|
1388
|
+
const emphMatch = tok.args.match(/\bemphasis=["']?([^"'\s]+)["']?/);
|
|
1389
|
+
const densMatch = tok.args.match(/\bdensity=["']?([^"'\s]+)["']?/);
|
|
1390
|
+
const title = titleMatch ? titleMatch[1] : this.stripQuotes(tok.args) || "Hero";
|
|
1391
|
+
const children = [];
|
|
1392
|
+
let nextIdx = idx + 1;
|
|
1393
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1394
|
+
const childRes = this.parseElement(tokens, nextIdx);
|
|
1395
|
+
if (childRes) {
|
|
1396
|
+
children.push(childRes.element);
|
|
1397
|
+
nextIdx = childRes.nextIdx;
|
|
1398
|
+
} else {
|
|
1399
|
+
nextIdx++;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const heroEl = createHero(title, subMatch ? subMatch[1] : void 0, children.length > 0 ? children : void 0, {
|
|
1403
|
+
tagline: tagMatch ? tagMatch[1] : void 0,
|
|
1404
|
+
align: alignMatch ? alignMatch[1] : void 0,
|
|
1405
|
+
emphasis: emphMatch ? emphMatch[1] : void 0,
|
|
1406
|
+
density: densMatch ? densMatch[1] : void 0
|
|
1407
|
+
});
|
|
1408
|
+
heroEl.loc = {
|
|
1409
|
+
start: { line: tok.lineNum, column: 1 },
|
|
1410
|
+
end: { line: tok.lineNum, column: tok.text.length }
|
|
1411
|
+
};
|
|
1412
|
+
return { element: heroEl, nextIdx };
|
|
1413
|
+
}
|
|
1414
|
+
case "callout": {
|
|
1415
|
+
const titleMatch = tok.args.match(/title=["']([^"']+)["']/);
|
|
1416
|
+
const sevMatch = tok.args.match(/\b(?:severity|variant)=["']?([^"'\s]+)["']?/);
|
|
1417
|
+
const iconMatch = tok.args.match(/\bicon=["']?([^"'\s]+)["']?/);
|
|
1418
|
+
let text = this.stripQuotes(tok.args.replace(/title=["'][^"']+["']/, "").replace(/(?:severity|variant|icon)=["']?[^"'\s]+["']?/g, ""));
|
|
1419
|
+
let nextIdx = idx + 1;
|
|
1420
|
+
if (!text) {
|
|
1421
|
+
const cLines = [];
|
|
1422
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1423
|
+
cLines.push(tokens[nextIdx].text);
|
|
1424
|
+
nextIdx++;
|
|
1425
|
+
}
|
|
1426
|
+
text = cLines.join(" ");
|
|
1427
|
+
}
|
|
1428
|
+
const calloutEl = createCallout(text || "Note", sevMatch ? sevMatch[1] : "info", titleMatch ? titleMatch[1] : void 0, iconMatch ? iconMatch[1] : void 0);
|
|
1429
|
+
calloutEl.loc = {
|
|
1430
|
+
start: { line: tok.lineNum, column: 1 },
|
|
1431
|
+
end: { line: tok.lineNum, column: tok.text.length }
|
|
1432
|
+
};
|
|
1433
|
+
return { element: calloutEl, nextIdx };
|
|
1434
|
+
}
|
|
1334
1435
|
case "heading":
|
|
1335
1436
|
case "h1":
|
|
1336
1437
|
case "h2":
|
|
1337
1438
|
case "h3": {
|
|
1338
1439
|
const level = tok.command === "h1" ? 1 : tok.command === "h2" ? 2 : tok.command === "h3" ? 3 : 1;
|
|
1339
1440
|
const text = this.stripQuotes(tok.args);
|
|
1340
|
-
|
|
1441
|
+
const el = createHeading(text, level);
|
|
1442
|
+
el.loc = {
|
|
1443
|
+
start: { line: tok.lineNum, column: 1 },
|
|
1444
|
+
end: { line: tok.lineNum, column: tok.text.length }
|
|
1445
|
+
};
|
|
1446
|
+
return { element: el, nextIdx: idx + 1 };
|
|
1341
1447
|
}
|
|
1342
1448
|
case "paragraph":
|
|
1343
1449
|
case "text":
|
|
@@ -1501,6 +1607,147 @@ var NativeYumiaParser = class {
|
|
|
1501
1607
|
}
|
|
1502
1608
|
return { element: createList(items, false), nextIdx };
|
|
1503
1609
|
}
|
|
1610
|
+
case "image":
|
|
1611
|
+
case "img": {
|
|
1612
|
+
const srcMatch = tok.args.match(/^(?:src=)?["']([^"']+)["']/);
|
|
1613
|
+
const altMatch = tok.args.match(/\balt=["']([^"']+)["']/);
|
|
1614
|
+
const capMatch = tok.args.match(/\bcaption=["']([^"']+)["']/);
|
|
1615
|
+
const fitMatch = tok.args.match(/\bfit=["']?([^"'\s]+)["']?/);
|
|
1616
|
+
const widthMatch = tok.args.match(/\bwidth=["']?([^"'\s]+)["']?/);
|
|
1617
|
+
const heightMatch = tok.args.match(/\bheight=["']?([^"'\s]+)["']?/);
|
|
1618
|
+
const radiusMatch = tok.args.match(/\bradius=["']?([^"'\s]+)["']?/);
|
|
1619
|
+
const aspectMatch = tok.args.match(/\baspect=["']?([^"'\s]+)["']?/);
|
|
1620
|
+
const src = srcMatch ? srcMatch[1] : this.stripQuotes(tok.args);
|
|
1621
|
+
return {
|
|
1622
|
+
element: createImage(src, altMatch ? altMatch[1] : void 0, capMatch ? capMatch[1] : void 0, {
|
|
1623
|
+
fit: fitMatch ? fitMatch[1] : void 0,
|
|
1624
|
+
width: widthMatch ? widthMatch[1] : void 0,
|
|
1625
|
+
height: heightMatch ? heightMatch[1] : void 0,
|
|
1626
|
+
radius: radiusMatch ? radiusMatch[1] : void 0,
|
|
1627
|
+
aspectRatio: aspectMatch ? aspectMatch[1] : void 0
|
|
1628
|
+
}),
|
|
1629
|
+
nextIdx: idx + 1
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
case "chart": {
|
|
1633
|
+
const typeMatch = tok.args.match(/type=["']?([^"'\s]+)["']?/);
|
|
1634
|
+
const titleMatch = tok.args.match(/title=["']([^"']+)["']/);
|
|
1635
|
+
const chartType = typeMatch ? typeMatch[1] : "bar";
|
|
1636
|
+
const title = titleMatch ? titleMatch[1] : void 0;
|
|
1637
|
+
const labels = [];
|
|
1638
|
+
const series = [];
|
|
1639
|
+
let nextIdx = idx + 1;
|
|
1640
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1641
|
+
const sub = tokens[nextIdx];
|
|
1642
|
+
if (sub.command === "labels") {
|
|
1643
|
+
const rawLabels = sub.args.split(",").map((l) => this.stripQuotes(l.trim()));
|
|
1644
|
+
labels.push(...rawLabels);
|
|
1645
|
+
} else if (sub.command === "series") {
|
|
1646
|
+
const sNameMatch = sub.args.match(/^["']?([^:"']+):/);
|
|
1647
|
+
const name = sNameMatch ? sNameMatch[1].trim() : "Series";
|
|
1648
|
+
const dataStr = sub.args.replace(/^["']?[^:"']+:?\s*/, "").replace(/["']$/, "");
|
|
1649
|
+
const values = dataStr.split(",").map((v) => parseFloat(v.trim())).filter((v) => !isNaN(v));
|
|
1650
|
+
series.push({ name, values });
|
|
1651
|
+
}
|
|
1652
|
+
nextIdx++;
|
|
1653
|
+
}
|
|
1654
|
+
if (labels.length === 0)
|
|
1655
|
+
labels.push("A", "B", "C");
|
|
1656
|
+
if (series.length === 0)
|
|
1657
|
+
series.push({ name: "Data", values: [10, 20, 30] });
|
|
1658
|
+
return { element: createChart(chartType, labels, series, title), nextIdx };
|
|
1659
|
+
}
|
|
1660
|
+
case "compare": {
|
|
1661
|
+
const leftTitleMatch = tok.args.match(/left(?:Title)?=["']([^"']+)["']/);
|
|
1662
|
+
const rightTitleMatch = tok.args.match(/right(?:Title)?=["']([^"']+)["']/);
|
|
1663
|
+
const leftTitle = leftTitleMatch ? leftTitleMatch[1] : void 0;
|
|
1664
|
+
const rightTitle = rightTitleMatch ? rightTitleMatch[1] : void 0;
|
|
1665
|
+
const leftEls = [];
|
|
1666
|
+
const rightEls = [];
|
|
1667
|
+
let currentSide = "left";
|
|
1668
|
+
let nextIdx = idx + 1;
|
|
1669
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1670
|
+
const sub = tokens[nextIdx];
|
|
1671
|
+
if (sub.command === "left") {
|
|
1672
|
+
currentSide = "left";
|
|
1673
|
+
nextIdx++;
|
|
1674
|
+
continue;
|
|
1675
|
+
} else if (sub.command === "right") {
|
|
1676
|
+
currentSide = "right";
|
|
1677
|
+
nextIdx++;
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
const childRes = this.parseElement(tokens, nextIdx);
|
|
1681
|
+
if (childRes) {
|
|
1682
|
+
if (currentSide === "left")
|
|
1683
|
+
leftEls.push(childRes.element);
|
|
1684
|
+
else
|
|
1685
|
+
rightEls.push(childRes.element);
|
|
1686
|
+
nextIdx = childRes.nextIdx;
|
|
1687
|
+
} else {
|
|
1688
|
+
nextIdx++;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return { element: createCompare(leftEls, rightEls, leftTitle, rightTitle), nextIdx };
|
|
1692
|
+
}
|
|
1693
|
+
case "timeline": {
|
|
1694
|
+
const layoutMatch = tok.args.match(/layout=["']?(horizontal|vertical)["']?/);
|
|
1695
|
+
const layout = layoutMatch ? layoutMatch[1] : "horizontal";
|
|
1696
|
+
const items = [];
|
|
1697
|
+
let nextIdx = idx + 1;
|
|
1698
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1699
|
+
const sub = tokens[nextIdx];
|
|
1700
|
+
if (sub.command === "item") {
|
|
1701
|
+
const dateMatch = sub.args.match(/date=["']([^"']+)["']/);
|
|
1702
|
+
const titleMatch = sub.args.match(/title=["']([^"']+)["']/);
|
|
1703
|
+
const descMatch = sub.args.match(/desc(?:ription)?=["']([^"']+)["']/);
|
|
1704
|
+
items.push({
|
|
1705
|
+
date: dateMatch ? dateMatch[1] : "2026",
|
|
1706
|
+
title: titleMatch ? titleMatch[1] : "Milestone",
|
|
1707
|
+
description: descMatch ? descMatch[1] : void 0
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
nextIdx++;
|
|
1711
|
+
}
|
|
1712
|
+
return { element: createTimeline(items, layout), nextIdx };
|
|
1713
|
+
}
|
|
1714
|
+
case "mermaid": {
|
|
1715
|
+
const mLines = [];
|
|
1716
|
+
let nextIdx = idx + 1;
|
|
1717
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1718
|
+
mLines.push(tokens[nextIdx].text);
|
|
1719
|
+
nextIdx++;
|
|
1720
|
+
}
|
|
1721
|
+
return { element: createMermaid(mLines.join("\n")), nextIdx };
|
|
1722
|
+
}
|
|
1723
|
+
case "math": {
|
|
1724
|
+
let expr = this.stripQuotes(tok.args);
|
|
1725
|
+
let nextIdx = idx + 1;
|
|
1726
|
+
if (!expr) {
|
|
1727
|
+
const mathLines = [];
|
|
1728
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1729
|
+
mathLines.push(tokens[nextIdx].text);
|
|
1730
|
+
nextIdx++;
|
|
1731
|
+
}
|
|
1732
|
+
expr = mathLines.join("\n");
|
|
1733
|
+
}
|
|
1734
|
+
return { element: createMath(expr), nextIdx };
|
|
1735
|
+
}
|
|
1736
|
+
case "table": {
|
|
1737
|
+
let headers = void 0;
|
|
1738
|
+
const rows = [];
|
|
1739
|
+
let nextIdx = idx + 1;
|
|
1740
|
+
while (nextIdx < tokens.length && tokens[nextIdx].indent > baseIndent) {
|
|
1741
|
+
const sub = tokens[nextIdx];
|
|
1742
|
+
if (sub.command === "headers" || sub.command === "header") {
|
|
1743
|
+
headers = sub.args.split(",").map((h) => this.stripQuotes(h.trim()));
|
|
1744
|
+
} else if (sub.command === "row") {
|
|
1745
|
+
rows.push(sub.args.split(",").map((c) => this.stripQuotes(c.trim())));
|
|
1746
|
+
}
|
|
1747
|
+
nextIdx++;
|
|
1748
|
+
}
|
|
1749
|
+
return { element: createTable(rows, headers), nextIdx };
|
|
1750
|
+
}
|
|
1504
1751
|
case "quote": {
|
|
1505
1752
|
const authorMatch = tok.args.match(/\bauthor=["']([^"']+)["']/);
|
|
1506
1753
|
const textLines = [];
|
|
@@ -3162,13 +3409,147 @@ var YumiaLinter = class {
|
|
|
3162
3409
|
const errors = issues.filter((i) => i.severity === "error");
|
|
3163
3410
|
const warnings = issues.filter((i) => i.severity === "warning");
|
|
3164
3411
|
const infos = issues.filter((i) => i.severity === "info");
|
|
3412
|
+
const score = Math.max(0, Math.min(100, 100 - errors.length * 20 - warnings.length * 5 - infos.length * 1));
|
|
3413
|
+
const suggestions = [];
|
|
3414
|
+
if (warnings.some((w) => w.code === "YUM001")) {
|
|
3415
|
+
suggestions.push("Reduce text length or split overflowing slides using slide breaks.");
|
|
3416
|
+
}
|
|
3417
|
+
if (warnings.some((w) => w.code === "YUM004")) {
|
|
3418
|
+
suggestions.push("High density detected: consider converting dense bullet lists into cards or a multi-column layout.");
|
|
3419
|
+
}
|
|
3420
|
+
if (warnings.some((w) => w.code === "YUM007")) {
|
|
3421
|
+
suggestions.push('Add alt="description" to all images for full accessibility.');
|
|
3422
|
+
}
|
|
3423
|
+
if (warnings.some((w) => w.code === "YUM010")) {
|
|
3424
|
+
suggestions.push("Increase color contrast between text and background to meet WCAG AA (4.5:1).");
|
|
3425
|
+
}
|
|
3426
|
+
if (suggestions.length === 0 && score === 100) {
|
|
3427
|
+
suggestions.push("Design layout and visual hierarchy are optimal.");
|
|
3428
|
+
}
|
|
3165
3429
|
return {
|
|
3166
3430
|
passed: options.strict ? errors.length === 0 && warnings.length === 0 : errors.length === 0,
|
|
3167
3431
|
totalSlides: presentation.slides.length,
|
|
3168
3432
|
issueCount: issues.length,
|
|
3433
|
+
score,
|
|
3169
3434
|
errors,
|
|
3170
3435
|
warnings,
|
|
3171
|
-
infos
|
|
3436
|
+
infos,
|
|
3437
|
+
suggestions
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
explain(presentation, options = {}) {
|
|
3441
|
+
const lintReport = this.lint(presentation, options);
|
|
3442
|
+
const themeMeta = presentation.metadata.theme;
|
|
3443
|
+
const themeName = typeof themeMeta === "string" ? themeMeta : themeMeta && typeof themeMeta === "object" && "name" in themeMeta ? themeMeta.name || "custom" : "default";
|
|
3444
|
+
const aspectRatio = presentation.metadata.aspectRatio || "16:9";
|
|
3445
|
+
const composition = {
|
|
3446
|
+
heroSlides: 0,
|
|
3447
|
+
metricSlides: 0,
|
|
3448
|
+
comparisonSlides: 0,
|
|
3449
|
+
chartSlides: 0,
|
|
3450
|
+
timelineSlides: 0,
|
|
3451
|
+
cardSlides: 0,
|
|
3452
|
+
contentSlides: 0
|
|
3453
|
+
};
|
|
3454
|
+
const slideTypes = [];
|
|
3455
|
+
const wordCounts = [];
|
|
3456
|
+
presentation.slides.forEach((slide) => {
|
|
3457
|
+
let type = "content";
|
|
3458
|
+
let hasHero = false;
|
|
3459
|
+
let hasMetric = false;
|
|
3460
|
+
let hasCompare = false;
|
|
3461
|
+
let hasChart = false;
|
|
3462
|
+
let hasTimeline = false;
|
|
3463
|
+
let hasCard = false;
|
|
3464
|
+
const inspectEls = (els) => {
|
|
3465
|
+
for (const el of els) {
|
|
3466
|
+
if (el.type === "hero" || el.purpose === "hero" || el.semanticRole === "hero") {
|
|
3467
|
+
hasHero = true;
|
|
3468
|
+
}
|
|
3469
|
+
if (el.type === "metric")
|
|
3470
|
+
hasMetric = true;
|
|
3471
|
+
if (el.type === "compare")
|
|
3472
|
+
hasCompare = true;
|
|
3473
|
+
if (el.type === "chart")
|
|
3474
|
+
hasChart = true;
|
|
3475
|
+
if (el.type === "timeline")
|
|
3476
|
+
hasTimeline = true;
|
|
3477
|
+
if (el.type === "card" || el.type === "grid" || el.type === "columns")
|
|
3478
|
+
hasCard = true;
|
|
3479
|
+
if (el.type === "card" && el.elements) {
|
|
3480
|
+
inspectEls(el.elements);
|
|
3481
|
+
}
|
|
3482
|
+
if (el.type === "columns") {
|
|
3483
|
+
for (const col of el.columns) {
|
|
3484
|
+
inspectEls(col.elements);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
};
|
|
3489
|
+
inspectEls(slide.elements);
|
|
3490
|
+
if (hasHero) {
|
|
3491
|
+
type = "hero";
|
|
3492
|
+
composition.heroSlides++;
|
|
3493
|
+
} else if (hasMetric) {
|
|
3494
|
+
type = "metric";
|
|
3495
|
+
composition.metricSlides++;
|
|
3496
|
+
} else if (hasCompare) {
|
|
3497
|
+
type = "comparison";
|
|
3498
|
+
composition.comparisonSlides++;
|
|
3499
|
+
} else if (hasChart) {
|
|
3500
|
+
type = "chart";
|
|
3501
|
+
composition.chartSlides++;
|
|
3502
|
+
} else if (hasTimeline) {
|
|
3503
|
+
type = "timeline";
|
|
3504
|
+
composition.timelineSlides++;
|
|
3505
|
+
} else if (hasCard) {
|
|
3506
|
+
type = "card";
|
|
3507
|
+
composition.cardSlides++;
|
|
3508
|
+
} else {
|
|
3509
|
+
composition.contentSlides++;
|
|
3510
|
+
}
|
|
3511
|
+
slideTypes.push(type);
|
|
3512
|
+
const { wordCount } = this.calculateSlideDensity(slide.elements);
|
|
3513
|
+
wordCounts.push(wordCount);
|
|
3514
|
+
});
|
|
3515
|
+
const diagnostics = [];
|
|
3516
|
+
const avgWords = wordCounts.length > 0 ? wordCounts.reduce((a, b) => a + b, 0) / wordCounts.length : 0;
|
|
3517
|
+
wordCounts.forEach((wc, i) => {
|
|
3518
|
+
if (avgWords > 20 && wc > avgWords * 1.35) {
|
|
3519
|
+
const pct = Math.round((wc - avgWords) / avgWords * 100);
|
|
3520
|
+
diagnostics.push({
|
|
3521
|
+
type: "density",
|
|
3522
|
+
message: `Slide ${i + 1} contains ${pct}% more text than the presentation average.`
|
|
3523
|
+
});
|
|
3524
|
+
}
|
|
3525
|
+
});
|
|
3526
|
+
for (let i = 0; i <= slideTypes.length - 3; i++) {
|
|
3527
|
+
const t1 = slideTypes[i];
|
|
3528
|
+
const t2 = slideTypes[i + 1];
|
|
3529
|
+
const t3 = slideTypes[i + 2];
|
|
3530
|
+
if (t1 && t1 === t2 && t2 === t3 && t1 !== "hero") {
|
|
3531
|
+
diagnostics.push({
|
|
3532
|
+
type: "repetition",
|
|
3533
|
+
message: `Slides ${i + 1}\u2013${i + 3} use identical '${t1}' composition. \u2192 Consider introducing a visual break (e.g. metric, quote, or split compare).`
|
|
3534
|
+
});
|
|
3535
|
+
i += 2;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
const visualHierarchyScore = Math.min(100, Math.max(60, Math.round(100 - lintReport.warnings.filter((w) => w.code === "YUM003" || w.code === "YUM006").length * 10)));
|
|
3539
|
+
return {
|
|
3540
|
+
slidesCount: presentation.slides.length,
|
|
3541
|
+
theme: themeName,
|
|
3542
|
+
aspectRatio,
|
|
3543
|
+
composition,
|
|
3544
|
+
design: {
|
|
3545
|
+
typographyScale: "\u2713 Scaled (Modular 1.25)",
|
|
3546
|
+
contrast: lintReport.warnings.some((w) => w.code === "YUM010") ? "AA Warning" : "AAA / WCAG 2.1",
|
|
3547
|
+
safeArea: "\u2713 Compliant (90px margin buffer)",
|
|
3548
|
+
densityScore: lintReport.score,
|
|
3549
|
+
visualHierarchyScore
|
|
3550
|
+
},
|
|
3551
|
+
diagnostics,
|
|
3552
|
+
suggestions: lintReport.suggestions
|
|
3172
3553
|
};
|
|
3173
3554
|
}
|
|
3174
3555
|
getLuminance(hex) {
|
|
@@ -3368,6 +3749,13 @@ var YumiaCompiler = class {
|
|
|
3368
3749
|
...options
|
|
3369
3750
|
});
|
|
3370
3751
|
}
|
|
3752
|
+
explain(sourceOrPresentation, options) {
|
|
3753
|
+
const presentation = typeof sourceOrPresentation === "string" ? this.parse(sourceOrPresentation) : sourceOrPresentation;
|
|
3754
|
+
return this.linter.explain(presentation, {
|
|
3755
|
+
...this.viewport ? { viewport: this.viewport } : {},
|
|
3756
|
+
...options
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3371
3759
|
layout(presentation, viewport) {
|
|
3372
3760
|
return this.layoutEngine.computePresentation(presentation, viewport ?? this.viewport);
|
|
3373
3761
|
}
|
|
@@ -3790,6 +4178,12 @@ var PptxRenderer = class {
|
|
|
3790
4178
|
case "table":
|
|
3791
4179
|
this.renderTable(pptxSlide, element, rect, theme);
|
|
3792
4180
|
break;
|
|
4181
|
+
case "hero":
|
|
4182
|
+
this.renderHero(pptxSlide, pptx, node, scaleX, scaleY, theme, presentation);
|
|
4183
|
+
break;
|
|
4184
|
+
case "callout":
|
|
4185
|
+
this.renderCallout(pptxSlide, pptx, element, rect, theme);
|
|
4186
|
+
break;
|
|
3793
4187
|
case "image":
|
|
3794
4188
|
this.renderImage(pptxSlide, element, rect);
|
|
3795
4189
|
break;
|
|
@@ -4256,6 +4650,95 @@ var PptxRenderer = class {
|
|
|
4256
4650
|
valign: "middle"
|
|
4257
4651
|
});
|
|
4258
4652
|
}
|
|
4653
|
+
renderCallout(pptxSlide, pptx, callout, rect, theme) {
|
|
4654
|
+
const sevColor = this.cleanHexColor(callout.severity === "warning" ? theme.colors.warning || "#f59e0b" : callout.severity === "danger" ? theme.colors.danger || "#ef4444" : callout.severity === "success" ? theme.colors.success || "#10b981" : theme.colors.info || theme.colors.primary);
|
|
4655
|
+
pptxSlide.addShape(pptx.ShapeType.roundRect, {
|
|
4656
|
+
x: rect.x,
|
|
4657
|
+
y: rect.y,
|
|
4658
|
+
w: rect.w,
|
|
4659
|
+
h: rect.h,
|
|
4660
|
+
fill: { color: this.cleanHexColor(theme.colors.surface) },
|
|
4661
|
+
line: { color: sevColor, width: 2 },
|
|
4662
|
+
rectRadius: 0.08
|
|
4663
|
+
});
|
|
4664
|
+
const titleH = callout.title ? 0.35 : 0;
|
|
4665
|
+
if (callout.title) {
|
|
4666
|
+
pptxSlide.addText(callout.title, {
|
|
4667
|
+
x: rect.x + 0.15,
|
|
4668
|
+
y: rect.y + 0.08,
|
|
4669
|
+
w: rect.w - 0.3,
|
|
4670
|
+
h: 0.3,
|
|
4671
|
+
fontSize: 13,
|
|
4672
|
+
bold: true,
|
|
4673
|
+
color: sevColor,
|
|
4674
|
+
fontFace: cleanFontFace(theme.typography.headingFont)
|
|
4675
|
+
});
|
|
4676
|
+
}
|
|
4677
|
+
pptxSlide.addText(callout.text, {
|
|
4678
|
+
x: rect.x + 0.15,
|
|
4679
|
+
y: rect.y + titleH + 0.08,
|
|
4680
|
+
w: rect.w - 0.3,
|
|
4681
|
+
h: rect.h - titleH - 0.16,
|
|
4682
|
+
fontSize: 12,
|
|
4683
|
+
color: this.cleanHexColor(theme.colors.text),
|
|
4684
|
+
fontFace: cleanFontFace(theme.typography.bodyFont),
|
|
4685
|
+
valign: "top"
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
renderHero(pptxSlide, pptx, node, scaleX, scaleY, theme, presentation) {
|
|
4689
|
+
const hero = node.element;
|
|
4690
|
+
const rect = {
|
|
4691
|
+
x: node.bounds.x * scaleX,
|
|
4692
|
+
y: node.bounds.y * scaleY,
|
|
4693
|
+
w: node.bounds.width * scaleX,
|
|
4694
|
+
h: node.bounds.height * scaleY
|
|
4695
|
+
};
|
|
4696
|
+
let curY = rect.y;
|
|
4697
|
+
if (hero.tagline) {
|
|
4698
|
+
pptxSlide.addText(hero.tagline.toUpperCase(), {
|
|
4699
|
+
x: rect.x,
|
|
4700
|
+
y: curY,
|
|
4701
|
+
w: rect.w,
|
|
4702
|
+
h: 0.35,
|
|
4703
|
+
fontSize: 11,
|
|
4704
|
+
bold: true,
|
|
4705
|
+
color: this.cleanHexColor(theme.colors.primary),
|
|
4706
|
+
fontFace: cleanFontFace(theme.typography.headingFont),
|
|
4707
|
+
align: hero.align || "center"
|
|
4708
|
+
});
|
|
4709
|
+
curY += 0.4;
|
|
4710
|
+
}
|
|
4711
|
+
pptxSlide.addText(hero.title, {
|
|
4712
|
+
x: rect.x,
|
|
4713
|
+
y: curY,
|
|
4714
|
+
w: rect.w,
|
|
4715
|
+
h: 0.9,
|
|
4716
|
+
fontSize: 32,
|
|
4717
|
+
bold: true,
|
|
4718
|
+
color: this.cleanHexColor(theme.colors.text),
|
|
4719
|
+
fontFace: cleanFontFace(theme.typography.headingFont),
|
|
4720
|
+
align: hero.align || "center"
|
|
4721
|
+
});
|
|
4722
|
+
curY += 0.95;
|
|
4723
|
+
if (hero.subtitle) {
|
|
4724
|
+
pptxSlide.addText(hero.subtitle, {
|
|
4725
|
+
x: rect.x,
|
|
4726
|
+
y: curY,
|
|
4727
|
+
w: rect.w,
|
|
4728
|
+
h: 0.6,
|
|
4729
|
+
fontSize: 16,
|
|
4730
|
+
color: this.cleanHexColor(theme.colors.muted || theme.colors.text),
|
|
4731
|
+
fontFace: cleanFontFace(theme.typography.bodyFont),
|
|
4732
|
+
align: hero.align || "center"
|
|
4733
|
+
});
|
|
4734
|
+
curY += 0.65;
|
|
4735
|
+
}
|
|
4736
|
+
if (node.children) {
|
|
4737
|
+
for (const childNode of node.children) {
|
|
4738
|
+
this.renderNode(pptxSlide, pptx, childNode, scaleX, scaleY, theme, presentation);
|
|
4739
|
+
}
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4259
4742
|
renderChart(pptxSlide, pptx, chart, rect, theme) {
|
|
4260
4743
|
const series = chart.series || [];
|
|
4261
4744
|
const labels = chart.labels || [];
|
|
@@ -4798,6 +5281,7 @@ var HtmlRenderer = class {
|
|
|
4798
5281
|
<title>${this.escapeHtml(title)}</title>
|
|
4799
5282
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
4800
5283
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
5284
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700;900&family=Fira+Code:wght@400;500;600;700&family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;0,800;1,400&family=Orbitron:wght@500;700;900&family=Outfit:wght@400;500;600;700;800;900&family=Playfair+Display:ital,wght@0,500;0,700;0,900;1,400;1,700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Space+Grotesk:wght@400;600;700&display=swap">
|
|
4801
5285
|
${customStyles}
|
|
4802
5286
|
${customScripts}
|
|
4803
5287
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" crossorigin="anonymous">
|
|
@@ -5946,15 +6430,35 @@ var HtmlRenderer = class {
|
|
|
5946
6430
|
height: 160% !important;
|
|
5947
6431
|
}
|
|
5948
6432
|
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6433
|
+
/* Visual Inspector Overlay */
|
|
6434
|
+
body.yumia-inspect-active * {
|
|
6435
|
+
cursor: crosshair !important;
|
|
6436
|
+
}
|
|
6437
|
+
.yumia-inspect-highlight {
|
|
6438
|
+
outline: 2px dashed var(--yumia-accent) !important;
|
|
6439
|
+
outline-offset: 3px !important;
|
|
6440
|
+
box-shadow: 0 0 15px var(--yumia-accent) !important;
|
|
6441
|
+
}
|
|
6442
|
+
.yumia-inspector-popup {
|
|
6443
|
+
position: fixed;
|
|
6444
|
+
bottom: 60px;
|
|
6445
|
+
right: 20px;
|
|
6446
|
+
z-index: 9999;
|
|
6447
|
+
background: rgba(10, 15, 30, 0.95);
|
|
6448
|
+
backdrop-filter: blur(16px);
|
|
6449
|
+
border: 1.5px solid var(--yumia-primary);
|
|
6450
|
+
border-radius: 10px;
|
|
6451
|
+
padding: 12px 18px;
|
|
6452
|
+
color: #fff;
|
|
6453
|
+
font-size: 13px;
|
|
6454
|
+
font-family: var(--yumia-font-code);
|
|
6455
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.7), 0 0 15px rgba(0, 240, 255, 0.25);
|
|
6456
|
+
display: none;
|
|
6457
|
+
max-width: 380px;
|
|
6458
|
+
line-height: 1.5;
|
|
6459
|
+
}
|
|
6460
|
+
.yumia-inspector-popup.open {
|
|
6461
|
+
display: block;
|
|
5958
6462
|
}
|
|
5959
6463
|
</style>
|
|
5960
6464
|
</head>
|
|
@@ -5970,10 +6474,12 @@ var HtmlRenderer = class {
|
|
|
5970
6474
|
<button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
|
|
5971
6475
|
<button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
|
|
5972
6476
|
<button class="yumia-btn" id="btn-speaker" title="Speaker View (S)">\u{1F3A4}</button>
|
|
6477
|
+
<button class="yumia-btn" id="btn-inspect" title="Visual Inspector (I / Alt+Click)">\u{1F50D}</button>
|
|
5973
6478
|
<button class="yumia-btn" id="btn-print" title="Stampa / Salva PDF (Ctrl+P)">\u{1F5A8}\uFE0F</button>
|
|
5974
6479
|
<button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
|
|
5975
6480
|
<button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
|
|
5976
6481
|
</div>
|
|
6482
|
+
<div id="inspector-popup" class="yumia-inspector-popup"></div>
|
|
5977
6483
|
|
|
5978
6484
|
<div id="notes-drawer" class="yumia-notes-drawer">
|
|
5979
6485
|
<div class="yumia-notes-header">
|
|
@@ -6280,6 +6786,14 @@ var HtmlRenderer = class {
|
|
|
6280
6786
|
prev: function() { goToSlide(currentIdx - 1); },
|
|
6281
6787
|
toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
|
|
6282
6788
|
toggleOverview: function() { toggleOverview(); },
|
|
6789
|
+
toggleInspect: function() {
|
|
6790
|
+
const isActive = document.body.classList.toggle('yumia-inspect-active');
|
|
6791
|
+
const popup = document.getElementById('inspector-popup');
|
|
6792
|
+
if (!isActive && popup) {
|
|
6793
|
+
popup.classList.remove('open');
|
|
6794
|
+
document.querySelectorAll('.yumia-inspect-highlight').forEach((el) => el.classList.remove('yumia-inspect-highlight'));
|
|
6795
|
+
}
|
|
6796
|
+
},
|
|
6283
6797
|
openSpeaker: openSpeakerWindow,
|
|
6284
6798
|
print: function() { window.print(); },
|
|
6285
6799
|
toggleFs: function() {
|
|
@@ -6294,6 +6808,7 @@ var HtmlRenderer = class {
|
|
|
6294
6808
|
document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
|
|
6295
6809
|
document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
|
|
6296
6810
|
document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
|
|
6811
|
+
document.getElementById('btn-inspect')?.addEventListener('click', window.deckController.toggleInspect);
|
|
6297
6812
|
document.getElementById('btn-close-notes')?.addEventListener('click', () => {
|
|
6298
6813
|
notesDrawer?.classList.remove('open');
|
|
6299
6814
|
});
|
|
@@ -6303,6 +6818,30 @@ var HtmlRenderer = class {
|
|
|
6303
6818
|
document.getElementById('btn-print')?.addEventListener('click', window.deckController.print);
|
|
6304
6819
|
document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
|
|
6305
6820
|
|
|
6821
|
+
// Interactive Element Inspector Hover & Click
|
|
6822
|
+
document.addEventListener('mouseover', function(e) {
|
|
6823
|
+
if (!document.body.classList.contains('yumia-inspect-active')) return;
|
|
6824
|
+
const target = e.target.closest('[data-yumia-role], .yumia-card, .yumia-metric, .yumia-hero, .yumia-callout, .yumia-grid, .yumia-stack, h1, h2, h3, p, img, pre');
|
|
6825
|
+
if (!target || target.closest('#deck-controls') || target.closest('#inspector-popup')) return;
|
|
6826
|
+
|
|
6827
|
+
document.querySelectorAll('.yumia-inspect-highlight').forEach((el) => el.classList.remove('yumia-inspect-highlight'));
|
|
6828
|
+
target.classList.add('yumia-inspect-highlight');
|
|
6829
|
+
|
|
6830
|
+
const popup = document.getElementById('inspector-popup');
|
|
6831
|
+
if (popup) {
|
|
6832
|
+
const role = target.getAttribute('data-yumia-role') || target.tagName.toLowerCase();
|
|
6833
|
+
const variant = target.getAttribute('data-variant') || target.getAttribute('data-severity') || 'default';
|
|
6834
|
+
const loc = target.getAttribute('data-yumia-loc') || 'slide ' + (currentIdx + 1);
|
|
6835
|
+
popup.innerHTML = \`
|
|
6836
|
+
<div style="color:var(--yumia-primary); font-weight:800; font-size:14px; margin-bottom:4px;">\u{1F50D} \${role.toUpperCase()}</div>
|
|
6837
|
+
<div><strong>Variant:</strong> <span style="color:var(--yumia-accent);">\${variant}</span></div>
|
|
6838
|
+
<div><strong>Location:</strong> \${loc}</div>
|
|
6839
|
+
<div style="margin-top:6px; font-size:11px; color:var(--yumia-muted);">Press 'I' to exit inspector</div>
|
|
6840
|
+
\`;
|
|
6841
|
+
popup.classList.add('open');
|
|
6842
|
+
}
|
|
6843
|
+
});
|
|
6844
|
+
|
|
6306
6845
|
window.addEventListener('keydown', function(e) {
|
|
6307
6846
|
if (overviewModal && overviewModal.classList.contains('open')) {
|
|
6308
6847
|
if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
@@ -6337,6 +6876,8 @@ var HtmlRenderer = class {
|
|
|
6337
6876
|
window.deckController.print();
|
|
6338
6877
|
} else if (e.key.toLowerCase() === 'n') {
|
|
6339
6878
|
window.deckController.toggleNotes();
|
|
6879
|
+
} else if (e.key.toLowerCase() === 'i') {
|
|
6880
|
+
window.deckController.toggleInspect();
|
|
6340
6881
|
} else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
6341
6882
|
window.deckController.toggleOverview();
|
|
6342
6883
|
}
|
|
@@ -6372,8 +6913,8 @@ var HtmlRenderer = class {
|
|
|
6372
6913
|
const transition = slide.transition || "fade";
|
|
6373
6914
|
const transType = typeof transition === "string" ? transition : transition.type;
|
|
6374
6915
|
const transClass = `transition-${transType || "fade"}`;
|
|
6375
|
-
const isWatermarkEnabled = watermarkOpt !== false;
|
|
6376
|
-
const watermarkText = typeof watermarkOpt === "string" && watermarkOpt.trim().length > 0 ? watermarkOpt : "
|
|
6916
|
+
const isWatermarkEnabled = watermarkOpt !== void 0 && watermarkOpt !== false && watermarkOpt !== "none" && watermarkOpt !== "false" && watermarkOpt !== "off";
|
|
6917
|
+
const watermarkText = typeof watermarkOpt === "string" && watermarkOpt.trim().length > 0 && watermarkOpt !== "true" && watermarkOpt !== "yes" && watermarkOpt !== "on" ? watermarkOpt : "Yumia";
|
|
6377
6918
|
const watermarkHtml = isWatermarkEnabled ? `<div class="yumia-watermark">${this.escapeHtml(watermarkText)}</div>` : "";
|
|
6378
6919
|
const elementsHtml = slide.elements.map((el) => this.renderElement(el, theme, presentation)).join("\n");
|
|
6379
6920
|
return `
|
|
@@ -6385,6 +6926,32 @@ var HtmlRenderer = class {
|
|
|
6385
6926
|
}
|
|
6386
6927
|
renderElement(element, theme, presentation) {
|
|
6387
6928
|
switch (element.type) {
|
|
6929
|
+
case "hero": {
|
|
6930
|
+
const hero = element;
|
|
6931
|
+
const align = hero.align || "center";
|
|
6932
|
+
const tagHtml = hero.tagline ? `<div class="yumia-hero-tagline" style="display:inline-flex; align-items:center; padding:4px 14px; border-radius:999px; background:rgba(255,255,255,0.08); border:1px solid var(--yumia-primary); color:var(--yumia-primary); font-size:0.85rem; font-weight:700; margin-bottom:1rem; letter-spacing:0.06em; text-transform:uppercase;">${this.formatInline(hero.tagline)}</div>` : "";
|
|
6933
|
+
const titleHtml = `<h1 class="yumia-hero-title" style="font-size:clamp(2.4rem, 4.5vw, 4rem); font-weight:800; line-height:1.1; color:var(--yumia-text); margin-bottom:0.8rem;">${this.formatInline(hero.title)}</h1>`;
|
|
6934
|
+
const subHtml = hero.subtitle ? `<p class="yumia-hero-subtitle" style="font-size:clamp(1.1rem, 1.8vw, 1.5rem); color:var(--yumia-muted); max-width:800px; margin-bottom:1.5rem; line-height:1.5;">${this.formatInline(hero.subtitle)}</p>` : "";
|
|
6935
|
+
const innerHtml = hero.elements ? hero.elements.map((child) => this.renderElement(child, theme, presentation)).join("\n") : "";
|
|
6936
|
+
return `
|
|
6937
|
+
<div class="yumia-hero" data-align="${align}" data-yumia-role="hero" style="display:flex; flex-direction:column; align-items:${align === "center" ? "center" : align === "right" ? "flex-end" : "flex-start"}; text-align:${align}; justify-content:center; flex:1; width:100%; margin:auto 0;">
|
|
6938
|
+
${tagHtml}
|
|
6939
|
+
${titleHtml}
|
|
6940
|
+
${subHtml}
|
|
6941
|
+
${innerHtml ? `<div style="width:100%; margin-top:1rem;">${innerHtml}</div>` : ""}
|
|
6942
|
+
</div>`;
|
|
6943
|
+
}
|
|
6944
|
+
case "callout": {
|
|
6945
|
+
const c = element;
|
|
6946
|
+
const sev = c.severity || "info";
|
|
6947
|
+
const colorVar = sev === "warning" ? "var(--yumia-warning)" : sev === "danger" ? "var(--yumia-danger)" : sev === "success" ? "var(--yumia-success)" : "var(--yumia-info)";
|
|
6948
|
+
const titleHtml = c.title ? `<div style="font-weight:700; font-size:1.05rem; color:${colorVar}; margin-bottom:4px;">${this.formatInline(c.title)}</div>` : "";
|
|
6949
|
+
return `
|
|
6950
|
+
<div class="yumia-callout" data-severity="${sev}" data-yumia-role="callout" style="background:var(--yumia-surface); border-left:4px solid ${colorVar}; border-radius:var(--yumia-radius-card); padding:1rem 1.4rem; margin:0.8rem 0; width:100%;">
|
|
6951
|
+
${titleHtml}
|
|
6952
|
+
<div style="font-size:0.95rem; color:var(--yumia-text); line-height:1.5;">${this.formatInline(c.text)}</div>
|
|
6953
|
+
</div>`;
|
|
6954
|
+
}
|
|
6388
6955
|
case "heading": {
|
|
6389
6956
|
const h = element;
|
|
6390
6957
|
const tag = `h${Math.min(4, Math.max(1, h.level))}`;
|
|
@@ -6525,7 +7092,18 @@ var HtmlRenderer = class {
|
|
|
6525
7092
|
case "image": {
|
|
6526
7093
|
const img = element;
|
|
6527
7094
|
const alt = img.alt ? `alt="${this.escapeHtml(img.alt)}"` : "";
|
|
6528
|
-
|
|
7095
|
+
const width = img.width ? typeof img.width === "number" ? `${img.width}px` : img.width : "100%";
|
|
7096
|
+
const height = img.height ? typeof img.height === "number" ? `${img.height}px` : img.height : "auto";
|
|
7097
|
+
const fit = img.fit || "cover";
|
|
7098
|
+
const radius = img.radius !== void 0 ? typeof img.radius === "number" ? `${img.radius}px` : img.radius : "var(--yumia-radius-card)";
|
|
7099
|
+
const shadowStyle = img.shadow ? "box-shadow: 0 12px 30px rgba(0,0,0,0.45);" : "";
|
|
7100
|
+
const aspect = img.aspectRatio ? `aspect-ratio: ${img.aspectRatio};` : "";
|
|
7101
|
+
const cap = img.caption ? `<figcaption style="margin-top: 6px; font-size: 0.85rem; color: var(--yumia-muted); text-align: center;">${this.formatInline(img.caption)}</figcaption>` : "";
|
|
7102
|
+
return `
|
|
7103
|
+
<figure class="yumia-image-wrapper" style="margin: 0.4rem 0; width: ${width}; max-width: 100%; display: flex; flex-direction: column; align-items: center;">
|
|
7104
|
+
<img src="${this.escapeHtml(img.src)}" ${alt} style="width: 100%; height: ${height}; object-fit: ${fit}; border-radius: ${radius}; ${aspect} ${shadowStyle} border: 1.5px solid var(--yumia-border);">
|
|
7105
|
+
${cap}
|
|
7106
|
+
</figure>`;
|
|
6529
7107
|
}
|
|
6530
7108
|
case "metric": {
|
|
6531
7109
|
const m = element;
|
|
@@ -6851,6 +7429,48 @@ var PdfRenderer = class {
|
|
|
6851
7429
|
}
|
|
6852
7430
|
renderElement(doc, element, x, y, width, theme, presentation) {
|
|
6853
7431
|
switch (element.type) {
|
|
7432
|
+
case "hero": {
|
|
7433
|
+
const hero = element;
|
|
7434
|
+
let curY = y;
|
|
7435
|
+
if (hero.tagline) {
|
|
7436
|
+
doc.font("Helvetica-Bold").fontSize(11).fillColor(theme.colors.primary);
|
|
7437
|
+
doc.text(this.stripFormatting(hero.tagline).toUpperCase(), x, curY, { width });
|
|
7438
|
+
curY += 20;
|
|
7439
|
+
}
|
|
7440
|
+
doc.font("Helvetica-Bold").fontSize(34).fillColor(theme.colors.text);
|
|
7441
|
+
doc.text(this.stripFormatting(hero.title), x, curY, { width, lineGap: 6 });
|
|
7442
|
+
curY += doc.heightOfString(this.stripFormatting(hero.title), { width }) + 8;
|
|
7443
|
+
if (hero.subtitle) {
|
|
7444
|
+
doc.font("Helvetica").fontSize(16).fillColor(theme.colors.muted || "#888888");
|
|
7445
|
+
doc.text(this.stripFormatting(hero.subtitle), x, curY, { width, lineGap: 4 });
|
|
7446
|
+
curY += doc.heightOfString(this.stripFormatting(hero.subtitle), { width }) + 12;
|
|
7447
|
+
}
|
|
7448
|
+
if (hero.elements) {
|
|
7449
|
+
for (const child of hero.elements) {
|
|
7450
|
+
curY = this.renderElement(doc, child, x, curY, width, theme, presentation) + 10;
|
|
7451
|
+
}
|
|
7452
|
+
}
|
|
7453
|
+
return curY;
|
|
7454
|
+
}
|
|
7455
|
+
case "callout": {
|
|
7456
|
+
const c = element;
|
|
7457
|
+
const sevColor = c.severity === "warning" ? theme.colors.warning || "#f59e0b" : c.severity === "danger" ? theme.colors.danger || "#ef4444" : c.severity === "success" ? theme.colors.success || "#10b981" : theme.colors.info || theme.colors.primary;
|
|
7458
|
+
doc.font("Helvetica").fontSize(12);
|
|
7459
|
+
const textH = doc.heightOfString(this.stripFormatting(c.text), { width: width - 30 });
|
|
7460
|
+
const titleH = c.title ? 20 : 0;
|
|
7461
|
+
const totalH = textH + titleH + 16;
|
|
7462
|
+
doc.roundedRect(x, y, width, totalH, 6).fill(theme.colors.surface || "#151522");
|
|
7463
|
+
doc.rect(x, y, 4, totalH).fill(sevColor);
|
|
7464
|
+
let curY = y + 8;
|
|
7465
|
+
if (c.title) {
|
|
7466
|
+
doc.font("Helvetica-Bold").fontSize(12).fillColor(sevColor);
|
|
7467
|
+
doc.text(this.stripFormatting(c.title), x + 16, curY, { width: width - 26 });
|
|
7468
|
+
curY += 18;
|
|
7469
|
+
}
|
|
7470
|
+
doc.font("Helvetica").fontSize(12).fillColor(theme.colors.text);
|
|
7471
|
+
doc.text(this.stripFormatting(c.text), x + 16, curY, { width: width - 26 });
|
|
7472
|
+
return y + totalH + 8;
|
|
7473
|
+
}
|
|
6854
7474
|
case "heading": {
|
|
6855
7475
|
const h = element;
|
|
6856
7476
|
const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
|
|
@@ -7421,7 +8041,7 @@ function startDevServer(filePath, options = {}) {
|
|
|
7421
8041
|
// src/cli.ts
|
|
7422
8042
|
import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
|
|
7423
8043
|
import { basename, dirname, extname, join, resolve as resolve2 } from "path";
|
|
7424
|
-
var VERSION = "0.1.
|
|
8044
|
+
var VERSION = "0.1.24";
|
|
7425
8045
|
function printHelp() {
|
|
7426
8046
|
return `
|
|
7427
8047
|
YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
|
|
@@ -7430,10 +8050,12 @@ Usage:
|
|
|
7430
8050
|
yumia <command> [options] [file]
|
|
7431
8051
|
|
|
7432
8052
|
Commands:
|
|
7433
|
-
init [name] Scaffold a new
|
|
7434
|
-
dev <file> Start live-reload dev server with instant HTML preview
|
|
8053
|
+
init [name] Scaffold a new Yumia presentation project
|
|
8054
|
+
dev <file> Start live-reload dev server with instant HTML preview & Inspector
|
|
8055
|
+
check <file> Run design audit, contrast & density checks (--optimize)
|
|
8056
|
+
explain <file> Explain document composition, design metrics, and visual rhythm
|
|
7435
8057
|
watch <file> Watch presentation file and recompile automatically on save
|
|
7436
|
-
validate <file> Validate a
|
|
8058
|
+
validate <file> Validate a presentation syntax and AST structure
|
|
7437
8059
|
lint <file> Analyze presentation for layout overflows and accessibility
|
|
7438
8060
|
inspect <file> Inspect the AST and geometric layout tree
|
|
7439
8061
|
schema Output machine-readable JSON schema for AI agents
|
|
@@ -7681,11 +8303,12 @@ ${errorLines.join("\n")}`
|
|
|
7681
8303
|
};
|
|
7682
8304
|
}
|
|
7683
8305
|
}
|
|
7684
|
-
if (command === "lint") {
|
|
8306
|
+
if (command === "lint" || command === "check") {
|
|
7685
8307
|
if (!target) {
|
|
7686
|
-
const msg = "Error: Please specify a file to lint.";
|
|
8308
|
+
const msg = "Error: Please specify a file to check or lint.";
|
|
7687
8309
|
return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
|
|
7688
8310
|
}
|
|
8311
|
+
const isOptimize = args.includes("--optimize");
|
|
7689
8312
|
try {
|
|
7690
8313
|
const source = readFileSync2(target, "utf-8");
|
|
7691
8314
|
const compiler = new YumiaCompiler();
|
|
@@ -7696,6 +8319,7 @@ ${errorLines.join("\n")}`
|
|
|
7696
8319
|
output: JSON.stringify(
|
|
7697
8320
|
{
|
|
7698
8321
|
target,
|
|
8322
|
+
optimize: isOptimize,
|
|
7699
8323
|
...report
|
|
7700
8324
|
},
|
|
7701
8325
|
null,
|
|
@@ -7703,10 +8327,15 @@ ${errorLines.join("\n")}`
|
|
|
7703
8327
|
)
|
|
7704
8328
|
};
|
|
7705
8329
|
}
|
|
8330
|
+
const scoreStr = `Visual Quality Score: ${report.score}/100`;
|
|
8331
|
+
const suggestionLines = report.suggestions && report.suggestions.length > 0 ? `
|
|
8332
|
+
Design Suggestions:
|
|
8333
|
+
${report.suggestions.map((s) => ` \u2192 ${s}`).join("\n")}` : "";
|
|
7706
8334
|
if (report.issueCount === 0) {
|
|
7707
8335
|
return {
|
|
7708
8336
|
exitCode: 0,
|
|
7709
|
-
output: `\u2713 Yumia Lint: All ${report.totalSlides} slide(s) passed with 0 issues
|
|
8337
|
+
output: `\u2713 Yumia Lint / Design Audit: All ${report.totalSlides} slide(s) passed with 0 issues.
|
|
8338
|
+
${scoreStr}${suggestionLines}`
|
|
7710
8339
|
};
|
|
7711
8340
|
} else {
|
|
7712
8341
|
const issueLines = [];
|
|
@@ -7723,13 +8352,83 @@ ${errorLines.join("\n")}`
|
|
|
7723
8352
|
const exitCode = report.passed ? 0 : 1;
|
|
7724
8353
|
return {
|
|
7725
8354
|
exitCode,
|
|
7726
|
-
output: `Yumia
|
|
8355
|
+
output: `Yumia Design Audit Report for '${target}':
|
|
7727
8356
|
|
|
7728
8357
|
${issueLines.join("\n")}
|
|
7729
8358
|
|
|
7730
|
-
${summary}
|
|
8359
|
+
${summary}
|
|
8360
|
+
${scoreStr}${suggestionLines}${isStrict && report.warnings.length > 0 ? "\n(failed due to --strict)" : ""}`
|
|
8361
|
+
};
|
|
8362
|
+
}
|
|
8363
|
+
} catch (err) {
|
|
8364
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8365
|
+
if (isJson) {
|
|
8366
|
+
return { exitCode: 1, output: JSON.stringify({ error: msg }) };
|
|
8367
|
+
}
|
|
8368
|
+
return {
|
|
8369
|
+
exitCode: 1,
|
|
8370
|
+
output: `\u2717 Design audit failed: ${msg}`
|
|
8371
|
+
};
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
if (command === "explain") {
|
|
8375
|
+
if (!target) {
|
|
8376
|
+
const msg = "Error: Please specify a file to explain.";
|
|
8377
|
+
return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
|
|
8378
|
+
}
|
|
8379
|
+
try {
|
|
8380
|
+
const source = readFileSync2(target, "utf-8");
|
|
8381
|
+
const compiler = new YumiaCompiler();
|
|
8382
|
+
const exp = compiler.explain(source, { strict: isStrict });
|
|
8383
|
+
if (isJson) {
|
|
8384
|
+
return {
|
|
8385
|
+
exitCode: 0,
|
|
8386
|
+
output: JSON.stringify({ target, ...exp }, null, 2)
|
|
7731
8387
|
};
|
|
7732
8388
|
}
|
|
8389
|
+
const compLines = [
|
|
8390
|
+
` Hero slides: ${exp.composition.heroSlides}`,
|
|
8391
|
+
` Metric slides: ${exp.composition.metricSlides}`,
|
|
8392
|
+
` Comparison slides: ${exp.composition.comparisonSlides}`,
|
|
8393
|
+
` Chart slides: ${exp.composition.chartSlides}`,
|
|
8394
|
+
` Timeline slides: ${exp.composition.timelineSlides}`,
|
|
8395
|
+
` Card / Grid: ${exp.composition.cardSlides}`,
|
|
8396
|
+
` Content slides: ${exp.composition.contentSlides}`
|
|
8397
|
+
];
|
|
8398
|
+
const designLines = [
|
|
8399
|
+
` Typography scale: ${exp.design.typographyScale}`,
|
|
8400
|
+
` Contrast: ${exp.design.contrast}`,
|
|
8401
|
+
` Safe area: ${exp.design.safeArea}`,
|
|
8402
|
+
` Density score: ${exp.design.densityScore}/100`,
|
|
8403
|
+
` Visual hierarchy: ${exp.design.visualHierarchyScore}/100`
|
|
8404
|
+
];
|
|
8405
|
+
const diagLines = exp.diagnostics.length > 0 ? exp.diagnostics.map((d) => ` \u26A0 ${d.message}`).join("\n") : " \u2713 No rhythm or density imbalances detected.";
|
|
8406
|
+
const sugLines = exp.suggestions.length > 0 ? exp.suggestions.map((s) => ` \u2192 ${s}`).join("\n") : " \u2713 Layout and structure are balanced.";
|
|
8407
|
+
const out = [
|
|
8408
|
+
`Yumia Design Explanation for '${target}'`,
|
|
8409
|
+
`================================================`,
|
|
8410
|
+
`Document Analysis`,
|
|
8411
|
+
` Slides: ${exp.slidesCount}`,
|
|
8412
|
+
` Theme: ${exp.theme}`,
|
|
8413
|
+
` Aspect Ratio: ${exp.aspectRatio}`,
|
|
8414
|
+
``,
|
|
8415
|
+
`Composition Distribution`,
|
|
8416
|
+
...compLines,
|
|
8417
|
+
``,
|
|
8418
|
+
`Design Intelligence`,
|
|
8419
|
+
...designLines,
|
|
8420
|
+
``,
|
|
8421
|
+
`Diagnostics & Rhythm`,
|
|
8422
|
+
diagLines,
|
|
8423
|
+
``,
|
|
8424
|
+
`Design Suggestions`,
|
|
8425
|
+
sugLines,
|
|
8426
|
+
`================================================`
|
|
8427
|
+
].join("\n");
|
|
8428
|
+
return {
|
|
8429
|
+
exitCode: 0,
|
|
8430
|
+
output: out
|
|
8431
|
+
};
|
|
7733
8432
|
} catch (err) {
|
|
7734
8433
|
const msg = err instanceof Error ? err.message : String(err);
|
|
7735
8434
|
if (isJson) {
|
|
@@ -7737,7 +8436,7 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
7737
8436
|
}
|
|
7738
8437
|
return {
|
|
7739
8438
|
exitCode: 1,
|
|
7740
|
-
output: `\u2717
|
|
8439
|
+
output: `\u2717 Explain failed: ${msg}`
|
|
7741
8440
|
};
|
|
7742
8441
|
}
|
|
7743
8442
|
}
|
|
@@ -7969,6 +8668,8 @@ export {
|
|
|
7969
8668
|
createStack,
|
|
7970
8669
|
createComponent,
|
|
7971
8670
|
createSlot,
|
|
8671
|
+
createHero,
|
|
8672
|
+
createCallout,
|
|
7972
8673
|
NativeYumiaParser,
|
|
7973
8674
|
parseNativeYumia,
|
|
7974
8675
|
parseHighlightLines,
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export * from '@yumiamd/renderer-pptx';
|
|
|
9
9
|
export * from '@yumiamd/renderer-html';
|
|
10
10
|
export * from '@yumiamd/renderer-pdf';
|
|
11
11
|
|
|
12
|
-
declare const VERSION = "0.1.
|
|
12
|
+
declare const VERSION = "0.1.24";
|
|
13
13
|
declare function printHelp(): string;
|
|
14
14
|
declare function runCli(argv: string[]): Promise<{
|
|
15
15
|
exitCode: number;
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
cleanFontFace,
|
|
17
17
|
corporateTheme,
|
|
18
18
|
createBadge,
|
|
19
|
+
createCallout,
|
|
19
20
|
createCard,
|
|
20
21
|
createChart,
|
|
21
22
|
createCode,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
createGrid,
|
|
27
28
|
createGroup,
|
|
28
29
|
createHeading,
|
|
30
|
+
createHero,
|
|
29
31
|
createIcon,
|
|
30
32
|
createImage,
|
|
31
33
|
createLayoutDirective,
|
|
@@ -66,7 +68,7 @@ import {
|
|
|
66
68
|
terminalTheme,
|
|
67
69
|
tokyoNightTheme,
|
|
68
70
|
validate
|
|
69
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-FKFINZOL.js";
|
|
70
72
|
export {
|
|
71
73
|
DEFAULT_VIEWPORT,
|
|
72
74
|
DefaultLayoutEngine,
|
|
@@ -85,6 +87,7 @@ export {
|
|
|
85
87
|
cleanFontFace,
|
|
86
88
|
corporateTheme,
|
|
87
89
|
createBadge,
|
|
90
|
+
createCallout,
|
|
88
91
|
createCard,
|
|
89
92
|
createChart,
|
|
90
93
|
createCode,
|
|
@@ -95,6 +98,7 @@ export {
|
|
|
95
98
|
createGrid,
|
|
96
99
|
createGroup,
|
|
97
100
|
createHeading,
|
|
101
|
+
createHero,
|
|
98
102
|
createIcon,
|
|
99
103
|
createImage,
|
|
100
104
|
createLayoutDirective,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yumiamd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"description": "Command line interface and compiler for YumiaMD presentations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"tsup": "^8.5.1",
|
|
24
|
-
"@yumiamd/ast": "0.1.
|
|
25
|
-
"@yumiamd/
|
|
26
|
-
"@yumiamd/renderer-html": "0.1.
|
|
27
|
-
"@yumiamd/
|
|
28
|
-
"@yumiamd/
|
|
29
|
-
"@yumiamd/renderer-
|
|
30
|
-
"@yumiamd/
|
|
31
|
-
"@yumiamd/
|
|
32
|
-
"@yumiamd/
|
|
24
|
+
"@yumiamd/ast": "0.1.24",
|
|
25
|
+
"@yumiamd/core": "0.1.24",
|
|
26
|
+
"@yumiamd/renderer-html": "0.1.24",
|
|
27
|
+
"@yumiamd/renderer": "0.1.24",
|
|
28
|
+
"@yumiamd/parser": "0.1.24",
|
|
29
|
+
"@yumiamd/renderer-pptx": "0.1.24",
|
|
30
|
+
"@yumiamd/layout": "0.1.24",
|
|
31
|
+
"@yumiamd/renderer-pdf": "0.1.24",
|
|
32
|
+
"@yumiamd/theme": "0.1.24"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|
|
35
35
|
"yumia",
|