yumiamd 0.1.7 → 0.1.9

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/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-GMIVPFBA.js";
4
+ } from "./chunk-F3CX7UG6.js";
5
5
 
6
6
  // src/bin.ts
7
7
  var result = await runCli(process.argv);
@@ -47,6 +47,17 @@ function createCard(elements, title, variant) {
47
47
  ...variant ? { variant } : {}
48
48
  };
49
49
  }
50
+ function createMetric(value, label, variant, description, unit, change) {
51
+ return {
52
+ type: "metric",
53
+ value,
54
+ label,
55
+ ...variant ? { variant } : {},
56
+ ...description ? { description } : {},
57
+ ...unit ? { unit } : {},
58
+ ...change ? { change } : {}
59
+ };
60
+ }
50
61
  function createCode(code, language, highlightLines) {
51
62
  return {
52
63
  type: "code",
@@ -111,13 +122,14 @@ var DefaultYumiaParser = class {
111
122
  }
112
123
  const normalized = source.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
113
124
  const { metadata, content, lineOffset } = this.extractFrontmatter(normalized);
125
+ if (!content.trim()) {
126
+ return createPresentation(metadata, []);
127
+ }
114
128
  const slideChunks = this.splitSlides(content, lineOffset);
115
129
  const slides = [];
116
130
  for (const chunk of slideChunks) {
117
131
  const slide = this.parseSlide(chunk.content, chunk.startLine);
118
- if (slide.elements.length > 0 || slide.notes !== void 0 || slide.background !== void 0) {
119
- slides.push(slide);
120
- }
132
+ slides.push(slide);
121
133
  }
122
134
  const presentation = createPresentation(metadata, slides);
123
135
  if (this.diagnostics.length > 0) {
@@ -201,6 +213,28 @@ var DefaultYumiaParser = class {
201
213
  result.theme = metadata["theme"];
202
214
  if (typeof metadata["aspectRatio"] === "string")
203
215
  result.aspectRatio = metadata["aspectRatio"];
216
+ const colors = {};
217
+ if (typeof metadata["background"] === "string")
218
+ colors.background = metadata["background"];
219
+ if (typeof metadata["bg"] === "string")
220
+ colors.background = metadata["bg"];
221
+ if (typeof metadata["primary"] === "string")
222
+ colors.primary = metadata["primary"];
223
+ if (typeof metadata["secondary"] === "string")
224
+ colors.secondary = metadata["secondary"];
225
+ if (typeof metadata["surface"] === "string")
226
+ colors.surface = metadata["surface"];
227
+ if (typeof metadata["text"] === "string")
228
+ colors.text = metadata["text"];
229
+ if (typeof metadata["muted"] === "string")
230
+ colors.muted = metadata["muted"];
231
+ if (typeof metadata["accent"] === "string")
232
+ colors.accent = metadata["accent"];
233
+ if (typeof metadata["border"] === "string")
234
+ colors.border = metadata["border"];
235
+ if (Object.keys(colors).length > 0) {
236
+ result.colors = colors;
237
+ }
204
238
  return result;
205
239
  }
206
240
  splitSlides(content, startLineOffset) {
@@ -346,6 +380,28 @@ var DefaultYumiaParser = class {
346
380
  i++;
347
381
  continue;
348
382
  }
383
+ if (directiveHeader.startsWith("metric")) {
384
+ const valueMatch = directiveHeader.match(/value=['"](.*?)['"]/);
385
+ const labelMatch = directiveHeader.match(/label=['"](.*?)['"]/);
386
+ const variantMatch = directiveHeader.match(/variant=['"](.*?)['"]/);
387
+ const descMatch = directiveHeader.match(/description=['"](.*?)['"]/);
388
+ const unitMatch = directiveHeader.match(/unit=['"](.*?)['"]/);
389
+ const changeMatch = directiveHeader.match(/change=['"](.*?)['"]/);
390
+ const value = valueMatch ? valueMatch[1] : "0";
391
+ const label = labelMatch ? labelMatch[1] : "";
392
+ const variant = variantMatch ? variantMatch[1] : void 0;
393
+ const desc = descMatch ? descMatch[1] : void 0;
394
+ const unit = unitMatch ? unitMatch[1] : void 0;
395
+ const change = changeMatch ? changeMatch[1] : void 0;
396
+ const el = createMetric(value, label, variant, desc, unit, change);
397
+ el.loc = {
398
+ start: { line: currentLineNum, column: 1 },
399
+ end: { line: currentLineNum, column: rawLine.length + 1 }
400
+ };
401
+ elements.push(el);
402
+ i++;
403
+ continue;
404
+ }
349
405
  const [directiveName, ...args] = directiveHeader.split(" ");
350
406
  const directiveArg = args.join(" ").trim();
351
407
  const blockLines = [];
@@ -354,7 +410,8 @@ var DefaultYumiaParser = class {
354
410
  let closed = false;
355
411
  while (i < lines.length) {
356
412
  const innerLine = lines[i]?.trim() ?? "";
357
- if (innerLine.startsWith(":::") && innerLine.length > 3) {
413
+ const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
414
+ if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
358
415
  nestedCount++;
359
416
  } else if (innerLine === ":::") {
360
417
  nestedCount--;
@@ -382,9 +439,17 @@ var DefaultYumiaParser = class {
382
439
  if (directiveName === "notes") {
383
440
  notes = blockLines.map((l) => l.trim()).filter(Boolean).join("\n");
384
441
  } else if (directiveName === "card") {
385
- const title = directiveArg.replace(/^['"](.*)['"]$/, "$1") || void 0;
442
+ let cardTitle;
443
+ let cardVariant;
444
+ const variantMatch = directiveArg.match(/variant=['"](.*?)['"]/);
445
+ if (variantMatch) {
446
+ cardVariant = variantMatch[1];
447
+ cardTitle = directiveArg.replace(/variant=['"].*?['"]/, "").trim().replace(/^['"](.*)['"]$/, "$1") || void 0;
448
+ } else {
449
+ cardTitle = directiveArg.replace(/^['"](.*)['"]$/, "$1") || void 0;
450
+ }
386
451
  const { elements: cardElements } = this.parseLines(blockLines, blockBaseLine);
387
- const el = createCard(cardElements, title);
452
+ const el = createCard(cardElements, cardTitle, cardVariant);
388
453
  el.loc = {
389
454
  start: { line: directiveStartLine, column: 1 },
390
455
  end: { line: baseLine + i - 1, column: 1 }
@@ -530,7 +595,8 @@ var DefaultYumiaParser = class {
530
595
  let nestedCount = 1;
531
596
  while (i < lines.length) {
532
597
  const innerLine = lines[i]?.trim() ?? "";
533
- if (innerLine.startsWith(":::") && innerLine.length > 3) {
598
+ const isSingleLine = innerLine.startsWith(":::metric") || innerLine.startsWith(":::layout");
599
+ if (innerLine.startsWith(":::") && innerLine.length > 3 && !isSingleLine) {
534
600
  nestedCount++;
535
601
  } else if (innerLine === ":::") {
536
602
  nestedCount--;
@@ -645,6 +711,10 @@ var DefaultLayoutEngine = class {
645
711
  const height = this.estimateImageHeight(element);
646
712
  return { element, bounds: { x, y, width, height } };
647
713
  }
714
+ case "metric": {
715
+ const height = this.estimateMetricHeight(element);
716
+ return { element, bounds: { x, y, width, height } };
717
+ }
648
718
  case "card": {
649
719
  return this.layoutCard(element, x, y, width, gap);
650
720
  }
@@ -749,9 +819,10 @@ var DefaultLayoutEngine = class {
749
819
  return lines * 34 + 32;
750
820
  }
751
821
  estimateTableHeight(table) {
752
- const headerHeight = table.headers && table.headers.length > 0 ? 56 : 0;
753
- const rowsHeight = (table.rows ? table.rows.length : 0) * 44;
754
- return Math.max(60, headerHeight + rowsHeight + 20);
822
+ const headerHeight = table.headers && table.headers.length > 0 ? 50 : 0;
823
+ const rowsCount = table.rows ? table.rows.length : 0;
824
+ const rowsHeight = rowsCount * 46;
825
+ return Math.max(80, headerHeight + rowsHeight + 20);
755
826
  }
756
827
  estimateImageHeight(image) {
757
828
  if (typeof image.height === "number") {
@@ -759,6 +830,9 @@ var DefaultLayoutEngine = class {
759
830
  }
760
831
  return 320;
761
832
  }
833
+ estimateMetricHeight(metric) {
834
+ return metric.change ? 160 : 130;
835
+ }
762
836
  };
763
837
 
764
838
  // ../theme/dist/default-theme.js
@@ -773,7 +847,11 @@ var defaultTheme = {
773
847
  text: "#0f172a",
774
848
  muted: "#64748b",
775
849
  accent: "#06b6d4",
776
- border: "#e2e8f0"
850
+ border: "#e2e8f0",
851
+ success: "#10b981",
852
+ warning: "#f59e0b",
853
+ danger: "#ef4444",
854
+ info: "#3b82f6"
777
855
  },
778
856
  typography: {
779
857
  headingFont: "Inter, system-ui, -apple-system, sans-serif",
@@ -830,29 +908,523 @@ var defaultTheme = {
830
908
  }
831
909
  }
832
910
  };
833
- function createTheme(overrides) {
911
+ var cyberpunkTheme = {
912
+ name: "cyberpunk",
913
+ description: "High-energy dark cyberpunk theme with neon pink and cyan accents",
914
+ colors: {
915
+ primary: "#FF2E88",
916
+ secondary: "#00F0FF",
917
+ background: "#0B0B12",
918
+ surface: "#151522",
919
+ text: "#FFFFFF",
920
+ muted: "#A7A7B5",
921
+ accent: "#7B61FF",
922
+ border: "#2E2E48",
923
+ success: "#00FF9F",
924
+ warning: "#FFE600",
925
+ danger: "#FF0055",
926
+ info: "#00F0FF"
927
+ },
928
+ typography: {
929
+ headingFont: "Outfit, Inter, sans-serif",
930
+ bodyFont: "Inter, sans-serif",
931
+ codeFont: "JetBrains Mono, monospace",
932
+ sizes: {
933
+ h1: 46,
934
+ h2: 38,
935
+ h3: 28,
936
+ h4: 22,
937
+ body: 18,
938
+ small: 14,
939
+ code: 16
940
+ }
941
+ },
942
+ spacing: {
943
+ unit: 8,
944
+ slidePadding: 48,
945
+ elementGap: 28
946
+ },
947
+ radius: {
948
+ default: 12,
949
+ sm: 6,
950
+ md: 12,
951
+ lg: 20,
952
+ full: 9999
953
+ },
954
+ components: {
955
+ card: {
956
+ background: "#151522",
957
+ borderColor: "#FF2E88",
958
+ borderRadius: 16,
959
+ padding: 28
960
+ },
961
+ code: {
962
+ background: "#07070D",
963
+ textColor: "#00F0FF",
964
+ borderRadius: 8
965
+ }
966
+ }
967
+ };
968
+ var minimalTheme = {
969
+ name: "minimal",
970
+ description: "Monochrome Swiss-style design with maximum clarity and contrast",
971
+ colors: {
972
+ primary: "#111111",
973
+ secondary: "#333333",
974
+ background: "#FFFFFF",
975
+ surface: "#F5F5F7",
976
+ text: "#111111",
977
+ muted: "#777777",
978
+ accent: "#000000",
979
+ border: "#E5E5E5",
980
+ success: "#10B981",
981
+ warning: "#F59E0B",
982
+ danger: "#EF4444",
983
+ info: "#3B82F6"
984
+ },
985
+ typography: {
986
+ headingFont: "Helvetica Neue, Arial, sans-serif",
987
+ bodyFont: "Helvetica Neue, Arial, sans-serif",
988
+ codeFont: "Menlo, Monaco, monospace",
989
+ sizes: {
990
+ h1: 44,
991
+ h2: 34,
992
+ h3: 26,
993
+ h4: 20,
994
+ body: 18,
995
+ small: 14,
996
+ code: 15
997
+ }
998
+ },
999
+ spacing: {
1000
+ unit: 8,
1001
+ slidePadding: 56,
1002
+ elementGap: 24
1003
+ },
1004
+ radius: {
1005
+ default: 4,
1006
+ sm: 2,
1007
+ md: 4,
1008
+ lg: 8,
1009
+ full: 9999
1010
+ },
1011
+ components: {
1012
+ card: {
1013
+ background: "#F5F5F7",
1014
+ borderColor: "#E5E5E5",
1015
+ borderRadius: 6,
1016
+ padding: 24
1017
+ }
1018
+ }
1019
+ };
1020
+ var corporateTheme = {
1021
+ name: "corporate",
1022
+ description: "Executive navy blue palette tailored for business and enterprise decks",
1023
+ colors: {
1024
+ primary: "#0A2540",
1025
+ secondary: "#635BFF",
1026
+ background: "#FFFFFF",
1027
+ surface: "#F8FAFC",
1028
+ text: "#1A1F36",
1029
+ muted: "#4F566B",
1030
+ accent: "#00D4B2",
1031
+ border: "#E3E8EE",
1032
+ success: "#00D4B2",
1033
+ warning: "#FFC043",
1034
+ danger: "#DF1B41",
1035
+ info: "#635BFF"
1036
+ },
1037
+ typography: {
1038
+ headingFont: "Inter, Segoe UI, sans-serif",
1039
+ bodyFont: "Inter, Segoe UI, sans-serif",
1040
+ codeFont: "Consolas, monospace",
1041
+ sizes: {
1042
+ h1: 42,
1043
+ h2: 34,
1044
+ h3: 26,
1045
+ h4: 20,
1046
+ body: 18,
1047
+ small: 14,
1048
+ code: 15
1049
+ }
1050
+ },
1051
+ spacing: {
1052
+ unit: 8,
1053
+ slidePadding: 48,
1054
+ elementGap: 24
1055
+ },
1056
+ radius: {
1057
+ default: 8,
1058
+ sm: 4,
1059
+ md: 8,
1060
+ lg: 12,
1061
+ full: 9999
1062
+ },
1063
+ components: {
1064
+ card: {
1065
+ background: "#F8FAFC",
1066
+ borderColor: "#0A2540",
1067
+ borderRadius: 8,
1068
+ padding: 24
1069
+ }
1070
+ }
1071
+ };
1072
+ var terminalTheme = {
1073
+ name: "terminal",
1074
+ description: "Retro hacker green-on-dark theme with pure monospace aesthetic",
1075
+ colors: {
1076
+ primary: "#00FF66",
1077
+ secondary: "#33FF88",
1078
+ background: "#0C100C",
1079
+ surface: "#141A14",
1080
+ text: "#E0FFE0",
1081
+ muted: "#4E7A4E",
1082
+ accent: "#FFB000",
1083
+ border: "#1F331F",
1084
+ success: "#00FF66",
1085
+ warning: "#FFB000",
1086
+ danger: "#FF3333",
1087
+ info: "#00E5FF"
1088
+ },
1089
+ typography: {
1090
+ headingFont: "JetBrains Mono, Courier New, monospace",
1091
+ bodyFont: "JetBrains Mono, Courier New, monospace",
1092
+ codeFont: "JetBrains Mono, Courier New, monospace",
1093
+ sizes: {
1094
+ h1: 40,
1095
+ h2: 32,
1096
+ h3: 26,
1097
+ h4: 20,
1098
+ body: 17,
1099
+ small: 13,
1100
+ code: 15
1101
+ }
1102
+ },
1103
+ spacing: {
1104
+ unit: 8,
1105
+ slidePadding: 48,
1106
+ elementGap: 24
1107
+ },
1108
+ radius: {
1109
+ default: 0,
1110
+ sm: 0,
1111
+ md: 0,
1112
+ lg: 0,
1113
+ full: 0
1114
+ },
1115
+ components: {
1116
+ card: {
1117
+ background: "#141A14",
1118
+ borderColor: "#00FF66",
1119
+ borderRadius: 0,
1120
+ padding: 24
1121
+ }
1122
+ }
1123
+ };
1124
+ var academicTheme = {
1125
+ name: "academic",
1126
+ description: "Scholarly paper theme with serif typography and crimson accents",
1127
+ colors: {
1128
+ primary: "#8B0000",
1129
+ secondary: "#1C3F60",
1130
+ background: "#FCFBF7",
1131
+ surface: "#F5F2EB",
1132
+ text: "#2C2B29",
1133
+ muted: "#6B6862",
1134
+ accent: "#C29B38",
1135
+ border: "#E0DACD",
1136
+ success: "#2E7D32",
1137
+ warning: "#E65100",
1138
+ danger: "#C62828",
1139
+ info: "#1565C0"
1140
+ },
1141
+ typography: {
1142
+ headingFont: "Georgia, Times New Roman, serif",
1143
+ bodyFont: "Georgia, Times New Roman, serif",
1144
+ codeFont: "Courier New, monospace",
1145
+ sizes: {
1146
+ h1: 42,
1147
+ h2: 34,
1148
+ h3: 26,
1149
+ h4: 20,
1150
+ body: 18,
1151
+ small: 14,
1152
+ code: 15
1153
+ }
1154
+ },
1155
+ spacing: {
1156
+ unit: 8,
1157
+ slidePadding: 52,
1158
+ elementGap: 24
1159
+ },
1160
+ radius: {
1161
+ default: 4,
1162
+ sm: 2,
1163
+ md: 4,
1164
+ lg: 6,
1165
+ full: 9999
1166
+ },
1167
+ components: {
1168
+ card: {
1169
+ background: "#F5F2EB",
1170
+ borderColor: "#8B0000",
1171
+ borderRadius: 4,
1172
+ padding: 24
1173
+ }
1174
+ }
1175
+ };
1176
+ var THEME_REGISTRY = {
1177
+ default: defaultTheme,
1178
+ cyberpunk: cyberpunkTheme,
1179
+ minimal: minimalTheme,
1180
+ corporate: corporateTheme,
1181
+ terminal: terminalTheme,
1182
+ academic: academicTheme
1183
+ };
1184
+ function resolveTheme(themeNameOrRef, explicitOverrides) {
1185
+ const name = typeof themeNameOrRef === "string" ? themeNameOrRef : themeNameOrRef?.name || "default";
1186
+ const baseTheme = THEME_REGISTRY[name.toLowerCase()] || defaultTheme;
1187
+ const refOverrides = typeof themeNameOrRef === "object" ? themeNameOrRef.overrides : void 0;
1188
+ const mergedOverrides = {
1189
+ ...refOverrides,
1190
+ ...explicitOverrides,
1191
+ colors: { ...baseTheme.colors, ...refOverrides?.colors, ...explicitOverrides?.colors },
1192
+ typography: {
1193
+ ...baseTheme.typography,
1194
+ ...refOverrides?.typography,
1195
+ ...explicitOverrides?.typography
1196
+ },
1197
+ components: {
1198
+ ...baseTheme.components,
1199
+ ...refOverrides?.components,
1200
+ ...explicitOverrides?.components
1201
+ }
1202
+ };
834
1203
  return {
835
- ...defaultTheme,
836
- ...overrides,
837
- colors: { ...defaultTheme.colors, ...overrides.colors },
838
- typography: { ...defaultTheme.typography, ...overrides.typography },
839
- spacing: { ...defaultTheme.spacing, ...overrides.spacing },
840
- radius: { ...defaultTheme.radius, ...overrides.radius },
841
- shadows: { ...defaultTheme.shadows, ...overrides.shadows },
842
- components: { ...defaultTheme.components, ...overrides.components }
1204
+ ...baseTheme,
1205
+ ...mergedOverrides
843
1206
  };
844
1207
  }
1208
+ function createTheme(overrides) {
1209
+ return resolveTheme(overrides.name, overrides);
1210
+ }
1211
+
1212
+ // ../core/dist/linter.js
1213
+ var YumiaLinter = class {
1214
+ layoutEngine;
1215
+ constructor(layoutEngine) {
1216
+ this.layoutEngine = layoutEngine ?? new DefaultLayoutEngine();
1217
+ }
1218
+ lint(presentation, options = {}) {
1219
+ const issues = [];
1220
+ const layout = this.layoutEngine.computePresentation(presentation, options.viewport);
1221
+ if (presentation.diagnostics) {
1222
+ for (const diag of presentation.diagnostics) {
1223
+ issues.push({
1224
+ code: diag.code || "YUM002",
1225
+ slide: 1,
1226
+ message: diag.message,
1227
+ severity: diag.severity === "error" ? "error" : "warning",
1228
+ loc: diag.loc
1229
+ });
1230
+ }
1231
+ }
1232
+ presentation.slides.forEach((slide, index) => {
1233
+ const slideNum = index + 1;
1234
+ const slideLayout = layout.slides[index];
1235
+ if (slide.elements.length === 0) {
1236
+ issues.push({
1237
+ code: "YUM006",
1238
+ slide: slideNum,
1239
+ message: "Slide contains no content elements.",
1240
+ severity: "warning",
1241
+ loc: slide.loc
1242
+ });
1243
+ return;
1244
+ }
1245
+ if (slideLayout && slideLayout.overflow) {
1246
+ const overflowPx = Math.round(slideLayout.overflowAmount || 0);
1247
+ issues.push({
1248
+ code: "YUM001",
1249
+ slide: slideNum,
1250
+ message: `Content exceeds slide viewport bounds by ~${overflowPx}px. Consider reducing font size or splitting into multiple slides.`,
1251
+ severity: options.strict ? "error" : "warning",
1252
+ loc: slide.loc
1253
+ });
1254
+ }
1255
+ const hasHeading = this.slideHasHeading(slide.elements);
1256
+ if (!hasHeading) {
1257
+ issues.push({
1258
+ code: "YUM003",
1259
+ slide: slideNum,
1260
+ message: "Slide has no heading (h1-h4). Consider adding a descriptive title for structure.",
1261
+ severity: "warning",
1262
+ loc: slide.loc
1263
+ });
1264
+ }
1265
+ const { listCount, wordCount } = this.calculateSlideDensity(slide.elements);
1266
+ if (listCount > 7) {
1267
+ issues.push({
1268
+ code: "YUM004",
1269
+ slide: slideNum,
1270
+ message: `High information density: slide contains ${listCount} list items (recommended max: 7).`,
1271
+ severity: "warning",
1272
+ loc: slide.loc
1273
+ });
1274
+ } else if (wordCount > 120) {
1275
+ issues.push({
1276
+ code: "YUM004",
1277
+ slide: slideNum,
1278
+ message: `High information density: slide contains ~${wordCount} words (recommended max: 120 words).`,
1279
+ severity: "warning",
1280
+ loc: slide.loc
1281
+ });
1282
+ }
1283
+ this.checkImages(slide.elements, slideNum, issues);
1284
+ const maxDepth = this.getMaxNestingDepth(slide.elements, 0);
1285
+ if (maxDepth > 2) {
1286
+ issues.push({
1287
+ code: "YUM008",
1288
+ slide: slideNum,
1289
+ message: `Excessive directive nesting depth (${maxDepth} levels). Maximum recommended depth is 2.`,
1290
+ severity: "warning",
1291
+ loc: slide.loc
1292
+ });
1293
+ }
1294
+ if (options.includeOptionalNotesRule && (!slide.notes || slide.notes.trim() === "")) {
1295
+ issues.push({
1296
+ code: "YUM009",
1297
+ slide: slideNum,
1298
+ message: "Slide has no speaker notes (:::notes directive).",
1299
+ severity: "info",
1300
+ loc: slide.loc
1301
+ });
1302
+ }
1303
+ });
1304
+ const errors = issues.filter((i) => i.severity === "error");
1305
+ const warnings = issues.filter((i) => i.severity === "warning");
1306
+ const infos = issues.filter((i) => i.severity === "info");
1307
+ return {
1308
+ passed: options.strict ? errors.length === 0 && warnings.length === 0 : errors.length === 0,
1309
+ totalSlides: presentation.slides.length,
1310
+ issueCount: issues.length,
1311
+ errors,
1312
+ warnings,
1313
+ infos
1314
+ };
1315
+ }
1316
+ slideHasHeading(elements) {
1317
+ for (const el of elements) {
1318
+ if (el.type === "heading")
1319
+ return true;
1320
+ if (el.type === "card" && el.title)
1321
+ return true;
1322
+ if (el.type === "columns") {
1323
+ const cols = el.columns;
1324
+ for (const col of cols) {
1325
+ if (this.slideHasHeading(col.elements))
1326
+ return true;
1327
+ }
1328
+ }
1329
+ }
1330
+ return false;
1331
+ }
1332
+ calculateSlideDensity(elements) {
1333
+ let listCount = 0;
1334
+ let wordCount = 0;
1335
+ const traverse = (els) => {
1336
+ for (const el of els) {
1337
+ if (el.type === "list") {
1338
+ const list = el;
1339
+ listCount += list.items.length;
1340
+ for (const item of list.items) {
1341
+ wordCount += item.text.split(/\s+/).filter(Boolean).length;
1342
+ }
1343
+ } else if (el.type === "paragraph") {
1344
+ const p = el;
1345
+ wordCount += p.text.split(/\s+/).filter(Boolean).length;
1346
+ } else if (el.type === "card") {
1347
+ const card = el;
1348
+ if (card.title)
1349
+ wordCount += card.title.split(/\s+/).filter(Boolean).length;
1350
+ if (card.elements)
1351
+ traverse(card.elements);
1352
+ } else if (el.type === "columns") {
1353
+ const cols = el;
1354
+ for (const col of cols.columns) {
1355
+ traverse(col.elements);
1356
+ }
1357
+ }
1358
+ }
1359
+ };
1360
+ traverse(elements);
1361
+ return { listCount, wordCount };
1362
+ }
1363
+ checkImages(elements, slideNum, issues) {
1364
+ const traverse = (els) => {
1365
+ for (const el of els) {
1366
+ if (el.type === "image") {
1367
+ const img = el;
1368
+ if (!img.alt || img.alt.trim() === "") {
1369
+ issues.push({
1370
+ code: "YUM007",
1371
+ slide: slideNum,
1372
+ message: `Image '${img.src}' is missing descriptive alt text for accessibility.`,
1373
+ severity: "warning",
1374
+ loc: img.loc
1375
+ });
1376
+ }
1377
+ if (!img.src || img.src.trim() === "") {
1378
+ issues.push({
1379
+ code: "YUM005",
1380
+ slide: slideNum,
1381
+ message: "Image element has empty src attribute.",
1382
+ severity: "error",
1383
+ loc: img.loc
1384
+ });
1385
+ }
1386
+ } else if (el.type === "card" && el.elements) {
1387
+ traverse(el.elements);
1388
+ } else if (el.type === "columns") {
1389
+ for (const col of el.columns) {
1390
+ traverse(col.elements);
1391
+ }
1392
+ }
1393
+ }
1394
+ };
1395
+ traverse(elements);
1396
+ }
1397
+ getMaxNestingDepth(elements, currentDepth) {
1398
+ let max = currentDepth;
1399
+ for (const el of elements) {
1400
+ if (el.type === "card" && el.elements) {
1401
+ const d = this.getMaxNestingDepth(el.elements, currentDepth + 1);
1402
+ if (d > max)
1403
+ max = d;
1404
+ } else if (el.type === "columns") {
1405
+ for (const col of el.columns) {
1406
+ const d = this.getMaxNestingDepth(col.elements, currentDepth + 1);
1407
+ if (d > max)
1408
+ max = d;
1409
+ }
1410
+ }
1411
+ }
1412
+ return max;
1413
+ }
1414
+ };
845
1415
 
846
1416
  // ../core/dist/compiler.js
847
1417
  var YumiaCompiler = class {
848
1418
  parser;
849
1419
  layoutEngine;
850
1420
  defaultTheme;
1421
+ linter;
851
1422
  viewport;
852
1423
  constructor(config = {}) {
853
1424
  this.parser = config.parser ?? new DefaultYumiaParser();
854
1425
  this.layoutEngine = config.layoutEngine ?? new DefaultLayoutEngine();
855
1426
  this.defaultTheme = config.defaultTheme ?? defaultTheme;
1427
+ this.linter = new YumiaLinter(this.layoutEngine);
856
1428
  if (config.viewport) {
857
1429
  this.viewport = config.viewport;
858
1430
  }
@@ -872,12 +1444,20 @@ var YumiaCompiler = class {
872
1444
  warnings
873
1445
  };
874
1446
  }
1447
+ lint(sourceOrPresentation, options) {
1448
+ const presentation = typeof sourceOrPresentation === "string" ? this.parse(sourceOrPresentation) : sourceOrPresentation;
1449
+ return this.linter.lint(presentation, {
1450
+ ...this.viewport ? { viewport: this.viewport } : {},
1451
+ ...options
1452
+ });
1453
+ }
875
1454
  layout(presentation, viewport) {
876
1455
  return this.layoutEngine.computePresentation(presentation, viewport ?? this.viewport);
877
1456
  }
878
1457
  async render(presentation, renderer, contextOverrides = {}) {
879
1458
  const layout = contextOverrides.layout ?? this.layout(presentation);
880
- const theme = contextOverrides.theme ?? this.defaultTheme;
1459
+ const presentationTheme = presentation.metadata.theme ? resolveTheme(presentation.metadata.theme) : this.defaultTheme;
1460
+ const theme = contextOverrides.theme ?? presentationTheme;
881
1461
  const context = {
882
1462
  theme,
883
1463
  layout,
@@ -903,7 +1483,10 @@ var YumiaCompiler = class {
903
1483
  subtitle: { type: "string" },
904
1484
  author: { type: "string" },
905
1485
  date: { type: "string" },
906
- theme: { type: "string" },
1486
+ theme: {
1487
+ type: "string",
1488
+ enum: ["default", "cyberpunk", "minimal", "corporate", "terminal", "academic"]
1489
+ },
907
1490
  aspectRatio: { enum: ["16:9", "4:3", "16:10"] }
908
1491
  }
909
1492
  },
@@ -915,9 +1498,13 @@ var YumiaCompiler = class {
915
1498
  description: "Multi-column grid layout"
916
1499
  },
917
1500
  card: {
918
- syntax: ":::card [Title]\\n...\\n:::",
1501
+ syntax: ':::card [Title] [variant="primary|success|warning|danger|info"]\\n...\\n:::',
919
1502
  description: "Visual container card with theme border and background"
920
1503
  },
1504
+ metric: {
1505
+ syntax: ':::metric value="99.9%" label="Uptime" change="+0.4%" variant="success"',
1506
+ description: "Stat callout box with prominent value, label, and trend"
1507
+ },
921
1508
  notes: {
922
1509
  syntax: ":::notes\\nSpeaker notes text\\n:::",
923
1510
  description: "Speaker notes attached to slide metadata"
@@ -951,6 +1538,15 @@ var CSS_NAMED_COLORS = {
951
1538
  slate: "0f172a",
952
1539
  transparent: "ffffff"
953
1540
  };
1541
+ function cleanFontFace(fontString) {
1542
+ if (!fontString)
1543
+ return "Segoe UI";
1544
+ const first = fontString.split(",")[0]?.trim().replace(/^['"]|['"]$/g, "") || "";
1545
+ if (!first || first === "system-ui" || first === "-apple-system" || first === "sans-serif" || first === "monospace" || first === "serif") {
1546
+ return "Segoe UI";
1547
+ }
1548
+ return first;
1549
+ }
954
1550
  function parseInlineMarkdown(rawText, baseOptions) {
955
1551
  const chunks = [];
956
1552
  const regex = /(\*\*.*?\*\*|\*.*?\*|`.*?`)/g;
@@ -992,7 +1588,9 @@ var PptxRenderer = class {
992
1588
  async render(presentation, context = {}) {
993
1589
  const PptxConstructor = pptxgen.default || pptxgen;
994
1590
  const pptx = new PptxConstructor();
995
- const theme = context.theme || defaultTheme;
1591
+ const colorOverrides = presentation.metadata.colors ? { colors: presentation.metadata.colors } : void 0;
1592
+ const resolvedTheme = resolveTheme(presentation.metadata.theme, colorOverrides);
1593
+ const theme = context.theme || resolvedTheme;
996
1594
  const title = presentation.metadata.title || "Yumia Presentation";
997
1595
  const author = presentation.metadata.author || "YumiaMD";
998
1596
  pptx.title = title;
@@ -1052,6 +1650,9 @@ var PptxRenderer = class {
1052
1650
  case "image":
1053
1651
  this.renderImage(pptxSlide, element, rect);
1054
1652
  break;
1653
+ case "metric":
1654
+ this.renderMetric(pptxSlide, pptx, node, scaleX, scaleY, theme);
1655
+ break;
1055
1656
  case "card":
1056
1657
  this.renderCard(pptxSlide, pptx, node, scaleX, scaleY, theme);
1057
1658
  break;
@@ -1068,7 +1669,7 @@ var PptxRenderer = class {
1068
1669
  const chunks = parseInlineMarkdown(heading.text, {
1069
1670
  fontSize,
1070
1671
  bold: true,
1071
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial",
1672
+ fontFace: cleanFontFace(theme.typography.headingFont),
1072
1673
  color
1073
1674
  });
1074
1675
  pptxSlide.addText(chunks, {
@@ -1086,7 +1687,7 @@ var PptxRenderer = class {
1086
1687
  const color = this.cleanHexColor(theme.colors.text);
1087
1688
  const chunks = parseInlineMarkdown(paragraph.text, {
1088
1689
  fontSize,
1089
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1690
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1090
1691
  color
1091
1692
  });
1092
1693
  pptxSlide.addText(chunks, {
@@ -1103,11 +1704,11 @@ var PptxRenderer = class {
1103
1704
  const fontSize = theme.typography.sizes?.body || 18;
1104
1705
  const color = this.cleanHexColor(theme.colors.text);
1105
1706
  const allChunks = [];
1106
- list.items.forEach((item) => {
1707
+ list.items.forEach((item, itemIdx) => {
1107
1708
  const itemChunks = parseInlineMarkdown(item.text, {
1108
1709
  fontSize,
1109
1710
  color,
1110
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1711
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1111
1712
  indentLevel: item.depth || 0,
1112
1713
  paraSpaceAfter: 8
1113
1714
  });
@@ -1116,6 +1717,12 @@ var PptxRenderer = class {
1116
1717
  ...itemChunks[0].options,
1117
1718
  bullet: list.ordered ? { type: "number" } : true
1118
1719
  };
1720
+ if (itemIdx < list.items.length - 1) {
1721
+ itemChunks[itemChunks.length - 1].options = {
1722
+ ...itemChunks[itemChunks.length - 1].options,
1723
+ breakLine: true
1724
+ };
1725
+ }
1119
1726
  }
1120
1727
  allChunks.push(...itemChunks);
1121
1728
  });
@@ -1132,6 +1739,10 @@ var PptxRenderer = class {
1132
1739
  const tableRows = [];
1133
1740
  const headerBg = this.cleanHexColor(theme.colors.primary);
1134
1741
  const borderColor = this.cleanHexColor(theme.colors.border || "#cbd5e1");
1742
+ const isDark = this.isDarkColor(theme.colors.background);
1743
+ const rowBg1 = this.cleanHexColor(theme.components?.table?.rowAlternateBackground || (isDark ? theme.colors.surface : "ffffff"));
1744
+ const rowBg2 = this.cleanHexColor(isDark ? "#1a1a2e" : "f8fafc");
1745
+ const cellTextColor = this.cleanHexColor(theme.colors.text);
1135
1746
  if (table.headers && table.headers.length > 0) {
1136
1747
  tableRows.push(table.headers.map((h) => ({
1137
1748
  text: h.replace(/\*\*/g, ""),
@@ -1140,19 +1751,21 @@ var PptxRenderer = class {
1140
1751
  color: "ffffff",
1141
1752
  fill: { color: headerBg },
1142
1753
  fontSize: 14,
1754
+ fontFace: cleanFontFace(theme.typography.headingFont),
1143
1755
  align: "center"
1144
1756
  }
1145
1757
  })));
1146
1758
  }
1147
1759
  if (table.rows) {
1148
1760
  table.rows.forEach((row, rowIndex) => {
1149
- const rowBg = rowIndex % 2 === 1 ? "f8fafc" : "ffffff";
1761
+ const rowBg = rowIndex % 2 === 1 ? rowBg2 : rowBg1;
1150
1762
  tableRows.push(row.map((cell) => ({
1151
1763
  text: cell.replace(/\*\*/g, ""),
1152
1764
  options: {
1153
- color: this.cleanHexColor(theme.colors.text),
1765
+ color: cellTextColor,
1154
1766
  fill: { color: rowBg },
1155
- fontSize: 13
1767
+ fontSize: 13,
1768
+ fontFace: cleanFontFace(theme.typography.bodyFont)
1156
1769
  }
1157
1770
  })));
1158
1771
  });
@@ -1163,7 +1776,7 @@ var PptxRenderer = class {
1163
1776
  y: rect.y,
1164
1777
  w: rect.w,
1165
1778
  border: { type: "solid", pt: 1, color: borderColor },
1166
- margin: [4, 8, 4, 8]
1779
+ margin: [6, 10, 6, 10]
1167
1780
  });
1168
1781
  }
1169
1782
  }
@@ -1197,12 +1810,80 @@ var PptxRenderer = class {
1197
1810
  });
1198
1811
  }
1199
1812
  }
1813
+ renderMetric(pptxSlide, pptx, node, scaleX, scaleY, theme) {
1814
+ const metric = node.element;
1815
+ const rect = this.toInches(node.bounds, scaleX, scaleY);
1816
+ const cardTheme = theme.components?.card;
1817
+ let accentColor = theme.colors.primary;
1818
+ if (metric.variant && theme.colors[metric.variant]) {
1819
+ accentColor = theme.colors[metric.variant];
1820
+ }
1821
+ const fillColor = this.cleanHexColor(cardTheme?.background || theme.colors.surface);
1822
+ const borderColor = this.cleanHexColor(metric.variant && theme.colors[metric.variant] ? theme.colors[metric.variant] : cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1823
+ pptxSlide.addShape(pptx.ShapeType.roundRect, {
1824
+ x: rect.x,
1825
+ y: rect.y,
1826
+ w: rect.w,
1827
+ h: rect.h,
1828
+ fill: { color: fillColor },
1829
+ line: { color: borderColor, width: 1.5 },
1830
+ rectRadius: 0.08
1831
+ });
1832
+ const hasChange = Boolean(metric.change);
1833
+ pptxSlide.addText(metric.label.toUpperCase(), {
1834
+ x: rect.x + 0.1,
1835
+ y: rect.y + 0.1,
1836
+ w: rect.w - 0.2,
1837
+ h: 0.22,
1838
+ fontSize: 10,
1839
+ bold: true,
1840
+ color: this.cleanHexColor(theme.colors.muted || "#64748b"),
1841
+ fontFace: cleanFontFace(theme.typography.headingFont),
1842
+ align: "center",
1843
+ valign: "middle"
1844
+ });
1845
+ const displayValue = metric.unit ? `${metric.value} ${metric.unit}` : metric.value;
1846
+ pptxSlide.addText(displayValue, {
1847
+ x: rect.x + 0.1,
1848
+ y: rect.y + 0.34,
1849
+ w: rect.w - 0.2,
1850
+ h: 0.44,
1851
+ fontSize: 26,
1852
+ bold: true,
1853
+ color: this.cleanHexColor(accentColor),
1854
+ fontFace: cleanFontFace(theme.typography.headingFont),
1855
+ align: "center",
1856
+ valign: "middle"
1857
+ });
1858
+ if (hasChange && metric.change) {
1859
+ const isPositive = metric.change.startsWith("+");
1860
+ const changeColor = isPositive ? this.cleanHexColor(theme.colors.success || "#10b981") : this.cleanHexColor(theme.colors.danger || "#ef4444");
1861
+ pptxSlide.addText(metric.change, {
1862
+ x: rect.x + 0.1,
1863
+ y: rect.y + 0.8,
1864
+ w: rect.w - 0.2,
1865
+ h: 0.22,
1866
+ fontSize: 11,
1867
+ bold: true,
1868
+ color: changeColor,
1869
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1870
+ align: "center",
1871
+ valign: "middle"
1872
+ });
1873
+ }
1874
+ }
1200
1875
  renderCard(pptxSlide, pptx, node, scaleX, scaleY, theme) {
1201
1876
  const card = node.element;
1202
1877
  const rect = this.toInches(node.bounds, scaleX, scaleY);
1203
1878
  const cardTheme = theme.components?.card;
1204
1879
  const fillColor = this.cleanHexColor(cardTheme?.background || theme.colors.surface);
1205
- const borderColor = this.cleanHexColor(cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1880
+ let borderColor = this.cleanHexColor(cardTheme?.borderColor || theme.colors.border || "#cbd5e1");
1881
+ let titleColor = this.cleanHexColor(theme.colors.primary);
1882
+ if (card.variant && theme.colors[card.variant]) {
1883
+ const variantHex = this.cleanHexColor(theme.colors[card.variant]);
1884
+ borderColor = variantHex;
1885
+ titleColor = variantHex;
1886
+ }
1206
1887
  pptxSlide.addShape(pptx.ShapeType.roundRect, {
1207
1888
  x: rect.x,
1208
1889
  y: rect.y,
@@ -1220,8 +1901,8 @@ var PptxRenderer = class {
1220
1901
  h: 0.35,
1221
1902
  fontSize: 20,
1222
1903
  bold: true,
1223
- color: this.cleanHexColor(theme.colors.primary),
1224
- fontFace: theme.typography.headingFont.split(",")[0]?.trim() || "Arial"
1904
+ color: titleColor,
1905
+ fontFace: cleanFontFace(theme.typography.headingFont)
1225
1906
  });
1226
1907
  }
1227
1908
  if (node.children) {
@@ -1282,7 +1963,7 @@ var PptxRenderer = class {
1282
1963
  fontSize: 18,
1283
1964
  italic: true,
1284
1965
  color: this.cleanHexColor(theme.colors.muted || theme.colors.text),
1285
- fontFace: theme.typography.bodyFont.split(",")[0]?.trim() || "Arial",
1966
+ fontFace: cleanFontFace(theme.typography.bodyFont),
1286
1967
  valign: "middle"
1287
1968
  });
1288
1969
  }
@@ -1309,6 +1990,14 @@ var PptxRenderer = class {
1309
1990
  return 18;
1310
1991
  }
1311
1992
  }
1993
+ isDarkColor(rawHex) {
1994
+ const hex = this.cleanHexColor(rawHex);
1995
+ const r = parseInt(hex.substring(0, 2), 16) || 0;
1996
+ const g = parseInt(hex.substring(2, 4), 16) || 0;
1997
+ const b = parseInt(hex.substring(4, 6), 16) || 0;
1998
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
1999
+ return luminance < 0.5;
2000
+ }
1312
2001
  cleanHexColor(raw) {
1313
2002
  if (!raw)
1314
2003
  return "000000";
@@ -1331,7 +2020,7 @@ var PptxRenderer = class {
1331
2020
  // src/cli.ts
1332
2021
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
1333
2022
  import { basename, dirname, extname, join, resolve } from "path";
1334
- var VERSION = "0.1.7";
2023
+ var VERSION = "0.1.9";
1335
2024
  function printHelp() {
1336
2025
  return `
1337
2026
  YumiaMD \u2014 Markdown-based presentation compiler (v${VERSION})
@@ -1347,15 +2036,39 @@ Commands:
1347
2036
  schema Output machine-readable JSON schema for AI agents
1348
2037
  build <file> Compile a presentation to editable PowerPoint (.pptx)
1349
2038
 
1350
- Options:
2039
+ Theming & Color Options:
2040
+ --theme, -t <name> Base theme: default | cyberpunk | minimal | corporate | terminal | academic
2041
+ --bg, --background Hex background color (e.g. "#0B0B12" or "#FFFFFF")
2042
+ --primary, -p Hex primary accent color (e.g. "#FF2E88" or "#2563EB")
2043
+ --secondary Hex secondary color (e.g. "#00F0FF")
2044
+ --text Hex text color (e.g. "#FFFFFF" or "#0F172A")
2045
+ --accent Hex accent bar & highlight color
2046
+
2047
+ Compiler & Output Options:
1351
2048
  --out, -o <file> Specify output file path (default: dist/<name>.pptx)
1352
2049
  --format, -f <fmt> Target output format: pptx (default)
2050
+ --strict Enforce zero warnings in 'lint' (exits with code 1 on warning)
1353
2051
  --json Output results formatted as JSON for CI/CD and AI tools
1354
2052
  --layout Show computed geometric bounding boxes in 'inspect'
1355
2053
  -h, --help Show this help message
1356
2054
  -v, --version Show CLI version
1357
2055
  `.trim();
1358
2056
  }
2057
+ function getFlagValue(args, flagNames) {
2058
+ for (let i = 0; i < args.length; i++) {
2059
+ const arg = args[i];
2060
+ if (!arg) continue;
2061
+ for (const flag of flagNames) {
2062
+ if (arg === flag && args[i + 1] && !args[i + 1].startsWith("-")) {
2063
+ return args[i + 1];
2064
+ }
2065
+ if (arg.startsWith(`${flag}=`)) {
2066
+ return arg.slice(flag.length + 1);
2067
+ }
2068
+ }
2069
+ }
2070
+ return void 0;
2071
+ }
1359
2072
  async function runCli(argv) {
1360
2073
  const args = argv.slice(2);
1361
2074
  if (args.length === 0 || args.includes("-h") || args.includes("--help")) {
@@ -1365,9 +2078,16 @@ async function runCli(argv) {
1365
2078
  return { exitCode: 0, output: `yumia v${VERSION}` };
1366
2079
  }
1367
2080
  const isJson = args.includes("--json");
2081
+ const isStrict = args.includes("--strict");
1368
2082
  const nonFlagArgs = args.filter((a) => !a.startsWith("-"));
1369
2083
  const command = nonFlagArgs[0];
1370
2084
  const target = nonFlagArgs[1];
2085
+ const cliTheme = getFlagValue(args, ["--theme", "-t"]);
2086
+ const cliBg = getFlagValue(args, ["--bg", "--background"]);
2087
+ const cliPrimary = getFlagValue(args, ["--primary", "-p"]);
2088
+ const cliSecondary = getFlagValue(args, ["--secondary"]);
2089
+ const cliText = getFlagValue(args, ["--text"]);
2090
+ const cliAccent = getFlagValue(args, ["--accent"]);
1371
2091
  if (command === "schema") {
1372
2092
  const compiler = new YumiaCompiler();
1373
2093
  return {
@@ -1382,10 +2102,19 @@ async function runCli(argv) {
1382
2102
  mkdirSync(projectDir, { recursive: true });
1383
2103
  mkdirSync(join(projectDir, "assets"), { recursive: true });
1384
2104
  mkdirSync(join(projectDir, "themes"), { recursive: true });
2105
+ const chosenTheme = cliTheme || "default";
2106
+ const colorLines = [];
2107
+ if (cliBg) colorLines.push(`background: "${cliBg}"`);
2108
+ if (cliPrimary) colorLines.push(`primary: "${cliPrimary}"`);
2109
+ if (cliSecondary) colorLines.push(`secondary: "${cliSecondary}"`);
2110
+ if (cliText) colorLines.push(`text: "${cliText}"`);
2111
+ if (cliAccent) colorLines.push(`accent: "${cliAccent}"`);
2112
+ const colorsYaml = colorLines.length > 0 ? `
2113
+ ${colorLines.join("\n")}` : "";
1385
2114
  const sampleContent = `---
1386
2115
  title: ${projectName}
1387
- theme: default
1388
- aspectRatio: "16:9"
2116
+ theme: ${chosenTheme}
2117
+ aspectRatio: "16:9"${colorsYaml}
1389
2118
  ---
1390
2119
 
1391
2120
  # ${projectName}
@@ -1402,7 +2131,7 @@ Opening slide introducing the presentation deck.
1402
2131
  :::columns ratios="50:50"
1403
2132
 
1404
2133
  :::column
1405
- :::card Core Pipeline
2134
+ :::card Core Pipeline variant="primary"
1406
2135
  - Semantic AST model
1407
2136
  - Markdown + Directive parser
1408
2137
  - 100% Deterministic layout
@@ -1410,7 +2139,7 @@ Opening slide introducing the presentation deck.
1410
2139
  :::
1411
2140
 
1412
2141
  :::column
1413
- :::card Native Output
2142
+ :::card Native Output variant="warning"
1414
2143
  - Fully editable PowerPoint objects
1415
2144
  - Vector PDF documents
1416
2145
  - Interactive HTML decks
@@ -1428,7 +2157,8 @@ Opening slide introducing the presentation deck.
1428
2157
  success: true,
1429
2158
  project: projectName,
1430
2159
  path: projectDir,
1431
- entry: join(projectDir, "presentation.yumia.md")
2160
+ entry: join(projectDir, "presentation.yumia.md"),
2161
+ theme: chosenTheme
1432
2162
  },
1433
2163
  null,
1434
2164
  2
@@ -1438,6 +2168,7 @@ Opening slide introducing the presentation deck.
1438
2168
  return {
1439
2169
  exitCode: 0,
1440
2170
  output: `\u2713 Created presentation project '${projectName}' at ./${projectName}
2171
+ Theme: ${chosenTheme}${colorsYaml ? ` (with custom colors)` : ""}
1441
2172
  Edit ./${projectName}/presentation.yumia.md to get started!
1442
2173
  Then compile with: yumia build ./${projectName}/presentation.yumia.md`
1443
2174
  };
@@ -1501,57 +2232,46 @@ ${errorLines.join("\n")}`
1501
2232
  }
1502
2233
  try {
1503
2234
  const source = readFileSync(target, "utf-8");
1504
- const presentation = parseYumia(source);
1505
- const engine = new DefaultLayoutEngine();
1506
- const layout = engine.computePresentation(presentation);
1507
- const issues = [];
1508
- layout.slides.forEach((slideResult, index) => {
1509
- const slideNum = index + 1;
1510
- if (slideResult.overflow) {
1511
- issues.push({
1512
- slide: slideNum,
1513
- type: "overflow",
1514
- message: `Content exceeds slide height by ~${Math.round(slideResult.overflowAmount || 0)}px`
1515
- });
1516
- }
1517
- const slide = presentation.slides[index];
1518
- if (slide.elements.length === 0) {
1519
- issues.push({
1520
- slide: slideNum,
1521
- type: "empty",
1522
- message: "Slide has no content elements."
1523
- });
1524
- }
1525
- });
2235
+ const compiler = new YumiaCompiler();
2236
+ const report = compiler.lint(source, { strict: isStrict });
1526
2237
  if (isJson) {
1527
2238
  return {
1528
- exitCode: 0,
2239
+ exitCode: report.passed ? 0 : 1,
1529
2240
  output: JSON.stringify(
1530
2241
  {
1531
2242
  target,
1532
- passed: issues.length === 0,
1533
- slideCount: presentation.slides.length,
1534
- issues
2243
+ ...report
1535
2244
  },
1536
2245
  null,
1537
2246
  2
1538
2247
  )
1539
2248
  };
1540
2249
  }
1541
- if (issues.length === 0) {
2250
+ if (report.issueCount === 0) {
1542
2251
  return {
1543
2252
  exitCode: 0,
1544
- output: `\u2713 Yumia Lint: All ${presentation.slides.length} slide(s) passed with 0 issues.`
2253
+ output: `\u2713 Yumia Lint: All ${report.totalSlides} slide(s) passed with 0 issues.`
1545
2254
  };
1546
2255
  } else {
1547
- const formatted = issues.map((i) => `\u26A0 Slide ${i.slide}: ${i.message}`).join("\n");
2256
+ const issueLines = [];
2257
+ for (const e of report.errors) {
2258
+ issueLines.push(` \u2717 [${e.code}] Slide ${e.slide}: ${e.message}`);
2259
+ }
2260
+ for (const w of report.warnings) {
2261
+ issueLines.push(` \u26A0 [${w.code}] Slide ${w.slide}: ${w.message}`);
2262
+ }
2263
+ for (const info of report.infos) {
2264
+ issueLines.push(` \u2139 [${info.code}] Slide ${info.slide}: ${info.message}`);
2265
+ }
2266
+ const summary = `Found ${report.errors.length} error(s), ${report.warnings.length} warning(s).`;
2267
+ const exitCode = report.passed ? 0 : 1;
1548
2268
  return {
1549
- exitCode: 0,
2269
+ exitCode,
1550
2270
  output: `Yumia Lint Report for '${target}':
1551
2271
 
1552
- ${formatted}
2272
+ ${issueLines.join("\n")}
1553
2273
 
1554
- Found ${issues.length} warning(s).`
2274
+ ${summary}${isStrict && report.warnings.length > 0 ? " (failed due to --strict)" : ""}`
1555
2275
  };
1556
2276
  }
1557
2277
  } catch (err) {
@@ -1615,7 +2335,18 @@ Found ${issues.length} warning(s).`
1615
2335
  mkdirSync(dirname(outputPath), { recursive: true });
1616
2336
  const compiler = new YumiaCompiler();
1617
2337
  const renderer = new PptxRenderer();
1618
- const result = await compiler.compile(source, renderer);
2338
+ const cliColorOverrides = {};
2339
+ if (cliBg) cliColorOverrides.background = cliBg;
2340
+ if (cliPrimary) cliColorOverrides.primary = cliPrimary;
2341
+ if (cliSecondary) cliColorOverrides.secondary = cliSecondary;
2342
+ if (cliText) cliColorOverrides.text = cliText;
2343
+ if (cliAccent) cliColorOverrides.accent = cliAccent;
2344
+ const renderTheme = cliTheme || Object.keys(cliColorOverrides).length > 0 ? resolveTheme(cliTheme || "default", {
2345
+ ...Object.keys(cliColorOverrides).length > 0 ? { colors: cliColorOverrides } : {}
2346
+ }) : void 0;
2347
+ const result = await compiler.compile(source, renderer, {
2348
+ ...renderTheme ? { renderContext: { theme: renderTheme } } : {}
2349
+ });
1619
2350
  const buffer = result.data instanceof Uint8Array ? Buffer.from(result.data) : Buffer.from(new Uint8Array(result.data));
1620
2351
  writeFileSync(outputPath, buffer);
1621
2352
  if (isJson) {
@@ -1663,6 +2394,7 @@ export {
1663
2394
  createList,
1664
2395
  createImage,
1665
2396
  createCard,
2397
+ createMetric,
1666
2398
  createCode,
1667
2399
  createQuote,
1668
2400
  createTable,
@@ -1676,8 +2408,17 @@ export {
1676
2408
  VIEWPORT_PRESETS,
1677
2409
  DefaultLayoutEngine,
1678
2410
  defaultTheme,
2411
+ cyberpunkTheme,
2412
+ minimalTheme,
2413
+ corporateTheme,
2414
+ terminalTheme,
2415
+ academicTheme,
2416
+ THEME_REGISTRY,
2417
+ resolveTheme,
1679
2418
  createTheme,
2419
+ YumiaLinter,
1680
2420
  YumiaCompiler,
2421
+ cleanFontFace,
1681
2422
  parseInlineMarkdown,
1682
2423
  PptxRenderer,
1683
2424
  VERSION,
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export * from '@yumiamd/layout';
6
6
  export * from '@yumiamd/renderer';
7
7
  export * from '@yumiamd/renderer-pptx';
8
8
 
9
- declare const VERSION = "0.1.7";
9
+ declare const VERSION = "0.1.9";
10
10
  declare function printHelp(): string;
11
11
  declare function runCli(argv: string[]): Promise<{
12
12
  exitCode: number;
package/dist/index.js CHANGED
@@ -3,9 +3,14 @@ import {
3
3
  DefaultLayoutEngine,
4
4
  DefaultYumiaParser,
5
5
  PptxRenderer,
6
+ THEME_REGISTRY,
6
7
  VERSION,
7
8
  VIEWPORT_PRESETS,
8
9
  YumiaCompiler,
10
+ YumiaLinter,
11
+ academicTheme,
12
+ cleanFontFace,
13
+ corporateTheme,
9
14
  createCard,
10
15
  createCode,
11
16
  createColumn,
@@ -15,26 +20,36 @@ import {
15
20
  createImage,
16
21
  createLayoutDirective,
17
22
  createList,
23
+ createMetric,
18
24
  createParagraph,
19
25
  createPresentation,
20
26
  createQuote,
21
27
  createSlide,
22
28
  createTable,
23
29
  createTheme,
30
+ cyberpunkTheme,
24
31
  defaultTheme,
32
+ minimalTheme,
25
33
  parseInlineMarkdown,
26
34
  parseYumia,
27
35
  printHelp,
28
- runCli
29
- } from "./chunk-GMIVPFBA.js";
36
+ resolveTheme,
37
+ runCli,
38
+ terminalTheme
39
+ } from "./chunk-F3CX7UG6.js";
30
40
  export {
31
41
  DEFAULT_VIEWPORT,
32
42
  DefaultLayoutEngine,
33
43
  DefaultYumiaParser,
34
44
  PptxRenderer,
45
+ THEME_REGISTRY,
35
46
  VERSION,
36
47
  VIEWPORT_PRESETS,
37
48
  YumiaCompiler,
49
+ YumiaLinter,
50
+ academicTheme,
51
+ cleanFontFace,
52
+ corporateTheme,
38
53
  createCard,
39
54
  createCode,
40
55
  createColumn,
@@ -44,15 +59,20 @@ export {
44
59
  createImage,
45
60
  createLayoutDirective,
46
61
  createList,
62
+ createMetric,
47
63
  createParagraph,
48
64
  createPresentation,
49
65
  createQuote,
50
66
  createSlide,
51
67
  createTable,
52
68
  createTheme,
69
+ cyberpunkTheme,
53
70
  defaultTheme,
71
+ minimalTheme,
54
72
  parseInlineMarkdown,
55
73
  parseYumia,
56
74
  printHelp,
57
- runCli
75
+ resolveTheme,
76
+ runCli,
77
+ terminalTheme
58
78
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yumiamd",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Command line interface and compiler for YumiaMD presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,13 +20,13 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "tsup": "^8.5.1",
23
- "@yumiamd/ast": "0.1.7",
24
- "@yumiamd/parser": "0.1.7",
25
- "@yumiamd/renderer": "0.1.7",
26
- "@yumiamd/core": "0.1.7",
27
- "@yumiamd/renderer-pptx": "0.1.7",
28
- "@yumiamd/layout": "0.1.7",
29
- "@yumiamd/theme": "0.1.7"
23
+ "@yumiamd/core": "0.1.9",
24
+ "@yumiamd/ast": "0.1.9",
25
+ "@yumiamd/parser": "0.1.9",
26
+ "@yumiamd/renderer": "0.1.9",
27
+ "@yumiamd/layout": "0.1.9",
28
+ "@yumiamd/renderer-pptx": "0.1.9",
29
+ "@yumiamd/theme": "0.1.9"
30
30
  },
31
31
  "keywords": [
32
32
  "yumia",