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 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-GKERS3TA.js";
4
+ } from "./chunk-FKFINZOL.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -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
- 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 };
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
- .speaker-notes-box {
6093
- flex: 1;
6094
- background: #11111a;
6095
- border-radius: 8px;
6096
- padding: 16px;
6097
- font-size: 16px;
6098
- line-height: 1.7;
6099
- color: #e2e8f0;
6100
- 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;
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.23";
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 YumiaMD presentation project
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 .yumia.md presentation syntax and structure
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 Lint Report for '${target}':
8355
+ output: `Yumia Design Audit Report for '${target}':
7881
8356
 
7882
8357
  ${issueLines.join("\n")}
7883
8358
 
7884
- ${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)" : ""}`
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 Linting failed: ${msg}`
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.23";
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-GKERS3TA.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.23",
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.23",
25
- "@yumiamd/core": "0.1.23",
26
- "@yumiamd/parser": "0.1.23",
27
- "@yumiamd/layout": "0.1.23",
28
- "@yumiamd/renderer": "0.1.23",
29
- "@yumiamd/renderer-html": "0.1.23",
30
- "@yumiamd/renderer-pdf": "0.1.23",
31
- "@yumiamd/renderer-pptx": "0.1.23",
32
- "@yumiamd/theme": "0.1.23"
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",