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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # YumiaMD (`yumiamd`)
2
2
 
3
- > **A Markdown-based presentation language & compiler designed for humans and AI.**
4
- > Author slide decks in clean Markdown and compile directly to **100% native, editable PowerPoint (.pptx)** presentations, vector PDFs, and interactive HTML5 slides.
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
  [![npm version](https://img.shields.io/npm/v/yumiamd.svg?color=blue)](https://www.npmjs.com/package/yumiamd)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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 YumiaMD immediately without installing anything via `npx`:
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 & AI Tooling
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: default
102
+ theme: corporate
97
103
  aspectRatio: '16:9'
98
- author: Biagio Scaglia
104
+ author: Engineering Team
99
105
  ---
100
106
 
101
- # High-Performance Presentation Engine
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
- :::notes
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
- # Architecture & Capabilities
114
+ <!-- Slide 2: Primitives & Visual Intent -->
115
115
 
116
- :::columns ratios="50:50"
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
- # Roadmap Timeline
125
+ - Global Anycast DNS
126
+ - Sub-5ms SSL Termination
127
+ :::
147
128
 
148
- :::timeline layout="horizontal"
129
+ :::card "Stateful Compute" variant="success"
149
130
 
150
- - [Phase 1] AST & Compiler Core
151
- - [Phase 2] Multi-Format Renderers
152
- - [Phase 3] Rich Directives & Charts
153
- - [Phase 4] Cloud Deployments
131
+ - Autonomous Actor Runtime
132
+ - Distributed Raft Consensus
154
133
  :::
155
134
 
156
- :::step
135
+ :::card "Vector Store" variant="accent"
157
136
 
158
- - 🚀 Seamless deployment via `yumia deploy`
137
+ - Multi-index HNSW Search
138
+ - 10M query/sec throughput
139
+ :::
159
140
  :::
160
- ```
161
141
 
162
142
  ---
163
143
 
164
- ## 💻 JavaScript / TypeScript API
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
- ```typescript
169
- import { compile, parse, YumiaCompiler } from 'yumiamd';
170
- import fs from 'node:fs';
146
+ # Operational Performance
171
147
 
172
- const markdown = fs.readFileSync('presentation.yumia.md', 'utf-8');
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
- // 1. Validate syntax and collect diagnostics
175
- const compiler = new YumiaCompiler();
176
- const validation = compiler.validate(markdown);
177
- console.log(`Valid: ${validation.valid}, Slides: ${validation.slideCount}`);
157
+ ---
178
158
 
179
- // 2. Compile directly to PPTX buffer
180
- const { buffer, errors } = await compile(markdown, { format: 'pptx' });
159
+ <!-- Slide 4: Data Visualization -->
181
160
 
182
- if (errors.length === 0) {
183
- fs.writeFileSync('output.pptx', buffer);
184
- console.log('✅ Presentation generated successfully!');
185
- }
161
+ # Global Throughput & Adoption
186
162
 
187
- // 3. Export JSON schema for LLM generation
188
- const schema = compiler.getSchema();
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
- ## Key Features
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
- ## 📄 License
171
+ When previewing in HTML5 (`yumia dev` or `--format html`):
206
172
 
207
- MIT © [Biagio Scaglia](https://github.com/biagio-scaglia)
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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-KNQTEIHN.js";
4
+ } from "./chunk-FKFINZOL.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -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
- return { element: createHeading(text, level), nextIdx: idx + 1 };
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
- .speaker-notes-box {
5950
- flex: 1;
5951
- background: #11111a;
5952
- border-radius: 8px;
5953
- padding: 16px;
5954
- font-size: 16px;
5955
- line-height: 1.7;
5956
- color: #e2e8f0;
5957
- overflow-y: auto;
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 : "YumiaMD";
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
- return `<img src="${this.escapeHtml(img.src)}" ${alt} style="max-width: 100%; border-radius: 8px;">`;
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.22";
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 YumiaMD presentation project
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 .yumia.md presentation syntax and structure
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 Lint Report for '${target}':
8355
+ output: `Yumia Design Audit Report for '${target}':
7727
8356
 
7728
8357
  ${issueLines.join("\n")}
7729
8358
 
7730
- ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)" : ""}`
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 Linting failed: ${msg}`
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.22";
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-KNQTEIHN.js";
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.22",
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.22",
25
- "@yumiamd/layout": "0.1.22",
26
- "@yumiamd/renderer-html": "0.1.22",
27
- "@yumiamd/core": "0.1.22",
28
- "@yumiamd/renderer": "0.1.22",
29
- "@yumiamd/renderer-pdf": "0.1.22",
30
- "@yumiamd/parser": "0.1.22",
31
- "@yumiamd/theme": "0.1.22",
32
- "@yumiamd/renderer-pptx": "0.1.22"
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",