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.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
@@ -822,14 +891,14 @@ var ContentAnalyzer = class {
822
891
  const text = this.parseContent(content, contentType);
823
892
  const title = this.extractTitle(text);
824
893
  const detectedType = this.detectPresentationType(text);
825
- console.log(` \u2713 Detected type: ${detectedType}`);
894
+ logger.step(`Detected type: ${detectedType}`);
826
895
  const sections = this.extractSections(text);
827
- console.log(` \u2713 Found ${sections.length} sections`);
896
+ logger.step(`Found ${sections.length} sections`);
828
897
  const scqa = this.extractSCQA(text);
829
898
  const sparkline = this.extractSparkline(text);
830
899
  const keyMessages = this.extractKeyMessages(text);
831
900
  const dataPoints = this.extractDataPoints(text);
832
- console.log(` \u2713 Found ${dataPoints.length} data points`);
901
+ logger.step(`Found ${dataPoints.length} data points`);
833
902
  const titles = sections.map((s) => s.header).filter((h) => h.length > 0);
834
903
  const starMoments = this.extractStarMoments(text);
835
904
  const estimatedSlideCount = Math.max(5, sections.length + 3);
@@ -949,12 +1018,16 @@ var ContentAnalyzer = class {
949
1018
  }
950
1019
  const bulletMatch = trimmedLine.match(/^[-*+]\s+(.+)$/);
951
1020
  if (bulletMatch && bulletMatch[1] && currentSection) {
952
- currentSection.bullets.push(bulletMatch[1]);
1021
+ const bulletText = bulletMatch[1];
1022
+ currentSection.bullets.push(bulletText);
1023
+ this.extractMetricsFromText(bulletText, currentSection.metrics);
953
1024
  continue;
954
1025
  }
955
1026
  const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
956
1027
  if (numberedMatch && numberedMatch[1] && currentSection) {
957
- currentSection.bullets.push(numberedMatch[1]);
1028
+ const itemText = numberedMatch[1];
1029
+ currentSection.bullets.push(itemText);
1030
+ this.extractMetricsFromText(itemText, currentSection.metrics);
958
1031
  continue;
959
1032
  }
960
1033
  const metricMatch = trimmedLine.match(/\$?([\d,]+\.?\d*)[%]?\s*[-–—:]\s*(.+)/);
@@ -965,6 +1038,9 @@ var ContentAnalyzer = class {
965
1038
  });
966
1039
  continue;
967
1040
  }
1041
+ if (currentSection && trimmedLine) {
1042
+ this.extractMetricsFromText(trimmedLine, currentSection.metrics);
1043
+ }
968
1044
  if (trimmedLine.includes("|") && currentSection) {
969
1045
  const cells = trimmedLine.split("|").map((c) => c.trim()).filter((c) => c && !c.match(/^-+$/));
970
1046
  if (cells.length >= 2) {
@@ -1336,6 +1412,49 @@ var ContentAnalyzer = class {
1336
1412
  const fallback = cleaned.slice(0, 150);
1337
1413
  return fallback.length >= 20 ? fallback : "";
1338
1414
  }
1415
+ /**
1416
+ * Extract metrics from natural language text
1417
+ * Handles formats like "40% productivity loss", "$2.5M investment"
1418
+ */
1419
+ extractMetricsFromText(text, metrics) {
1420
+ const usedValues = new Set(metrics.map((m) => m.value));
1421
+ const moneyMatches = text.matchAll(/(\$[\d,.]+[MBK]?)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1422
+ for (const match of moneyMatches) {
1423
+ const value = match[1];
1424
+ const rawLabel = match[2]?.trim();
1425
+ if (value && rawLabel && !usedValues.has(value)) {
1426
+ usedValues.add(value);
1427
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1428
+ }
1429
+ }
1430
+ const percentMatches = text.matchAll(/(\d+(?:\.\d+)?%)\s+([a-zA-Z][a-zA-Z\s]{2,30})/g);
1431
+ for (const match of percentMatches) {
1432
+ const value = match[1];
1433
+ const rawLabel = match[2]?.trim();
1434
+ if (value && rawLabel && !usedValues.has(value)) {
1435
+ usedValues.add(value);
1436
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1437
+ }
1438
+ }
1439
+ const multMatches = text.matchAll(/(\d+[xX])\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1440
+ for (const match of multMatches) {
1441
+ const value = match[1];
1442
+ const rawLabel = match[2]?.trim();
1443
+ if (value && rawLabel && !usedValues.has(value)) {
1444
+ usedValues.add(value);
1445
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1446
+ }
1447
+ }
1448
+ const numMatches = text.matchAll(/(\d{3,}\+?)\s+([a-zA-Z][a-zA-Z\s]{2,20})/g);
1449
+ for (const match of numMatches) {
1450
+ const value = match[1];
1451
+ const rawLabel = match[2]?.trim();
1452
+ if (value && rawLabel && !usedValues.has(value)) {
1453
+ usedValues.add(value);
1454
+ metrics.push({ value, label: this.cleanMetricLabel(rawLabel) });
1455
+ }
1456
+ }
1457
+ }
1339
1458
  };
1340
1459
 
1341
1460
  // src/core/ContentPatternClassifier.ts
@@ -1520,8 +1639,8 @@ var SlideFactory = class {
1520
1639
  this.usedContent = /* @__PURE__ */ new Set();
1521
1640
  this.usedTitles = /* @__PURE__ */ new Set();
1522
1641
  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`);
1642
+ logger.step(`SlideFactory v7.1.0 initialized for ${type} (${this.config.mode} mode)`);
1643
+ logger.step(`KB Config loaded: ${this.config.allowedTypes.length} allowed types`);
1525
1644
  }
1526
1645
  /**
1527
1646
  * Load ALL configuration from KB - ZERO hardcoded values after this.
@@ -1549,7 +1668,7 @@ var SlideFactory = class {
1549
1668
  async createSlides(analysis) {
1550
1669
  const slides = [];
1551
1670
  const storyStructure = this.kb.getStoryStructure(this.presentationType);
1552
- console.log(` \u2713 Using ${storyStructure.framework} story framework`);
1671
+ logger.step(`Using ${storyStructure.framework} story framework`);
1553
1672
  slides.push(this.createTitleSlide(0, analysis));
1554
1673
  const minSectionsForAgenda = Math.max(3, this.config.rules.bulletsPerSlide.max - 2);
1555
1674
  if (this.config.mode === "business" && analysis.sections.length >= minSectionsForAgenda) {
@@ -1587,7 +1706,7 @@ var SlideFactory = class {
1587
1706
  const validatedSlides = this.validateAndFixSlides(slides);
1588
1707
  this.checkRequiredElements(validatedSlides);
1589
1708
  this.checkAntiPatterns(validatedSlides);
1590
- console.log(` \u2713 Created ${validatedSlides.length} validated slides`);
1709
+ logger.step(`Created ${validatedSlides.length} validated slides`);
1591
1710
  return validatedSlides;
1592
1711
  }
1593
1712
  // ===========================================================================
@@ -1628,7 +1747,7 @@ var SlideFactory = class {
1628
1747
  case "technical":
1629
1748
  return this.createCodeSlide(index, section);
1630
1749
  default:
1631
- console.log(` \u26A0 Unknown slide type '${type}', using bullet-points`);
1750
+ logger.warn(`Unknown slide type '${type}', using bullet-points`);
1632
1751
  return this.createBulletSlide(index, section);
1633
1752
  }
1634
1753
  }
@@ -1761,15 +1880,19 @@ var SlideFactory = class {
1761
1880
  // CONTENT SLIDES - ALL values from KB
1762
1881
  // ===========================================================================
1763
1882
  createBigNumberSlide(index, section, pattern) {
1764
- const bigNumber = this.classifier.extractBigNumber(
1765
- `${section.header} ${section.content} ${section.bullets.join(" ")}`
1766
- );
1883
+ const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
1884
+ const bigNumber = this.classifier.extractBigNumber(fullContent);
1885
+ const actualValue = bigNumber?.value || pattern.bigNumberValue;
1886
+ if (!actualValue) {
1887
+ logger.warn(`No number found for big-number slide, falling back to single-statement`);
1888
+ return this.createSingleStatementSlide(index, section);
1889
+ }
1767
1890
  return {
1768
1891
  index,
1769
1892
  type: "big-number",
1770
1893
  data: {
1771
1894
  title: this.createTitle(section.header, section),
1772
- keyMessage: bigNumber?.value || pattern.bigNumberValue || "0",
1895
+ keyMessage: actualValue,
1773
1896
  body: bigNumber?.context || this.truncateText(
1774
1897
  section.content,
1775
1898
  this.config.defaults.context.maxWords
@@ -2038,7 +2161,7 @@ var SlideFactory = class {
2038
2161
  slide.data.bullets = slide.data.bullets.slice(0, validation.fixes.bulletCount);
2039
2162
  }
2040
2163
  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`);
2164
+ logger.warn(`Slide ${slide.index} (${slide.type}): Fixed ${Object.keys(validation.fixes).length} issues, ${validation.violations.length} remaining`);
2042
2165
  }
2043
2166
  if (validation.warnings.length > 0) {
2044
2167
  slide.notes = (slide.notes || "") + "\nWarnings: " + validation.warnings.join(", ");
@@ -2078,7 +2201,7 @@ var SlideFactory = class {
2078
2201
  }
2079
2202
  }
2080
2203
  if (missingElements.length > 0) {
2081
- console.log(` \u26A0 Missing required elements: ${missingElements.join(", ")}`);
2204
+ logger.warn(`Missing required elements: ${missingElements.join(", ")}`);
2082
2205
  }
2083
2206
  }
2084
2207
  /**
@@ -2109,7 +2232,7 @@ var SlideFactory = class {
2109
2232
  }
2110
2233
  }
2111
2234
  if (violations.length > 0) {
2112
- console.log(` \u26A0 Anti-pattern violations: ${violations.join(", ")}`);
2235
+ logger.warn(`Anti-pattern violations: ${violations.join(", ")}`);
2113
2236
  }
2114
2237
  }
2115
2238
  // ===========================================================================
@@ -2454,9 +2577,12 @@ var TemplateEngine = class {
2454
2577
  this.templates.set("big-number", this.handlebars.compile(`
2455
2578
  <section class="{{classes}}" data-slide-index="{{slideIndex}}" style="{{styles}}">
2456
2579
  <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>
2580
+ {{#if title}}
2581
+ <h2 class="title animate-fadeIn">{{title}}</h2>
2582
+ {{/if}}
2583
+ <div class="number animate-zoomIn">{{keyMessage}}</div>
2584
+ {{#if body}}
2585
+ <p class="number-context animate-fadeIn delay-300">{{body}}</p>
2460
2586
  {{/if}}
2461
2587
  {{> source}}
2462
2588
  </div>
@@ -4840,6 +4966,12 @@ ${slides}
4840
4966
  const highlight = palette.accent || "#e94560";
4841
4967
  const text = palette.text || "#1a1a2e";
4842
4968
  const background = palette.background || "#ffffff";
4969
+ const isDark = this.isDarkPalette(palette);
4970
+ const defaultBg = isDark ? background : "#ffffff";
4971
+ const defaultText = isDark ? text : "#1a1a2e";
4972
+ const headingColor = isDark ? primary : primary;
4973
+ const titleBg = isDark ? background : primary;
4974
+ const titleBgEnd = isDark ? this.lightenColor(background, 10) : this.darkenColor(primary, 15);
4843
4975
  return `
4844
4976
  /* Base Styles - KB-Driven Design System */
4845
4977
  :root {
@@ -4866,7 +4998,13 @@ ${slides}
4866
4998
  font-family: var(--font-body);
4867
4999
  font-size: ${fontSize};
4868
5000
  line-height: ${lineHeight};
4869
- color: var(--color-text);
5001
+ color: ${defaultText};
5002
+ background: ${defaultBg};
5003
+ }
5004
+
5005
+ /* Override reveal.js default backgrounds for dark mode */
5006
+ .reveal .slides section {
5007
+ background: ${defaultBg};
4870
5008
  }
4871
5009
 
4872
5010
  .reveal .slides {
@@ -4895,10 +5033,17 @@ ${slides}
4895
5033
  font-family: var(--font-heading);
4896
5034
  font-weight: 700;
4897
5035
  letter-spacing: -0.02em;
4898
- color: var(--color-primary);
5036
+ color: ${isDark ? primary : primary};
4899
5037
  margin-bottom: 0.5em;
4900
5038
  }
4901
5039
 
5040
+ /* Dark mode text visibility */
5041
+ ${isDark ? `
5042
+ .reveal, .reveal p, .reveal li, .reveal span {
5043
+ color: ${text};
5044
+ }
5045
+ ` : ""}
5046
+
4902
5047
  .reveal h1 { font-size: 2.5em; }
4903
5048
  .reveal h2 { font-size: 1.8em; }
4904
5049
  .reveal h3 { font-size: 1.3em; }
@@ -5076,6 +5221,124 @@ ${slides}
5076
5221
  font-weight: 600;
5077
5222
  margin-top: 1em;
5078
5223
  }
5224
+
5225
+ /* ================================================================
5226
+ SLIDE TYPE BACKGROUNDS - KB-Driven Visual Design
5227
+ Creates visual hierarchy and differentiation between slide types
5228
+ ================================================================ */
5229
+
5230
+ /* Title slide: Bold background - makes strong first impression */
5231
+ .reveal .slide-title {
5232
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5233
+ }
5234
+ .reveal .slide-title h1,
5235
+ .reveal .slide-title h2,
5236
+ .reveal .slide-title p,
5237
+ .reveal .slide-title .subtitle {
5238
+ color: ${isDark ? primary : "#ffffff"};
5239
+ }
5240
+
5241
+ /* Section dividers: Subtle visual breaks */
5242
+ .reveal .slide-section-divider {
5243
+ background: ${isDark ? this.lightenColor(background, 8) : `linear-gradient(180deg, ${this.lightenColor(primary, 85)} 0%, ${this.lightenColor(primary, 92)} 100%)`};
5244
+ }
5245
+
5246
+ /* Big number slides: Clean with accent highlight - emphasizes the data */
5247
+ .reveal .slide-big-number {
5248
+ background: var(--color-background);
5249
+ border-left: 8px solid var(--color-highlight);
5250
+ }
5251
+ .reveal .slide-big-number .number {
5252
+ font-size: 5em;
5253
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5254
+ -webkit-background-clip: text;
5255
+ -webkit-text-fill-color: transparent;
5256
+ background-clip: text;
5257
+ }
5258
+
5259
+ /* Metrics grid: Light accent background - data-focused */
5260
+ .reveal .slide-metrics-grid {
5261
+ background: ${isDark ? this.lightenColor(background, 8) : this.lightenColor(accent, 90)};
5262
+ }
5263
+ ${isDark ? `.reveal .slide-metrics-grid { color: ${text}; }` : ""}
5264
+
5265
+ /* CTA slide: Highlight color - drives action */
5266
+ .reveal .slide-cta {
5267
+ background: linear-gradient(135deg, var(--color-highlight) 0%, var(--color-accent) 100%);
5268
+ }
5269
+ .reveal .slide-cta h2,
5270
+ .reveal .slide-cta p {
5271
+ color: #ffffff;
5272
+ }
5273
+ .reveal .slide-cta .cta-button {
5274
+ background: #ffffff;
5275
+ color: var(--color-highlight);
5276
+ }
5277
+
5278
+ /* Thank you slide: Matches title for bookend effect */
5279
+ .reveal .slide-thank-you {
5280
+ background: linear-gradient(135deg, ${titleBg} 0%, ${titleBgEnd} 100%);
5281
+ }
5282
+ .reveal .slide-thank-you h2,
5283
+ .reveal .slide-thank-you p,
5284
+ .reveal .slide-thank-you .subtitle {
5285
+ color: ${isDark ? primary : "#ffffff"};
5286
+ }
5287
+
5288
+ /* Quote slides: Elegant subtle background */
5289
+ .reveal .slide-quote {
5290
+ background: ${isDark ? this.lightenColor(background, 5) : this.lightenColor(secondary, 92)};
5291
+ }
5292
+ .reveal .slide-quote blockquote {
5293
+ border-left-color: var(--color-accent);
5294
+ font-size: 1.3em;
5295
+ }
5296
+ ${isDark ? `.reveal .slide-quote { color: ${text}; }` : ""}
5297
+
5298
+ /* Single statement: Clean, centered, impactful */
5299
+ .reveal .slide-single-statement {
5300
+ background: var(--color-background);
5301
+ }
5302
+ .reveal .slide-single-statement .statement,
5303
+ .reveal .slide-single-statement .big-idea-text {
5304
+ font-size: 2.2em;
5305
+ max-width: 80%;
5306
+ margin: 0 auto;
5307
+ }
5308
+
5309
+ /* Comparison slides: Split visual */
5310
+ .reveal .slide-comparison .columns {
5311
+ gap: 60px;
5312
+ }
5313
+ .reveal .slide-comparison .column:first-child {
5314
+ border-right: 2px solid ${this.lightenColor(secondary, 70)};
5315
+ padding-right: 30px;
5316
+ }
5317
+
5318
+ /* Timeline/Process: Visual flow */
5319
+ .reveal .slide-timeline .steps,
5320
+ .reveal .slide-process .steps {
5321
+ display: flex;
5322
+ gap: 20px;
5323
+ }
5324
+ .reveal .slide-timeline .step,
5325
+ .reveal .slide-process .step {
5326
+ flex: 1;
5327
+ padding: 20px;
5328
+ background: ${isDark ? this.lightenColor(background, 10) : this.lightenColor(secondary, 92)};
5329
+ border-radius: 8px;
5330
+ border-top: 4px solid var(--color-accent);
5331
+ ${isDark ? `color: ${text};` : ""}
5332
+ }
5333
+
5334
+ /* Progress bar enhancement */
5335
+ .reveal .progress {
5336
+ background: ${this.lightenColor(primary, 80)};
5337
+ height: 4px;
5338
+ }
5339
+ .reveal .progress span {
5340
+ background: var(--color-highlight);
5341
+ }
5079
5342
  `;
5080
5343
  }
5081
5344
  /**
@@ -5091,6 +5354,37 @@ ${slides}
5091
5354
  b = Math.min(255, Math.floor(b + (255 - b) * (percent / 100)));
5092
5355
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5093
5356
  }
5357
+ /**
5358
+ * Check if a palette is "dark mode" (dark background, light text)
5359
+ */
5360
+ isDarkPalette(palette) {
5361
+ const bgLuminance = this.getLuminance(palette.background || "#ffffff");
5362
+ const textLuminance = this.getLuminance(palette.text || "#000000");
5363
+ return bgLuminance < textLuminance;
5364
+ }
5365
+ /**
5366
+ * Get relative luminance of a hex color (0-1, 0=black, 1=white)
5367
+ */
5368
+ getLuminance(hex) {
5369
+ hex = hex.replace("#", "");
5370
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
5371
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
5372
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
5373
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
5374
+ }
5375
+ /**
5376
+ * Darken a hex color
5377
+ */
5378
+ darkenColor(hex, percent) {
5379
+ hex = hex.replace("#", "");
5380
+ let r = parseInt(hex.substring(0, 2), 16);
5381
+ let g = parseInt(hex.substring(2, 4), 16);
5382
+ let b = parseInt(hex.substring(4, 6), 16);
5383
+ r = Math.max(0, Math.floor(r * (1 - percent / 100)));
5384
+ g = Math.max(0, Math.floor(g * (1 - percent / 100)));
5385
+ b = Math.max(0, Math.floor(b * (1 - percent / 100)));
5386
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
5387
+ }
5094
5388
  /**
5095
5389
  * Get theme-specific styles - KB-DRIVEN
5096
5390
  */
@@ -5307,15 +5601,15 @@ var IterativeQAEngine = class {
5307
5601
  let autoFixSummary = "";
5308
5602
  let totalFixesApplied = 0;
5309
5603
  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("");
5604
+ logger.progress("\n\u{1F504} Starting Iterative QA Process");
5605
+ logger.info(` Target Score: ${opts.minScore}/100`);
5606
+ logger.info(` Max Iterations: ${opts.maxIterations}`);
5607
+ logger.progress("");
5314
5608
  }
5315
5609
  for (let i = 0; i < opts.maxIterations; i++) {
5316
5610
  const iterationNum = i + 1;
5317
5611
  if (opts.verbose) {
5318
- console.log(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5612
+ logger.progress(`\u{1F4CA} Iteration ${iterationNum}/${opts.maxIterations}...`);
5319
5613
  }
5320
5614
  scoringResult = await this.scorer.score(currentSlides, currentHtml, opts.minScore);
5321
5615
  iterations.push({
@@ -5334,17 +5628,17 @@ var IterativeQAEngine = class {
5334
5628
  timestamp: /* @__PURE__ */ new Date()
5335
5629
  });
5336
5630
  if (opts.verbose) {
5337
- console.log(` Score: ${scoringResult.overallScore}/100`);
5631
+ logger.info(` Score: ${scoringResult.overallScore}/100`);
5338
5632
  }
5339
5633
  if (scoringResult.passed) {
5340
5634
  if (opts.verbose) {
5341
- console.log(` \u2705 PASSED - Score meets threshold (${opts.minScore})`);
5635
+ logger.success(`PASSED - Score meets threshold (${opts.minScore})`);
5342
5636
  }
5343
5637
  break;
5344
5638
  }
5345
5639
  if (i < opts.maxIterations - 1) {
5346
5640
  if (opts.verbose) {
5347
- console.log(` \u26A0\uFE0F Below threshold, applying auto-fixes...`);
5641
+ logger.warn("Below threshold, applying auto-fixes...");
5348
5642
  }
5349
5643
  const fixResult = await this.fixer.fix(currentSlides, scoringResult);
5350
5644
  if (fixResult.fixesApplied.length > 0) {
@@ -5356,12 +5650,12 @@ var IterativeQAEngine = class {
5356
5650
  }
5357
5651
  totalFixesApplied += fixResult.fixesApplied.length;
5358
5652
  if (opts.verbose) {
5359
- console.log(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5653
+ logger.info(` \u{1F527} Applied ${fixResult.fixesApplied.length} fixes`);
5360
5654
  }
5361
5655
  autoFixSummary += fixResult.summary + "\n";
5362
5656
  } else {
5363
5657
  if (opts.verbose) {
5364
- console.log(` \u26A0\uFE0F No auto-fixes available, manual review needed`);
5658
+ logger.warn("No auto-fixes available, manual review needed");
5365
5659
  }
5366
5660
  break;
5367
5661
  }
@@ -5377,7 +5671,7 @@ var IterativeQAEngine = class {
5377
5671
  totalFixesApplied
5378
5672
  );
5379
5673
  if (opts.verbose) {
5380
- console.log(report);
5674
+ logger.info(report);
5381
5675
  }
5382
5676
  return {
5383
5677
  finalScore: scoringResult.overallScore,
@@ -6179,6 +6473,172 @@ var PowerPointGenerator = class {
6179
6473
  }
6180
6474
  };
6181
6475
 
6476
+ // src/generators/PDFGenerator.ts
6477
+ import puppeteer from "puppeteer";
6478
+ var PDFGenerator = class {
6479
+ defaultOptions = {
6480
+ orientation: "landscape",
6481
+ printBackground: true,
6482
+ format: "Slide",
6483
+ scale: 1
6484
+ };
6485
+ /**
6486
+ * Generate PDF from HTML presentation
6487
+ * @param html - The HTML content of the presentation
6488
+ * @param options - PDF generation options
6489
+ * @returns Buffer containing the PDF file
6490
+ */
6491
+ async generate(html, options = {}) {
6492
+ const opts = { ...this.defaultOptions, ...options };
6493
+ logger.progress("\u{1F4C4} Generating PDF...");
6494
+ let browser;
6495
+ try {
6496
+ browser = await puppeteer.launch({
6497
+ headless: true,
6498
+ args: [
6499
+ "--no-sandbox",
6500
+ "--disable-setuid-sandbox",
6501
+ "--disable-dev-shm-usage"
6502
+ ]
6503
+ });
6504
+ const page = await browser.newPage();
6505
+ const printHtml = this.preparePrintHtml(html);
6506
+ await page.setViewport({
6507
+ width: 1920,
6508
+ height: 1080
6509
+ });
6510
+ await page.setContent(printHtml, {
6511
+ waitUntil: ["networkidle0", "domcontentloaded"]
6512
+ });
6513
+ await page.evaluate(() => {
6514
+ return new Promise((resolve) => {
6515
+ if (document.fonts && document.fonts.ready) {
6516
+ document.fonts.ready.then(() => resolve());
6517
+ } else {
6518
+ setTimeout(resolve, 1e3);
6519
+ }
6520
+ });
6521
+ });
6522
+ const pdfBuffer = await page.pdf({
6523
+ width: "1920px",
6524
+ height: "1080px",
6525
+ printBackground: opts.printBackground,
6526
+ scale: opts.scale,
6527
+ landscape: opts.orientation === "landscape",
6528
+ margin: {
6529
+ top: "0px",
6530
+ right: "0px",
6531
+ bottom: "0px",
6532
+ left: "0px"
6533
+ }
6534
+ });
6535
+ logger.step("PDF generated successfully");
6536
+ return Buffer.from(pdfBuffer);
6537
+ } catch (error) {
6538
+ const errorMessage = error instanceof Error ? error.message : String(error);
6539
+ logger.error(`PDF generation failed: ${errorMessage}`);
6540
+ throw new Error(`PDF generation failed: ${errorMessage}`);
6541
+ } finally {
6542
+ if (browser) {
6543
+ await browser.close();
6544
+ }
6545
+ }
6546
+ }
6547
+ /**
6548
+ * Prepare HTML for print/PDF output
6549
+ * Modifies the HTML to work better as a printed document
6550
+ */
6551
+ preparePrintHtml(html) {
6552
+ const printCss = `
6553
+ <style id="pdf-print-styles">
6554
+ /* Print-specific overrides */
6555
+ @page {
6556
+ size: 1920px 1080px;
6557
+ margin: 0;
6558
+ }
6559
+
6560
+ @media print {
6561
+ html, body {
6562
+ margin: 0;
6563
+ padding: 0;
6564
+ width: 1920px;
6565
+ height: 1080px;
6566
+ }
6567
+
6568
+ .reveal .slides {
6569
+ width: 100% !important;
6570
+ height: 100% !important;
6571
+ margin: 0 !important;
6572
+ padding: 0 !important;
6573
+ transform: none !important;
6574
+ }
6575
+
6576
+ .reveal .slides section {
6577
+ page-break-after: always;
6578
+ page-break-inside: avoid;
6579
+ width: 1920px !important;
6580
+ height: 1080px !important;
6581
+ margin: 0 !important;
6582
+ padding: 60px !important;
6583
+ box-sizing: border-box;
6584
+ position: relative !important;
6585
+ top: auto !important;
6586
+ left: auto !important;
6587
+ transform: none !important;
6588
+ display: flex !important;
6589
+ opacity: 1 !important;
6590
+ visibility: visible !important;
6591
+ }
6592
+
6593
+ .reveal .slides section:last-child {
6594
+ page-break-after: auto;
6595
+ }
6596
+
6597
+ /* Make all slides visible for printing */
6598
+ .reveal .slides section.future,
6599
+ .reveal .slides section.past {
6600
+ display: flex !important;
6601
+ opacity: 1 !important;
6602
+ visibility: visible !important;
6603
+ }
6604
+
6605
+ /* Disable animations for print */
6606
+ * {
6607
+ animation: none !important;
6608
+ transition: none !important;
6609
+ }
6610
+
6611
+ /* Hide Reveal.js controls */
6612
+ .reveal .controls,
6613
+ .reveal .progress,
6614
+ .reveal .slide-number,
6615
+ .reveal .speaker-notes,
6616
+ .reveal .navigate-left,
6617
+ .reveal .navigate-right,
6618
+ .reveal .navigate-up,
6619
+ .reveal .navigate-down {
6620
+ display: none !important;
6621
+ }
6622
+ }
6623
+
6624
+ /* Force print mode in Puppeteer */
6625
+ .reveal .slides section {
6626
+ page-break-after: always;
6627
+ page-break-inside: avoid;
6628
+ }
6629
+ </style>
6630
+ `;
6631
+ if (html.includes("</head>")) {
6632
+ return html.replace("</head>", `${printCss}</head>`);
6633
+ } else {
6634
+ return printCss + html;
6635
+ }
6636
+ }
6637
+ };
6638
+ function createPDFGenerator() {
6639
+ return new PDFGenerator();
6640
+ }
6641
+
6182
6642
  // src/core/PresentationEngine.ts
6183
6643
  var PresentationEngine = class {
6184
6644
  contentAnalyzer;
@@ -6187,6 +6647,7 @@ var PresentationEngine = class {
6187
6647
  qaEngine;
6188
6648
  htmlGenerator;
6189
6649
  pptxGenerator;
6650
+ pdfGenerator;
6190
6651
  kb;
6191
6652
  constructor() {
6192
6653
  this.contentAnalyzer = new ContentAnalyzer();
@@ -6195,6 +6656,7 @@ var PresentationEngine = class {
6195
6656
  this.qaEngine = new QAEngine();
6196
6657
  this.htmlGenerator = new RevealJsGenerator();
6197
6658
  this.pptxGenerator = new PowerPointGenerator();
6659
+ this.pdfGenerator = createPDFGenerator();
6198
6660
  }
6199
6661
  /**
6200
6662
  * Generate a presentation from content.
@@ -6204,26 +6666,26 @@ var PresentationEngine = class {
6204
6666
  */
6205
6667
  async generate(config) {
6206
6668
  this.validateConfig(config);
6207
- console.log("\u{1F4DA} Loading knowledge base...");
6669
+ logger.progress("\u{1F4DA} Loading knowledge base...");
6208
6670
  this.kb = await getKnowledgeGateway();
6209
- console.log("\u{1F4DD} Analyzing content...");
6671
+ logger.progress("\u{1F4DD} Analyzing content...");
6210
6672
  const analysis = await this.contentAnalyzer.analyze(config.content, config.contentType);
6211
6673
  const presentationType = config.presentationType || analysis.detectedType;
6212
- console.log(` \u2713 Presentation type: ${presentationType}`);
6674
+ logger.step(`Presentation type: ${presentationType}`);
6213
6675
  const slideFactory = createSlideFactory(this.kb, presentationType);
6214
- console.log("\u{1F3A8} Creating slides...");
6676
+ logger.progress("\u{1F3A8} Creating slides...");
6215
6677
  const slides = await slideFactory.createSlides(analysis);
6216
- console.log("\u2705 Validating structure...");
6678
+ logger.progress("\u2705 Validating structure...");
6217
6679
  const structureErrors = this.validateStructure(slides, config.mode);
6218
6680
  if (structureErrors.length > 0) {
6219
6681
  if (config.skipQA) {
6220
- console.log("\u26A0\uFE0F Structure warnings (bypassed):");
6221
- structureErrors.forEach((e) => console.log(` \u2022 ${e}`));
6682
+ logger.warn("Structure warnings (bypassed):");
6683
+ structureErrors.forEach((e) => logger.warn(` \u2022 ${e}`));
6222
6684
  } else {
6223
6685
  throw new ValidationError(structureErrors, "Slide structure validation failed");
6224
6686
  }
6225
6687
  }
6226
- console.log("\u{1F528} Generating outputs...");
6688
+ logger.progress("\u{1F528} Generating outputs...");
6227
6689
  const outputs = {};
6228
6690
  if (config.format.includes("html")) {
6229
6691
  outputs.html = await this.htmlGenerator.generate(slides, config);
@@ -6241,7 +6703,7 @@ var PresentationEngine = class {
6241
6703
  const maxIterations = config.maxIterations ?? 5;
6242
6704
  const useIterativeQA = config.useIterativeQA !== false;
6243
6705
  if (useIterativeQA) {
6244
- console.log("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6706
+ logger.progress("\u{1F50D} Running Iterative QA (7-Dimension Scoring)...");
6245
6707
  const iterativeEngine = createIterativeQAEngine(
6246
6708
  config.mode,
6247
6709
  presentationType,
@@ -6263,20 +6725,30 @@ var PresentationEngine = class {
6263
6725
  throw new QAFailureError(score, threshold, qaResults);
6264
6726
  }
6265
6727
  } else {
6266
- console.log("\u{1F50D} Running QA validation (legacy mode)...");
6728
+ logger.progress("\u{1F50D} Running QA validation (legacy mode)...");
6267
6729
  qaResults = await this.qaEngine.validate(outputs.html, {
6268
6730
  mode: config.mode,
6269
6731
  strictMode: true
6270
6732
  });
6271
6733
  score = this.scoreCalculator.calculate(qaResults);
6272
- console.log(`\u{1F4CA} QA Score: ${score}/100`);
6734
+ logger.info(`\u{1F4CA} QA Score: ${score}/100`);
6273
6735
  if (score < threshold) {
6274
6736
  throw new QAFailureError(score, threshold, qaResults);
6275
6737
  }
6276
6738
  }
6277
6739
  } else {
6278
6740
  qaResults = this.qaEngine.createEmptyResults();
6279
- console.log("\u26A0\uFE0F QA validation skipped (NOT RECOMMENDED)");
6741
+ logger.warn("QA validation skipped (NOT RECOMMENDED)");
6742
+ }
6743
+ if (outputs.html && (config.format.includes("pdf") || config.generatePdf !== false)) {
6744
+ try {
6745
+ logger.progress("\u{1F4C4} Generating PDF from validated HTML...");
6746
+ outputs.pdf = await this.pdfGenerator.generate(outputs.html);
6747
+ logger.step("PDF generated successfully");
6748
+ } catch (pdfError) {
6749
+ const errorMsg = pdfError instanceof Error ? pdfError.message : String(pdfError);
6750
+ logger.warn(`PDF generation failed (non-critical): ${errorMsg}`);
6751
+ }
6280
6752
  }
6281
6753
  const metadata = this.buildMetadata(config, analysis, finalSlides, iterativeResult);
6282
6754
  return {
@@ -6364,15 +6836,28 @@ var PresentationEngine = class {
6364
6836
  if (slides.length < 3) {
6365
6837
  errors.push("Presentation must have at least 3 slides");
6366
6838
  }
6839
+ const sparseByDesignTypes = [
6840
+ "title",
6841
+ "section-divider",
6842
+ "thank-you",
6843
+ "big-number",
6844
+ "single-statement",
6845
+ "cta",
6846
+ "agenda",
6847
+ "metrics-grid",
6848
+ "quote",
6849
+ "image-focus"
6850
+ ];
6367
6851
  slides.forEach((slide, index) => {
6368
6852
  const wordCount = this.countWords(slide);
6369
6853
  if (mode === "keynote") {
6370
- if (wordCount > 25) {
6854
+ if (wordCount > 25 && !sparseByDesignTypes.includes(slide.type)) {
6371
6855
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds keynote limit of 25`);
6372
6856
  }
6373
6857
  } 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`);
6858
+ const requiresMinWords = ["bullet-points", "two-column", "process-flow"];
6859
+ if (wordCount < 20 && requiresMinWords.includes(slide.type)) {
6860
+ logger.warn(`Slide ${index + 1}: ${wordCount} words is sparse for ${slide.type} slide`);
6376
6861
  }
6377
6862
  if (wordCount > 100) {
6378
6863
  errors.push(`Slide ${index + 1}: ${wordCount} words exceeds business limit of 100`);
@@ -6466,11 +6951,11 @@ var SlideGenerator = class {
6466
6951
  await this.initialize();
6467
6952
  this.presentationType = type || analysis.detectedType;
6468
6953
  this.mode = this.kb.getModeForType(this.presentationType);
6469
- console.log(` \u2713 Using ${this.mode} mode for ${this.presentationType}`);
6954
+ logger.step(`Using ${this.mode} mode for ${this.presentationType}`);
6470
6955
  this.factory = createSlideFactory(this.kb, this.presentationType);
6471
6956
  const slides = await this.factory.createSlides(analysis);
6472
6957
  const legacySlides = this.convertToLegacyFormat(slides);
6473
- console.log(` \u2713 Generated ${legacySlides.length} KB-validated slides`);
6958
+ logger.step(`Generated ${legacySlides.length} KB-validated slides`);
6474
6959
  return legacySlides;
6475
6960
  }
6476
6961
  /**