claude-presentation-master 7.2.0 → 7.3.0

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/index.mjs CHANGED
@@ -31,6 +31,75 @@ import { readFileSync } from "fs";
31
31
  import { join, dirname } from "path";
32
32
  import { fileURLToPath } from "url";
33
33
  import * as yaml from "yaml";
34
+
35
+ // src/utils/Logger.ts
36
+ var Logger = class {
37
+ level;
38
+ prefix;
39
+ timestamps;
40
+ constructor(options = {}) {
41
+ this.level = options.level ?? 1 /* INFO */;
42
+ this.prefix = options.prefix ?? "";
43
+ this.timestamps = options.timestamps ?? false;
44
+ }
45
+ setLevel(level) {
46
+ this.level = level;
47
+ }
48
+ formatMessage(message) {
49
+ const parts = [];
50
+ if (this.timestamps) {
51
+ parts.push(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
52
+ }
53
+ if (this.prefix) {
54
+ parts.push(`[${this.prefix}]`);
55
+ }
56
+ parts.push(message);
57
+ return parts.join(" ");
58
+ }
59
+ debug(message, ...args) {
60
+ if (this.level <= 0 /* DEBUG */) {
61
+ console.debug(this.formatMessage(message), ...args);
62
+ }
63
+ }
64
+ info(message, ...args) {
65
+ if (this.level <= 1 /* INFO */) {
66
+ console.info(this.formatMessage(message), ...args);
67
+ }
68
+ }
69
+ warn(message, ...args) {
70
+ if (this.level <= 2 /* WARN */) {
71
+ console.warn(this.formatMessage(message), ...args);
72
+ }
73
+ }
74
+ error(message, ...args) {
75
+ if (this.level <= 3 /* ERROR */) {
76
+ console.error(this.formatMessage(message), ...args);
77
+ }
78
+ }
79
+ // Progress messages (always shown unless silent)
80
+ progress(message) {
81
+ if (this.level < 4 /* SILENT */) {
82
+ console.info(message);
83
+ }
84
+ }
85
+ // Success messages
86
+ success(message) {
87
+ if (this.level < 4 /* SILENT */) {
88
+ console.info(`\u2705 ${message}`);
89
+ }
90
+ }
91
+ // Step messages for workflow progress
92
+ step(message) {
93
+ if (this.level <= 1 /* INFO */) {
94
+ console.info(` \u2713 ${message}`);
95
+ }
96
+ }
97
+ };
98
+ var logger = new Logger({
99
+ level: process.env.LOG_LEVEL ? parseInt(process.env.LOG_LEVEL) : 1 /* INFO */
100
+ });
101
+
102
+ // src/kb/KnowledgeGateway.ts
34
103
  function getModuleDir() {
35
104
  if (typeof import.meta !== "undefined" && import.meta.url) {
36
105
  return dirname(fileURLToPath(import.meta.url));
@@ -41,7 +110,7 @@ function getModuleDir() {
41
110
  return process.cwd();
42
111
  }
43
112
  var KnowledgeGateway = class {
44
- kb;
113
+ kb = {};
45
114
  loaded = false;
46
115
  constructor() {
47
116
  }
@@ -64,7 +133,7 @@ var KnowledgeGateway = class {
64
133
  const content = readFileSync(path, "utf-8");
65
134
  this.kb = yaml.parse(content);
66
135
  this.loaded = true;
67
- console.log(` \u2713 Knowledge base loaded from ${path}`);
136
+ logger.step(`Knowledge base loaded from ${path}`);
68
137
  return;
69
138
  } catch {
70
139
  }
@@ -255,7 +324,7 @@ var KnowledgeGateway = class {
255
324
  */
256
325
  getChartGuidance(purpose) {
257
326
  this.ensureLoaded();
258
- return this.kb.chart_selection_guide.by_purpose[purpose];
327
+ return this.kb.chart_selection_guide.by_purpose?.[purpose];
259
328
  }
260
329
  /**
261
330
  * Get charts to avoid
@@ -270,7 +339,7 @@ var KnowledgeGateway = class {
270
339
  */
271
340
  getIBPitchBookStructure(type) {
272
341
  this.ensureLoaded();
273
- return this.kb.investment_banking?.pitch_book_types[type];
342
+ return this.kb.investment_banking?.pitch_book_types?.[type];
274
343
  }
275
344
  /**
276
345
  * Check if knowledge base is loaded
@@ -820,22 +889,27 @@ var ContentAnalyzer = class {
820
889
  async analyze(content, contentType) {
821
890
  await this.initialize();
822
891
  const text = this.parseContent(content, contentType);
823
- const title = this.extractTitle(text);
892
+ const { title, subtitle } = this.extractTitleAndSubtitle(text);
893
+ logger.step(`Title: "${title}", Subtitle: "${subtitle || "(none)"}"`);
824
894
  const detectedType = this.detectPresentationType(text);
825
- console.log(` \u2713 Detected type: ${detectedType}`);
895
+ logger.step(`Detected type: ${detectedType}`);
826
896
  const sections = this.extractSections(text);
827
- console.log(` \u2713 Found ${sections.length} sections`);
897
+ logger.step(`Found ${sections.length} sections`);
828
898
  const scqa = this.extractSCQA(text);
829
899
  const sparkline = this.extractSparkline(text);
830
900
  const keyMessages = this.extractKeyMessages(text);
831
901
  const dataPoints = this.extractDataPoints(text);
832
- console.log(` \u2713 Found ${dataPoints.length} data points`);
902
+ logger.step(`Found ${dataPoints.length} data points`);
833
903
  const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
834
904
  const starMoments = this.extractStarMoments(text);
835
905
  const estimatedSlideCount = Math.max(5, sections.length + 3);
906
+ const whatIsContent = scqa.situation ? [scqa.situation] : [];
907
+ const whatCouldBeContent = scqa.answer ? [scqa.answer] : keyMessages.slice(0, 2);
836
908
  return {
837
909
  detectedType,
838
910
  title,
911
+ subtitle,
912
+ // NEW: Use explicit subtitle from content
839
913
  sections,
840
914
  keyMessages,
841
915
  dataPoints,
@@ -846,8 +920,10 @@ var ContentAnalyzer = class {
846
920
  answer: scqa.answer || ""
847
921
  },
848
922
  sparkline: {
849
- whatIs: sparkline.callToAction ? [sparkline.callToAction] : [],
850
- whatCouldBe: keyMessages,
923
+ whatIs: whatIsContent,
924
+ // FIX: Use situation, not CTA
925
+ whatCouldBe: whatCouldBeContent,
926
+ // FIX: Use answer/vision, not random messages
851
927
  callToAdventure: sparkline.callToAction || ""
852
928
  },
853
929
  titles,
@@ -875,18 +951,46 @@ var ContentAnalyzer = class {
875
951
  return text;
876
952
  }
877
953
  /**
878
- * Extract the main title from content
954
+ * Extract the main title AND subtitle from content
955
+ * Subtitle is the line immediately after the H1 title (if not another header)
879
956
  */
880
- extractTitle(text) {
881
- const h1Match = text.match(/^#\s+(.+)$/m);
882
- if (h1Match && h1Match[1]) {
883
- return h1Match[1].replace(/\*\*/g, "").trim();
957
+ extractTitleAndSubtitle(text) {
958
+ const lines = text.split("\n");
959
+ let title = "Presentation";
960
+ let subtitle = "";
961
+ let foundTitle = false;
962
+ let titleLineIndex = -1;
963
+ for (let i = 0; i < lines.length; i++) {
964
+ const rawLine = lines[i];
965
+ if (!rawLine) continue;
966
+ const line = rawLine.trim();
967
+ const h1Match = line.match(/^#\s+(.+)$/);
968
+ if (h1Match && h1Match[1] && !foundTitle) {
969
+ title = h1Match[1].replace(/\*\*/g, "").trim();
970
+ foundTitle = true;
971
+ titleLineIndex = i;
972
+ continue;
973
+ }
974
+ if (foundTitle && i > titleLineIndex && line.length > 0) {
975
+ if (line.startsWith("#")) break;
976
+ if (line.startsWith("-") || line.startsWith("*") || /^\d+\./.test(line)) break;
977
+ subtitle = line.replace(/\*\*/g, "").trim().slice(0, 100);
978
+ break;
979
+ }
884
980
  }
885
- const lines = text.split("\n").filter((l) => l.trim().length > 0);
886
- if (lines.length > 0 && lines[0]) {
887
- return lines[0].replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
981
+ if (!foundTitle) {
982
+ const firstLine = lines.find((l) => l.trim().length > 0);
983
+ if (firstLine) {
984
+ title = firstLine.replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
985
+ }
888
986
  }
889
- return "Presentation";
987
+ return { title, subtitle };
988
+ }
989
+ /**
990
+ * Extract the main title from content (legacy wrapper)
991
+ */
992
+ extractTitle(text) {
993
+ return this.extractTitleAndSubtitle(text).title;
890
994
  }
891
995
  /**
892
996
  * Detect presentation type from content signals
@@ -949,12 +1053,16 @@ var ContentAnalyzer = class {
949
1053
  }
950
1054
  const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
951
1055
  if (bulletMatch && bulletMatch[1] && currentSection) {
952
- currentSection.bullets.push(bulletMatch[1]);
1056
+ const bulletText = bulletMatch[1];
1057
+ currentSection.bullets.push(bulletText);
1058
+ this.extractMetricsFromText(bulletText, currentSection.metrics);
953
1059
  continue;
954
1060
  }
955
1061
  const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
956
1062
  if (numberedMatch && numberedMatch[1] && currentSection) {
957
- currentSection.bullets.push(numberedMatch[1]);
1063
+ const itemText = numberedMatch[1];
1064
+ currentSection.bullets.push(itemText);
1065
+ this.extractMetricsFromText(itemText, currentSection.metrics);
958
1066
  continue;
959
1067
  }
960
1068
  const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
@@ -965,6 +1073,9 @@ var ContentAnalyzer = class {
965
1073
  });
966
1074
  continue;
967
1075
  }
1076
+ if (currentSection && trimmedLine) {
1077
+ this.extractMetricsFromText(trimmedLine, currentSection.metrics);
1078
+ }
968
1079
  if (trimmedLine.includes("|") && currentSection) {
969
1080
  const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
970
1081
  if (cells.length >= 2) {
@@ -1336,6 +1447,49 @@ var ContentAnalyzer = class {
1336
1447
  const fallback = cleaned.slice(0, 150);
1337
1448
  return fallback.length >= 20 ? fallback : "";
1338
1449
  }
1450
+ /**
1451
+ * Extract metrics from natural language text
1452
+ * Handles formats like "40% productivity loss", "$2.5M investment"
1453
+ */
1454
+ extractMetricsFromText(text, metrics) {
1455
+ const usedValues = new Set(metrics.map((m) => m.value));
1456
+ const moneyMatches = text.matchAll(/(\$[\d,.]+[MBK]?)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1457
+ for (const match of moneyMatches) {
1458
+ const value = match[1];
1459
+ const rawLabel = match[2]?.trim();
1460
+ if (value && rawLabel && !usedValues.has(value)) {
1461
+ usedValues.add(value);
1462
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1463
+ }
1464
+ }
1465
+ const percentMatches = text.matchAll(/(\d+(?:\.\d+)?%)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1466
+ for (const match of percentMatches) {
1467
+ const value = match[1];
1468
+ const rawLabel = match[2]?.trim();
1469
+ if (value && rawLabel && !usedValues.has(value)) {
1470
+ usedValues.add(value);
1471
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1472
+ }
1473
+ }
1474
+ const multMatches = text.matchAll(/(\d+[xX])\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1475
+ for (const match of multMatches) {
1476
+ const value = match[1];
1477
+ const rawLabel = match[2]?.trim();
1478
+ if (value && rawLabel && !usedValues.has(value)) {
1479
+ usedValues.add(value);
1480
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1481
+ }
1482
+ }
1483
+ const numMatches = text.matchAll(/(\d{3,}\+?)\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1484
+ for (const match of numMatches) {
1485
+ const value = match[1];
1486
+ const rawLabel = match[2]?.trim();
1487
+ if (value && rawLabel && !usedValues.has(value)) {
1488
+ usedValues.add(value);
1489
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1490
+ }
1491
+ }
1492
+ }
1339
1493
  };
1340
1494
 
1341
1495
  // src/core/ContentPatternClassifier.ts
@@ -1520,8 +1674,8 @@ var SlideFactory = class {
1520
1674
  this.usedContent = /* @__PURE__ */ new Set();
1521
1675
  this.usedTitles = /* @__PURE__ */ new Set();
1522
1676
  this.config = this.loadKBConfig(type);
1523
- console.log(` \u2713 SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1524
- console.log(` \u2713 KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1677
+ logger.step(`SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1678
+ logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1525
1679
  }
1526
1680
  /**
1527
1681
  * Load ALL configuration from KB - ZERO hardcoded values after this.
@@ -1549,7 +1703,7 @@ var SlideFactory = class {
1549
1703
  async createSlides(analysis) {
1550
1704
  const slides = [];
1551
1705
  const storyStructure = this.kb.getStoryStructure(this.presentationType);
1552
- console.log(` \u2713 Using ${storyStructure.framework} story framework`);
1706
+ logger.step(`Using ${storyStructure.framework} story framework`);
1553
1707
  slides.push(this.createTitleSlide(0, analysis));
1554
1708
  const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
1555
1709
  if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
@@ -1587,7 +1741,7 @@ var SlideFactory = class {
1587
1741
  const validatedSlides = this.validateAndFixSlides(slides);
1588
1742
  this.checkRequiredElements(validatedSlides);
1589
1743
  this.checkAntiPatterns(validatedSlides);
1590
- console.log(` \u2713 Created ${validatedSlides.length} validated slides`);
1744
+ logger.step(`Created ${validatedSlides.length} validated slides`);
1591
1745
  return validatedSlides;
1592
1746
  }
1593
1747
  // ===========================================================================
@@ -1628,7 +1782,7 @@ var SlideFactory = class {
1628
1782
  case "technical":
1629
1783
  return this.createCodeSlide(index, section);
1630
1784
  default:
1631
- console.log(` \u26A0 Unknown slide type '${type}', using bullet-points`);
1785
+ logger.warn(`Unknown slide type '${type}', using bullet-points`);
1632
1786
  return this.createBulletSlide(index, section);
1633
1787
  }
1634
1788
  }
@@ -1639,9 +1793,10 @@ var SlideFactory = class {
1639
1793
  const data = {
1640
1794
  title: this.truncateText(analysis.title, this.config.rules.wordsPerSlide.max)
1641
1795
  };
1642
- if (analysis.keyMessages[0]) {
1796
+ const subtitleSource = analysis.subtitle || analysis.keyMessages[0] || "";
1797
+ if (subtitleSource) {
1643
1798
  data.subtitle = this.truncateText(
1644
- analysis.keyMessages[0],
1799
+ subtitleSource,
1645
1800
  this.config.defaults.subtitle.maxWords
1646
1801
  // FROM KB
1647
1802
  );
@@ -1685,7 +1840,9 @@ var SlideFactory = class {
1685
1840
  addSCQASlides(slides, analysis) {
1686
1841
  const scqa = analysis.scqa;
1687
1842
  const titles = this.config.scqaTitles;
1688
- if (scqa?.situation && !this.usedContent.has("scqa-situation")) {
1843
+ const minBodyLength = 20;
1844
+ const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
1845
+ if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
1689
1846
  this.usedContent.add("scqa-situation");
1690
1847
  slides.push({
1691
1848
  index: slides.length,
@@ -1693,12 +1850,13 @@ var SlideFactory = class {
1693
1850
  data: {
1694
1851
  title: titles.situation,
1695
1852
  // FROM KB - not hardcoded 'Current Situation'
1696
- body: this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max)
1853
+ body: situationBody
1697
1854
  },
1698
1855
  classes: ["situation-slide"]
1699
1856
  });
1700
1857
  }
1701
- if (scqa?.complication && !this.usedContent.has("scqa-complication")) {
1858
+ const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
1859
+ if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
1702
1860
  this.usedContent.add("scqa-complication");
1703
1861
  slides.push({
1704
1862
  index: slides.length,
@@ -1706,12 +1864,13 @@ var SlideFactory = class {
1706
1864
  data: {
1707
1865
  title: titles.complication,
1708
1866
  // FROM KB - not hardcoded 'The Challenge'
1709
- body: this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max)
1867
+ body: complicationBody
1710
1868
  },
1711
1869
  classes: ["complication-slide"]
1712
1870
  });
1713
1871
  }
1714
- if (scqa?.question && !this.usedContent.has("scqa-question")) {
1872
+ const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
1873
+ if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
1715
1874
  this.usedContent.add("scqa-question");
1716
1875
  slides.push({
1717
1876
  index: slides.length,
@@ -1719,7 +1878,7 @@ var SlideFactory = class {
1719
1878
  data: {
1720
1879
  title: titles.question,
1721
1880
  // FROM KB - not hardcoded 'The Question'
1722
- body: this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max)
1881
+ body: questionBody
1723
1882
  },
1724
1883
  classes: ["question-slide"]
1725
1884
  });
@@ -1729,7 +1888,8 @@ var SlideFactory = class {
1729
1888
  const spark = analysis.sparkline;
1730
1889
  const titles = this.config.sparklineTitles;
1731
1890
  const whatIsFirst = spark?.whatIs?.[0];
1732
- if (whatIsFirst && !this.usedContent.has("spark-what-is")) {
1891
+ const whatIsBody = whatIsFirst ? this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max) : "";
1892
+ if (whatIsBody.length >= 20 && !this.usedContent.has("spark-what-is")) {
1733
1893
  this.usedContent.add("spark-what-is");
1734
1894
  slides.push({
1735
1895
  index: slides.length,
@@ -1737,13 +1897,14 @@ var SlideFactory = class {
1737
1897
  data: {
1738
1898
  title: titles.whatIs,
1739
1899
  // FROM KB - not hardcoded 'Where We Are Today'
1740
- body: this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max)
1900
+ body: whatIsBody
1741
1901
  },
1742
1902
  classes: ["what-is-slide"]
1743
1903
  });
1744
1904
  }
1745
1905
  const whatCouldBeFirst = spark?.whatCouldBe?.[0];
1746
- if (whatCouldBeFirst && !this.usedContent.has("spark-could-be")) {
1906
+ const whatCouldBeBody = whatCouldBeFirst ? this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max) : "";
1907
+ if (whatCouldBeBody.length >= 20 && !this.usedContent.has("spark-could-be")) {
1747
1908
  this.usedContent.add("spark-could-be");
1748
1909
  slides.push({
1749
1910
  index: slides.length,
@@ -1751,7 +1912,7 @@ var SlideFactory = class {
1751
1912
  data: {
1752
1913
  title: titles.whatCouldBe,
1753
1914
  // FROM KB - not hardcoded 'What Could Be'
1754
- body: this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max)
1915
+ body: whatCouldBeBody
1755
1916
  },
1756
1917
  classes: ["what-could-be-slide"]
1757
1918
  });
@@ -1761,15 +1922,19 @@ var SlideFactory = class {
1761
1922
  // CONTENT SLIDES - ALL values from KB
1762
1923
  // ===========================================================================
1763
1924
  createBigNumberSlide(index, section, pattern) {
1764
- const bigNumber = this.classifier.extractBigNumber(
1765
- `${section.header} ${section.content} ${section.bullets.join(" ")}`
1766
- );
1925
+ const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
1926
+ const bigNumber = this.classifier.extractBigNumber(fullContent);
1927
+ const actualValue = bigNumber?.value || pattern.bigNumberValue;
1928
+ if (!actualValue) {
1929
+ logger.warn(`No number found for big-number slide, falling back to single-statement`);
1930
+ return this.createSingleStatementSlide(index, section);
1931
+ }
1767
1932
  return {
1768
1933
  index,
1769
1934
  type: "big-number",
1770
1935
  data: {
1771
1936
  title: this.createTitle(section.header, section),
1772
- keyMessage: bigNumber?.value || pattern.bigNumberValue || "0",
1937
+ keyMessage: actualValue,
1773
1938
  body: bigNumber?.context || this.truncateText(
1774
1939
  section.content,
1775
1940
  this.config.defaults.context.maxWords
@@ -1956,16 +2121,18 @@ var SlideFactory = class {
1956
2121
  };
1957
2122
  }
1958
2123
  createSingleStatementSlide(index, section) {
2124
+ const bodyContent = section.content || section.bullets.join(" ") || "";
2125
+ const truncatedBody = this.truncateText(bodyContent, this.config.rules.wordsPerSlide.max);
2126
+ if (truncatedBody.length < 15 && section.bullets.length > 0) {
2127
+ return this.createBulletSlide(index, section);
2128
+ }
1959
2129
  return {
1960
2130
  index,
1961
2131
  type: "single-statement",
1962
2132
  data: {
1963
2133
  title: this.createTitle(section.header, section),
1964
- body: this.truncateText(
1965
- section.content || section.bullets[0] || "",
1966
- this.config.rules.wordsPerSlide.max
1967
- // FROM KB
1968
- )
2134
+ body: truncatedBody || section.header
2135
+ // Fallback to header if no body
1969
2136
  },
1970
2137
  classes: ["single-statement-slide"]
1971
2138
  };
@@ -2038,7 +2205,7 @@ var SlideFactory = class {
2038
2205
  slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
2039
2206
  }
2040
2207
  if (validation.violations.length > 0) {
2041
- console.log(` \u26A0 Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2208
+ logger.warn(`Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2042
2209
  }
2043
2210
  if (validation.warnings.length > 0) {
2044
2211
  slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
@@ -2078,7 +2245,7 @@ var SlideFactory = class {
2078
2245
  }
2079
2246
  }
2080
2247
  if (missingElements.length > 0) {
2081
- console.log(` \u26A0 Missing required elements: ${missingElements.join(", ")}`);
2248
+ logger.warn(`Missing required elements: ${missingElements.join(", ")}`);
2082
2249
  }
2083
2250
  }
2084
2251
  /**
@@ -2109,7 +2276,7 @@ var SlideFactory = class {
2109
2276
  }
2110
2277
  }
2111
2278
  if (violations.length > 0) {
2112
- console.log(` \u26A0 Anti-pattern violations: ${violations.join(", ")}`);
2279
+ logger.warn(`Anti-pattern violations: ${violations.join(", ")}`);
2113
2280
  }
2114
2281
  }
2115
2282
  // ===========================================================================
@@ -2454,9 +2621,12 @@ var TemplateEngine = class {
2454
2621
  this.templates.set("big-number", this.handlebars.compile(`
2455
2622
  <section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
2456
2623
  <div class="slide-content big-number-content">
2457
- <div class="number animate-zoomIn">{{title}}</div>
2458
- {{#if subtitle}}
2459
- <p class="number-context animate-fadeIn delay-300">{{subtitle}}</p>
2624
+ {{#if title}}
2625
+ <h2 class="title animate-fadeIn">{{title}}</h2>
2626
+ {{/if}}
2627
+ <div class="number animate-zoomIn">{{keyMessage}}</div>
2628
+ {{#if body}}
2629
+ <p class="number-context animate-fadeIn delay-300">{{body}}</p>
2460
2630
  {{/if}}
2461
2631
  {{> source}}
2462
2632
  </div>
@@ -4840,6 +5010,12 @@ ${slides}
4840
5010
  const highlight = palette.accent || "#e94560";
4841
5011
  const text = palette.text || "#1a1a2e";
4842
5012
  const background = palette.background || "#ffffff";
5013
+ const isDark = this.isDarkPalette(palette);
5014
+ const defaultBg = isDark ? background : "#ffffff";
5015
+ const defaultText = isDark ? text : "#1a1a2e";
5016
+ const headingColor = isDark ? primary : primary;
5017
+ const titleBg = isDark ? background : primary;
5018
+ const titleBgEnd = isDark ? this.lightenColor(background, 10) : this.darkenColor(primary, 15);
4843
5019
  return `
4844
5020
  /* Base Styles - KB-Driven Design System */
4845
5021
  :root {
@@ -4866,7 +5042,13 @@ ${slides}
4866
5042
  font-family: var(--font-body);
4867
5043
  font-size: ${fontSize};
4868
5044
  line-height: ${lineHeight};
4869
- color: var(--color-text);
5045
+ color: ${defaultText};
5046
+ background: ${defaultBg};
5047
+ }
5048
+
5049
+ /* Override reveal.js default backgrounds for dark mode */
5050
+ .reveal .slides section {
5051
+ background: ${defaultBg};
4870
5052
  }
4871
5053
 
4872
5054
  .reveal .slides {
@@ -4895,10 +5077,17 @@ ${slides}
4895
5077
  font-family: var(--font-heading);
4896
5078
  font-weight: 700;
4897
5079
  letter-spacing: -0.02em;
4898
- color: var(--color-primary);
5080
+ color: ${isDark ? primary : primary};
4899
5081
  margin-bottom: 0.5em;
4900
5082
  }
4901
5083
 
5084
+ /* Dark mode text visibility */
5085
+ ${isDark ? `
5086
+ .reveal, .reveal p, .reveal li, .reveal span {
5087
+ color: ${text};
5088
+ }
5089
+ ` : ""}
5090
+
4902
5091
  .reveal h1 { font-size: 2.5em; }
4903
5092
  .reveal h2 { font-size: 1.8em; }
4904
5093
  .reveal h3 { font-size: 1.3em; }
@@ -5076,6 +5265,124 @@ ${slides}
5076
5265
  font-weight: 600;
5077
5266
  margin-top: 1em;
5078
5267
  }
5268
+
5269
+ /* ================================================================
5270
+ SLIDE TYPE BACKGROUNDS - KB-Driven Visual Design
5271
+ Creates visual hierarchy and differentiation between slide types
5272
+ ================================================================ */
5273
+
5274
+ /* Title slide: Bold background - makes strong first impression */
5275
+ .reveal .slide-title {
5276
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5277
+ }
5278
+ .reveal .slide-title h1,
5279
+ .reveal .slide-title h2,
5280
+ .reveal .slide-title p,
5281
+ .reveal .slide-title .subtitle {
5282
+ color: ${isDark ? primary : "#ffffff"};
5283
+ }
5284
+
5285
+ /* Section dividers: Subtle visual breaks */
5286
+ .reveal .slide-section-divider {
5287
+ background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
5288
+ }
5289
+
5290
+ /* Big number slides: Clean with accent highlight - emphasizes the data */
5291
+ .reveal .slide-big-number {
5292
+ background: var(--color-background);
5293
+ border-left: 8px solid var(--color-highlight);
5294
+ }
5295
+ .reveal .slide-big-number .number {
5296
+ font-size: 5em;
5297
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5298
+ -webkit-background-clip: text;
5299
+ -webkit-text-fill-color: transparent;
5300
+ background-clip: text;
5301
+ }
5302
+
5303
+ /* Metrics grid: Light accent background - data-focused */
5304
+ .reveal .slide-metrics-grid {
5305
+ background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
5306
+ }
5307
+ ${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
5308
+
5309
+ /* CTA slide: Highlight color - drives action */
5310
+ .reveal .slide-cta {
5311
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5312
+ }
5313
+ .reveal .slide-cta h2,
5314
+ .reveal .slide-cta p {
5315
+ color: #ffffff;
5316
+ }
5317
+ .reveal .slide-cta .cta-button {
5318
+ background: #ffffff;
5319
+ color: var(--color-highlight);
5320
+ }
5321
+
5322
+ /* Thank you slide: Matches title for bookend effect */
5323
+ .reveal .slide-thank-you {
5324
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5325
+ }
5326
+ .reveal .slide-thank-you h2,
5327
+ .reveal .slide-thank-you p,
5328
+ .reveal .slide-thank-you .subtitle {
5329
+ color: ${isDark ? primary : "#ffffff"};
5330
+ }
5331
+
5332
+ /* Quote slides: Elegant subtle background */
5333
+ .reveal .slide-quote {
5334
+ background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
5335
+ }
5336
+ .reveal .slide-quote blockquote {
5337
+ border-left-color: var(--color-accent);
5338
+ font-size: 1.3em;
5339
+ }
5340
+ ${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
5341
+
5342
+ /* Single statement: Clean, centered, impactful */
5343
+ .reveal .slide-single-statement {
5344
+ background: var(--color-background);
5345
+ }
5346
+ .reveal .slide-single-statement .statement,
5347
+ .reveal .slide-single-statement .big-idea-text {
5348
+ font-size: 2.2em;
5349
+ max-width: 80%;
5350
+ margin: 0 auto;
5351
+ }
5352
+
5353
+ /* Comparison slides: Split visual */
5354
+ .reveal .slide-comparison .columns {
5355
+ gap: 60px;
5356
+ }
5357
+ .reveal .slide-comparison .column:first-child {
5358
+ border-right: 2px solid ${this.lightenColor(secondary, 70)};
5359
+ padding-right: 30px;
5360
+ }
5361
+
5362
+ /* Timeline/Process: Visual flow */
5363
+ .reveal .slide-timeline .steps,
5364
+ .reveal .slide-process .steps {
5365
+ display: flex;
5366
+ gap: 20px;
5367
+ }
5368
+ .reveal .slide-timeline .step,
5369
+ .reveal .slide-process .step {
5370
+ flex: 1;
5371
+ padding: 20px;
5372
+ background: ${isDark ? this.lightenColor(background, 10) : this.lightenColor(secondary, 92)};
5373
+ border-radius: 8px;
5374
+ border-top: 4px solid var(--color-accent);
5375
+ ${isDark ? `color: ${text};` : ""}
5376
+ }
5377
+
5378
+ /* Progress bar enhancement */
5379
+ .reveal .progress {
5380
+ background: ${this.lightenColor(primary, 80)};
5381
+ height: 4px;
5382
+ }
5383
+ .reveal .progress span {
5384
+ background: var(--color-highlight);
5385
+ }
5079
5386
  `;
5080
5387
  }
5081
5388
  /**
@@ -5091,6 +5398,37 @@ ${slides}
5091
5398
  b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
5092
5399
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5093
5400
  }
5401
+ /**
5402
+ * Check if a palette is "dark mode" (dark background, light text)
5403
+ */
5404
+ isDarkPalette(palette) {
5405
+ const bgLuminance = this.getLuminance(palette.background || "#ffffff");
5406
+ const textLuminance = this.getLuminance(palette.text || "#000000");
5407
+ return bgLuminance < textLuminance;
5408
+ }
5409
+ /**
5410
+ * Get relative luminance of a hex color (0-1, 0=black, 1=white)
5411
+ */
5412
+ getLuminance(hex) {
5413
+ hex = hex.replace("#", "");
5414
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
5415
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
5416
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
5417
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5418
+ }
5419
+ /**
5420
+ * Darken a hex color
5421
+ */
5422
+ darkenColor(hex, percent) {
5423
+ hex = hex.replace("#", "");
5424
+ let r = parseInt(hex.substring(0, 2), 16);
5425
+ let g = parseInt(hex.substring(2, 4), 16);
5426
+ let b = parseInt(hex.substring(4, 6), 16);
5427
+ r = Math.max(0, Math.floor(r * (1 - percent / 100)));
5428
+ g = Math.max(0, Math.floor(g * (1 - percent / 100)));
5429
+ b = Math.max(0, Math.floor(b * (1 - percent / 100)));
5430
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5431
+ }
5094
5432
  /**
5095
5433
  * Get theme-specific styles - KB-DRIVEN
5096
5434
  */
@@ -5307,15 +5645,15 @@ var IterativeQAEngine = class {
5307
5645
  let autoFixSummary = "";
5308
5646
  let totalFixesApplied = 0;
5309
5647
  if (opts.verbose) {
5310
- console.log("\n\u{1F504} Starting Iterative QA Process");
5311
- console.log(` Target Score: ${opts.minScore}/100`);
5312
- console.log(` Max Iterations: ${opts.maxIterations}`);
5313
- console.log("");
5648
+ logger.progress("\n\u{1F504} Starting Iterative QA Process");
5649
+ logger.info(` Target Score: ${opts.minScore}/100`);
5650
+ logger.info(` Max Iterations: ${opts.maxIterations}`);
5651
+ logger.progress("");
5314
5652
  }
5315
5653
  for (let i = 0; i < opts.maxIterations; i++) {
5316
5654
  const iterationNum = i + 1;
5317
5655
  if (opts.verbose) {
5318
- console.log(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5656
+ logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5319
5657
  }
5320
5658
  scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
5321
5659
  iterations.push({
@@ -5334,17 +5672,17 @@ var IterativeQAEngine = class {
5334
5672
  timestamp: /* @__PURE__ */ new Date()
5335
5673
  });
5336
5674
  if (opts.verbose) {
5337
- console.log(` Score: ${scoringResult.overallScore}/100`);
5675
+ logger.info(` Score: ${scoringResult.overallScore}/100`);
5338
5676
  }
5339
5677
  if (scoringResult.passed) {
5340
5678
  if (opts.verbose) {
5341
- console.log(` \u2705 PASSED - Score meets threshold (${opts.minScore})`);
5679
+ logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
5342
5680
  }
5343
5681
  break;
5344
5682
  }
5345
5683
  if (i < opts.maxIterations - 1) {
5346
5684
  if (opts.verbose) {
5347
- console.log(` \u26A0\uFE0F Below threshold, applying auto-fixes...`);
5685
+ logger.warn("Below threshold, applying auto-fixes...");
5348
5686
  }
5349
5687
  const fixResult = await this.fixer.fix(currentSlides, scoringResult);
5350
5688
  if (fixResult.fixesApplied.length > 0) {
@@ -5356,12 +5694,12 @@ var IterativeQAEngine = class {
5356
5694
  }
5357
5695
  totalFixesApplied += fixResult.fixesApplied.length;
5358
5696
  if (opts.verbose) {
5359
- console.log(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5697
+ logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5360
5698
  }
5361
5699
  autoFixSummary += fixResult.summary + "\n";
5362
5700
  } else {
5363
5701
  if (opts.verbose) {
5364
- console.log(` \u26A0\uFE0F No auto-fixes available, manual review needed`);
5702
+ logger.warn("No auto-fixes available, manual review needed");
5365
5703
  }
5366
5704
  break;
5367
5705
  }
@@ -5377,7 +5715,7 @@ var IterativeQAEngine = class {
5377
5715
  totalFixesApplied
5378
5716
  );
5379
5717
  if (opts.verbose) {
5380
- console.log(report);
5718
+ logger.info(report);
5381
5719
  }
5382
5720
  return {
5383
5721
  finalScore: scoringResult.overallScore,
@@ -6179,6 +6517,172 @@ var PowerPointGenerator = class {
6179
6517
  }
6180
6518
  };
6181
6519
 
6520
+ // src/generators/PDFGenerator.ts
6521
+ import puppeteer from "puppeteer";
6522
+ var PDFGenerator = class {
6523
+ defaultOptions = {
6524
+ orientation: "landscape",
6525
+ printBackground: true,
6526
+ format: "Slide",
6527
+ scale: 1
6528
+ };
6529
+ /**
6530
+ * Generate PDF from HTML presentation
6531
+ * @param html - The HTML content of the presentation
6532
+ * @param options - PDF generation options
6533
+ * @returns Buffer containing the PDF file
6534
+ */
6535
+ async generate(html, options = {}) {
6536
+ const opts = { ...this.defaultOptions, ...options };
6537
+ logger.progress("\u{1F4C4} Generating PDF...");
6538
+ let browser;
6539
+ try {
6540
+ browser = await puppeteer.launch({
6541
+ headless: true,
6542
+ args: [
6543
+ "--no-sandbox",
6544
+ "--disable-setuid-sandbox",
6545
+ "--disable-dev-shm-usage"
6546
+ ]
6547
+ });
6548
+ const page = await browser.newPage();
6549
+ const printHtml = this.preparePrintHtml(html);
6550
+ await page.setViewport({
6551
+ width: 1920,
6552
+ height: 1080
6553
+ });
6554
+ await page.setContent(printHtml, {
6555
+ waitUntil: ["networkidle0", "domcontentloaded"]
6556
+ });
6557
+ await page.evaluate(() => {
6558
+ return new Promise((resolve) => {
6559
+ if (document.fonts && document.fonts.ready) {
6560
+ document.fonts.ready.then(() => resolve());
6561
+ } else {
6562
+ setTimeout(resolve, 1e3);
6563
+ }
6564
+ });
6565
+ });
6566
+ const pdfBuffer = await page.pdf({
6567
+ width: "1920px",
6568
+ height: "1080px",
6569
+ printBackground: opts.printBackground,
6570
+ scale: opts.scale,
6571
+ landscape: opts.orientation === "landscape",
6572
+ margin: {
6573
+ top: "0px",
6574
+ right: "0px",
6575
+ bottom: "0px",
6576
+ left: "0px"
6577
+ }
6578
+ });
6579
+ logger.step("PDF generated successfully");
6580
+ return Buffer.from(pdfBuffer);
6581
+ } catch (error) {
6582
+ const errorMessage = error instanceof Error ? error.message : String(error);
6583
+ logger.error(`PDF generation failed: ${errorMessage}`);
6584
+ throw new Error(`PDF generation failed: ${errorMessage}`);
6585
+ } finally {
6586
+ if (browser) {
6587
+ await browser.close();
6588
+ }
6589
+ }
6590
+ }
6591
+ /**
6592
+ * Prepare HTML for print/PDF output
6593
+ * Modifies the HTML to work better as a printed document
6594
+ */
6595
+ preparePrintHtml(html) {
6596
+ const printCss = `
6597
+ <style id="pdf-print-styles">
6598
+ /* Print-specific overrides */
6599
+ @page {
6600
+ size: 1920px 1080px;
6601
+ margin: 0;
6602
+ }
6603
+
6604
+ @media print {
6605
+ html, body {
6606
+ margin: 0;
6607
+ padding: 0;
6608
+ width: 1920px;
6609
+ height: 1080px;
6610
+ }
6611
+
6612
+ .reveal .slides {
6613
+ width: 100% !important;
6614
+ height: 100% !important;
6615
+ margin: 0 !important;
6616
+ padding: 0 !important;
6617
+ transform: none !important;
6618
+ }
6619
+
6620
+ .reveal .slides section {
6621
+ page-break-after: always;
6622
+ page-break-inside: avoid;
6623
+ width: 1920px !important;
6624
+ height: 1080px !important;
6625
+ margin: 0 !important;
6626
+ padding: 60px !important;
6627
+ box-sizing: border-box;
6628
+ position: relative !important;
6629
+ top: auto !important;
6630
+ left: auto !important;
6631
+ transform: none !important;
6632
+ display: flex !important;
6633
+ opacity: 1 !important;
6634
+ visibility: visible !important;
6635
+ }
6636
+
6637
+ .reveal .slides section:last-child {
6638
+ page-break-after: auto;
6639
+ }
6640
+
6641
+ /* Make all slides visible for printing */
6642
+ .reveal .slides section.future,
6643
+ .reveal .slides section.past {
6644
+ display: flex !important;
6645
+ opacity: 1 !important;
6646
+ visibility: visible !important;
6647
+ }
6648
+
6649
+ /* Disable animations for print */
6650
+ * {
6651
+ animation: none !important;
6652
+ transition: none !important;
6653
+ }
6654
+
6655
+ /* Hide Reveal.js controls */
6656
+ .reveal .controls,
6657
+ .reveal .progress,
6658
+ .reveal .slide-number,
6659
+ .reveal .speaker-notes,
6660
+ .reveal .navigate-left,
6661
+ .reveal .navigate-right,
6662
+ .reveal .navigate-up,
6663
+ .reveal .navigate-down {
6664
+ display: none !important;
6665
+ }
6666
+ }
6667
+
6668
+ /* Force print mode in Puppeteer */
6669
+ .reveal .slides section {
6670
+ page-break-after: always;
6671
+ page-break-inside: avoid;
6672
+ }
6673
+ </style>
6674
+ `;
6675
+ if (html.includes("</head>")) {
6676
+ return html.replace("</head>", `${printCss}</head>`);
6677
+ } else {
6678
+ return printCss + html;
6679
+ }
6680
+ }
6681
+ };
6682
+ function createPDFGenerator() {
6683
+ return new PDFGenerator();
6684
+ }
6685
+
6182
6686
  // src/core/PresentationEngine.ts
6183
6687
  var PresentationEngine = class {
6184
6688
  contentAnalyzer;
@@ -6187,6 +6691,7 @@ var PresentationEngine = class {
6187
6691
  qaEngine;
6188
6692
  htmlGenerator;
6189
6693
  pptxGenerator;
6694
+ pdfGenerator;
6190
6695
  kb;
6191
6696
  constructor() {
6192
6697
  this.contentAnalyzer = new ContentAnalyzer();
@@ -6195,6 +6700,7 @@ var PresentationEngine = class {
6195
6700
  this.qaEngine = new QAEngine();
6196
6701
  this.htmlGenerator = new RevealJsGenerator();
6197
6702
  this.pptxGenerator = new PowerPointGenerator();
6703
+ this.pdfGenerator = createPDFGenerator();
6198
6704
  }
6199
6705
  /**
6200
6706
  * Generate a presentation from content.
@@ -6204,26 +6710,26 @@ var PresentationEngine = class {
6204
6710
  */
6205
6711
  async generate(config) {
6206
6712
  this.validateConfig(config);
6207
- console.log("\u{1F4DA} Loading knowledge base...");
6713
+ logger.progress("\u{1F4DA} Loading knowledge base...");
6208
6714
  this.kb = await getKnowledgeGateway();
6209
- console.log("\u{1F4DD} Analyzing content...");
6715
+ logger.progress("\u{1F4DD} Analyzing content...");
6210
6716
  const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
6211
6717
  const presentationType = config.presentationType || analysis.detectedType;
6212
- console.log(` \u2713 Presentation type: ${presentationType}`);
6718
+ logger.step(`Presentation type: ${presentationType}`);
6213
6719
  const slideFactory = createSlideFactory(this.kb, presentationType);
6214
- console.log("\u{1F3A8} Creating slides...");
6720
+ logger.progress("\u{1F3A8} Creating slides...");
6215
6721
  const slides = await slideFactory.createSlides(analysis);
6216
- console.log("\u2705 Validating structure...");
6722
+ logger.progress("\u2705 Validating structure...");
6217
6723
  const structureErrors = this.validateStructure(slides, config.mode);
6218
6724
  if (structureErrors.length > 0) {
6219
6725
  if (config.skipQA) {
6220
- console.log("\u26A0\uFE0F Structure warnings (bypassed):");
6221
- structureErrors.forEach((e) => console.log(` \u2022 ${e}`));
6726
+ logger.warn("Structure warnings (bypassed):");
6727
+ structureErrors.forEach((e) => logger.warn(` \u2022 ${e}`));
6222
6728
  } else {
6223
6729
  throw new ValidationError(structureErrors, "Slide structure validation failed");
6224
6730
  }
6225
6731
  }
6226
- console.log("\u{1F528} Generating outputs...");
6732
+ logger.progress("\u{1F528} Generating outputs...");
6227
6733
  const outputs = {};
6228
6734
  if (config.format.includes("html")) {
6229
6735
  outputs.html = await this.htmlGenerator.generate(slides, config);
@@ -6241,7 +6747,7 @@ var PresentationEngine = class {
6241
6747
  const maxIterations = config.maxIterations ?? 5;
6242
6748
  const useIterativeQA = config.useIterativeQA !== false;
6243
6749
  if (useIterativeQA) {
6244
- console.log("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6750
+ logger.progress("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6245
6751
  const iterativeEngine = createIterativeQAEngine(
6246
6752
  config.mode,
6247
6753
  presentationType,
@@ -6263,20 +6769,30 @@ var PresentationEngine = class {
6263
6769
  throw new QAFailureError(score, threshold, qaResults);
6264
6770
  }
6265
6771
  } else {
6266
- console.log("\u{1F50D} Running QA validation (legacy mode)...");
6772
+ logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
6267
6773
  qaResults = await this.qaEngine.validate(outputs.html, {
6268
6774
  mode: config.mode,
6269
6775
  strictMode: true
6270
6776
  });
6271
6777
  score = this.scoreCalculator.calculate(qaResults);
6272
- console.log(`\u{1F4CA} QA Score: ${score}/100`);
6778
+ logger.info(`\u{1F4CA} QA Score: ${score}/100`);
6273
6779
  if (score < threshold) {
6274
6780
  throw new QAFailureError(score, threshold, qaResults);
6275
6781
  }
6276
6782
  }
6277
6783
  } else {
6278
6784
  qaResults = this.qaEngine.createEmptyResults();
6279
- console.log("\u26A0\uFE0F QA validation skipped (NOT RECOMMENDED)");
6785
+ logger.warn("QA validation skipped (NOT RECOMMENDED)");
6786
+ }
6787
+ if (outputs.html && (config.format.includes("pdf") || config.generatePdf !== false)) {
6788
+ try {
6789
+ logger.progress("\u{1F4C4} Generating PDF from validated HTML...");
6790
+ outputs.pdf = await this.pdfGenerator.generate(outputs.html);
6791
+ logger.step("PDF generated successfully");
6792
+ } catch (pdfError) {
6793
+ const errorMsg = pdfError instanceof Error ? pdfError.message : String(pdfError);
6794
+ logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
6795
+ }
6280
6796
  }
6281
6797
  const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
6282
6798
  return {
@@ -6364,15 +6880,28 @@ var PresentationEngine = class {
6364
6880
  if (slides.length < 3) {
6365
6881
  errors.push("Presentation must have at least 3 slides");
6366
6882
  }
6883
+ const sparseByDesignTypes = [
6884
+ "title",
6885
+ "section-divider",
6886
+ "thank-you",
6887
+ "big-number",
6888
+ "single-statement",
6889
+ "cta",
6890
+ "agenda",
6891
+ "metrics-grid",
6892
+ "quote",
6893
+ "image-focus"
6894
+ ];
6367
6895
  slides.forEach((slide, index) => {
6368
6896
  const wordCount = this.countWords(slide);
6369
6897
  if (mode === "keynote") {
6370
- if (wordCount > 25) {
6898
+ if (wordCount > 25 && !sparseByDesignTypes.includes(slide.type)) {
6371
6899
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds keynote limit of 25`);
6372
6900
  }
6373
6901
  } else {
6374
- if (wordCount < 20 && !["title", "section-divider", "thank-you"].includes(slide.type)) {
6375
- errors.push(`Slide ${index + 1}: ${wordCount} words may be too sparse for business mode`);
6902
+ const requiresMinWords = ["bullet-points", "two-column", "process-flow"];
6903
+ if (wordCount < 20 && requiresMinWords.includes(slide.type)) {
6904
+ logger.warn(`Slide ${index + 1}: ${wordCount} words is sparse for ${slide.type} slide`);
6376
6905
  }
6377
6906
  if (wordCount > 100) {
6378
6907
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds business limit of 100`);
@@ -6466,11 +6995,11 @@ var SlideGenerator = class {
6466
6995
  await this.initialize();
6467
6996
  this.presentationType = type || analysis.detectedType;
6468
6997
  this.mode = this.kb.getModeForType(this.presentationType);
6469
- console.log(` \u2713 Using ${this.mode} mode for ${this.presentationType}`);
6998
+ logger.step(`Using ${this.mode} mode for ${this.presentationType}`);
6470
6999
  this.factory = createSlideFactory(this.kb, this.presentationType);
6471
7000
  const slides = await this.factory.createSlides(analysis);
6472
7001
  const legacySlides = this.convertToLegacyFormat(slides);
6473
- console.log(` \u2713 Generated ${legacySlides.length} KB-validated slides`);
7002
+ logger.step(`Generated ${legacySlides.length} KB-validated slides`);
6474
7003
  return legacySlides;
6475
7004
  }
6476
7005
  /**