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.mjs CHANGED
@@ -778,7 +778,8 @@ var KnowledgeGateway = class {
778
778
  // Subtitle needs more room for complete phrases - at least 10 words for keynote
779
779
  subtitle: { maxWords: mode === "keynote" ? 12 : Math.min(20, Math.floor(maxWords / 2)) },
780
780
  context: { maxWords: Math.min(30, Math.floor(maxWords / 2)) },
781
- step: { maxWords: Math.min(20, Math.floor(maxWords / 4)) },
781
+ // Step descriptions need minimum 8 words for meaningful content
782
+ step: { maxWords: Math.max(8, Math.min(20, Math.floor(maxWords / 3))) },
782
783
  columnContent: { maxWords: Math.min(25, Math.floor(maxWords / 3)) }
783
784
  };
784
785
  }
@@ -2826,9 +2827,8 @@ var SlideFactory = class {
2826
2827
  const steps = this.classifier.extractSteps(section);
2827
2828
  const maxSteps = Math.min(
2828
2829
  steps.length,
2829
- this.config.rules.bulletsPerSlide.max,
2830
2830
  this.config.millersLaw.maxItems
2831
- // FROM KB - 7±2 rule
2831
+ // FROM KB - 7±2 rule (max 9)
2832
2832
  );
2833
2833
  return {
2834
2834
  index,
@@ -2850,7 +2850,7 @@ var SlideFactory = class {
2850
2850
  }
2851
2851
  createProcessSlide(index, section) {
2852
2852
  const steps = this.classifier.extractSteps(section);
2853
- const maxSteps = Math.min(steps.length, this.config.rules.bulletsPerSlide.max);
2853
+ const maxSteps = Math.min(steps.length, this.config.millersLaw.maxItems);
2854
2854
  return {
2855
2855
  index,
2856
2856
  type: "process",
@@ -5257,169 +5257,422 @@ var VisualQualityEvaluator = class {
5257
5257
  const title = currentSlide.querySelector("h1, h2, .title")?.textContent?.trim() || "";
5258
5258
  const body = currentSlide.querySelector(".body, p:not(.subtitle)")?.textContent?.trim() || "";
5259
5259
  const bullets = Array.from(currentSlide.querySelectorAll("li")).map((li) => li.textContent?.trim() || "");
5260
+ const hasSteps = !!currentSlide.querySelector(".steps, .process-steps, .timeline");
5261
+ const steps = Array.from(currentSlide.querySelectorAll(".step, .process-step, .timeline-item")).map(
5262
+ (s) => s.textContent?.trim() || ""
5263
+ );
5264
+ const hasMetrics = !!currentSlide.querySelector(".metrics, .metric");
5260
5265
  const hasImage = !!currentSlide.querySelector("img");
5261
5266
  const hasChart = !!currentSlide.querySelector(".chart, svg, canvas");
5262
5267
  const classList = Array.from(currentSlide.classList);
5263
5268
  const backgroundColor = window.getComputedStyle(currentSlide).backgroundColor;
5264
5269
  const titleEl = currentSlide.querySelector("h1, h2, .title");
5265
5270
  const titleStyles = titleEl ? window.getComputedStyle(titleEl) : null;
5271
+ const truncatedElements = [];
5272
+ const contentElements = currentSlide.querySelectorAll("h1, h2, h3, p, span, li, .body, .step-desc, .step-title, .timeline-content");
5273
+ contentElements.forEach((el, idx) => {
5274
+ const styles = window.getComputedStyle(el);
5275
+ const text = el.textContent?.trim() || "";
5276
+ if (text.length < 15) return;
5277
+ const isLayoutContainer = el.classList?.contains("slide-content") || el.classList?.contains("steps") || el.classList?.contains("timeline") || el.classList?.contains("process-steps");
5278
+ if (isLayoutContainer) return;
5279
+ if (styles.textOverflow === "ellipsis") {
5280
+ if (el.scrollWidth > el.clientWidth + 5) {
5281
+ truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated horizontally`);
5282
+ }
5283
+ }
5284
+ const scrollHeight = el.scrollHeight;
5285
+ const clientHeight = el.clientHeight;
5286
+ if (scrollHeight > clientHeight + 20) {
5287
+ const overflow = styles.overflow || styles.overflowY;
5288
+ if (overflow === "hidden" || overflow === "clip") {
5289
+ truncatedElements.push(`Element ${idx}: "${text.substring(0, 30)}..." is truncated vertically`);
5290
+ }
5291
+ }
5292
+ });
5293
+ const allVisibleText = Array.from(currentSlide.querySelectorAll("*")).map((el) => el.textContent?.trim() || "").join(" ").trim();
5294
+ const isEmptySlide = allVisibleText.length < 10 && !hasImage && !hasChart;
5295
+ const hasOnlyTitle = title.length > 0 && body.length === 0 && bullets.length === 0 && steps.length === 0 && !hasSteps && !hasMetrics && !hasImage && !hasChart;
5296
+ const titleLower = title.toLowerCase();
5297
+ const bodyLower = body.toLowerCase();
5298
+ const isRedundant = titleLower.length > 10 && bodyLower.length > 10 && (titleLower.includes(bodyLower) || bodyLower.includes(titleLower));
5299
+ const contrastIssues = [];
5300
+ const parseRGB = (color) => {
5301
+ const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
5302
+ if (match && match[1] !== void 0 && match[2] !== void 0 && match[3] !== void 0) {
5303
+ return { r: parseInt(match[1]), g: parseInt(match[2]), b: parseInt(match[3]) };
5304
+ }
5305
+ return null;
5306
+ };
5307
+ const getLuminance = (rgb) => {
5308
+ const values = [rgb.r, rgb.g, rgb.b].map((c) => {
5309
+ c = c / 255;
5310
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
5311
+ });
5312
+ return 0.2126 * (values[0] ?? 0) + 0.7152 * (values[1] ?? 0) + 0.0722 * (values[2] ?? 0);
5313
+ };
5314
+ const getContrastRatio = (fg, bg) => {
5315
+ const l1 = getLuminance(fg);
5316
+ const l2 = getLuminance(bg);
5317
+ const lighter = Math.max(l1, l2);
5318
+ const darker = Math.min(l1, l2);
5319
+ return (lighter + 0.05) / (darker + 0.05);
5320
+ };
5321
+ const bgRGB = parseRGB(backgroundColor);
5322
+ if (titleEl && bgRGB) {
5323
+ const titleRGB = parseRGB(titleStyles?.color || "");
5324
+ if (titleRGB) {
5325
+ const contrast = getContrastRatio(titleRGB, bgRGB);
5326
+ if (contrast < 4.5) {
5327
+ contrastIssues.push(`Title contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
5328
+ }
5329
+ }
5330
+ }
5331
+ const bodyEl = currentSlide.querySelector(".body, p:not(.subtitle)");
5332
+ if (bodyEl && bgRGB) {
5333
+ const bodyStyles2 = window.getComputedStyle(bodyEl);
5334
+ const bodyRGB = parseRGB(bodyStyles2.color);
5335
+ if (bodyRGB) {
5336
+ const contrast = getContrastRatio(bodyRGB, bgRGB);
5337
+ if (contrast < 4.5) {
5338
+ contrastIssues.push(`Body contrast ${contrast.toFixed(1)}:1 (need 4.5:1)`);
5339
+ }
5340
+ }
5341
+ }
5342
+ const hasContrastIssues = contrastIssues.length > 0;
5343
+ const layoutIssues = [];
5344
+ const slideRect = currentSlide.getBoundingClientRect();
5345
+ const layoutElements = currentSlide.querySelectorAll("h1, h2, h3, p, ul, ol, .number, .metric, img, canvas, svg");
5346
+ let topHeavy = 0;
5347
+ let bottomHeavy = 0;
5348
+ let leftHeavy = 0;
5349
+ let rightHeavy = 0;
5350
+ let centerY = slideRect.height / 2;
5351
+ let centerX = slideRect.width / 2;
5352
+ layoutElements.forEach((el) => {
5353
+ const rect = el.getBoundingClientRect();
5354
+ const elCenterY = rect.top + rect.height / 2 - slideRect.top;
5355
+ const elCenterX = rect.left + rect.width / 2 - slideRect.left;
5356
+ if (elCenterY < centerY) topHeavy++;
5357
+ else bottomHeavy++;
5358
+ if (elCenterX < centerX) leftHeavy++;
5359
+ else rightHeavy++;
5360
+ });
5361
+ const verticalBalance = Math.abs(topHeavy - bottomHeavy) / Math.max(1, topHeavy + bottomHeavy);
5362
+ const horizontalBalance = Math.abs(leftHeavy - rightHeavy) / Math.max(1, leftHeavy + rightHeavy);
5363
+ if (verticalBalance > 0.6 && contentElements.length > 2) {
5364
+ layoutIssues.push(`Vertical imbalance: ${topHeavy} top vs ${bottomHeavy} bottom`);
5365
+ }
5366
+ if (horizontalBalance > 0.6 && contentElements.length > 2) {
5367
+ layoutIssues.push(`Horizontal imbalance: ${leftHeavy} left vs ${rightHeavy} right`);
5368
+ }
5369
+ const hasLayoutIssues = layoutIssues.length > 0;
5370
+ const typographyIssues = [];
5371
+ const titleSize = parseFloat(titleStyles?.fontSize || "0");
5372
+ const bodyStyles = bodyEl ? window.getComputedStyle(bodyEl) : null;
5373
+ const bodySize = parseFloat(bodyStyles?.fontSize || "0");
5374
+ if (titleSize > 0 && bodySize > 0 && titleSize <= bodySize) {
5375
+ typographyIssues.push("Title not larger than body - hierarchy broken");
5376
+ }
5377
+ if (titleSize > 0 && bodySize > 0 && titleSize < bodySize * 1.3) {
5378
+ typographyIssues.push("Title not prominent enough vs body");
5379
+ }
5380
+ const hasTypographyIssues = typographyIssues.length > 0;
5381
+ const completenessIssues = [];
5382
+ const emptySteps = currentSlide.querySelectorAll(".steps:empty, .timeline:empty, .process-steps:empty, .steps > :empty");
5383
+ if (emptySteps.length > 0) {
5384
+ completenessIssues.push(`${emptySteps.length} empty step/timeline container(s) - content missing`);
5385
+ }
5386
+ if (body.includes("Lorem") || body.includes("placeholder") || body.includes("TODO")) {
5387
+ completenessIssues.push("Contains placeholder text");
5388
+ }
5389
+ const emptyBullets = bullets.filter((b) => b.length < 3).length;
5390
+ if (emptyBullets > 0 && bullets.length > 0) {
5391
+ completenessIssues.push(`${emptyBullets} empty/minimal bullets`);
5392
+ }
5393
+ const hasCompletenessIssues = completenessIssues.length > 0;
5394
+ const visualNeedsIssues = [];
5395
+ const visualRequiredTypes = ["data", "chart", "metrics", "comparison", "process", "timeline"];
5396
+ const visualRecommendedTypes = ["big-number", "quote", "testimonial"];
5397
+ const slideClasses = Array.from(currentSlide.classList).join(" ").toLowerCase();
5398
+ const needsVisual = visualRequiredTypes.some((t) => slideClasses.includes(t));
5399
+ const visualRecommended = visualRecommendedTypes.some((t) => slideClasses.includes(t));
5400
+ const contentText = (title + " " + body).toLowerCase();
5401
+ const dataIndicators = /\d+%|\$[\d,]+|\d+x|million|billion|percent|growth|increase|decrease|comparison|versus|vs\b|before|after/i;
5402
+ const hasDataContent = dataIndicators.test(contentText);
5403
+ const hasAnyVisual = hasImage || hasChart;
5404
+ if (needsVisual && !hasAnyVisual) {
5405
+ visualNeedsIssues.push("Data/process slide missing visual - needs chart, diagram, or image");
5406
+ } else if (hasDataContent && !hasAnyVisual && bullets.length === 0) {
5407
+ visualNeedsIssues.push("Content has numbers/data but no visualization - chart would help");
5408
+ } else if (visualRecommended && !hasAnyVisual) {
5409
+ visualNeedsIssues.push("Visual recommended for this slide type");
5410
+ }
5411
+ if (hasImage) {
5412
+ const img = currentSlide.querySelector("img");
5413
+ const imgSrc = img?.getAttribute("src") || "";
5414
+ if (imgSrc.includes("placeholder") || imgSrc.includes("picsum") || imgSrc.includes("via.placeholder")) {
5415
+ visualNeedsIssues.push("Placeholder image detected - needs real visual");
5416
+ }
5417
+ }
5418
+ const needsVisualButMissing = visualNeedsIssues.length > 0;
5419
+ const visualScore = hasAnyVisual ? 10 : needsVisual || hasDataContent ? 0 : 5;
5266
5420
  return {
5267
5421
  title,
5268
5422
  body,
5269
5423
  bullets,
5424
+ steps,
5425
+ // NEW: Timeline/process step content
5426
+ hasSteps,
5427
+ // NEW: Has .steps/.process-steps container
5428
+ hasMetrics,
5429
+ // NEW: Has metrics content
5270
5430
  hasImage,
5271
5431
  hasChart,
5272
5432
  classList,
5273
5433
  backgroundColor,
5274
5434
  titleFontSize: titleStyles?.fontSize || "",
5275
5435
  titleColor: titleStyles?.color || "",
5276
- contentLength: (title + body + bullets.join(" ")).length
5436
+ // Include steps in content length calculation
5437
+ contentLength: (title + body + bullets.join(" ") + steps.join(" ")).length,
5438
+ // Visual quality flags
5439
+ hasTruncatedText: truncatedElements.length > 0,
5440
+ truncatedElements,
5441
+ isEmptySlide,
5442
+ hasOnlyTitle,
5443
+ isRedundant,
5444
+ allVisibleTextLength: allVisibleText.length,
5445
+ // NEW: Expert quality checks
5446
+ hasContrastIssues,
5447
+ contrastIssues,
5448
+ hasLayoutIssues,
5449
+ layoutIssues,
5450
+ hasTypographyIssues,
5451
+ typographyIssues,
5452
+ hasCompletenessIssues,
5453
+ completenessIssues,
5454
+ // Visual needs analysis
5455
+ needsVisualButMissing,
5456
+ visualNeedsIssues,
5457
+ visualScore
5277
5458
  };
5278
5459
  });
5279
5460
  return this.scoreSlide(slideIndex, slideData, screenshotPath);
5280
5461
  }
5462
+ /**
5463
+ * EXPERT-LEVEL SLIDE SCORING
5464
+ *
5465
+ * This evaluates each slide like Nancy Duarte, Carmine Gallo, or a McKinsey partner would.
5466
+ * It's not about rules - it's about whether the slide WORKS.
5467
+ */
5281
5468
  scoreSlide(slideIndex, slideData, screenshotPath) {
5282
5469
  if (!slideData) {
5283
- return {
5284
- slideIndex,
5285
- slideType: "unknown",
5286
- visualImpact: 0,
5287
- visualImpactNotes: "Could not analyze slide",
5288
- contentClarity: 0,
5289
- contentClarityNotes: "Could not analyze slide",
5290
- professionalPolish: 0,
5291
- professionalPolishNotes: "Could not analyze slide",
5292
- themeCoherence: 0,
5293
- themeCoherenceNotes: "Could not analyze slide",
5294
- totalScore: 0,
5295
- screenshotPath
5296
- };
5470
+ return this.createFailedSlideScore(slideIndex, "unknown", "Could not analyze slide", screenshotPath);
5297
5471
  }
5298
5472
  const slideType = this.inferSlideType(slideData);
5299
- let visualImpact = 5;
5300
- const visualNotes = [];
5301
- if (slideData.hasImage) {
5302
- visualImpact += 2;
5303
- visualNotes.push("Has imagery");
5473
+ const criticalFailures = [];
5474
+ if (slideData.isEmptySlide) {
5475
+ criticalFailures.push("EMPTY SLIDE: No meaningful content - slide serves no purpose");
5304
5476
  }
5305
- if (slideData.hasChart) {
5306
- visualImpact += 2;
5307
- visualNotes.push("Has data visualization");
5477
+ if (slideData.hasOnlyTitle && !["title", "agenda", "thank-you", "cta"].includes(slideType)) {
5478
+ criticalFailures.push("INCOMPLETE: Slide has title but no body content - looks unfinished");
5308
5479
  }
5309
- const highImpactTypes = [
5310
- "big-number",
5311
- "big_number",
5312
- "metrics-grid",
5313
- "metrics_grid",
5314
- "three-column",
5315
- "three_column",
5316
- "three-points",
5317
- "three_points",
5318
- "title-impact",
5319
- "title_impact",
5320
- "cta",
5321
- "call-to-action",
5322
- "comparison",
5323
- "timeline",
5324
- "process",
5325
- "quote",
5326
- "testimonial"
5327
- ];
5328
- if (highImpactTypes.some((t) => slideType.includes(t.replace(/_/g, "-")) || slideType.includes(t.replace(/-/g, "_")))) {
5329
- visualImpact += 2;
5330
- visualNotes.push("High-impact slide type");
5480
+ if (slideData.hasTruncatedText) {
5481
+ criticalFailures.push(`TRUNCATED TEXT: ${slideData.truncatedElements.length} element(s) cut off - message incomplete`);
5331
5482
  }
5332
- if (slideType === "single-statement" && slideData.body.length < 50) {
5333
- visualImpact += 2;
5334
- visualNotes.push("Clean single statement");
5483
+ if (slideData.isRedundant) {
5484
+ criticalFailures.push("REDUNDANT: Title and body say the same thing - violates one-idea rule");
5335
5485
  }
5336
- if (slideType === "title" && slideData.title.length > 0 && slideData.title.length < 80) {
5337
- visualImpact += 1;
5338
- visualNotes.push("Strong title");
5486
+ if (slideData.hasCompletenessIssues) {
5487
+ slideData.completenessIssues.forEach((issue) => {
5488
+ if (issue.includes("empty")) {
5489
+ criticalFailures.push(`INCOMPLETE: ${issue}`);
5490
+ }
5491
+ });
5339
5492
  }
5340
- if (slideData.contentLength > 300) {
5341
- visualImpact -= 3;
5342
- visualNotes.push("Too much text - overwhelming");
5493
+ if (criticalFailures.length > 0) {
5494
+ return this.createFailedSlideScore(slideIndex, slideType, criticalFailures.join("; "), screenshotPath, criticalFailures);
5495
+ }
5496
+ let glanceTest = 8;
5497
+ const glanceNotes = [];
5498
+ const wordCount = slideData.contentLength / 6;
5499
+ if (wordCount > 40) {
5500
+ glanceTest = 3;
5501
+ glanceNotes.push("Too much text - fails glance test");
5502
+ } else if (wordCount > 25) {
5503
+ glanceTest -= 3;
5504
+ glanceNotes.push("Text-heavy - borderline glance test");
5505
+ } else if (wordCount < 15) {
5506
+ glanceTest += 1;
5507
+ glanceNotes.push("Clean, minimal text - passes glance test easily");
5343
5508
  }
5344
5509
  if (slideData.bullets.length > 5) {
5345
- visualImpact -= 2;
5346
- visualNotes.push("Too many bullets");
5347
- }
5348
- visualImpact = Math.max(0, Math.min(10, visualImpact));
5349
- let contentClarity = 7;
5350
- const clarityNotes = [];
5351
- if (slideData.title.length > 80) {
5352
- contentClarity -= 2;
5353
- clarityNotes.push("Title too long");
5354
- }
5355
- if (slideData.title.length === 0) {
5356
- contentClarity -= 3;
5357
- clarityNotes.push("No title");
5358
- }
5359
- if (slideData.body && slideData.body.length > 0 && slideData.body.length < 200) {
5360
- contentClarity += 1;
5361
- clarityNotes.push("Good content length");
5362
- }
5363
- const avgBulletLength = slideData.bullets.length > 0 ? slideData.bullets.reduce((sum, b) => sum + b.length, 0) / slideData.bullets.length : 0;
5364
- if (avgBulletLength > 100) {
5365
- contentClarity -= 2;
5366
- clarityNotes.push("Bullets too long - not scannable");
5367
- }
5368
- contentClarity = Math.max(0, Math.min(10, contentClarity));
5369
- let professionalPolish = 6;
5370
- const polishNotes = [];
5510
+ glanceTest -= 4;
5511
+ glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
5512
+ } else if (slideData.bullets.length > 3) {
5513
+ glanceTest -= 2;
5514
+ glanceNotes.push("Multiple bullets - needs careful structuring");
5515
+ }
5516
+ if (slideData.hasImage || slideData.hasChart) {
5517
+ glanceTest += 1;
5518
+ glanceNotes.push("Visual element aids quick comprehension");
5519
+ }
5520
+ glanceTest = Math.max(0, Math.min(10, glanceTest));
5521
+ let oneIdea = 7;
5522
+ const oneIdeaNotes = [];
5523
+ if (slideData.title.length > 0 && slideData.title.length < 60) {
5524
+ oneIdea += 1;
5525
+ oneIdeaNotes.push("Clear, focused title");
5526
+ } else if (slideData.title.length > 80) {
5527
+ oneIdea -= 2;
5528
+ oneIdeaNotes.push("Title too long - multiple ideas?");
5529
+ } else if (slideData.title.length === 0) {
5530
+ oneIdea -= 4;
5531
+ oneIdeaNotes.push("No title - what is the one idea?");
5532
+ }
5533
+ const focusedTypes = ["big-number", "single-statement", "quote", "cta", "title"];
5534
+ if (focusedTypes.some((t) => slideType.includes(t))) {
5535
+ oneIdea += 2;
5536
+ oneIdeaNotes.push("Slide type naturally focuses on one idea");
5537
+ }
5538
+ if (slideData.bullets.length > 4) {
5539
+ oneIdea -= 3;
5540
+ oneIdeaNotes.push("Multiple bullets dilute the message");
5541
+ }
5542
+ oneIdea = Math.max(0, Math.min(10, oneIdea));
5543
+ let dataInkRatio = 7;
5544
+ const dataInkNotes = [];
5545
+ const structuredTypes = ["big-number", "metrics-grid", "three-column", "timeline", "process", "comparison"];
5546
+ if (structuredTypes.some((t) => slideType.includes(t))) {
5547
+ dataInkRatio += 1;
5548
+ dataInkNotes.push("Structured layout");
5549
+ }
5550
+ if (slideData.hasChart) {
5551
+ dataInkRatio += 2;
5552
+ dataInkNotes.push("Data visualization present - excellent");
5553
+ }
5554
+ if (slideData.hasImage) {
5555
+ dataInkRatio += 1;
5556
+ dataInkNotes.push("Supporting visual present");
5557
+ }
5558
+ if (slideData.needsVisualButMissing) {
5559
+ dataInkRatio -= 3;
5560
+ slideData.visualNeedsIssues.forEach((issue) => {
5561
+ dataInkNotes.push(`Visual: ${issue}`);
5562
+ });
5563
+ } else if (slideData.visualScore === 10) {
5564
+ dataInkNotes.push("Appropriate visuals for content type");
5565
+ }
5566
+ if (slideData.contentLength > 400) {
5567
+ dataInkRatio -= 4;
5568
+ dataInkNotes.push("Excessive text - low information density");
5569
+ }
5570
+ dataInkRatio = Math.max(0, Math.min(10, dataInkRatio));
5571
+ let professionalExecution = 7;
5572
+ const executionNotes = [];
5371
5573
  const titleFontSize = parseFloat(slideData.titleFontSize || "0");
5372
5574
  if (titleFontSize >= 40) {
5373
- professionalPolish += 2;
5374
- polishNotes.push("Strong title typography");
5375
- } else if (titleFontSize < 24) {
5376
- professionalPolish -= 1;
5377
- polishNotes.push("Title could be more prominent");
5575
+ professionalExecution += 1;
5576
+ executionNotes.push("Strong title typography");
5577
+ } else if (titleFontSize > 0 && titleFontSize < 24) {
5578
+ professionalExecution -= 1;
5579
+ executionNotes.push("Title could be more prominent");
5378
5580
  }
5379
5581
  if (slideData.contentLength > 10 && slideData.contentLength < 200) {
5380
- professionalPolish += 1;
5381
- polishNotes.push("Well-balanced content");
5582
+ professionalExecution += 1;
5583
+ executionNotes.push("Well-balanced content density");
5382
5584
  }
5383
- const polishedTypes = [
5384
- "big-number",
5385
- "metrics-grid",
5386
- "three-column",
5387
- "three-points",
5388
- "comparison",
5389
- "timeline",
5390
- "process",
5391
- "cta",
5392
- "title",
5393
- "thank-you"
5394
- ];
5395
- if (polishedTypes.some((t) => slideType.includes(t))) {
5396
- professionalPolish += 1;
5397
- polishNotes.push("Well-structured layout");
5398
- }
5399
- professionalPolish = Math.max(0, Math.min(10, professionalPolish));
5400
- let themeCoherence = 7;
5401
- const coherenceNotes = [];
5402
5585
  if (slideData.classList.some((c) => c.includes("slide-"))) {
5403
- themeCoherence += 1;
5404
- coherenceNotes.push("Has slide type class");
5586
+ professionalExecution += 1;
5587
+ executionNotes.push("Consistent with design system");
5588
+ }
5589
+ if (slideData.hasContrastIssues) {
5590
+ professionalExecution -= 3;
5591
+ slideData.contrastIssues.forEach((issue) => {
5592
+ executionNotes.push(`Contrast: ${issue}`);
5593
+ });
5594
+ } else {
5595
+ executionNotes.push("Good text contrast");
5405
5596
  }
5406
- themeCoherence = Math.max(0, Math.min(10, themeCoherence));
5407
- const totalScore = visualImpact + contentClarity + professionalPolish + themeCoherence;
5597
+ if (slideData.hasLayoutIssues) {
5598
+ professionalExecution -= 2;
5599
+ slideData.layoutIssues.forEach((issue) => {
5600
+ executionNotes.push(`Layout: ${issue}`);
5601
+ });
5602
+ } else if (slideData.contentLength > 50) {
5603
+ executionNotes.push("Balanced layout");
5604
+ }
5605
+ if (slideData.hasTypographyIssues) {
5606
+ professionalExecution -= 2;
5607
+ slideData.typographyIssues.forEach((issue) => {
5608
+ executionNotes.push(`Typography: ${issue}`);
5609
+ });
5610
+ } else if (titleFontSize > 0) {
5611
+ executionNotes.push("Good type hierarchy");
5612
+ }
5613
+ professionalExecution = Math.max(0, Math.min(10, professionalExecution));
5614
+ const totalScore = glanceTest + oneIdea + dataInkRatio + professionalExecution;
5615
+ const visualImpact = Math.round((glanceTest + oneIdea) / 2);
5616
+ const contentClarity = oneIdea;
5617
+ const professionalPolish = Math.round((dataInkRatio + professionalExecution) / 2);
5618
+ const themeCoherence = professionalExecution;
5408
5619
  return {
5409
5620
  slideIndex,
5410
5621
  slideType,
5622
+ // New expert dimensions
5623
+ glanceTest,
5624
+ glanceTestNotes: glanceNotes.join("; ") || "Passes glance test",
5625
+ oneIdea,
5626
+ oneIdeaNotes: oneIdeaNotes.join("; ") || "Clear single message",
5627
+ dataInkRatio,
5628
+ dataInkNotes: dataInkNotes.join("; ") || "Good information density",
5629
+ professionalExecution,
5630
+ professionalExecutionNotes: executionNotes.join("; ") || "Professional quality",
5631
+ // Critical failures
5632
+ hasCriticalFailure: false,
5633
+ criticalFailures: [],
5634
+ // Legacy dimensions (for compatibility)
5411
5635
  visualImpact,
5412
- visualImpactNotes: visualNotes.join("; ") || "Standard",
5636
+ visualImpactNotes: glanceNotes.join("; ") || "Standard",
5413
5637
  contentClarity,
5414
- contentClarityNotes: clarityNotes.join("; ") || "Good",
5638
+ contentClarityNotes: oneIdeaNotes.join("; ") || "Good",
5415
5639
  professionalPolish,
5416
- professionalPolishNotes: polishNotes.join("; ") || "Acceptable",
5640
+ professionalPolishNotes: executionNotes.join("; ") || "Acceptable",
5417
5641
  themeCoherence,
5418
- themeCoherenceNotes: coherenceNotes.join("; ") || "Consistent",
5642
+ themeCoherenceNotes: "Consistent",
5419
5643
  totalScore,
5420
5644
  screenshotPath
5421
5645
  };
5422
5646
  }
5647
+ /**
5648
+ * Create a failed slide score (for critical failures)
5649
+ */
5650
+ createFailedSlideScore(slideIndex, slideType, reason, screenshotPath, criticalFailures = []) {
5651
+ return {
5652
+ slideIndex,
5653
+ slideType,
5654
+ glanceTest: 0,
5655
+ glanceTestNotes: "CRITICAL FAILURE: " + reason,
5656
+ oneIdea: 0,
5657
+ oneIdeaNotes: "CRITICAL FAILURE: " + reason,
5658
+ dataInkRatio: 0,
5659
+ dataInkNotes: "CRITICAL FAILURE: " + reason,
5660
+ professionalExecution: 0,
5661
+ professionalExecutionNotes: "CRITICAL FAILURE: " + reason,
5662
+ hasCriticalFailure: true,
5663
+ criticalFailures: criticalFailures.length > 0 ? criticalFailures : [reason],
5664
+ visualImpact: 0,
5665
+ visualImpactNotes: "CRITICAL: " + reason,
5666
+ contentClarity: 0,
5667
+ contentClarityNotes: "CRITICAL: " + reason,
5668
+ professionalPolish: 0,
5669
+ professionalPolishNotes: "CRITICAL: " + reason,
5670
+ themeCoherence: 0,
5671
+ themeCoherenceNotes: "CRITICAL: " + reason,
5672
+ totalScore: 0,
5673
+ screenshotPath
5674
+ };
5675
+ }
5423
5676
  inferSlideType(slideData) {
5424
5677
  const classList = slideData.classList || [];
5425
5678
  for (const cls of classList) {
@@ -5531,34 +5784,44 @@ var VisualQualityEvaluator = class {
5531
5784
  evaluateContentQuality(slideScores) {
5532
5785
  let score = 25;
5533
5786
  const notes = [];
5534
- const clarityScores = slideScores.map((s) => s.contentClarity);
5535
- const avgClarity = clarityScores.reduce((a, b) => a + b, 0) / clarityScores.length;
5536
- const messagesAreClear = avgClarity >= 7;
5537
- if (!messagesAreClear) {
5538
- score -= 7;
5539
- notes.push("Messages could be clearer");
5787
+ const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
5788
+ if (criticalFailureSlides.length > 0) {
5789
+ score = Math.max(0, 25 - criticalFailureSlides.length * 8);
5790
+ notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures`);
5791
+ criticalFailureSlides.forEach((s) => {
5792
+ s.criticalFailures.forEach((f) => notes.push(` - Slide ${s.slideIndex}: ${f}`));
5793
+ });
5540
5794
  }
5541
- const lowClarity = slideScores.filter((s) => s.contentClarity < 5).length;
5542
- const appropriateDepth = lowClarity < slideScores.length * 0.2;
5795
+ const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
5796
+ const passesGlanceTest = avgGlance >= 6;
5797
+ if (!passesGlanceTest) {
5798
+ score -= 5;
5799
+ notes.push(`Glance Test: Average ${avgGlance.toFixed(1)}/10 - too text-heavy`);
5800
+ }
5801
+ const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
5802
+ const hasOneIdeaPerSlide = avgOneIdea >= 6;
5803
+ if (!hasOneIdeaPerSlide) {
5804
+ score -= 5;
5805
+ notes.push(`One Idea Rule: Average ${avgOneIdea.toFixed(1)}/10 - messages diluted`);
5806
+ }
5807
+ const messagesAreClear = avgOneIdea >= 7;
5808
+ const lowScoreSlides = slideScores.filter((s) => s.totalScore < 20).length;
5809
+ const appropriateDepth = lowScoreSlides < slideScores.length * 0.2;
5543
5810
  if (!appropriateDepth) {
5544
5811
  score -= 5;
5545
- notes.push("Some slides have content issues");
5812
+ notes.push("Some slides have quality issues");
5546
5813
  }
5547
- const overloadedSlides = slideScores.filter(
5548
- (s) => s.contentClarityNotes.includes("too long") || s.visualImpactNotes.includes("Too much")
5549
- ).length;
5814
+ const overloadedSlides = slideScores.filter((s) => s.glanceTest < 5).length;
5550
5815
  const noOverload = overloadedSlides === 0;
5551
5816
  if (!noOverload) {
5552
- score -= 5;
5553
- notes.push(`${overloadedSlides} slides have too much content`);
5817
+ score -= 3;
5818
+ notes.push(`${overloadedSlides} slides fail glance test - too dense`);
5554
5819
  }
5555
- const insightSlides = slideScores.filter(
5556
- (s) => s.visualImpact >= 7 && s.contentClarity >= 7
5557
- ).length;
5558
- const actionableInsights = insightSlides >= slideScores.length * 0.3;
5820
+ const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
5821
+ const actionableInsights = excellentSlides >= slideScores.length * 0.3;
5559
5822
  if (!actionableInsights) {
5560
- score -= 5;
5561
- notes.push("Need more high-impact insight slides");
5823
+ score -= 3;
5824
+ notes.push("Need more high-impact slides");
5562
5825
  }
5563
5826
  return {
5564
5827
  score: Math.max(0, score),
@@ -5572,30 +5835,50 @@ var VisualQualityEvaluator = class {
5572
5835
  evaluateExecutiveReadiness(slideScores) {
5573
5836
  let score = 25;
5574
5837
  const notes = [];
5575
- const avgVisual = slideScores.reduce((sum, s) => sum + s.visualImpact, 0) / slideScores.length;
5576
- const avgPolish = slideScores.reduce((sum, s) => sum + s.professionalPolish, 0) / slideScores.length;
5577
- const avgClarity = slideScores.reduce((sum, s) => sum + s.contentClarity, 0) / slideScores.length;
5838
+ const criticalFailureSlides = slideScores.filter((s) => s.hasCriticalFailure);
5839
+ if (criticalFailureSlides.length > 0) {
5840
+ score = 0;
5841
+ notes.push(`CRITICAL: ${criticalFailureSlides.length} slide(s) have critical failures - CANNOT show to executives`);
5842
+ criticalFailureSlides.forEach((s) => {
5843
+ notes.push(` - Slide ${s.slideIndex} (${s.slideType}): ${s.criticalFailures[0]}`);
5844
+ });
5845
+ return {
5846
+ score: 0,
5847
+ wouldImpress: false,
5848
+ readyForBoardroom: false,
5849
+ compelling: false,
5850
+ shareworthy: false,
5851
+ notes: notes.join(". ")
5852
+ };
5853
+ }
5854
+ const avgGlance = slideScores.reduce((sum, s) => sum + s.glanceTest, 0) / slideScores.length;
5855
+ const avgOneIdea = slideScores.reduce((sum, s) => sum + s.oneIdea, 0) / slideScores.length;
5856
+ const avgDataInk = slideScores.reduce((sum, s) => sum + s.dataInkRatio, 0) / slideScores.length;
5857
+ const avgExecution = slideScores.reduce((sum, s) => sum + s.professionalExecution, 0) / slideScores.length;
5578
5858
  const avgTotal = slideScores.reduce((sum, s) => sum + s.totalScore, 0) / slideScores.length;
5579
- const wouldImpress = avgTotal >= 26;
5859
+ const wouldImpress = avgGlance >= 7 && avgOneIdea >= 7 && avgExecution >= 7;
5580
5860
  if (!wouldImpress) {
5581
5861
  score -= 7;
5582
5862
  notes.push("Needs more visual impact to impress");
5583
5863
  }
5584
- const readyForBoardroom = avgPolish >= 6 && avgClarity >= 6;
5864
+ const readyForBoardroom = avgExecution >= 6 && avgDataInk >= 6;
5585
5865
  if (!readyForBoardroom) {
5586
5866
  score -= 7;
5587
- notes.push("Needs more polish for executive audience");
5867
+ notes.push(`Boardroom readiness: Execution ${avgExecution.toFixed(1)}/10, Data-Ink ${avgDataInk.toFixed(1)}/10`);
5588
5868
  }
5589
- const compelling = avgVisual >= 6;
5869
+ const compelling = avgGlance >= 6 && avgOneIdea >= 6;
5590
5870
  if (!compelling) {
5591
5871
  score -= 5;
5592
- notes.push("Could be more visually compelling");
5872
+ notes.push(`Compelling: Glance ${avgGlance.toFixed(1)}/10, OneIdea ${avgOneIdea.toFixed(1)}/10`);
5593
5873
  }
5594
5874
  const excellentSlides = slideScores.filter((s) => s.totalScore >= 30).length;
5595
5875
  const shareworthy = excellentSlides >= slideScores.length * 0.4;
5596
5876
  if (!shareworthy) {
5597
5877
  score -= 5;
5598
- notes.push("Less than 40% of slides are excellent");
5878
+ notes.push(`Shareworthy: ${excellentSlides}/${slideScores.length} excellent slides (need 40%)`);
5879
+ }
5880
+ if (wouldImpress && readyForBoardroom && compelling) {
5881
+ notes.push("Meets expert standards - McKinsey/TED quality");
5599
5882
  }
5600
5883
  return {
5601
5884
  score: Math.max(0, score),