claude-presentation-master 7.2.0 → 7.2.1

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
@@ -894,14 +963,14 @@ var ContentAnalyzer = class {
894
963
  const text = this.parseContent(content, contentType);
895
964
  const title = this.extractTitle(text);
896
965
  const detectedType = this.detectPresentationType(text);
897
- console.log(` \u2713 Detected type: ${detectedType}`);
966
+ logger.step(`Detected type: ${detectedType}`);
898
967
  const sections = this.extractSections(text);
899
- console.log(` \u2713 Found ${sections.length} sections`);
968
+ logger.step(`Found ${sections.length} sections`);
900
969
  const scqa = this.extractSCQA(text);
901
970
  const sparkline = this.extractSparkline(text);
902
971
  const keyMessages = this.extractKeyMessages(text);
903
972
  const dataPoints = this.extractDataPoints(text);
904
- console.log(` \u2713 Found ${dataPoints.length} data points`);
973
+ logger.step(`Found ${dataPoints.length} data points`);
905
974
  const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
906
975
  const starMoments = this.extractStarMoments(text);
907
976
  const estimatedSlideCount = Math.max(5, sections.length + 3);
@@ -1021,12 +1090,16 @@ var ContentAnalyzer = class {
1021
1090
  }
1022
1091
  const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
1023
1092
  if (bulletMatch && bulletMatch[1] && currentSection) {
1024
- currentSection.bullets.push(bulletMatch[1]);
1093
+ const bulletText = bulletMatch[1];
1094
+ currentSection.bullets.push(bulletText);
1095
+ this.extractMetricsFromText(bulletText, currentSection.metrics);
1025
1096
  continue;
1026
1097
  }
1027
1098
  const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
1028
1099
  if (numberedMatch && numberedMatch[1] && currentSection) {
1029
- currentSection.bullets.push(numberedMatch[1]);
1100
+ const itemText = numberedMatch[1];
1101
+ currentSection.bullets.push(itemText);
1102
+ this.extractMetricsFromText(itemText, currentSection.metrics);
1030
1103
  continue;
1031
1104
  }
1032
1105
  const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
@@ -1037,6 +1110,9 @@ var ContentAnalyzer = class {
1037
1110
  });
1038
1111
  continue;
1039
1112
  }
1113
+ if (currentSection && trimmedLine) {
1114
+ this.extractMetricsFromText(trimmedLine, currentSection.metrics);
1115
+ }
1040
1116
  if (trimmedLine.includes("|") && currentSection) {
1041
1117
  const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
1042
1118
  if (cells.length >= 2) {
@@ -1408,6 +1484,49 @@ var ContentAnalyzer = class {
1408
1484
  const fallback = cleaned.slice(0, 150);
1409
1485
  return fallback.length >= 20 ? fallback : "";
1410
1486
  }
1487
+ /**
1488
+ * Extract metrics from natural language text
1489
+ * Handles formats like "40% productivity loss", "$2.5M investment"
1490
+ */
1491
+ extractMetricsFromText(text, metrics) {
1492
+ const usedValues = new Set(metrics.map((m) => m.value));
1493
+ const moneyMatches = text.matchAll(/(\$[\d,.]+[MBK]?)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1494
+ for (const match of moneyMatches) {
1495
+ const value = match[1];
1496
+ const rawLabel = match[2]?.trim();
1497
+ if (value && rawLabel && !usedValues.has(value)) {
1498
+ usedValues.add(value);
1499
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1500
+ }
1501
+ }
1502
+ const percentMatches = text.matchAll(/(\d+(?:\.\d+)?%)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1503
+ for (const match of percentMatches) {
1504
+ const value = match[1];
1505
+ const rawLabel = match[2]?.trim();
1506
+ if (value && rawLabel && !usedValues.has(value)) {
1507
+ usedValues.add(value);
1508
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1509
+ }
1510
+ }
1511
+ const multMatches = text.matchAll(/(\d+[xX])\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1512
+ for (const match of multMatches) {
1513
+ const value = match[1];
1514
+ const rawLabel = match[2]?.trim();
1515
+ if (value && rawLabel && !usedValues.has(value)) {
1516
+ usedValues.add(value);
1517
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1518
+ }
1519
+ }
1520
+ const numMatches = text.matchAll(/(\d{3,}\+?)\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1521
+ for (const match of numMatches) {
1522
+ const value = match[1];
1523
+ const rawLabel = match[2]?.trim();
1524
+ if (value && rawLabel && !usedValues.has(value)) {
1525
+ usedValues.add(value);
1526
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1527
+ }
1528
+ }
1529
+ }
1411
1530
  };
1412
1531
 
1413
1532
  // src/core/ContentPatternClassifier.ts
@@ -1592,8 +1711,8 @@ var SlideFactory = class {
1592
1711
  this.usedContent = /* @__PURE__ */ new Set();
1593
1712
  this.usedTitles = /* @__PURE__ */ new Set();
1594
1713
  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`);
1714
+ logger.step(`SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1715
+ logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1597
1716
  }
1598
1717
  /**
1599
1718
  * Load ALL configuration from KB - ZERO hardcoded values after this.
@@ -1621,7 +1740,7 @@ var SlideFactory = class {
1621
1740
  async createSlides(analysis) {
1622
1741
  const slides = [];
1623
1742
  const storyStructure = this.kb.getStoryStructure(this.presentationType);
1624
- console.log(` \u2713 Using ${storyStructure.framework} story framework`);
1743
+ logger.step(`Using ${storyStructure.framework} story framework`);
1625
1744
  slides.push(this.createTitleSlide(0, analysis));
1626
1745
  const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
1627
1746
  if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
@@ -1659,7 +1778,7 @@ var SlideFactory = class {
1659
1778
  const validatedSlides = this.validateAndFixSlides(slides);
1660
1779
  this.checkRequiredElements(validatedSlides);
1661
1780
  this.checkAntiPatterns(validatedSlides);
1662
- console.log(` \u2713 Created ${validatedSlides.length} validated slides`);
1781
+ logger.step(`Created ${validatedSlides.length} validated slides`);
1663
1782
  return validatedSlides;
1664
1783
  }
1665
1784
  // ===========================================================================
@@ -1700,7 +1819,7 @@ var SlideFactory = class {
1700
1819
  case "technical":
1701
1820
  return this.createCodeSlide(index, section);
1702
1821
  default:
1703
- console.log(` \u26A0 Unknown slide type '${type}', using bullet-points`);
1822
+ logger.warn(`Unknown slide type '${type}', using bullet-points`);
1704
1823
  return this.createBulletSlide(index, section);
1705
1824
  }
1706
1825
  }
@@ -1833,15 +1952,19 @@ var SlideFactory = class {
1833
1952
  // CONTENT SLIDES - ALL values from KB
1834
1953
  // ===========================================================================
1835
1954
  createBigNumberSlide(index, section, pattern) {
1836
- const bigNumber = this.classifier.extractBigNumber(
1837
- `${section.header} ${section.content} ${section.bullets.join(" ")}`
1838
- );
1955
+ const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
1956
+ const bigNumber = this.classifier.extractBigNumber(fullContent);
1957
+ const actualValue = bigNumber?.value || pattern.bigNumberValue;
1958
+ if (!actualValue) {
1959
+ logger.warn(`No number found for big-number slide, falling back to single-statement`);
1960
+ return this.createSingleStatementSlide(index, section);
1961
+ }
1839
1962
  return {
1840
1963
  index,
1841
1964
  type: "big-number",
1842
1965
  data: {
1843
1966
  title: this.createTitle(section.header, section),
1844
- keyMessage: bigNumber?.value || pattern.bigNumberValue || "0",
1967
+ keyMessage: actualValue,
1845
1968
  body: bigNumber?.context || this.truncateText(
1846
1969
  section.content,
1847
1970
  this.config.defaults.context.maxWords
@@ -2110,7 +2233,7 @@ var SlideFactory = class {
2110
2233
  slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
2111
2234
  }
2112
2235
  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`);
2236
+ logger.warn(`Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2114
2237
  }
2115
2238
  if (validation.warnings.length > 0) {
2116
2239
  slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
@@ -2150,7 +2273,7 @@ var SlideFactory = class {
2150
2273
  }
2151
2274
  }
2152
2275
  if (missingElements.length > 0) {
2153
- console.log(` \u26A0 Missing required elements: ${missingElements.join(", ")}`);
2276
+ logger.warn(`Missing required elements: ${missingElements.join(", ")}`);
2154
2277
  }
2155
2278
  }
2156
2279
  /**
@@ -2181,7 +2304,7 @@ var SlideFactory = class {
2181
2304
  }
2182
2305
  }
2183
2306
  if (violations.length > 0) {
2184
- console.log(` \u26A0 Anti-pattern violations: ${violations.join(", ")}`);
2307
+ logger.warn(`Anti-pattern violations: ${violations.join(", ")}`);
2185
2308
  }
2186
2309
  }
2187
2310
  // ===========================================================================
@@ -2526,9 +2649,12 @@ var TemplateEngine = class {
2526
2649
  this.templates.set("big-number", this.handlebars.compile(`
2527
2650
  <section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
2528
2651
  <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>
2652
+ {{#if title}}
2653
+ <h2 class="title animate-fadeIn">{{title}}</h2>
2654
+ {{/if}}
2655
+ <div class="number animate-zoomIn">{{keyMessage}}</div>
2656
+ {{#if body}}
2657
+ <p class="number-context animate-fadeIn delay-300">{{body}}</p>
2532
2658
  {{/if}}
2533
2659
  {{> source}}
2534
2660
  </div>
@@ -4912,6 +5038,12 @@ ${slides}
4912
5038
  const highlight = palette.accent || "#e94560";
4913
5039
  const text = palette.text || "#1a1a2e";
4914
5040
  const background = palette.background || "#ffffff";
5041
+ const isDark = this.isDarkPalette(palette);
5042
+ const defaultBg = isDark ? background : "#ffffff";
5043
+ const defaultText = isDark ? text : "#1a1a2e";
5044
+ const headingColor = isDark ? primary : primary;
5045
+ const titleBg = isDark ? background : primary;
5046
+ const titleBgEnd = isDark ? this.lightenColor(background, 10) : this.darkenColor(primary, 15);
4915
5047
  return `
4916
5048
  /* Base Styles - KB-Driven Design System */
4917
5049
  :root {
@@ -4938,7 +5070,13 @@ ${slides}
4938
5070
  font-family: var(--font-body);
4939
5071
  font-size: ${fontSize};
4940
5072
  line-height: ${lineHeight};
4941
- color: var(--color-text);
5073
+ color: ${defaultText};
5074
+ background: ${defaultBg};
5075
+ }
5076
+
5077
+ /* Override reveal.js default backgrounds for dark mode */
5078
+ .reveal .slides section {
5079
+ background: ${defaultBg};
4942
5080
  }
4943
5081
 
4944
5082
  .reveal .slides {
@@ -4967,10 +5105,17 @@ ${slides}
4967
5105
  font-family: var(--font-heading);
4968
5106
  font-weight: 700;
4969
5107
  letter-spacing: -0.02em;
4970
- color: var(--color-primary);
5108
+ color: ${isDark ? primary : primary};
4971
5109
  margin-bottom: 0.5em;
4972
5110
  }
4973
5111
 
5112
+ /* Dark mode text visibility */
5113
+ ${isDark ? `
5114
+ .reveal, .reveal p, .reveal li, .reveal span {
5115
+ color: ${text};
5116
+ }
5117
+ ` : ""}
5118
+
4974
5119
  .reveal h1 { font-size: 2.5em; }
4975
5120
  .reveal h2 { font-size: 1.8em; }
4976
5121
  .reveal h3 { font-size: 1.3em; }
@@ -5148,6 +5293,124 @@ ${slides}
5148
5293
  font-weight: 600;
5149
5294
  margin-top: 1em;
5150
5295
  }
5296
+
5297
+ /* ================================================================
5298
+ SLIDE TYPE BACKGROUNDS - KB-Driven Visual Design
5299
+ Creates visual hierarchy and differentiation between slide types
5300
+ ================================================================ */
5301
+
5302
+ /* Title slide: Bold background - makes strong first impression */
5303
+ .reveal .slide-title {
5304
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5305
+ }
5306
+ .reveal .slide-title h1,
5307
+ .reveal .slide-title h2,
5308
+ .reveal .slide-title p,
5309
+ .reveal .slide-title .subtitle {
5310
+ color: ${isDark ? primary : "#ffffff"};
5311
+ }
5312
+
5313
+ /* Section dividers: Subtle visual breaks */
5314
+ .reveal .slide-section-divider {
5315
+ background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
5316
+ }
5317
+
5318
+ /* Big number slides: Clean with accent highlight - emphasizes the data */
5319
+ .reveal .slide-big-number {
5320
+ background: var(--color-background);
5321
+ border-left: 8px solid var(--color-highlight);
5322
+ }
5323
+ .reveal .slide-big-number .number {
5324
+ font-size: 5em;
5325
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5326
+ -webkit-background-clip: text;
5327
+ -webkit-text-fill-color: transparent;
5328
+ background-clip: text;
5329
+ }
5330
+
5331
+ /* Metrics grid: Light accent background - data-focused */
5332
+ .reveal .slide-metrics-grid {
5333
+ background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
5334
+ }
5335
+ ${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
5336
+
5337
+ /* CTA slide: Highlight color - drives action */
5338
+ .reveal .slide-cta {
5339
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5340
+ }
5341
+ .reveal .slide-cta h2,
5342
+ .reveal .slide-cta p {
5343
+ color: #ffffff;
5344
+ }
5345
+ .reveal .slide-cta .cta-button {
5346
+ background: #ffffff;
5347
+ color: var(--color-highlight);
5348
+ }
5349
+
5350
+ /* Thank you slide: Matches title for bookend effect */
5351
+ .reveal .slide-thank-you {
5352
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5353
+ }
5354
+ .reveal .slide-thank-you h2,
5355
+ .reveal .slide-thank-you p,
5356
+ .reveal .slide-thank-you .subtitle {
5357
+ color: ${isDark ? primary : "#ffffff"};
5358
+ }
5359
+
5360
+ /* Quote slides: Elegant subtle background */
5361
+ .reveal .slide-quote {
5362
+ background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
5363
+ }
5364
+ .reveal .slide-quote blockquote {
5365
+ border-left-color: var(--color-accent);
5366
+ font-size: 1.3em;
5367
+ }
5368
+ ${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
5369
+
5370
+ /* Single statement: Clean, centered, impactful */
5371
+ .reveal .slide-single-statement {
5372
+ background: var(--color-background);
5373
+ }
5374
+ .reveal .slide-single-statement .statement,
5375
+ .reveal .slide-single-statement .big-idea-text {
5376
+ font-size: 2.2em;
5377
+ max-width: 80%;
5378
+ margin: 0 auto;
5379
+ }
5380
+
5381
+ /* Comparison slides: Split visual */
5382
+ .reveal .slide-comparison .columns {
5383
+ gap: 60px;
5384
+ }
5385
+ .reveal .slide-comparison .column:first-child {
5386
+ border-right: 2px solid ${this.lightenColor(secondary, 70)};
5387
+ padding-right: 30px;
5388
+ }
5389
+
5390
+ /* Timeline/Process: Visual flow */
5391
+ .reveal .slide-timeline .steps,
5392
+ .reveal .slide-process .steps {
5393
+ display: flex;
5394
+ gap: 20px;
5395
+ }
5396
+ .reveal .slide-timeline .step,
5397
+ .reveal .slide-process .step {
5398
+ flex: 1;
5399
+ padding: 20px;
5400
+ background: ${isDark ? this.lightenColor(background, 10) : this.lightenColor(secondary, 92)};
5401
+ border-radius: 8px;
5402
+ border-top: 4px solid var(--color-accent);
5403
+ ${isDark ? `color: ${text};` : ""}
5404
+ }
5405
+
5406
+ /* Progress bar enhancement */
5407
+ .reveal .progress {
5408
+ background: ${this.lightenColor(primary, 80)};
5409
+ height: 4px;
5410
+ }
5411
+ .reveal .progress span {
5412
+ background: var(--color-highlight);
5413
+ }
5151
5414
  `;
5152
5415
  }
5153
5416
  /**
@@ -5163,6 +5426,37 @@ ${slides}
5163
5426
  b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
5164
5427
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5165
5428
  }
5429
+ /**
5430
+ * Check if a palette is "dark mode" (dark background, light text)
5431
+ */
5432
+ isDarkPalette(palette) {
5433
+ const bgLuminance = this.getLuminance(palette.background || "#ffffff");
5434
+ const textLuminance = this.getLuminance(palette.text || "#000000");
5435
+ return bgLuminance < textLuminance;
5436
+ }
5437
+ /**
5438
+ * Get relative luminance of a hex color (0-1, 0=black, 1=white)
5439
+ */
5440
+ getLuminance(hex) {
5441
+ hex = hex.replace("#", "");
5442
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
5443
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
5444
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
5445
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5446
+ }
5447
+ /**
5448
+ * Darken a hex color
5449
+ */
5450
+ darkenColor(hex, percent) {
5451
+ hex = hex.replace("#", "");
5452
+ let r = parseInt(hex.substring(0, 2), 16);
5453
+ let g = parseInt(hex.substring(2, 4), 16);
5454
+ let b = parseInt(hex.substring(4, 6), 16);
5455
+ r = Math.max(0, Math.floor(r * (1 - percent / 100)));
5456
+ g = Math.max(0, Math.floor(g * (1 - percent / 100)));
5457
+ b = Math.max(0, Math.floor(b * (1 - percent / 100)));
5458
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5459
+ }
5166
5460
  /**
5167
5461
  * Get theme-specific styles - KB-DRIVEN
5168
5462
  */
@@ -5379,15 +5673,15 @@ var IterativeQAEngine = class {
5379
5673
  let autoFixSummary = "";
5380
5674
  let totalFixesApplied = 0;
5381
5675
  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("");
5676
+ logger.progress("\n\u{1F504} Starting Iterative QA Process");
5677
+ logger.info(` Target Score: ${opts.minScore}/100`);
5678
+ logger.info(` Max Iterations: ${opts.maxIterations}`);
5679
+ logger.progress("");
5386
5680
  }
5387
5681
  for (let i = 0; i < opts.maxIterations; i++) {
5388
5682
  const iterationNum = i + 1;
5389
5683
  if (opts.verbose) {
5390
- console.log(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5684
+ logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5391
5685
  }
5392
5686
  scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
5393
5687
  iterations.push({
@@ -5406,17 +5700,17 @@ var IterativeQAEngine = class {
5406
5700
  timestamp: /* @__PURE__ */ new Date()
5407
5701
  });
5408
5702
  if (opts.verbose) {
5409
- console.log(` Score: ${scoringResult.overallScore}/100`);
5703
+ logger.info(` Score: ${scoringResult.overallScore}/100`);
5410
5704
  }
5411
5705
  if (scoringResult.passed) {
5412
5706
  if (opts.verbose) {
5413
- console.log(` \u2705 PASSED - Score meets threshold (${opts.minScore})`);
5707
+ logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
5414
5708
  }
5415
5709
  break;
5416
5710
  }
5417
5711
  if (i < opts.maxIterations - 1) {
5418
5712
  if (opts.verbose) {
5419
- console.log(` \u26A0\uFE0F Below threshold, applying auto-fixes...`);
5713
+ logger.warn("Below threshold, applying auto-fixes...");
5420
5714
  }
5421
5715
  const fixResult = await this.fixer.fix(currentSlides, scoringResult);
5422
5716
  if (fixResult.fixesApplied.length > 0) {
@@ -5428,12 +5722,12 @@ var IterativeQAEngine = class {
5428
5722
  }
5429
5723
  totalFixesApplied += fixResult.fixesApplied.length;
5430
5724
  if (opts.verbose) {
5431
- console.log(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5725
+ logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5432
5726
  }
5433
5727
  autoFixSummary += fixResult.summary + "\n";
5434
5728
  } else {
5435
5729
  if (opts.verbose) {
5436
- console.log(` \u26A0\uFE0F No auto-fixes available, manual review needed`);
5730
+ logger.warn("No auto-fixes available, manual review needed");
5437
5731
  }
5438
5732
  break;
5439
5733
  }
@@ -5449,7 +5743,7 @@ var IterativeQAEngine = class {
5449
5743
  totalFixesApplied
5450
5744
  );
5451
5745
  if (opts.verbose) {
5452
- console.log(report);
5746
+ logger.info(report);
5453
5747
  }
5454
5748
  return {
5455
5749
  finalScore: scoringResult.overallScore,
@@ -6251,6 +6545,172 @@ var PowerPointGenerator = class {
6251
6545
  }
6252
6546
  };
6253
6547
 
6548
+ // src/generators/PDFGenerator.ts
6549
+ var import_puppeteer = __toESM(require("puppeteer"));
6550
+ var PDFGenerator = class {
6551
+ defaultOptions = {
6552
+ orientation: "landscape",
6553
+ printBackground: true,
6554
+ format: "Slide",
6555
+ scale: 1
6556
+ };
6557
+ /**
6558
+ * Generate PDF from HTML presentation
6559
+ * @param html - The HTML content of the presentation
6560
+ * @param options - PDF generation options
6561
+ * @returns Buffer containing the PDF file
6562
+ */
6563
+ async generate(html, options = {}) {
6564
+ const opts = { ...this.defaultOptions, ...options };
6565
+ logger.progress("\u{1F4C4} Generating PDF...");
6566
+ let browser;
6567
+ try {
6568
+ browser = await import_puppeteer.default.launch({
6569
+ headless: true,
6570
+ args: [
6571
+ "--no-sandbox",
6572
+ "--disable-setuid-sandbox",
6573
+ "--disable-dev-shm-usage"
6574
+ ]
6575
+ });
6576
+ const page = await browser.newPage();
6577
+ const printHtml = this.preparePrintHtml(html);
6578
+ await page.setViewport({
6579
+ width: 1920,
6580
+ height: 1080
6581
+ });
6582
+ await page.setContent(printHtml, {
6583
+ waitUntil: ["networkidle0", "domcontentloaded"]
6584
+ });
6585
+ await page.evaluate(() => {
6586
+ return new Promise((resolve) => {
6587
+ if (document.fonts && document.fonts.ready) {
6588
+ document.fonts.ready.then(() => resolve());
6589
+ } else {
6590
+ setTimeout(resolve, 1e3);
6591
+ }
6592
+ });
6593
+ });
6594
+ const pdfBuffer = await page.pdf({
6595
+ width: "1920px",
6596
+ height: "1080px",
6597
+ printBackground: opts.printBackground,
6598
+ scale: opts.scale,
6599
+ landscape: opts.orientation === "landscape",
6600
+ margin: {
6601
+ top: "0px",
6602
+ right: "0px",
6603
+ bottom: "0px",
6604
+ left: "0px"
6605
+ }
6606
+ });
6607
+ logger.step("PDF generated successfully");
6608
+ return Buffer.from(pdfBuffer);
6609
+ } catch (error) {
6610
+ const errorMessage = error instanceof Error ? error.message : String(error);
6611
+ logger.error(`PDF generation failed: ${errorMessage}`);
6612
+ throw new Error(`PDF generation failed: ${errorMessage}`);
6613
+ } finally {
6614
+ if (browser) {
6615
+ await browser.close();
6616
+ }
6617
+ }
6618
+ }
6619
+ /**
6620
+ * Prepare HTML for print/PDF output
6621
+ * Modifies the HTML to work better as a printed document
6622
+ */
6623
+ preparePrintHtml(html) {
6624
+ const printCss = `
6625
+ <style id="pdf-print-styles">
6626
+ /* Print-specific overrides */
6627
+ @page {
6628
+ size: 1920px 1080px;
6629
+ margin: 0;
6630
+ }
6631
+
6632
+ @media print {
6633
+ html, body {
6634
+ margin: 0;
6635
+ padding: 0;
6636
+ width: 1920px;
6637
+ height: 1080px;
6638
+ }
6639
+
6640
+ .reveal .slides {
6641
+ width: 100% !important;
6642
+ height: 100% !important;
6643
+ margin: 0 !important;
6644
+ padding: 0 !important;
6645
+ transform: none !important;
6646
+ }
6647
+
6648
+ .reveal .slides section {
6649
+ page-break-after: always;
6650
+ page-break-inside: avoid;
6651
+ width: 1920px !important;
6652
+ height: 1080px !important;
6653
+ margin: 0 !important;
6654
+ padding: 60px !important;
6655
+ box-sizing: border-box;
6656
+ position: relative !important;
6657
+ top: auto !important;
6658
+ left: auto !important;
6659
+ transform: none !important;
6660
+ display: flex !important;
6661
+ opacity: 1 !important;
6662
+ visibility: visible !important;
6663
+ }
6664
+
6665
+ .reveal .slides section:last-child {
6666
+ page-break-after: auto;
6667
+ }
6668
+
6669
+ /* Make all slides visible for printing */
6670
+ .reveal .slides section.future,
6671
+ .reveal .slides section.past {
6672
+ display: flex !important;
6673
+ opacity: 1 !important;
6674
+ visibility: visible !important;
6675
+ }
6676
+
6677
+ /* Disable animations for print */
6678
+ * {
6679
+ animation: none !important;
6680
+ transition: none !important;
6681
+ }
6682
+
6683
+ /* Hide Reveal.js controls */
6684
+ .reveal .controls,
6685
+ .reveal .progress,
6686
+ .reveal .slide-number,
6687
+ .reveal .speaker-notes,
6688
+ .reveal .navigate-left,
6689
+ .reveal .navigate-right,
6690
+ .reveal .navigate-up,
6691
+ .reveal .navigate-down {
6692
+ display: none !important;
6693
+ }
6694
+ }
6695
+
6696
+ /* Force print mode in Puppeteer */
6697
+ .reveal .slides section {
6698
+ page-break-after: always;
6699
+ page-break-inside: avoid;
6700
+ }
6701
+ </style>
6702
+ `;
6703
+ if (html.includes("</head>")) {
6704
+ return html.replace("</head>", `${printCss}</head>`);
6705
+ } else {
6706
+ return printCss + html;
6707
+ }
6708
+ }
6709
+ };
6710
+ function createPDFGenerator() {
6711
+ return new PDFGenerator();
6712
+ }
6713
+
6254
6714
  // src/core/PresentationEngine.ts
6255
6715
  var PresentationEngine = class {
6256
6716
  contentAnalyzer;
@@ -6259,6 +6719,7 @@ var PresentationEngine = class {
6259
6719
  qaEngine;
6260
6720
  htmlGenerator;
6261
6721
  pptxGenerator;
6722
+ pdfGenerator;
6262
6723
  kb;
6263
6724
  constructor() {
6264
6725
  this.contentAnalyzer = new ContentAnalyzer();
@@ -6267,6 +6728,7 @@ var PresentationEngine = class {
6267
6728
  this.qaEngine = new QAEngine();
6268
6729
  this.htmlGenerator = new RevealJsGenerator();
6269
6730
  this.pptxGenerator = new PowerPointGenerator();
6731
+ this.pdfGenerator = createPDFGenerator();
6270
6732
  }
6271
6733
  /**
6272
6734
  * Generate a presentation from content.
@@ -6276,26 +6738,26 @@ var PresentationEngine = class {
6276
6738
  */
6277
6739
  async generate(config) {
6278
6740
  this.validateConfig(config);
6279
- console.log("\u{1F4DA} Loading knowledge base...");
6741
+ logger.progress("\u{1F4DA} Loading knowledge base...");
6280
6742
  this.kb = await getKnowledgeGateway();
6281
- console.log("\u{1F4DD} Analyzing content...");
6743
+ logger.progress("\u{1F4DD} Analyzing content...");
6282
6744
  const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
6283
6745
  const presentationType = config.presentationType || analysis.detectedType;
6284
- console.log(` \u2713 Presentation type: ${presentationType}`);
6746
+ logger.step(`Presentation type: ${presentationType}`);
6285
6747
  const slideFactory = createSlideFactory(this.kb, presentationType);
6286
- console.log("\u{1F3A8} Creating slides...");
6748
+ logger.progress("\u{1F3A8} Creating slides...");
6287
6749
  const slides = await slideFactory.createSlides(analysis);
6288
- console.log("\u2705 Validating structure...");
6750
+ logger.progress("\u2705 Validating structure...");
6289
6751
  const structureErrors = this.validateStructure(slides, config.mode);
6290
6752
  if (structureErrors.length > 0) {
6291
6753
  if (config.skipQA) {
6292
- console.log("\u26A0\uFE0F Structure warnings (bypassed):");
6293
- structureErrors.forEach((e) => console.log(` \u2022 ${e}`));
6754
+ logger.warn("Structure warnings (bypassed):");
6755
+ structureErrors.forEach((e) => logger.warn(` \u2022 ${e}`));
6294
6756
  } else {
6295
6757
  throw new ValidationError(structureErrors, "Slide structure validation failed");
6296
6758
  }
6297
6759
  }
6298
- console.log("\u{1F528} Generating outputs...");
6760
+ logger.progress("\u{1F528} Generating outputs...");
6299
6761
  const outputs = {};
6300
6762
  if (config.format.includes("html")) {
6301
6763
  outputs.html = await this.htmlGenerator.generate(slides, config);
@@ -6313,7 +6775,7 @@ var PresentationEngine = class {
6313
6775
  const maxIterations = config.maxIterations ?? 5;
6314
6776
  const useIterativeQA = config.useIterativeQA !== false;
6315
6777
  if (useIterativeQA) {
6316
- console.log("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6778
+ logger.progress("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6317
6779
  const iterativeEngine = createIterativeQAEngine(
6318
6780
  config.mode,
6319
6781
  presentationType,
@@ -6335,20 +6797,30 @@ var PresentationEngine = class {
6335
6797
  throw new QAFailureError(score, threshold, qaResults);
6336
6798
  }
6337
6799
  } else {
6338
- console.log("\u{1F50D} Running QA validation (legacy mode)...");
6800
+ logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
6339
6801
  qaResults = await this.qaEngine.validate(outputs.html, {
6340
6802
  mode: config.mode,
6341
6803
  strictMode: true
6342
6804
  });
6343
6805
  score = this.scoreCalculator.calculate(qaResults);
6344
- console.log(`\u{1F4CA} QA Score: ${score}/100`);
6806
+ logger.info(`\u{1F4CA} QA Score: ${score}/100`);
6345
6807
  if (score < threshold) {
6346
6808
  throw new QAFailureError(score, threshold, qaResults);
6347
6809
  }
6348
6810
  }
6349
6811
  } else {
6350
6812
  qaResults = this.qaEngine.createEmptyResults();
6351
- console.log("\u26A0\uFE0F QA validation skipped (NOT RECOMMENDED)");
6813
+ logger.warn("QA validation skipped (NOT RECOMMENDED)");
6814
+ }
6815
+ if (outputs.html && (config.format.includes("pdf") || config.generatePdf !== false)) {
6816
+ try {
6817
+ logger.progress("\u{1F4C4} Generating PDF from validated HTML...");
6818
+ outputs.pdf = await this.pdfGenerator.generate(outputs.html);
6819
+ logger.step("PDF generated successfully");
6820
+ } catch (pdfError) {
6821
+ const errorMsg = pdfError instanceof Error ? pdfError.message : String(pdfError);
6822
+ logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
6823
+ }
6352
6824
  }
6353
6825
  const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
6354
6826
  return {
@@ -6436,15 +6908,28 @@ var PresentationEngine = class {
6436
6908
  if (slides.length < 3) {
6437
6909
  errors.push("Presentation must have at least 3 slides");
6438
6910
  }
6911
+ const sparseByDesignTypes = [
6912
+ "title",
6913
+ "section-divider",
6914
+ "thank-you",
6915
+ "big-number",
6916
+ "single-statement",
6917
+ "cta",
6918
+ "agenda",
6919
+ "metrics-grid",
6920
+ "quote",
6921
+ "image-focus"
6922
+ ];
6439
6923
  slides.forEach((slide, index) => {
6440
6924
  const wordCount = this.countWords(slide);
6441
6925
  if (mode === "keynote") {
6442
- if (wordCount > 25) {
6926
+ if (wordCount > 25 && !sparseByDesignTypes.includes(slide.type)) {
6443
6927
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds keynote limit of 25`);
6444
6928
  }
6445
6929
  } 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`);
6930
+ const requiresMinWords = ["bullet-points", "two-column", "process-flow"];
6931
+ if (wordCount < 20 && requiresMinWords.includes(slide.type)) {
6932
+ logger.warn(`Slide ${index + 1}: ${wordCount} words is sparse for ${slide.type} slide`);
6448
6933
  }
6449
6934
  if (wordCount > 100) {
6450
6935
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds business limit of 100`);
@@ -6538,11 +7023,11 @@ var SlideGenerator = class {
6538
7023
  await this.initialize();
6539
7024
  this.presentationType = type || analysis.detectedType;
6540
7025
  this.mode = this.kb.getModeForType(this.presentationType);
6541
- console.log(` \u2713 Using ${this.mode} mode for ${this.presentationType}`);
7026
+ logger.step(`Using ${this.mode} mode for ${this.presentationType}`);
6542
7027
  this.factory = createSlideFactory(this.kb, this.presentationType);
6543
7028
  const slides = await this.factory.createSlides(analysis);
6544
7029
  const legacySlides = this.convertToLegacyFormat(slides);
6545
- console.log(` \u2713 Generated ${legacySlides.length} KB-validated slides`);
7030
+ logger.step(`Generated ${legacySlides.length} KB-validated slides`);
6546
7031
  return legacySlides;
6547
7032
  }
6548
7033
  /**