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.js CHANGED
@@ -102,6 +102,75 @@ var import_fs = require("fs");
102
102
  var import_path = require("path");
103
103
  var import_url = require("url");
104
104
  var yaml = __toESM(require("yaml"));
105
+
106
+ // src/utils/Logger.ts
107
+ var Logger = class {
108
+ level;
109
+ prefix;
110
+ timestamps;
111
+ constructor(options = {}) {
112
+ this.level = options.level ?? 1 /* INFO */;
113
+ this.prefix = options.prefix ?? "";
114
+ this.timestamps = options.timestamps ?? false;
115
+ }
116
+ setLevel(level) {
117
+ this.level = level;
118
+ }
119
+ formatMessage(message) {
120
+ const parts = [];
121
+ if (this.timestamps) {
122
+ parts.push(`[${(/* @__PURE__ */ new Date()).toISOString()}]`);
123
+ }
124
+ if (this.prefix) {
125
+ parts.push(`[${this.prefix}]`);
126
+ }
127
+ parts.push(message);
128
+ return parts.join(" ");
129
+ }
130
+ debug(message, ...args) {
131
+ if (this.level <= 0 /* DEBUG */) {
132
+ console.debug(this.formatMessage(message), ...args);
133
+ }
134
+ }
135
+ info(message, ...args) {
136
+ if (this.level <= 1 /* INFO */) {
137
+ console.info(this.formatMessage(message), ...args);
138
+ }
139
+ }
140
+ warn(message, ...args) {
141
+ if (this.level <= 2 /* WARN */) {
142
+ console.warn(this.formatMessage(message), ...args);
143
+ }
144
+ }
145
+ error(message, ...args) {
146
+ if (this.level <= 3 /* ERROR */) {
147
+ console.error(this.formatMessage(message), ...args);
148
+ }
149
+ }
150
+ // Progress messages (always shown unless silent)
151
+ progress(message) {
152
+ if (this.level < 4 /* SILENT */) {
153
+ console.info(message);
154
+ }
155
+ }
156
+ // Success messages
157
+ success(message) {
158
+ if (this.level < 4 /* SILENT */) {
159
+ console.info(`\u2705 ${message}`);
160
+ }
161
+ }
162
+ // Step messages for workflow progress
163
+ step(message) {
164
+ if (this.level <= 1 /* INFO */) {
165
+ console.info(` \u2713 ${message}`);
166
+ }
167
+ }
168
+ };
169
+ var logger = new Logger({
170
+ level: process.env.LOG_LEVEL ? parseInt(process.env.LOG_LEVEL) : 1 /* INFO */
171
+ });
172
+
173
+ // src/kb/KnowledgeGateway.ts
105
174
  var import_meta = {};
106
175
  function getModuleDir() {
107
176
  if (typeof import_meta !== "undefined" && import_meta.url) {
@@ -113,7 +182,7 @@ function getModuleDir() {
113
182
  return process.cwd();
114
183
  }
115
184
  var KnowledgeGateway = class {
116
- kb;
185
+ kb = {};
117
186
  loaded = false;
118
187
  constructor() {
119
188
  }
@@ -136,7 +205,7 @@ var KnowledgeGateway = class {
136
205
  const content = (0, import_fs.readFileSync)(path, "utf-8");
137
206
  this.kb = yaml.parse(content);
138
207
  this.loaded = true;
139
- console.log(` \u2713 Knowledge base loaded from ${path}`);
208
+ logger.step(`Knowledge base loaded from ${path}`);
140
209
  return;
141
210
  } catch {
142
211
  }
@@ -327,7 +396,7 @@ var KnowledgeGateway = class {
327
396
  */
328
397
  getChartGuidance(purpose) {
329
398
  this.ensureLoaded();
330
- return this.kb.chart_selection_guide.by_purpose[purpose];
399
+ return this.kb.chart_selection_guide.by_purpose?.[purpose];
331
400
  }
332
401
  /**
333
402
  * Get charts to avoid
@@ -342,7 +411,7 @@ var KnowledgeGateway = class {
342
411
  */
343
412
  getIBPitchBookStructure(type) {
344
413
  this.ensureLoaded();
345
- return this.kb.investment_banking?.pitch_book_types[type];
414
+ return this.kb.investment_banking?.pitch_book_types?.[type];
346
415
  }
347
416
  /**
348
417
  * Check if knowledge base is loaded
@@ -892,22 +961,27 @@ var ContentAnalyzer = class {
892
961
  async analyze(content, contentType) {
893
962
  await this.initialize();
894
963
  const text = this.parseContent(content, contentType);
895
- const title = this.extractTitle(text);
964
+ const { title, subtitle } = this.extractTitleAndSubtitle(text);
965
+ logger.step(`Title: "${title}", Subtitle: "${subtitle || "(none)"}"`);
896
966
  const detectedType = this.detectPresentationType(text);
897
- console.log(` \u2713 Detected type: ${detectedType}`);
967
+ logger.step(`Detected type: ${detectedType}`);
898
968
  const sections = this.extractSections(text);
899
- console.log(` \u2713 Found ${sections.length} sections`);
969
+ logger.step(`Found ${sections.length} sections`);
900
970
  const scqa = this.extractSCQA(text);
901
971
  const sparkline = this.extractSparkline(text);
902
972
  const keyMessages = this.extractKeyMessages(text);
903
973
  const dataPoints = this.extractDataPoints(text);
904
- console.log(` \u2713 Found ${dataPoints.length} data points`);
974
+ logger.step(`Found ${dataPoints.length} data points`);
905
975
  const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
906
976
  const starMoments = this.extractStarMoments(text);
907
977
  const estimatedSlideCount = Math.max(5, sections.length + 3);
978
+ const whatIsContent = scqa.situation ? [scqa.situation] : [];
979
+ const whatCouldBeContent = scqa.answer ? [scqa.answer] : keyMessages.slice(0, 2);
908
980
  return {
909
981
  detectedType,
910
982
  title,
983
+ subtitle,
984
+ // NEW: Use explicit subtitle from content
911
985
  sections,
912
986
  keyMessages,
913
987
  dataPoints,
@@ -918,8 +992,10 @@ var ContentAnalyzer = class {
918
992
  answer: scqa.answer || ""
919
993
  },
920
994
  sparkline: {
921
- whatIs: sparkline.callToAction ? [sparkline.callToAction] : [],
922
- whatCouldBe: keyMessages,
995
+ whatIs: whatIsContent,
996
+ // FIX: Use situation, not CTA
997
+ whatCouldBe: whatCouldBeContent,
998
+ // FIX: Use answer/vision, not random messages
923
999
  callToAdventure: sparkline.callToAction || ""
924
1000
  },
925
1001
  titles,
@@ -947,18 +1023,46 @@ var ContentAnalyzer = class {
947
1023
  return text;
948
1024
  }
949
1025
  /**
950
- * Extract the main title from content
1026
+ * Extract the main title AND subtitle from content
1027
+ * Subtitle is the line immediately after the H1 title (if not another header)
951
1028
  */
952
- extractTitle(text) {
953
- const h1Match = text.match(/^#\s+(.+)$/m);
954
- if (h1Match && h1Match[1]) {
955
- return h1Match[1].replace(/\*\*/g, "").trim();
1029
+ extractTitleAndSubtitle(text) {
1030
+ const lines = text.split("\n");
1031
+ let title = "Presentation";
1032
+ let subtitle = "";
1033
+ let foundTitle = false;
1034
+ let titleLineIndex = -1;
1035
+ for (let i = 0; i < lines.length; i++) {
1036
+ const rawLine = lines[i];
1037
+ if (!rawLine) continue;
1038
+ const line = rawLine.trim();
1039
+ const h1Match = line.match(/^#\s+(.+)$/);
1040
+ if (h1Match && h1Match[1] && !foundTitle) {
1041
+ title = h1Match[1].replace(/\*\*/g, "").trim();
1042
+ foundTitle = true;
1043
+ titleLineIndex = i;
1044
+ continue;
1045
+ }
1046
+ if (foundTitle && i > titleLineIndex && line.length > 0) {
1047
+ if (line.startsWith("#")) break;
1048
+ if (line.startsWith("-") || line.startsWith("*") || /^\d+\./.test(line)) break;
1049
+ subtitle = line.replace(/\*\*/g, "").trim().slice(0, 100);
1050
+ break;
1051
+ }
956
1052
  }
957
- const lines = text.split("\n").filter((l) => l.trim().length > 0);
958
- if (lines.length > 0 && lines[0]) {
959
- return lines[0].replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
1053
+ if (!foundTitle) {
1054
+ const firstLine = lines.find((l) => l.trim().length > 0);
1055
+ if (firstLine) {
1056
+ title = firstLine.replace(/^#+\s*/, "").replace(/\*\*/g, "").trim().slice(0, 80);
1057
+ }
960
1058
  }
961
- return "Presentation";
1059
+ return { title, subtitle };
1060
+ }
1061
+ /**
1062
+ * Extract the main title from content (legacy wrapper)
1063
+ */
1064
+ extractTitle(text) {
1065
+ return this.extractTitleAndSubtitle(text).title;
962
1066
  }
963
1067
  /**
964
1068
  * Detect presentation type from content signals
@@ -1021,12 +1125,16 @@ var ContentAnalyzer = class {
1021
1125
  }
1022
1126
  const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
1023
1127
  if (bulletMatch && bulletMatch[1] && currentSection) {
1024
- currentSection.bullets.push(bulletMatch[1]);
1128
+ const bulletText = bulletMatch[1];
1129
+ currentSection.bullets.push(bulletText);
1130
+ this.extractMetricsFromText(bulletText, currentSection.metrics);
1025
1131
  continue;
1026
1132
  }
1027
1133
  const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
1028
1134
  if (numberedMatch && numberedMatch[1] && currentSection) {
1029
- currentSection.bullets.push(numberedMatch[1]);
1135
+ const itemText = numberedMatch[1];
1136
+ currentSection.bullets.push(itemText);
1137
+ this.extractMetricsFromText(itemText, currentSection.metrics);
1030
1138
  continue;
1031
1139
  }
1032
1140
  const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
@@ -1037,6 +1145,9 @@ var ContentAnalyzer = class {
1037
1145
  });
1038
1146
  continue;
1039
1147
  }
1148
+ if (currentSection && trimmedLine) {
1149
+ this.extractMetricsFromText(trimmedLine, currentSection.metrics);
1150
+ }
1040
1151
  if (trimmedLine.includes("|") && currentSection) {
1041
1152
  const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
1042
1153
  if (cells.length >= 2) {
@@ -1408,6 +1519,49 @@ var ContentAnalyzer = class {
1408
1519
  const fallback = cleaned.slice(0, 150);
1409
1520
  return fallback.length >= 20 ? fallback : "";
1410
1521
  }
1522
+ /**
1523
+ * Extract metrics from natural language text
1524
+ * Handles formats like "40% productivity loss", "$2.5M investment"
1525
+ */
1526
+ extractMetricsFromText(text, metrics) {
1527
+ const usedValues = new Set(metrics.map((m) => m.value));
1528
+ const moneyMatches = text.matchAll(/(\$[\d,.]+[MBK]?)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1529
+ for (const match of moneyMatches) {
1530
+ const value = match[1];
1531
+ const rawLabel = match[2]?.trim();
1532
+ if (value && rawLabel && !usedValues.has(value)) {
1533
+ usedValues.add(value);
1534
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1535
+ }
1536
+ }
1537
+ const percentMatches = text.matchAll(/(\d+(?:\.\d+)?%)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1538
+ for (const match of percentMatches) {
1539
+ const value = match[1];
1540
+ const rawLabel = match[2]?.trim();
1541
+ if (value && rawLabel && !usedValues.has(value)) {
1542
+ usedValues.add(value);
1543
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1544
+ }
1545
+ }
1546
+ const multMatches = text.matchAll(/(\d+[xX])\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1547
+ for (const match of multMatches) {
1548
+ const value = match[1];
1549
+ const rawLabel = match[2]?.trim();
1550
+ if (value && rawLabel && !usedValues.has(value)) {
1551
+ usedValues.add(value);
1552
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1553
+ }
1554
+ }
1555
+ const numMatches = text.matchAll(/(\d{3,}\+?)\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1556
+ for (const match of numMatches) {
1557
+ const value = match[1];
1558
+ const rawLabel = match[2]?.trim();
1559
+ if (value && rawLabel && !usedValues.has(value)) {
1560
+ usedValues.add(value);
1561
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1562
+ }
1563
+ }
1564
+ }
1411
1565
  };
1412
1566
 
1413
1567
  // src/core/ContentPatternClassifier.ts
@@ -1592,8 +1746,8 @@ var SlideFactory = class {
1592
1746
  this.usedContent = /* @__PURE__ */ new Set();
1593
1747
  this.usedTitles = /* @__PURE__ */ new Set();
1594
1748
  this.config = this.loadKBConfig(type);
1595
- console.log(` \u2713 SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1596
- console.log(` \u2713 KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1749
+ logger.step(`SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1750
+ logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1597
1751
  }
1598
1752
  /**
1599
1753
  * Load ALL configuration from KB - ZERO hardcoded values after this.
@@ -1621,7 +1775,7 @@ var SlideFactory = class {
1621
1775
  async createSlides(analysis) {
1622
1776
  const slides = [];
1623
1777
  const storyStructure = this.kb.getStoryStructure(this.presentationType);
1624
- console.log(` \u2713 Using ${storyStructure.framework} story framework`);
1778
+ logger.step(`Using ${storyStructure.framework} story framework`);
1625
1779
  slides.push(this.createTitleSlide(0, analysis));
1626
1780
  const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
1627
1781
  if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
@@ -1659,7 +1813,7 @@ var SlideFactory = class {
1659
1813
  const validatedSlides = this.validateAndFixSlides(slides);
1660
1814
  this.checkRequiredElements(validatedSlides);
1661
1815
  this.checkAntiPatterns(validatedSlides);
1662
- console.log(` \u2713 Created ${validatedSlides.length} validated slides`);
1816
+ logger.step(`Created ${validatedSlides.length} validated slides`);
1663
1817
  return validatedSlides;
1664
1818
  }
1665
1819
  // ===========================================================================
@@ -1700,7 +1854,7 @@ var SlideFactory = class {
1700
1854
  case "technical":
1701
1855
  return this.createCodeSlide(index, section);
1702
1856
  default:
1703
- console.log(` \u26A0 Unknown slide type '${type}', using bullet-points`);
1857
+ logger.warn(`Unknown slide type '${type}', using bullet-points`);
1704
1858
  return this.createBulletSlide(index, section);
1705
1859
  }
1706
1860
  }
@@ -1711,9 +1865,10 @@ var SlideFactory = class {
1711
1865
  const data = {
1712
1866
  title: this.truncateText(analysis.title, this.config.rules.wordsPerSlide.max)
1713
1867
  };
1714
- if (analysis.keyMessages[0]) {
1868
+ const subtitleSource = analysis.subtitle || analysis.keyMessages[0] || "";
1869
+ if (subtitleSource) {
1715
1870
  data.subtitle = this.truncateText(
1716
- analysis.keyMessages[0],
1871
+ subtitleSource,
1717
1872
  this.config.defaults.subtitle.maxWords
1718
1873
  // FROM KB
1719
1874
  );
@@ -1757,7 +1912,9 @@ var SlideFactory = class {
1757
1912
  addSCQASlides(slides, analysis) {
1758
1913
  const scqa = analysis.scqa;
1759
1914
  const titles = this.config.scqaTitles;
1760
- if (scqa?.situation && !this.usedContent.has("scqa-situation")) {
1915
+ const minBodyLength = 20;
1916
+ const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
1917
+ if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
1761
1918
  this.usedContent.add("scqa-situation");
1762
1919
  slides.push({
1763
1920
  index: slides.length,
@@ -1765,12 +1922,13 @@ var SlideFactory = class {
1765
1922
  data: {
1766
1923
  title: titles.situation,
1767
1924
  // FROM KB - not hardcoded 'Current Situation'
1768
- body: this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max)
1925
+ body: situationBody
1769
1926
  },
1770
1927
  classes: ["situation-slide"]
1771
1928
  });
1772
1929
  }
1773
- if (scqa?.complication && !this.usedContent.has("scqa-complication")) {
1930
+ const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
1931
+ if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
1774
1932
  this.usedContent.add("scqa-complication");
1775
1933
  slides.push({
1776
1934
  index: slides.length,
@@ -1778,12 +1936,13 @@ var SlideFactory = class {
1778
1936
  data: {
1779
1937
  title: titles.complication,
1780
1938
  // FROM KB - not hardcoded 'The Challenge'
1781
- body: this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max)
1939
+ body: complicationBody
1782
1940
  },
1783
1941
  classes: ["complication-slide"]
1784
1942
  });
1785
1943
  }
1786
- if (scqa?.question && !this.usedContent.has("scqa-question")) {
1944
+ const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
1945
+ if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
1787
1946
  this.usedContent.add("scqa-question");
1788
1947
  slides.push({
1789
1948
  index: slides.length,
@@ -1791,7 +1950,7 @@ var SlideFactory = class {
1791
1950
  data: {
1792
1951
  title: titles.question,
1793
1952
  // FROM KB - not hardcoded 'The Question'
1794
- body: this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max)
1953
+ body: questionBody
1795
1954
  },
1796
1955
  classes: ["question-slide"]
1797
1956
  });
@@ -1801,7 +1960,8 @@ var SlideFactory = class {
1801
1960
  const spark = analysis.sparkline;
1802
1961
  const titles = this.config.sparklineTitles;
1803
1962
  const whatIsFirst = spark?.whatIs?.[0];
1804
- if (whatIsFirst && !this.usedContent.has("spark-what-is")) {
1963
+ const whatIsBody = whatIsFirst ? this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max) : "";
1964
+ if (whatIsBody.length >= 20 && !this.usedContent.has("spark-what-is")) {
1805
1965
  this.usedContent.add("spark-what-is");
1806
1966
  slides.push({
1807
1967
  index: slides.length,
@@ -1809,13 +1969,14 @@ var SlideFactory = class {
1809
1969
  data: {
1810
1970
  title: titles.whatIs,
1811
1971
  // FROM KB - not hardcoded 'Where We Are Today'
1812
- body: this.truncateText(whatIsFirst, this.config.rules.wordsPerSlide.max)
1972
+ body: whatIsBody
1813
1973
  },
1814
1974
  classes: ["what-is-slide"]
1815
1975
  });
1816
1976
  }
1817
1977
  const whatCouldBeFirst = spark?.whatCouldBe?.[0];
1818
- if (whatCouldBeFirst && !this.usedContent.has("spark-could-be")) {
1978
+ const whatCouldBeBody = whatCouldBeFirst ? this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max) : "";
1979
+ if (whatCouldBeBody.length >= 20 && !this.usedContent.has("spark-could-be")) {
1819
1980
  this.usedContent.add("spark-could-be");
1820
1981
  slides.push({
1821
1982
  index: slides.length,
@@ -1823,7 +1984,7 @@ var SlideFactory = class {
1823
1984
  data: {
1824
1985
  title: titles.whatCouldBe,
1825
1986
  // FROM KB - not hardcoded 'What Could Be'
1826
- body: this.truncateText(whatCouldBeFirst, this.config.rules.wordsPerSlide.max)
1987
+ body: whatCouldBeBody
1827
1988
  },
1828
1989
  classes: ["what-could-be-slide"]
1829
1990
  });
@@ -1833,15 +1994,19 @@ var SlideFactory = class {
1833
1994
  // CONTENT SLIDES - ALL values from KB
1834
1995
  // ===========================================================================
1835
1996
  createBigNumberSlide(index, section, pattern) {
1836
- const bigNumber = this.classifier.extractBigNumber(
1837
- `${section.header} ${section.content} ${section.bullets.join(" ")}`
1838
- );
1997
+ const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
1998
+ const bigNumber = this.classifier.extractBigNumber(fullContent);
1999
+ const actualValue = bigNumber?.value || pattern.bigNumberValue;
2000
+ if (!actualValue) {
2001
+ logger.warn(`No number found for big-number slide, falling back to single-statement`);
2002
+ return this.createSingleStatementSlide(index, section);
2003
+ }
1839
2004
  return {
1840
2005
  index,
1841
2006
  type: "big-number",
1842
2007
  data: {
1843
2008
  title: this.createTitle(section.header, section),
1844
- keyMessage: bigNumber?.value || pattern.bigNumberValue || "0",
2009
+ keyMessage: actualValue,
1845
2010
  body: bigNumber?.context || this.truncateText(
1846
2011
  section.content,
1847
2012
  this.config.defaults.context.maxWords
@@ -2028,16 +2193,18 @@ var SlideFactory = class {
2028
2193
  };
2029
2194
  }
2030
2195
  createSingleStatementSlide(index, section) {
2196
+ const bodyContent = section.content || section.bullets.join(" ") || "";
2197
+ const truncatedBody = this.truncateText(bodyContent, this.config.rules.wordsPerSlide.max);
2198
+ if (truncatedBody.length < 15 && section.bullets.length > 0) {
2199
+ return this.createBulletSlide(index, section);
2200
+ }
2031
2201
  return {
2032
2202
  index,
2033
2203
  type: "single-statement",
2034
2204
  data: {
2035
2205
  title: this.createTitle(section.header, section),
2036
- body: this.truncateText(
2037
- section.content || section.bullets[0] || "",
2038
- this.config.rules.wordsPerSlide.max
2039
- // FROM KB
2040
- )
2206
+ body: truncatedBody || section.header
2207
+ // Fallback to header if no body
2041
2208
  },
2042
2209
  classes: ["single-statement-slide"]
2043
2210
  };
@@ -2110,7 +2277,7 @@ var SlideFactory = class {
2110
2277
  slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
2111
2278
  }
2112
2279
  if (validation.violations.length > 0) {
2113
- console.log(` \u26A0 Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2280
+ logger.warn(`Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2114
2281
  }
2115
2282
  if (validation.warnings.length > 0) {
2116
2283
  slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
@@ -2150,7 +2317,7 @@ var SlideFactory = class {
2150
2317
  }
2151
2318
  }
2152
2319
  if (missingElements.length > 0) {
2153
- console.log(` \u26A0 Missing required elements: ${missingElements.join(", ")}`);
2320
+ logger.warn(`Missing required elements: ${missingElements.join(", ")}`);
2154
2321
  }
2155
2322
  }
2156
2323
  /**
@@ -2181,7 +2348,7 @@ var SlideFactory = class {
2181
2348
  }
2182
2349
  }
2183
2350
  if (violations.length > 0) {
2184
- console.log(` \u26A0 Anti-pattern violations: ${violations.join(", ")}`);
2351
+ logger.warn(`Anti-pattern violations: ${violations.join(", ")}`);
2185
2352
  }
2186
2353
  }
2187
2354
  // ===========================================================================
@@ -2526,9 +2693,12 @@ var TemplateEngine = class {
2526
2693
  this.templates.set("big-number", this.handlebars.compile(`
2527
2694
  <section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
2528
2695
  <div class="slide-content big-number-content">
2529
- <div class="number animate-zoomIn">{{title}}</div>
2530
- {{#if subtitle}}
2531
- <p class="number-context animate-fadeIn delay-300">{{subtitle}}</p>
2696
+ {{#if title}}
2697
+ <h2 class="title animate-fadeIn">{{title}}</h2>
2698
+ {{/if}}
2699
+ <div class="number animate-zoomIn">{{keyMessage}}</div>
2700
+ {{#if body}}
2701
+ <p class="number-context animate-fadeIn delay-300">{{body}}</p>
2532
2702
  {{/if}}
2533
2703
  {{> source}}
2534
2704
  </div>
@@ -4912,6 +5082,12 @@ ${slides}
4912
5082
  const highlight = palette.accent || "#e94560";
4913
5083
  const text = palette.text || "#1a1a2e";
4914
5084
  const background = palette.background || "#ffffff";
5085
+ const isDark = this.isDarkPalette(palette);
5086
+ const defaultBg = isDark ? background : "#ffffff";
5087
+ const defaultText = isDark ? text : "#1a1a2e";
5088
+ const headingColor = isDark ? primary : primary;
5089
+ const titleBg = isDark ? background : primary;
5090
+ const titleBgEnd = isDark ? this.lightenColor(background, 10) : this.darkenColor(primary, 15);
4915
5091
  return `
4916
5092
  /* Base Styles - KB-Driven Design System */
4917
5093
  :root {
@@ -4938,7 +5114,13 @@ ${slides}
4938
5114
  font-family: var(--font-body);
4939
5115
  font-size: ${fontSize};
4940
5116
  line-height: ${lineHeight};
4941
- color: var(--color-text);
5117
+ color: ${defaultText};
5118
+ background: ${defaultBg};
5119
+ }
5120
+
5121
+ /* Override reveal.js default backgrounds for dark mode */
5122
+ .reveal .slides section {
5123
+ background: ${defaultBg};
4942
5124
  }
4943
5125
 
4944
5126
  .reveal .slides {
@@ -4967,10 +5149,17 @@ ${slides}
4967
5149
  font-family: var(--font-heading);
4968
5150
  font-weight: 700;
4969
5151
  letter-spacing: -0.02em;
4970
- color: var(--color-primary);
5152
+ color: ${isDark ? primary : primary};
4971
5153
  margin-bottom: 0.5em;
4972
5154
  }
4973
5155
 
5156
+ /* Dark mode text visibility */
5157
+ ${isDark ? `
5158
+ .reveal, .reveal p, .reveal li, .reveal span {
5159
+ color: ${text};
5160
+ }
5161
+ ` : ""}
5162
+
4974
5163
  .reveal h1 { font-size: 2.5em; }
4975
5164
  .reveal h2 { font-size: 1.8em; }
4976
5165
  .reveal h3 { font-size: 1.3em; }
@@ -5148,6 +5337,124 @@ ${slides}
5148
5337
  font-weight: 600;
5149
5338
  margin-top: 1em;
5150
5339
  }
5340
+
5341
+ /* ================================================================
5342
+ SLIDE TYPE BACKGROUNDS - KB-Driven Visual Design
5343
+ Creates visual hierarchy and differentiation between slide types
5344
+ ================================================================ */
5345
+
5346
+ /* Title slide: Bold background - makes strong first impression */
5347
+ .reveal .slide-title {
5348
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5349
+ }
5350
+ .reveal .slide-title h1,
5351
+ .reveal .slide-title h2,
5352
+ .reveal .slide-title p,
5353
+ .reveal .slide-title .subtitle {
5354
+ color: ${isDark ? primary : "#ffffff"};
5355
+ }
5356
+
5357
+ /* Section dividers: Subtle visual breaks */
5358
+ .reveal .slide-section-divider {
5359
+ background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
5360
+ }
5361
+
5362
+ /* Big number slides: Clean with accent highlight - emphasizes the data */
5363
+ .reveal .slide-big-number {
5364
+ background: var(--color-background);
5365
+ border-left: 8px solid var(--color-highlight);
5366
+ }
5367
+ .reveal .slide-big-number .number {
5368
+ font-size: 5em;
5369
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5370
+ -webkit-background-clip: text;
5371
+ -webkit-text-fill-color: transparent;
5372
+ background-clip: text;
5373
+ }
5374
+
5375
+ /* Metrics grid: Light accent background - data-focused */
5376
+ .reveal .slide-metrics-grid {
5377
+ background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
5378
+ }
5379
+ ${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
5380
+
5381
+ /* CTA slide: Highlight color - drives action */
5382
+ .reveal .slide-cta {
5383
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5384
+ }
5385
+ .reveal .slide-cta h2,
5386
+ .reveal .slide-cta p {
5387
+ color: #ffffff;
5388
+ }
5389
+ .reveal .slide-cta .cta-button {
5390
+ background: #ffffff;
5391
+ color: var(--color-highlight);
5392
+ }
5393
+
5394
+ /* Thank you slide: Matches title for bookend effect */
5395
+ .reveal .slide-thank-you {
5396
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5397
+ }
5398
+ .reveal .slide-thank-you h2,
5399
+ .reveal .slide-thank-you p,
5400
+ .reveal .slide-thank-you .subtitle {
5401
+ color: ${isDark ? primary : "#ffffff"};
5402
+ }
5403
+
5404
+ /* Quote slides: Elegant subtle background */
5405
+ .reveal .slide-quote {
5406
+ background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
5407
+ }
5408
+ .reveal .slide-quote blockquote {
5409
+ border-left-color: var(--color-accent);
5410
+ font-size: 1.3em;
5411
+ }
5412
+ ${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
5413
+
5414
+ /* Single statement: Clean, centered, impactful */
5415
+ .reveal .slide-single-statement {
5416
+ background: var(--color-background);
5417
+ }
5418
+ .reveal .slide-single-statement .statement,
5419
+ .reveal .slide-single-statement .big-idea-text {
5420
+ font-size: 2.2em;
5421
+ max-width: 80%;
5422
+ margin: 0 auto;
5423
+ }
5424
+
5425
+ /* Comparison slides: Split visual */
5426
+ .reveal .slide-comparison .columns {
5427
+ gap: 60px;
5428
+ }
5429
+ .reveal .slide-comparison .column:first-child {
5430
+ border-right: 2px solid ${this.lightenColor(secondary, 70)};
5431
+ padding-right: 30px;
5432
+ }
5433
+
5434
+ /* Timeline/Process: Visual flow */
5435
+ .reveal .slide-timeline .steps,
5436
+ .reveal .slide-process .steps {
5437
+ display: flex;
5438
+ gap: 20px;
5439
+ }
5440
+ .reveal .slide-timeline .step,
5441
+ .reveal .slide-process .step {
5442
+ flex: 1;
5443
+ padding: 20px;
5444
+ background: ${isDark ? this.lightenColor(background, 10) : this.lightenColor(secondary, 92)};
5445
+ border-radius: 8px;
5446
+ border-top: 4px solid var(--color-accent);
5447
+ ${isDark ? `color: ${text};` : ""}
5448
+ }
5449
+
5450
+ /* Progress bar enhancement */
5451
+ .reveal .progress {
5452
+ background: ${this.lightenColor(primary, 80)};
5453
+ height: 4px;
5454
+ }
5455
+ .reveal .progress span {
5456
+ background: var(--color-highlight);
5457
+ }
5151
5458
  `;
5152
5459
  }
5153
5460
  /**
@@ -5163,6 +5470,37 @@ ${slides}
5163
5470
  b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
5164
5471
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5165
5472
  }
5473
+ /**
5474
+ * Check if a palette is "dark mode" (dark background, light text)
5475
+ */
5476
+ isDarkPalette(palette) {
5477
+ const bgLuminance = this.getLuminance(palette.background || "#ffffff");
5478
+ const textLuminance = this.getLuminance(palette.text || "#000000");
5479
+ return bgLuminance < textLuminance;
5480
+ }
5481
+ /**
5482
+ * Get relative luminance of a hex color (0-1, 0=black, 1=white)
5483
+ */
5484
+ getLuminance(hex) {
5485
+ hex = hex.replace("#", "");
5486
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
5487
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
5488
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
5489
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5490
+ }
5491
+ /**
5492
+ * Darken a hex color
5493
+ */
5494
+ darkenColor(hex, percent) {
5495
+ hex = hex.replace("#", "");
5496
+ let r = parseInt(hex.substring(0, 2), 16);
5497
+ let g = parseInt(hex.substring(2, 4), 16);
5498
+ let b = parseInt(hex.substring(4, 6), 16);
5499
+ r = Math.max(0, Math.floor(r * (1 - percent / 100)));
5500
+ g = Math.max(0, Math.floor(g * (1 - percent / 100)));
5501
+ b = Math.max(0, Math.floor(b * (1 - percent / 100)));
5502
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5503
+ }
5166
5504
  /**
5167
5505
  * Get theme-specific styles - KB-DRIVEN
5168
5506
  */
@@ -5379,15 +5717,15 @@ var IterativeQAEngine = class {
5379
5717
  let autoFixSummary = "";
5380
5718
  let totalFixesApplied = 0;
5381
5719
  if (opts.verbose) {
5382
- console.log("\n\u{1F504} Starting Iterative QA Process");
5383
- console.log(` Target Score: ${opts.minScore}/100`);
5384
- console.log(` Max Iterations: ${opts.maxIterations}`);
5385
- console.log("");
5720
+ logger.progress("\n\u{1F504} Starting Iterative QA Process");
5721
+ logger.info(` Target Score: ${opts.minScore}/100`);
5722
+ logger.info(` Max Iterations: ${opts.maxIterations}`);
5723
+ logger.progress("");
5386
5724
  }
5387
5725
  for (let i = 0; i < opts.maxIterations; i++) {
5388
5726
  const iterationNum = i + 1;
5389
5727
  if (opts.verbose) {
5390
- console.log(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5728
+ logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5391
5729
  }
5392
5730
  scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
5393
5731
  iterations.push({
@@ -5406,17 +5744,17 @@ var IterativeQAEngine = class {
5406
5744
  timestamp: /* @__PURE__ */ new Date()
5407
5745
  });
5408
5746
  if (opts.verbose) {
5409
- console.log(` Score: ${scoringResult.overallScore}/100`);
5747
+ logger.info(` Score: ${scoringResult.overallScore}/100`);
5410
5748
  }
5411
5749
  if (scoringResult.passed) {
5412
5750
  if (opts.verbose) {
5413
- console.log(` \u2705 PASSED - Score meets threshold (${opts.minScore})`);
5751
+ logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
5414
5752
  }
5415
5753
  break;
5416
5754
  }
5417
5755
  if (i < opts.maxIterations - 1) {
5418
5756
  if (opts.verbose) {
5419
- console.log(` \u26A0\uFE0F Below threshold, applying auto-fixes...`);
5757
+ logger.warn("Below threshold, applying auto-fixes...");
5420
5758
  }
5421
5759
  const fixResult = await this.fixer.fix(currentSlides, scoringResult);
5422
5760
  if (fixResult.fixesApplied.length > 0) {
@@ -5428,12 +5766,12 @@ var IterativeQAEngine = class {
5428
5766
  }
5429
5767
  totalFixesApplied += fixResult.fixesApplied.length;
5430
5768
  if (opts.verbose) {
5431
- console.log(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5769
+ logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5432
5770
  }
5433
5771
  autoFixSummary += fixResult.summary + "\n";
5434
5772
  } else {
5435
5773
  if (opts.verbose) {
5436
- console.log(` \u26A0\uFE0F No auto-fixes available, manual review needed`);
5774
+ logger.warn("No auto-fixes available, manual review needed");
5437
5775
  }
5438
5776
  break;
5439
5777
  }
@@ -5449,7 +5787,7 @@ var IterativeQAEngine = class {
5449
5787
  totalFixesApplied
5450
5788
  );
5451
5789
  if (opts.verbose) {
5452
- console.log(report);
5790
+ logger.info(report);
5453
5791
  }
5454
5792
  return {
5455
5793
  finalScore: scoringResult.overallScore,
@@ -6251,6 +6589,172 @@ var PowerPointGenerator = class {
6251
6589
  }
6252
6590
  };
6253
6591
 
6592
+ // src/generators/PDFGenerator.ts
6593
+ var import_puppeteer = __toESM(require("puppeteer"));
6594
+ var PDFGenerator = class {
6595
+ defaultOptions = {
6596
+ orientation: "landscape",
6597
+ printBackground: true,
6598
+ format: "Slide",
6599
+ scale: 1
6600
+ };
6601
+ /**
6602
+ * Generate PDF from HTML presentation
6603
+ * @param html - The HTML content of the presentation
6604
+ * @param options - PDF generation options
6605
+ * @returns Buffer containing the PDF file
6606
+ */
6607
+ async generate(html, options = {}) {
6608
+ const opts = { ...this.defaultOptions, ...options };
6609
+ logger.progress("\u{1F4C4} Generating PDF...");
6610
+ let browser;
6611
+ try {
6612
+ browser = await import_puppeteer.default.launch({
6613
+ headless: true,
6614
+ args: [
6615
+ "--no-sandbox",
6616
+ "--disable-setuid-sandbox",
6617
+ "--disable-dev-shm-usage"
6618
+ ]
6619
+ });
6620
+ const page = await browser.newPage();
6621
+ const printHtml = this.preparePrintHtml(html);
6622
+ await page.setViewport({
6623
+ width: 1920,
6624
+ height: 1080
6625
+ });
6626
+ await page.setContent(printHtml, {
6627
+ waitUntil: ["networkidle0", "domcontentloaded"]
6628
+ });
6629
+ await page.evaluate(() => {
6630
+ return new Promise((resolve) => {
6631
+ if (document.fonts && document.fonts.ready) {
6632
+ document.fonts.ready.then(() => resolve());
6633
+ } else {
6634
+ setTimeout(resolve, 1e3);
6635
+ }
6636
+ });
6637
+ });
6638
+ const pdfBuffer = await page.pdf({
6639
+ width: "1920px",
6640
+ height: "1080px",
6641
+ printBackground: opts.printBackground,
6642
+ scale: opts.scale,
6643
+ landscape: opts.orientation === "landscape",
6644
+ margin: {
6645
+ top: "0px",
6646
+ right: "0px",
6647
+ bottom: "0px",
6648
+ left: "0px"
6649
+ }
6650
+ });
6651
+ logger.step("PDF generated successfully");
6652
+ return Buffer.from(pdfBuffer);
6653
+ } catch (error) {
6654
+ const errorMessage = error instanceof Error ? error.message : String(error);
6655
+ logger.error(`PDF generation failed: ${errorMessage}`);
6656
+ throw new Error(`PDF generation failed: ${errorMessage}`);
6657
+ } finally {
6658
+ if (browser) {
6659
+ await browser.close();
6660
+ }
6661
+ }
6662
+ }
6663
+ /**
6664
+ * Prepare HTML for print/PDF output
6665
+ * Modifies the HTML to work better as a printed document
6666
+ */
6667
+ preparePrintHtml(html) {
6668
+ const printCss = `
6669
+ <style id="pdf-print-styles">
6670
+ /* Print-specific overrides */
6671
+ @page {
6672
+ size: 1920px 1080px;
6673
+ margin: 0;
6674
+ }
6675
+
6676
+ @media print {
6677
+ html, body {
6678
+ margin: 0;
6679
+ padding: 0;
6680
+ width: 1920px;
6681
+ height: 1080px;
6682
+ }
6683
+
6684
+ .reveal .slides {
6685
+ width: 100% !important;
6686
+ height: 100% !important;
6687
+ margin: 0 !important;
6688
+ padding: 0 !important;
6689
+ transform: none !important;
6690
+ }
6691
+
6692
+ .reveal .slides section {
6693
+ page-break-after: always;
6694
+ page-break-inside: avoid;
6695
+ width: 1920px !important;
6696
+ height: 1080px !important;
6697
+ margin: 0 !important;
6698
+ padding: 60px !important;
6699
+ box-sizing: border-box;
6700
+ position: relative !important;
6701
+ top: auto !important;
6702
+ left: auto !important;
6703
+ transform: none !important;
6704
+ display: flex !important;
6705
+ opacity: 1 !important;
6706
+ visibility: visible !important;
6707
+ }
6708
+
6709
+ .reveal .slides section:last-child {
6710
+ page-break-after: auto;
6711
+ }
6712
+
6713
+ /* Make all slides visible for printing */
6714
+ .reveal .slides section.future,
6715
+ .reveal .slides section.past {
6716
+ display: flex !important;
6717
+ opacity: 1 !important;
6718
+ visibility: visible !important;
6719
+ }
6720
+
6721
+ /* Disable animations for print */
6722
+ * {
6723
+ animation: none !important;
6724
+ transition: none !important;
6725
+ }
6726
+
6727
+ /* Hide Reveal.js controls */
6728
+ .reveal .controls,
6729
+ .reveal .progress,
6730
+ .reveal .slide-number,
6731
+ .reveal .speaker-notes,
6732
+ .reveal .navigate-left,
6733
+ .reveal .navigate-right,
6734
+ .reveal .navigate-up,
6735
+ .reveal .navigate-down {
6736
+ display: none !important;
6737
+ }
6738
+ }
6739
+
6740
+ /* Force print mode in Puppeteer */
6741
+ .reveal .slides section {
6742
+ page-break-after: always;
6743
+ page-break-inside: avoid;
6744
+ }
6745
+ </style>
6746
+ `;
6747
+ if (html.includes("</head>")) {
6748
+ return html.replace("</head>", `${printCss}</head>`);
6749
+ } else {
6750
+ return printCss + html;
6751
+ }
6752
+ }
6753
+ };
6754
+ function createPDFGenerator() {
6755
+ return new PDFGenerator();
6756
+ }
6757
+
6254
6758
  // src/core/PresentationEngine.ts
6255
6759
  var PresentationEngine = class {
6256
6760
  contentAnalyzer;
@@ -6259,6 +6763,7 @@ var PresentationEngine = class {
6259
6763
  qaEngine;
6260
6764
  htmlGenerator;
6261
6765
  pptxGenerator;
6766
+ pdfGenerator;
6262
6767
  kb;
6263
6768
  constructor() {
6264
6769
  this.contentAnalyzer = new ContentAnalyzer();
@@ -6267,6 +6772,7 @@ var PresentationEngine = class {
6267
6772
  this.qaEngine = new QAEngine();
6268
6773
  this.htmlGenerator = new RevealJsGenerator();
6269
6774
  this.pptxGenerator = new PowerPointGenerator();
6775
+ this.pdfGenerator = createPDFGenerator();
6270
6776
  }
6271
6777
  /**
6272
6778
  * Generate a presentation from content.
@@ -6276,26 +6782,26 @@ var PresentationEngine = class {
6276
6782
  */
6277
6783
  async generate(config) {
6278
6784
  this.validateConfig(config);
6279
- console.log("\u{1F4DA} Loading knowledge base...");
6785
+ logger.progress("\u{1F4DA} Loading knowledge base...");
6280
6786
  this.kb = await getKnowledgeGateway();
6281
- console.log("\u{1F4DD} Analyzing content...");
6787
+ logger.progress("\u{1F4DD} Analyzing content...");
6282
6788
  const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
6283
6789
  const presentationType = config.presentationType || analysis.detectedType;
6284
- console.log(` \u2713 Presentation type: ${presentationType}`);
6790
+ logger.step(`Presentation type: ${presentationType}`);
6285
6791
  const slideFactory = createSlideFactory(this.kb, presentationType);
6286
- console.log("\u{1F3A8} Creating slides...");
6792
+ logger.progress("\u{1F3A8} Creating slides...");
6287
6793
  const slides = await slideFactory.createSlides(analysis);
6288
- console.log("\u2705 Validating structure...");
6794
+ logger.progress("\u2705 Validating structure...");
6289
6795
  const structureErrors = this.validateStructure(slides, config.mode);
6290
6796
  if (structureErrors.length > 0) {
6291
6797
  if (config.skipQA) {
6292
- console.log("\u26A0\uFE0F Structure warnings (bypassed):");
6293
- structureErrors.forEach((e) => console.log(` \u2022 ${e}`));
6798
+ logger.warn("Structure warnings (bypassed):");
6799
+ structureErrors.forEach((e) => logger.warn(` \u2022 ${e}`));
6294
6800
  } else {
6295
6801
  throw new ValidationError(structureErrors, "Slide structure validation failed");
6296
6802
  }
6297
6803
  }
6298
- console.log("\u{1F528} Generating outputs...");
6804
+ logger.progress("\u{1F528} Generating outputs...");
6299
6805
  const outputs = {};
6300
6806
  if (config.format.includes("html")) {
6301
6807
  outputs.html = await this.htmlGenerator.generate(slides, config);
@@ -6313,7 +6819,7 @@ var PresentationEngine = class {
6313
6819
  const maxIterations = config.maxIterations ?? 5;
6314
6820
  const useIterativeQA = config.useIterativeQA !== false;
6315
6821
  if (useIterativeQA) {
6316
- console.log("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6822
+ logger.progress("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6317
6823
  const iterativeEngine = createIterativeQAEngine(
6318
6824
  config.mode,
6319
6825
  presentationType,
@@ -6335,20 +6841,30 @@ var PresentationEngine = class {
6335
6841
  throw new QAFailureError(score, threshold, qaResults);
6336
6842
  }
6337
6843
  } else {
6338
- console.log("\u{1F50D} Running QA validation (legacy mode)...");
6844
+ logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
6339
6845
  qaResults = await this.qaEngine.validate(outputs.html, {
6340
6846
  mode: config.mode,
6341
6847
  strictMode: true
6342
6848
  });
6343
6849
  score = this.scoreCalculator.calculate(qaResults);
6344
- console.log(`\u{1F4CA} QA Score: ${score}/100`);
6850
+ logger.info(`\u{1F4CA} QA Score: ${score}/100`);
6345
6851
  if (score < threshold) {
6346
6852
  throw new QAFailureError(score, threshold, qaResults);
6347
6853
  }
6348
6854
  }
6349
6855
  } else {
6350
6856
  qaResults = this.qaEngine.createEmptyResults();
6351
- console.log("\u26A0\uFE0F QA validation skipped (NOT RECOMMENDED)");
6857
+ logger.warn("QA validation skipped (NOT RECOMMENDED)");
6858
+ }
6859
+ if (outputs.html && (config.format.includes("pdf") || config.generatePdf !== false)) {
6860
+ try {
6861
+ logger.progress("\u{1F4C4} Generating PDF from validated HTML...");
6862
+ outputs.pdf = await this.pdfGenerator.generate(outputs.html);
6863
+ logger.step("PDF generated successfully");
6864
+ } catch (pdfError) {
6865
+ const errorMsg = pdfError instanceof Error ? pdfError.message : String(pdfError);
6866
+ logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
6867
+ }
6352
6868
  }
6353
6869
  const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
6354
6870
  return {
@@ -6436,15 +6952,28 @@ var PresentationEngine = class {
6436
6952
  if (slides.length < 3) {
6437
6953
  errors.push("Presentation must have at least 3 slides");
6438
6954
  }
6955
+ const sparseByDesignTypes = [
6956
+ "title",
6957
+ "section-divider",
6958
+ "thank-you",
6959
+ "big-number",
6960
+ "single-statement",
6961
+ "cta",
6962
+ "agenda",
6963
+ "metrics-grid",
6964
+ "quote",
6965
+ "image-focus"
6966
+ ];
6439
6967
  slides.forEach((slide, index) => {
6440
6968
  const wordCount = this.countWords(slide);
6441
6969
  if (mode === "keynote") {
6442
- if (wordCount > 25) {
6970
+ if (wordCount > 25 && !sparseByDesignTypes.includes(slide.type)) {
6443
6971
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds keynote limit of 25`);
6444
6972
  }
6445
6973
  } else {
6446
- if (wordCount < 20 && !["title", "section-divider", "thank-you"].includes(slide.type)) {
6447
- errors.push(`Slide ${index + 1}: ${wordCount} words may be too sparse for business mode`);
6974
+ const requiresMinWords = ["bullet-points", "two-column", "process-flow"];
6975
+ if (wordCount < 20 && requiresMinWords.includes(slide.type)) {
6976
+ logger.warn(`Slide ${index + 1}: ${wordCount} words is sparse for ${slide.type} slide`);
6448
6977
  }
6449
6978
  if (wordCount > 100) {
6450
6979
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds business limit of 100`);
@@ -6538,11 +7067,11 @@ var SlideGenerator = class {
6538
7067
  await this.initialize();
6539
7068
  this.presentationType = type || analysis.detectedType;
6540
7069
  this.mode = this.kb.getModeForType(this.presentationType);
6541
- console.log(` \u2713 Using ${this.mode} mode for ${this.presentationType}`);
7070
+ logger.step(`Using ${this.mode} mode for ${this.presentationType}`);
6542
7071
  this.factory = createSlideFactory(this.kb, this.presentationType);
6543
7072
  const slides = await this.factory.createSlides(analysis);
6544
7073
  const legacySlides = this.convertToLegacyFormat(slides);
6545
- console.log(` \u2713 Generated ${legacySlides.length} KB-validated slides`);
7074
+ logger.step(`Generated ${legacySlides.length} KB-validated slides`);
6546
7075
  return legacySlides;
6547
7076
  }
6548
7077
  /**