yumiamd 0.1.23 → 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-GKERS3TA.js → chunk-FKFINZOL.js} +568 -21
- 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
|
@@ -215,6 +215,24 @@ function createSlot(name, elements) {
|
|
|
215
215
|
...elements ? { elements } : {}
|
|
216
216
|
};
|
|
217
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
|
+
}
|
|
218
236
|
|
|
219
237
|
// ../parser/dist/parser.js
|
|
220
238
|
function parseHighlightLines(highlightStr) {
|
|
@@ -678,6 +696,36 @@ var DefaultYumiaParser = class {
|
|
|
678
696
|
const blockBaseLine = directiveStartLine + 1;
|
|
679
697
|
if (directiveName === "notes") {
|
|
680
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);
|
|
681
729
|
} else if (directiveName === "card") {
|
|
682
730
|
let cardTitle;
|
|
683
731
|
let cardVariant;
|
|
@@ -1332,13 +1380,70 @@ var NativeYumiaParser = class {
|
|
|
1332
1380
|
const tok = tokens[idx];
|
|
1333
1381
|
const baseIndent = tok.indent;
|
|
1334
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
|
+
}
|
|
1335
1435
|
case "heading":
|
|
1336
1436
|
case "h1":
|
|
1337
1437
|
case "h2":
|
|
1338
1438
|
case "h3": {
|
|
1339
1439
|
const level = tok.command === "h1" ? 1 : tok.command === "h2" ? 2 : tok.command === "h3" ? 3 : 1;
|
|
1340
1440
|
const text = this.stripQuotes(tok.args);
|
|
1341
|
-
|
|
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 };
|
|
1342
1447
|
}
|
|
1343
1448
|
case "paragraph":
|
|
1344
1449
|
case "text":
|
|
@@ -3304,13 +3409,147 @@ var YumiaLinter = class {
|
|
|
3304
3409
|
const errors = issues.filter((i) => i.severity === "error");
|
|
3305
3410
|
const warnings = issues.filter((i) => i.severity === "warning");
|
|
3306
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
|
+
}
|
|
3307
3429
|
return {
|
|
3308
3430
|
passed: options.strict ? errors.length === 0 && warnings.length === 0 : errors.length === 0,
|
|
3309
3431
|
totalSlides: presentation.slides.length,
|
|
3310
3432
|
issueCount: issues.length,
|
|
3433
|
+
score,
|
|
3311
3434
|
errors,
|
|
3312
3435
|
warnings,
|
|
3313
|
-
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
|
|
3314
3553
|
};
|
|
3315
3554
|
}
|
|
3316
3555
|
getLuminance(hex) {
|
|
@@ -3510,6 +3749,13 @@ var YumiaCompiler = class {
|
|
|
3510
3749
|
...options
|
|
3511
3750
|
});
|
|
3512
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
|
+
}
|
|
3513
3759
|
layout(presentation, viewport) {
|
|
3514
3760
|
return this.layoutEngine.computePresentation(presentation, viewport ?? this.viewport);
|
|
3515
3761
|
}
|
|
@@ -3932,6 +4178,12 @@ var PptxRenderer = class {
|
|
|
3932
4178
|
case "table":
|
|
3933
4179
|
this.renderTable(pptxSlide, element, rect, theme);
|
|
3934
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;
|
|
3935
4187
|
case "image":
|
|
3936
4188
|
this.renderImage(pptxSlide, element, rect);
|
|
3937
4189
|
break;
|
|
@@ -4398,6 +4650,95 @@ var PptxRenderer = class {
|
|
|
4398
4650
|
valign: "middle"
|
|
4399
4651
|
});
|
|
4400
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
|
+
}
|
|
4401
4742
|
renderChart(pptxSlide, pptx, chart, rect, theme) {
|
|
4402
4743
|
const series = chart.series || [];
|
|
4403
4744
|
const labels = chart.labels || [];
|
|
@@ -6089,15 +6430,35 @@ var HtmlRenderer = class {
|
|
|
6089
6430
|
height: 160% !important;
|
|
6090
6431
|
}
|
|
6091
6432
|
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
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;
|
|
6101
6462
|
}
|
|
6102
6463
|
</style>
|
|
6103
6464
|
</head>
|
|
@@ -6113,10 +6474,12 @@ var HtmlRenderer = class {
|
|
|
6113
6474
|
<button class="yumia-btn" id="btn-next" title="Next Slide (\u2192)">\u25B6</button>
|
|
6114
6475
|
<button class="yumia-btn" id="btn-overview" title="Slide Overview (ESC / O)">\u25A6</button>
|
|
6115
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>
|
|
6116
6478
|
<button class="yumia-btn" id="btn-print" title="Stampa / Salva PDF (Ctrl+P)">\u{1F5A8}\uFE0F</button>
|
|
6117
6479
|
<button class="yumia-btn" id="btn-notes" title="Toggle Notes (N)">\u{1F4DD}</button>
|
|
6118
6480
|
<button class="yumia-btn" id="btn-fs" title="Fullscreen (F)">\u26F6</button>
|
|
6119
6481
|
</div>
|
|
6482
|
+
<div id="inspector-popup" class="yumia-inspector-popup"></div>
|
|
6120
6483
|
|
|
6121
6484
|
<div id="notes-drawer" class="yumia-notes-drawer">
|
|
6122
6485
|
<div class="yumia-notes-header">
|
|
@@ -6423,6 +6786,14 @@ var HtmlRenderer = class {
|
|
|
6423
6786
|
prev: function() { goToSlide(currentIdx - 1); },
|
|
6424
6787
|
toggleNotes: function() { notesDrawer?.classList.toggle('open'); },
|
|
6425
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
|
+
},
|
|
6426
6797
|
openSpeaker: openSpeakerWindow,
|
|
6427
6798
|
print: function() { window.print(); },
|
|
6428
6799
|
toggleFs: function() {
|
|
@@ -6437,6 +6808,7 @@ var HtmlRenderer = class {
|
|
|
6437
6808
|
document.getElementById('btn-next')?.addEventListener('click', window.deckController.next);
|
|
6438
6809
|
document.getElementById('btn-prev')?.addEventListener('click', window.deckController.prev);
|
|
6439
6810
|
document.getElementById('btn-notes')?.addEventListener('click', window.deckController.toggleNotes);
|
|
6811
|
+
document.getElementById('btn-inspect')?.addEventListener('click', window.deckController.toggleInspect);
|
|
6440
6812
|
document.getElementById('btn-close-notes')?.addEventListener('click', () => {
|
|
6441
6813
|
notesDrawer?.classList.remove('open');
|
|
6442
6814
|
});
|
|
@@ -6446,6 +6818,30 @@ var HtmlRenderer = class {
|
|
|
6446
6818
|
document.getElementById('btn-print')?.addEventListener('click', window.deckController.print);
|
|
6447
6819
|
document.getElementById('btn-fs')?.addEventListener('click', window.deckController.toggleFs);
|
|
6448
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
|
+
|
|
6449
6845
|
window.addEventListener('keydown', function(e) {
|
|
6450
6846
|
if (overviewModal && overviewModal.classList.contains('open')) {
|
|
6451
6847
|
if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
@@ -6480,6 +6876,8 @@ var HtmlRenderer = class {
|
|
|
6480
6876
|
window.deckController.print();
|
|
6481
6877
|
} else if (e.key.toLowerCase() === 'n') {
|
|
6482
6878
|
window.deckController.toggleNotes();
|
|
6879
|
+
} else if (e.key.toLowerCase() === 'i') {
|
|
6880
|
+
window.deckController.toggleInspect();
|
|
6483
6881
|
} else if (e.key === 'Escape' || e.key.toLowerCase() === 'o') {
|
|
6484
6882
|
window.deckController.toggleOverview();
|
|
6485
6883
|
}
|
|
@@ -6528,6 +6926,32 @@ var HtmlRenderer = class {
|
|
|
6528
6926
|
}
|
|
6529
6927
|
renderElement(element, theme, presentation) {
|
|
6530
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
|
+
}
|
|
6531
6955
|
case "heading": {
|
|
6532
6956
|
const h = element;
|
|
6533
6957
|
const tag = `h${Math.min(4, Math.max(1, h.level))}`;
|
|
@@ -7005,6 +7429,48 @@ var PdfRenderer = class {
|
|
|
7005
7429
|
}
|
|
7006
7430
|
renderElement(doc, element, x, y, width, theme, presentation) {
|
|
7007
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
|
+
}
|
|
7008
7474
|
case "heading": {
|
|
7009
7475
|
const h = element;
|
|
7010
7476
|
const fontSize = h.level === 1 ? 28 : h.level === 2 ? 22 : 18;
|
|
@@ -7575,7 +8041,7 @@ function startDevServer(filePath, options = {}) {
|
|
|
7575
8041
|
// src/cli.ts
|
|
7576
8042
|
import { mkdirSync, readFileSync as readFileSync2, watch as fsWatch, writeFileSync } from "fs";
|
|
7577
8043
|
import { basename, dirname, extname, join, resolve as resolve2 } from "path";
|
|
7578
|
-
var VERSION = "0.1.
|
|
8044
|
+
var VERSION = "0.1.24";
|
|
7579
8045
|
function printHelp() {
|
|
7580
8046
|
return `
|
|
7581
8047
|
YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
|
|
@@ -7584,10 +8050,12 @@ Usage:
|
|
|
7584
8050
|
yumia <command> [options] [file]
|
|
7585
8051
|
|
|
7586
8052
|
Commands:
|
|
7587
|
-
init [name] Scaffold a new
|
|
7588
|
-
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
|
|
7589
8057
|
watch <file> Watch presentation file and recompile automatically on save
|
|
7590
|
-
validate <file> Validate a
|
|
8058
|
+
validate <file> Validate a presentation syntax and AST structure
|
|
7591
8059
|
lint <file> Analyze presentation for layout overflows and accessibility
|
|
7592
8060
|
inspect <file> Inspect the AST and geometric layout tree
|
|
7593
8061
|
schema Output machine-readable JSON schema for AI agents
|
|
@@ -7835,11 +8303,12 @@ ${errorLines.join("\n")}`
|
|
|
7835
8303
|
};
|
|
7836
8304
|
}
|
|
7837
8305
|
}
|
|
7838
|
-
if (command === "lint") {
|
|
8306
|
+
if (command === "lint" || command === "check") {
|
|
7839
8307
|
if (!target) {
|
|
7840
|
-
const msg = "Error: Please specify a file to lint.";
|
|
8308
|
+
const msg = "Error: Please specify a file to check or lint.";
|
|
7841
8309
|
return { exitCode: 1, output: isJson ? JSON.stringify({ error: msg }) : msg };
|
|
7842
8310
|
}
|
|
8311
|
+
const isOptimize = args.includes("--optimize");
|
|
7843
8312
|
try {
|
|
7844
8313
|
const source = readFileSync2(target, "utf-8");
|
|
7845
8314
|
const compiler = new YumiaCompiler();
|
|
@@ -7850,6 +8319,7 @@ ${errorLines.join("\n")}`
|
|
|
7850
8319
|
output: JSON.stringify(
|
|
7851
8320
|
{
|
|
7852
8321
|
target,
|
|
8322
|
+
optimize: isOptimize,
|
|
7853
8323
|
...report
|
|
7854
8324
|
},
|
|
7855
8325
|
null,
|
|
@@ -7857,10 +8327,15 @@ ${errorLines.join("\n")}`
|
|
|
7857
8327
|
)
|
|
7858
8328
|
};
|
|
7859
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")}` : "";
|
|
7860
8334
|
if (report.issueCount === 0) {
|
|
7861
8335
|
return {
|
|
7862
8336
|
exitCode: 0,
|
|
7863
|
-
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}`
|
|
7864
8339
|
};
|
|
7865
8340
|
} else {
|
|
7866
8341
|
const issueLines = [];
|
|
@@ -7877,11 +8352,12 @@ ${errorLines.join("\n")}`
|
|
|
7877
8352
|
const exitCode = report.passed ? 0 : 1;
|
|
7878
8353
|
return {
|
|
7879
8354
|
exitCode,
|
|
7880
|
-
output: `Yumia
|
|
8355
|
+
output: `Yumia Design Audit Report for '${target}':
|
|
7881
8356
|
|
|
7882
8357
|
${issueLines.join("\n")}
|
|
7883
8358
|
|
|
7884
|
-
${summary}
|
|
8359
|
+
${summary}
|
|
8360
|
+
${scoreStr}${suggestionLines}${isStrict && report.warnings.length > 0 ? "\n(failed due to --strict)" : ""}`
|
|
7885
8361
|
};
|
|
7886
8362
|
}
|
|
7887
8363
|
} catch (err) {
|
|
@@ -7891,7 +8367,76 @@ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)"
|
|
|
7891
8367
|
}
|
|
7892
8368
|
return {
|
|
7893
8369
|
exitCode: 1,
|
|
7894
|
-
output: `\u2717
|
|
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)
|
|
8387
|
+
};
|
|
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
|
+
};
|
|
8432
|
+
} catch (err) {
|
|
8433
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8434
|
+
if (isJson) {
|
|
8435
|
+
return { exitCode: 1, output: JSON.stringify({ error: msg }) };
|
|
8436
|
+
}
|
|
8437
|
+
return {
|
|
8438
|
+
exitCode: 1,
|
|
8439
|
+
output: `\u2717 Explain failed: ${msg}`
|
|
7895
8440
|
};
|
|
7896
8441
|
}
|
|
7897
8442
|
}
|
|
@@ -8123,6 +8668,8 @@ export {
|
|
|
8123
8668
|
createStack,
|
|
8124
8669
|
createComponent,
|
|
8125
8670
|
createSlot,
|
|
8671
|
+
createHero,
|
|
8672
|
+
createCallout,
|
|
8126
8673
|
NativeYumiaParser,
|
|
8127
8674
|
parseNativeYumia,
|
|
8128
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/core": "0.1.
|
|
26
|
-
"@yumiamd/
|
|
27
|
-
"@yumiamd/
|
|
28
|
-
"@yumiamd/
|
|
29
|
-
"@yumiamd/renderer-
|
|
30
|
-
"@yumiamd/
|
|
31
|
-
"@yumiamd/renderer-
|
|
32
|
-
"@yumiamd/theme": "0.1.
|
|
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",
|