claude-presentation-master 8.0.0 → 8.1.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
@@ -847,7 +847,8 @@ var KnowledgeGateway = class {
847
847
  // Subtitle needs more room for complete phrases - at least 10 words for keynote
848
848
  subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
849
849
  context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
850
- step: { maxWords: Math.min(20, Math.floor(maxWords / 4)) },
850
+ // Step descriptions need minimum 8 words for meaningful content
851
+ step: { maxWords: Math.max(8, Math.min(20, Math.floor(maxWords / 3))) },
851
852
  columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
852
853
  };
853
854
  }
@@ -2895,9 +2896,8 @@ var SlideFactory = class {
2895
2896
  const steps = this.classifier.extractSteps(section);
2896
2897
  const maxSteps = Math.min(
2897
2898
  steps.length,
2898
- this.config.rules.bulletsPerSlide.max,
2899
2899
  this.config.millersLaw.maxItems
2900
- // FROM KB - 7±2 rule
2900
+ // FROM KB - 7±2 rule (max 9)
2901
2901
  );
2902
2902
  return {
2903
2903
  index,
@@ -2919,7 +2919,7 @@ var SlideFactory = class {
2919
2919
  }
2920
2920
  createProcessSlide(index, section) {
2921
2921
  const steps = this.classifier.extractSteps(section);
2922
- const maxSteps = Math.min(steps.length, this.config.rules.bulletsPerSlide.max);
2922
+ const maxSteps = Math.min(steps.length, this.config.millersLaw.maxItems);
2923
2923
  return {
2924
2924
  index,
2925
2925
  type: "process",
@@ -5326,169 +5326,422 @@ var VisualQualityEvaluator = class {
5326
5326
  const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
5327
5327
  const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
5328
5328
  const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
5329
+ const hasSteps = !!currentSlide.querySelector(".steps, .process-steps, .timeline");
5330
+ const steps = Array.from(currentSlide.querySelectorAll(".step, .process-step, .timeline-item")).map(
5331
+ (s) => s.textContent?.trim() || ""
5332
+ );
5333
+ const hasMetrics = !!currentSlide.querySelector(".metrics, .metric");
5329
5334
  const hasImage = !!currentSlide.querySelector("img");
5330
5335
  const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
5331
5336
  const classList = Array.from(currentSlide.classList);
5332
5337
  const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
5333
5338
  const titleEl = currentSlide.querySelector("h1, h2, .title");
5334
5339
  const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
5340
+ const truncatedElements = [];
5341
+ const contentElements = currentSlide.querySelectorAll("h1, h2, h3, p, span, li, .body, .step-desc, .step-title, .timeline-content");
5342
+ contentElements.forEach((el, idx) => {
5343
+ const styles = window.getComputedStyle(el);
5344
+ const text = el.textContent?.trim() || "";
5345
+ if (text.length < 15) return;
5346
+ const isLayoutContainer = el.classList?.contains("slide-content") || el.classList?.contains("steps") || el.classList?.contains("timeline") || el.classList?.contains("process-steps");
5347
+ if (isLayoutContainer) return;
5348
+ if (styles.textOverflow === "ellipsis") {
5349
+ if (el.scrollWidth > el.clientWidth + 5) {
5350
+ truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated horizontally`);
5351
+ }
5352
+ }
5353
+ const scrollHeight = el.scrollHeight;
5354
+ const clientHeight = el.clientHeight;
5355
+ if (scrollHeight > clientHeight + 20) {
5356
+ const overflow = styles.overflow || styles.overflowY;
5357
+ if (overflow === "hidden" || overflow === "clip") {
5358
+ truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated vertically`);
5359
+ }
5360
+ }
5361
+ });
5362
+ const allVisibleText = Array.from(currentSlide.querySelectorAll("*")).map((el) => el.textContent?.trim() || "").join(" ").trim();
5363
+ const isEmptySlide = allVisibleText.length < 10 && !hasImage && !hasChart;
5364
+ const hasOnlyTitle = title.length > 0 && body.length === 0 && bullets.length === 0 && steps.length === 0 && !hasSteps && !hasMetrics && !hasImage && !hasChart;
5365
+ const titleLower = title.toLowerCase();
5366
+ const bodyLower = body.toLowerCase();
5367
+ const isRedundant = titleLower.length > 10 && bodyLower.length > 10 && (titleLower.includes(bodyLower) || bodyLower.includes(titleLower));
5368
+ const contrastIssues = [];
5369
+ const parseRGB = (color) => {
5370
+ const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
5371
+ if (match && match[1] !== void 0 && match[2] !== void 0 && match[3] !== void 0) {
5372
+ return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
5373
+ }
5374
+ return null;
5375
+ };
5376
+ const getLuminance = (rgb) => {
5377
+ const values = [rgb.r, rgb.g, rgb.b].map((c) => {
5378
+ c = c / 255;
5379
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
5380
+ });
5381
+ return 0.2126 * (values[0] ?? 0) + 0.7152 * (values[1] ?? 0) + 0.0722 * (values[2] ?? 0);
5382
+ };
5383
+ const getContrastRatio = (fg, bg) => {
5384
+ const l1 = getLuminance(fg);
5385
+ const l2 = getLuminance(bg);
5386
+ const lighter = Math.max(l1, l2);
5387
+ const darker = Math.min(l1, l2);
5388
+ return (lighter + 0.05) / (darker + 0.05);
5389
+ };
5390
+ const bgRGB = parseRGB(backgroundColor);
5391
+ if (titleEl && bgRGB) {
5392
+ const titleRGB = parseRGB(titleStyles?.color || "");
5393
+ if (titleRGB) {
5394
+ const contrast = getContrastRatio(titleRGB, bgRGB);
5395
+ if (contrast < 4.5) {
5396
+ contrastIssues.push(`Title contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
5397
+ }
5398
+ }
5399
+ }
5400
+ const bodyEl = currentSlide.querySelector(".body, p:not(.subtitle)");
5401
+ if (bodyEl && bgRGB) {
5402
+ const bodyStyles2 = window.getComputedStyle(bodyEl);
5403
+ const bodyRGB = parseRGB(bodyStyles2.color);
5404
+ if (bodyRGB) {
5405
+ const contrast = getContrastRatio(bodyRGB, bgRGB);
5406
+ if (contrast < 4.5) {
5407
+ contrastIssues.push(`Body contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
5408
+ }
5409
+ }
5410
+ }
5411
+ const hasContrastIssues = contrastIssues.length > 0;
5412
+ const layoutIssues = [];
5413
+ const slideRect = currentSlide.getBoundingClientRect();
5414
+ const layoutElements = currentSlide.querySelectorAll("h1, h2, h3, p, ul, ol, .number, .metric, img, canvas, svg");
5415
+ let topHeavy = 0;
5416
+ let bottomHeavy = 0;
5417
+ let leftHeavy = 0;
5418
+ let rightHeavy = 0;
5419
+ let centerY = slideRect.height / 2;
5420
+ let centerX = slideRect.width / 2;
5421
+ layoutElements.forEach((el) => {
5422
+ const rect = el.getBoundingClientRect();
5423
+ const elCenterY = rect.top + rect.height / 2 - slideRect.top;
5424
+ const elCenterX = rect.left + rect.width / 2 - slideRect.left;
5425
+ if (elCenterY < centerY) topHeavy++;
5426
+ else bottomHeavy++;
5427
+ if (elCenterX < centerX) leftHeavy++;
5428
+ else rightHeavy++;
5429
+ });
5430
+ const verticalBalance = Math.abs(topHeavy - bottomHeavy) / Math.max(1, topHeavy + bottomHeavy);
5431
+ const horizontalBalance = Math.abs(leftHeavy - rightHeavy) / Math.max(1, leftHeavy + rightHeavy);
5432
+ if (verticalBalance > 0.6 && contentElements.length > 2) {
5433
+ layoutIssues.push(`Vertical imbalance: ${topHeavy} top vs ${bottomHeavy} bottom`);
5434
+ }
5435
+ if (horizontalBalance > 0.6 && contentElements.length > 2) {
5436
+ layoutIssues.push(`Horizontal imbalance: ${leftHeavy} left vs ${rightHeavy} right`);
5437
+ }
5438
+ const hasLayoutIssues = layoutIssues.length > 0;
5439
+ const typographyIssues = [];
5440
+ const titleSize = parseFloat(titleStyles?.fontSize || "0");
5441
+ const bodyStyles = bodyEl ? window.getComputedStyle(bodyEl) : null;
5442
+ const bodySize = parseFloat(bodyStyles?.fontSize || "0");
5443
+ if (titleSize > 0 && bodySize > 0 && titleSize <= bodySize) {
5444
+ typographyIssues.push("Title not larger than body - hierarchy broken");
5445
+ }
5446
+ if (titleSize > 0 && bodySize > 0 && titleSize < bodySize * 1.3) {
5447
+ typographyIssues.push("Title not prominent enough vs body");
5448
+ }
5449
+ const hasTypographyIssues = typographyIssues.length > 0;
5450
+ const completenessIssues = [];
5451
+ const emptySteps = currentSlide.querySelectorAll(".steps:empty, .timeline:empty, .process-steps:empty, .steps > :empty");
5452
+ if (emptySteps.length > 0) {
5453
+ completenessIssues.push(`${emptySteps.length} empty step/timeline container(s) - content missing`);
5454
+ }
5455
+ if (body.includes("Lorem") || body.includes("placeholder") || body.includes("TODO")) {
5456
+ completenessIssues.push("Contains placeholder text");
5457
+ }
5458
+ const emptyBullets = bullets.filter((b) => b.length < 3).length;
5459
+ if (emptyBullets > 0 && bullets.length > 0) {
5460
+ completenessIssues.push(`${emptyBullets} empty/minimal bullets`);
5461
+ }
5462
+ const hasCompletenessIssues = completenessIssues.length > 0;
5463
+ const visualNeedsIssues = [];
5464
+ const visualRequiredTypes = ["data", "chart", "metrics", "comparison", "process", "timeline"];
5465
+ const visualRecommendedTypes = ["big-number", "quote", "testimonial"];
5466
+ const slideClasses = Array.from(currentSlide.classList).join(" ").toLowerCase();
5467
+ const needsVisual = visualRequiredTypes.some((t) => slideClasses.includes(t));
5468
+ const visualRecommended = visualRecommendedTypes.some((t) => slideClasses.includes(t));
5469
+ const contentText = (title + " " + body).toLowerCase();
5470
+ const dataIndicators = /\d+%|\$[\d,]+|\d+x|million|billion|percent|growth|increase|decrease|comparison|versus|vs\b|before|after/i;
5471
+ const hasDataContent = dataIndicators.test(contentText);
5472
+ const hasAnyVisual = hasImage || hasChart;
5473
+ if (needsVisual && !hasAnyVisual) {
5474
+ visualNeedsIssues.push("Data/process slide missing visual - needs chart, diagram, or image");
5475
+ } else if (hasDataContent && !hasAnyVisual && bullets.length === 0) {
5476
+ visualNeedsIssues.push("Content has numbers/data but no visualization - chart would help");
5477
+ } else if (visualRecommended && !hasAnyVisual) {
5478
+ visualNeedsIssues.push("Visual recommended for this slide type");
5479
+ }
5480
+ if (hasImage) {
5481
+ const img = currentSlide.querySelector("img");
5482
+ const imgSrc = img?.getAttribute("src") || "";
5483
+ if (imgSrc.includes("placeholder") || imgSrc.includes("picsum") || imgSrc.includes("via.placeholder")) {
5484
+ visualNeedsIssues.push("Placeholder image detected - needs real visual");
5485
+ }
5486
+ }
5487
+ const needsVisualButMissing = visualNeedsIssues.length > 0;
5488
+ const visualScore = hasAnyVisual ? 10 : needsVisual || hasDataContent ? 0 : 5;
5335
5489
  return {
5336
5490
  title,
5337
5491
  body,
5338
5492
  bullets,
5493
+ steps,
5494
+ // NEW: Timeline/process step content
5495
+ hasSteps,
5496
+ // NEW: Has .steps/.process-steps container
5497
+ hasMetrics,
5498
+ // NEW: Has metrics content
5339
5499
  hasImage,
5340
5500
  hasChart,
5341
5501
  classList,
5342
5502
  backgroundColor,
5343
5503
  titleFontSize: titleStyles?.fontSize || "",
5344
5504
  titleColor: titleStyles?.color || "",
5345
- contentLength: (title + body + bullets.join(" ")).length
5505
+ // Include steps in content length calculation
5506
+ contentLength: (title + body + bullets.join(" ") + steps.join(" ")).length,
5507
+ // Visual quality flags
5508
+ hasTruncatedText: truncatedElements.length > 0,
5509
+ truncatedElements,
5510
+ isEmptySlide,
5511
+ hasOnlyTitle,
5512
+ isRedundant,
5513
+ allVisibleTextLength: allVisibleText.length,
5514
+ // NEW: Expert quality checks
5515
+ hasContrastIssues,
5516
+ contrastIssues,
5517
+ hasLayoutIssues,
5518
+ layoutIssues,
5519
+ hasTypographyIssues,
5520
+ typographyIssues,
5521
+ hasCompletenessIssues,
5522
+ completenessIssues,
5523
+ // Visual needs analysis
5524
+ needsVisualButMissing,
5525
+ visualNeedsIssues,
5526
+ visualScore
5346
5527
  };
5347
5528
  });
5348
5529
  return this.scoreSlide(slideIndex, slideData, screenshotPath);
5349
5530
  }
5531
+ /**
5532
+ * EXPERT-LEVEL SLIDE SCORING
5533
+ *
5534
+ * This evaluates each slide like Nancy Duarte, Carmine Gallo, or a McKinsey partner would.
5535
+ * It's not about rules - it's about whether the slide WORKS.
5536
+ */
5350
5537
  scoreSlide(slideIndex, slideData, screenshotPath) {
5351
5538
  if (!slideData) {
5352
- return {
5353
- slideIndex,
5354
- slideType: "unknown",
5355
- visualImpact: 0,
5356
- visualImpactNotes: "Could not analyze slide",
5357
- contentClarity: 0,
5358
- contentClarityNotes: "Could not analyze slide",
5359
- professionalPolish: 0,
5360
- professionalPolishNotes: "Could not analyze slide",
5361
- themeCoherence: 0,
5362
- themeCoherenceNotes: "Could not analyze slide",
5363
- totalScore: 0,
5364
- screenshotPath
5365
- };
5539
+ return this.createFailedSlideScore(slideIndex, "unknown", "Could not analyze slide", screenshotPath);
5366
5540
  }
5367
5541
  const slideType = this.inferSlideType(slideData);
5368
- let visualImpact = 5;
5369
- const visualNotes = [];
5370
- if (slideData.hasImage) {
5371
- visualImpact += 2;
5372
- visualNotes.push("Has imagery");
5542
+ const criticalFailures = [];
5543
+ if (slideData.isEmptySlide) {
5544
+ criticalFailures.push("EMPTY SLIDE: No meaningful content - slide serves no purpose");
5373
5545
  }
5374
- if (slideData.hasChart) {
5375
- visualImpact += 2;
5376
- visualNotes.push("Has data visualization");
5546
+ if (slideData.hasOnlyTitle && !["title", "agenda", "thank-you", "cta"].includes(slideType)) {
5547
+ criticalFailures.push("INCOMPLETE: Slide has title but no body content - looks unfinished");
5377
5548
  }
5378
- const highImpactTypes = [
5379
- "big-number",
5380
- "big_number",
5381
- "metrics-grid",
5382
- "metrics_grid",
5383
- "three-column",
5384
- "three_column",
5385
- "three-points",
5386
- "three_points",
5387
- "title-impact",
5388
- "title_impact",
5389
- "cta",
5390
- "call-to-action",
5391
- "comparison",
5392
- "timeline",
5393
- "process",
5394
- "quote",
5395
- "testimonial"
5396
- ];
5397
- if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
5398
- visualImpact += 2;
5399
- visualNotes.push("High-impact slide type");
5549
+ if (slideData.hasTruncatedText) {
5550
+ criticalFailures.push(`TRUNCATED TEXT: ${slideData.truncatedElements.length} element(s) cut off - message incomplete`);
5400
5551
  }
5401
- if (slideType === "single-statement" && slideData.body.length < 50) {
5402
- visualImpact += 2;
5403
- visualNotes.push("Clean single statement");
5552
+ if (slideData.isRedundant) {
5553
+ criticalFailures.push("REDUNDANT: Title and body say the same thing - violates one-idea rule");
5404
5554
  }
5405
- if (slideType === "title" && slideData.title.length > 0 && slideData.title.length < 80) {
5406
- visualImpact += 1;
5407
- visualNotes.push("Strong title");
5555
+ if (slideData.hasCompletenessIssues) {
5556
+ slideData.completenessIssues.forEach((issue) => {
5557
+ if (issue.includes("empty")) {
5558
+ criticalFailures.push(`INCOMPLETE: ${issue}`);
5559
+ }
5560
+ });
5408
5561
  }
5409
- if (slideData.contentLength > 300) {
5410
- visualImpact -= 3;
5411
- visualNotes.push("Too much text - overwhelming");
5562
+ if (criticalFailures.length > 0) {
5563
+ return this.createFailedSlideScore(slideIndex, slideType, criticalFailures.join("; "), screenshotPath, criticalFailures);
5564
+ }
5565
+ let glanceTest = 8;
5566
+ const glanceNotes = [];
5567
+ const wordCount = slideData.contentLength / 6;
5568
+ if (wordCount > 40) {
5569
+ glanceTest = 3;
5570
+ glanceNotes.push("Too much text - fails glance test");
5571
+ } else if (wordCount > 25) {
5572
+ glanceTest -= 3;
5573
+ glanceNotes.push("Text-heavy - borderline glance test");
5574
+ } else if (wordCount < 15) {
5575
+ glanceTest += 1;
5576
+ glanceNotes.push("Clean, minimal text - passes glance test easily");
5412
5577
  }
5413
5578
  if (slideData.bullets.length > 5) {
5414
- visualImpact -= 2;
5415
- visualNotes.push("Too many bullets");
5416
- }
5417
- visualImpact = Math.max(0, Math.min(10, visualImpact));
5418
- let contentClarity = 7;
5419
- const clarityNotes = [];
5420
- if (slideData.title.length > 80) {
5421
- contentClarity -= 2;
5422
- clarityNotes.push("Title too long");
5423
- }
5424
- if (slideData.title.length === 0) {
5425
- contentClarity -= 3;
5426
- clarityNotes.push("No title");
5427
- }
5428
- if (slideData.body && slideData.body.length > 0 && slideData.body.length < 200) {
5429
- contentClarity += 1;
5430
- clarityNotes.push("Good content length");
5431
- }
5432
- const avgBulletLength = slideData.bullets.length > 0 ? slideData.bullets.reduce((sum, b) => sum + b.length, 0) / slideData.bullets.length : 0;
5433
- if (avgBulletLength > 100) {
5434
- contentClarity -= 2;
5435
- clarityNotes.push("Bullets too long - not scannable");
5436
- }
5437
- contentClarity = Math.max(0, Math.min(10, contentClarity));
5438
- let professionalPolish = 6;
5439
- const polishNotes = [];
5579
+ glanceTest -= 4;
5580
+ glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
5581
+ } else if (slideData.bullets.length > 3) {
5582
+ glanceTest -= 2;
5583
+ glanceNotes.push("Multiple bullets - needs careful structuring");
5584
+ }
5585
+ if (slideData.hasImage || slideData.hasChart) {
5586
+ glanceTest += 1;
5587
+ glanceNotes.push("Visual element aids quick comprehension");
5588
+ }
5589
+ glanceTest = Math.max(0, Math.min(10, glanceTest));
5590
+ let oneIdea = 7;
5591
+ const oneIdeaNotes = [];
5592
+ if (slideData.title.length > 0 && slideData.title.length < 60) {
5593
+ oneIdea += 1;
5594
+ oneIdeaNotes.push("Clear, focused title");
5595
+ } else if (slideData.title.length > 80) {
5596
+ oneIdea -= 2;
5597
+ oneIdeaNotes.push("Title too long - multiple ideas?");
5598
+ } else if (slideData.title.length === 0) {
5599
+ oneIdea -= 4;
5600
+ oneIdeaNotes.push("No title - what is the one idea?");
5601
+ }
5602
+ const focusedTypes = ["big-number", "single-statement", "quote", "cta", "title"];
5603
+ if (focusedTypes.some((t) => slideType.includes(t))) {
5604
+ oneIdea += 2;
5605
+ oneIdeaNotes.push("Slide type naturally focuses on one idea");
5606
+ }
5607
+ if (slideData.bullets.length > 4) {
5608
+ oneIdea -= 3;
5609
+ oneIdeaNotes.push("Multiple bullets dilute the message");
5610
+ }
5611
+ oneIdea = Math.max(0, Math.min(10, oneIdea));
5612
+ let dataInkRatio = 7;
5613
+ const dataInkNotes = [];
5614
+ const structuredTypes = ["big-number", "metrics-grid", "three-column", "timeline", "process", "comparison"];
5615
+ if (structuredTypes.some((t) => slideType.includes(t))) {
5616
+ dataInkRatio += 1;
5617
+ dataInkNotes.push("Structured layout");
5618
+ }
5619
+ if (slideData.hasChart) {
5620
+ dataInkRatio += 2;
5621
+ dataInkNotes.push("Data visualization present - excellent");
5622
+ }
5623
+ if (slideData.hasImage) {
5624
+ dataInkRatio += 1;
5625
+ dataInkNotes.push("Supporting visual present");
5626
+ }
5627
+ if (slideData.needsVisualButMissing) {
5628
+ dataInkRatio -= 3;
5629
+ slideData.visualNeedsIssues.forEach((issue) => {
5630
+ dataInkNotes.push(`Visual: ${issue}`);
5631
+ });
5632
+ } else if (slideData.visualScore === 10) {
5633
+ dataInkNotes.push("Appropriate visuals for content type");
5634
+ }
5635
+ if (slideData.contentLength > 400) {
5636
+ dataInkRatio -= 4;
5637
+ dataInkNotes.push("Excessive text - low information density");
5638
+ }
5639
+ dataInkRatio = Math.max(0, Math.min(10, dataInkRatio));
5640
+ let professionalExecution = 7;
5641
+ const executionNotes = [];
5440
5642
  const titleFontSize = parseFloat(slideData.titleFontSize || "0");
5441
5643
  if (titleFontSize >= 40) {
5442
- professionalPolish += 2;
5443
- polishNotes.push("Strong title typography");
5444
- } else if (titleFontSize < 24) {
5445
- professionalPolish -= 1;
5446
- polishNotes.push("Title could be more prominent");
5644
+ professionalExecution += 1;
5645
+ executionNotes.push("Strong title typography");
5646
+ } else if (titleFontSize > 0 && titleFontSize < 24) {
5647
+ professionalExecution -= 1;
5648
+ executionNotes.push("Title could be more prominent");
5447
5649
  }
5448
5650
  if (slideData.contentLength > 10 && slideData.contentLength < 200) {
5449
- professionalPolish += 1;
5450
- polishNotes.push("Well-balanced content");
5651
+ professionalExecution += 1;
5652
+ executionNotes.push("Well-balanced content density");
5451
5653
  }
5452
- const polishedTypes = [
5453
- "big-number",
5454
- "metrics-grid",
5455
- "three-column",
5456
- "three-points",
5457
- "comparison",
5458
- "timeline",
5459
- "process",
5460
- "cta",
5461
- "title",
5462
- "thank-you"
5463
- ];
5464
- if (polishedTypes.some((t) => slideType.includes(t))) {
5465
- professionalPolish += 1;
5466
- polishNotes.push("Well-structured layout");
5467
- }
5468
- professionalPolish = Math.max(0, Math.min(10, professionalPolish));
5469
- let themeCoherence = 7;
5470
- const coherenceNotes = [];
5471
5654
  if (slideData.classList.some((c) => c.includes("slide-"))) {
5472
- themeCoherence += 1;
5473
- coherenceNotes.push("Has slide type class");
5655
+ professionalExecution += 1;
5656
+ executionNotes.push("Consistent with design system");
5657
+ }
5658
+ if (slideData.hasContrastIssues) {
5659
+ professionalExecution -= 3;
5660
+ slideData.contrastIssues.forEach((issue) => {
5661
+ executionNotes.push(`Contrast: ${issue}`);
5662
+ });
5663
+ } else {
5664
+ executionNotes.push("Good text contrast");
5474
5665
  }
5475
- themeCoherence = Math.max(0, Math.min(10, themeCoherence));
5476
- const totalScore = visualImpact + contentClarity + professionalPolish + themeCoherence;
5666
+ if (slideData.hasLayoutIssues) {
5667
+ professionalExecution -= 2;
5668
+ slideData.layoutIssues.forEach((issue) => {
5669
+ executionNotes.push(`Layout: ${issue}`);
5670
+ });
5671
+ } else if (slideData.contentLength > 50) {
5672
+ executionNotes.push("Balanced layout");
5673
+ }
5674
+ if (slideData.hasTypographyIssues) {
5675
+ professionalExecution -= 2;
5676
+ slideData.typographyIssues.forEach((issue) => {
5677
+ executionNotes.push(`Typography: ${issue}`);
5678
+ });
5679
+ } else if (titleFontSize > 0) {
5680
+ executionNotes.push("Good type hierarchy");
5681
+ }
5682
+ professionalExecution = Math.max(0, Math.min(10, professionalExecution));
5683
+ const totalScore = glanceTest + oneIdea + dataInkRatio + professionalExecution;
5684
+ const visualImpact = Math.round((glanceTest + oneIdea) / 2);
5685
+ const contentClarity = oneIdea;
5686
+ const professionalPolish = Math.round((dataInkRatio + professionalExecution) / 2);
5687
+ const themeCoherence = professionalExecution;
5477
5688
  return {
5478
5689
  slideIndex,
5479
5690
  slideType,
5691
+ // New expert dimensions
5692
+ glanceTest,
5693
+ glanceTestNotes: glanceNotes.join("; ") || "Passes glance test",
5694
+ oneIdea,
5695
+ oneIdeaNotes: oneIdeaNotes.join("; ") || "Clear single message",
5696
+ dataInkRatio,
5697
+ dataInkNotes: dataInkNotes.join("; ") || "Good information density",
5698
+ professionalExecution,
5699
+ professionalExecutionNotes: executionNotes.join("; ") || "Professional quality",
5700
+ // Critical failures
5701
+ hasCriticalFailure: false,
5702
+ criticalFailures: [],
5703
+ // Legacy dimensions (for compatibility)
5480
5704
  visualImpact,
5481
- visualImpactNotes: visualNotes.join("; ") || "Standard",
5705
+ visualImpactNotes: glanceNotes.join("; ") || "Standard",
5482
5706
  contentClarity,
5483
- contentClarityNotes: clarityNotes.join("; ") || "Good",
5707
+ contentClarityNotes: oneIdeaNotes.join("; ") || "Good",
5484
5708
  professionalPolish,
5485
- professionalPolishNotes: polishNotes.join("; ") || "Acceptable",
5709
+ professionalPolishNotes: executionNotes.join("; ") || "Acceptable",
5486
5710
  themeCoherence,
5487
- themeCoherenceNotes: coherenceNotes.join("; ") || "Consistent",
5711
+ themeCoherenceNotes: "Consistent",
5488
5712
  totalScore,
5489
5713
  screenshotPath
5490
5714
  };
5491
5715
  }
5716
+ /**
5717
+ * Create a failed slide score (for critical failures)
5718
+ */
5719
+ createFailedSlideScore(slideIndex, slideType, reason, screenshotPath, criticalFailures = []) {
5720
+ return {
5721
+ slideIndex,
5722
+ slideType,
5723
+ glanceTest: 0,
5724
+ glanceTestNotes: "CRITICAL FAILURE: " + reason,
5725
+ oneIdea: 0,
5726
+ oneIdeaNotes: "CRITICAL FAILURE: " + reason,
5727
+ dataInkRatio: 0,
5728
+ dataInkNotes: "CRITICAL FAILURE: " + reason,
5729
+ professionalExecution: 0,
5730
+ professionalExecutionNotes: "CRITICAL FAILURE: " + reason,
5731
+ hasCriticalFailure: true,
5732
+ criticalFailures: criticalFailures.length > 0 ? criticalFailures : [reason],
5733
+ visualImpact: 0,
5734
+ visualImpactNotes: "CRITICAL: " + reason,
5735
+ contentClarity: 0,
5736
+ contentClarityNotes: "CRITICAL: " + reason,
5737
+ professionalPolish: 0,
5738
+ professionalPolishNotes: "CRITICAL: " + reason,
5739
+ themeCoherence: 0,
5740
+ themeCoherenceNotes: "CRITICAL: " + reason,
5741
+ totalScore: 0,
5742
+ screenshotPath
5743
+ };
5744
+ }
5492
5745
  inferSlideType(slideData) {
5493
5746
  const classList = slideData.classList || [];
5494
5747
  for (const cls of classList) {
@@ -5600,34 +5853,44 @@ var VisualQualityEvaluator = class {
5600
5853
  evaluateContentQuality(slideScores) {
5601
5854
  let score = 25;
5602
5855
  const notes = [];
5603
- const clarityScores = slideScores.map((s) => s.contentClarity);
5604
- const avgClarity = clarityScores.reduce((a, b) => a + b, 0) / clarityScores.length;
5605
- const messagesAreClear = avgClarity >= 7;
5606
- if (!messagesAreClear) {
5607
- score -= 7;
5608
- notes.push("Messages could be clearer");
5856
+ const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
5857
+ if (criticalFailureSlides.length > 0) {
5858
+ score = Math.max(0, 25 - criticalFailureSlides.length * 8);
5859
+ notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures`);
5860
+ criticalFailureSlides.forEach((s) => {
5861
+ s.criticalFailures.forEach((f) => notes.push(` - Slide ${s.slideIndex}: ${f}`));
5862
+ });
5609
5863
  }
5610
- const lowClarity = slideScores.filter((s) => s.contentClarity < 5).length;
5611
- const appropriateDepth = lowClarity < slideScores.length * 0.2;
5864
+ const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
5865
+ const passesGlanceTest = avgGlance >= 6;
5866
+ if (!passesGlanceTest) {
5867
+ score -= 5;
5868
+ notes.push(`Glance Test: Average ${avgGlance.toFixed(1)}/10 - too text-heavy`);
5869
+ }
5870
+ const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
5871
+ const hasOneIdeaPerSlide = avgOneIdea >= 6;
5872
+ if (!hasOneIdeaPerSlide) {
5873
+ score -= 5;
5874
+ notes.push(`One Idea Rule: Average ${avgOneIdea.toFixed(1)}/10 - messages diluted`);
5875
+ }
5876
+ const messagesAreClear = avgOneIdea >= 7;
5877
+ const lowScoreSlides = slideScores.filter((s) => s.totalScore < 20).length;
5878
+ const appropriateDepth = lowScoreSlides < slideScores.length * 0.2;
5612
5879
  if (!appropriateDepth) {
5613
5880
  score -= 5;
5614
- notes.push("Some slides have content issues");
5881
+ notes.push("Some slides have quality issues");
5615
5882
  }
5616
- const overloadedSlides = slideScores.filter(
5617
- (s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
5618
- ).length;
5883
+ const overloadedSlides = slideScores.filter((s) => s.glanceTest < 5).length;
5619
5884
  const noOverload = overloadedSlides === 0;
5620
5885
  if (!noOverload) {
5621
- score -= 5;
5622
- notes.push(`${overloadedSlides} slides have too much content`);
5886
+ score -= 3;
5887
+ notes.push(`${overloadedSlides} slides fail glance test - too dense`);
5623
5888
  }
5624
- const insightSlides = slideScores.filter(
5625
- (s) => s.visualImpact >= 7 && s.contentClarity >= 7
5626
- ).length;
5627
- const actionableInsights = insightSlides >= slideScores.length * 0.3;
5889
+ const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
5890
+ const actionableInsights = excellentSlides >= slideScores.length * 0.3;
5628
5891
  if (!actionableInsights) {
5629
- score -= 5;
5630
- notes.push("Need more high-impact insight slides");
5892
+ score -= 3;
5893
+ notes.push("Need more high-impact slides");
5631
5894
  }
5632
5895
  return {
5633
5896
  score: Math.max(0, score),
@@ -5641,30 +5904,50 @@ var VisualQualityEvaluator = class {
5641
5904
  evaluateExecutiveReadiness(slideScores) {
5642
5905
  let score = 25;
5643
5906
  const notes = [];
5644
- const avgVisual = slideScores.reduce((sum, s) => sum + s.visualImpact, 0) / slideScores.length;
5645
- const avgPolish = slideScores.reduce((sum, s) => sum + s.professionalPolish, 0) / slideScores.length;
5646
- const avgClarity = slideScores.reduce((sum, s) => sum + s.contentClarity, 0) / slideScores.length;
5907
+ const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
5908
+ if (criticalFailureSlides.length > 0) {
5909
+ score = 0;
5910
+ notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures - CANNOT show to executives`);
5911
+ criticalFailureSlides.forEach((s) => {
5912
+ notes.push(` - Slide ${s.slideIndex} (${s.slideType}): ${s.criticalFailures[0]}`);
5913
+ });
5914
+ return {
5915
+ score: 0,
5916
+ wouldImpress: false,
5917
+ readyForBoardroom: false,
5918
+ compelling: false,
5919
+ shareworthy: false,
5920
+ notes: notes.join(". ")
5921
+ };
5922
+ }
5923
+ const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
5924
+ const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
5925
+ const avgDataInk = slideScores.reduce((sum, s) => sum + s.dataInkRatio, 0) / slideScores.length;
5926
+ const avgExecution = slideScores.reduce((sum, s) => sum + s.professionalExecution, 0) / slideScores.length;
5647
5927
  const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
5648
- const wouldImpress = avgTotal >= 26;
5928
+ const wouldImpress = avgGlance >= 7 && avgOneIdea >= 7 && avgExecution >= 7;
5649
5929
  if (!wouldImpress) {
5650
5930
  score -= 7;
5651
5931
  notes.push("Needs more visual impact to impress");
5652
5932
  }
5653
- const readyForBoardroom = avgPolish >= 6 && avgClarity >= 6;
5933
+ const readyForBoardroom = avgExecution >= 6 && avgDataInk >= 6;
5654
5934
  if (!readyForBoardroom) {
5655
5935
  score -= 7;
5656
- notes.push("Needs more polish for executive audience");
5936
+ notes.push(`Boardroom readiness: Execution ${avgExecution.toFixed(1)}/10, Data-Ink ${avgDataInk.toFixed(1)}/10`);
5657
5937
  }
5658
- const compelling = avgVisual >= 6;
5938
+ const compelling = avgGlance >= 6 && avgOneIdea >= 6;
5659
5939
  if (!compelling) {
5660
5940
  score -= 5;
5661
- notes.push("Could be more visually compelling");
5941
+ notes.push(`Compelling: Glance ${avgGlance.toFixed(1)}/10, OneIdea ${avgOneIdea.toFixed(1)}/10`);
5662
5942
  }
5663
5943
  const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
5664
5944
  const shareworthy = excellentSlides >= slideScores.length * 0.4;
5665
5945
  if (!shareworthy) {
5666
5946
  score -= 5;
5667
- notes.push("Less than 40% of slides are excellent");
5947
+ notes.push(`Shareworthy: ${excellentSlides}/${slideScores.length} excellent slides (need 40%)`);
5948
+ }
5949
+ if (wouldImpress && readyForBoardroom && compelling) {
5950
+ notes.push("Meets expert standards - McKinsey/TED quality");
5668
5951
  }
5669
5952
  return {
5670
5953
  score: Math.max(0, score),