claude-presentation-master 8.2.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1067,6 +1067,8 @@ declare class ContentAnalyzer {
1067
1067
  /**
1068
1068
  * Extract SCQA structure (Barbara Minto)
1069
1069
  * CRITICAL: Answer must be clean prose, NOT bullet points
1070
+ * @param text - The content text to analyze
1071
+ * @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
1070
1072
  */
1071
1073
  private extractSCQA;
1072
1074
  /**
package/dist/index.d.ts CHANGED
@@ -1067,6 +1067,8 @@ declare class ContentAnalyzer {
1067
1067
  /**
1068
1068
  * Extract SCQA structure (Barbara Minto)
1069
1069
  * CRITICAL: Answer must be clean prose, NOT bullet points
1070
+ * @param text - The content text to analyze
1071
+ * @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
1070
1072
  */
1071
1073
  private extractSCQA;
1072
1074
  /**
package/dist/index.js CHANGED
@@ -1186,7 +1186,7 @@ var ContentAnalyzer = class {
1186
1186
  logger.step(`Detected type: ${detectedType}`);
1187
1187
  const sections = this.extractSections(text);
1188
1188
  logger.step(`Found ${sections.length} sections`);
1189
- const scqa = this.extractSCQA(text);
1189
+ const scqa = this.extractSCQA(text, subtitle);
1190
1190
  const sparkline = this.extractSparkline(text);
1191
1191
  const keyMessages = this.extractKeyMessages(text);
1192
1192
  const dataPoints = this.extractDataPoints(text);
@@ -1453,15 +1453,24 @@ var ContentAnalyzer = class {
1453
1453
  /**
1454
1454
  * Extract SCQA structure (Barbara Minto)
1455
1455
  * CRITICAL: Answer must be clean prose, NOT bullet points
1456
+ * @param text - The content text to analyze
1457
+ * @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
1456
1458
  */
1457
- extractSCQA(text) {
1459
+ extractSCQA(text, subtitle) {
1458
1460
  const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
1461
+ const normalizedSubtitle = (subtitle || "").toLowerCase().trim();
1462
+ const isSubtitle = (para) => {
1463
+ if (!normalizedSubtitle) return false;
1464
+ const normalized = para.toLowerCase().trim();
1465
+ return normalized === normalizedSubtitle || normalized.includes(normalizedSubtitle) || normalizedSubtitle.includes(normalized);
1466
+ };
1459
1467
  let situation = "";
1460
1468
  let complication = "";
1461
1469
  let question = "";
1462
1470
  let answer = "";
1463
1471
  for (const para of paragraphs.slice(0, 5)) {
1464
1472
  if (para.startsWith("-") || para.startsWith("#") || para.startsWith("|")) continue;
1473
+ if (isSubtitle(para)) continue;
1465
1474
  if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
1466
1475
  situation = this.extractFirstSentence(para);
1467
1476
  break;
@@ -1489,6 +1498,7 @@ var ContentAnalyzer = class {
1489
1498
  if (!situation && paragraphs.length > 0) {
1490
1499
  for (const para of paragraphs.slice(0, 5)) {
1491
1500
  if (!para.startsWith("-") && !para.startsWith("#") && para.length > 50) {
1501
+ if (isSubtitle(para)) continue;
1492
1502
  situation = this.extractFirstSentence(para);
1493
1503
  break;
1494
1504
  }
@@ -1935,7 +1945,8 @@ var ContentPatternClassifier = class {
1935
1945
  if (!match || !match[1]) return null;
1936
1946
  const value = match[1];
1937
1947
  const afterNumber = content.slice(content.indexOf(value) + value.length);
1938
- const context = afterNumber.trim().slice(0, 60).split(/[.!?]/)[0]?.trim() ?? "";
1948
+ const contextRaw = afterNumber.trim().split(/[\n\r]|-\s|[.!?]|(?=\d+%)|(?=\$\d)/)[0] || "";
1949
+ const context = contextRaw.trim().slice(0, 60);
1939
1950
  return { value, context };
1940
1951
  }
1941
1952
  /**
@@ -2632,6 +2643,16 @@ var SlideFactory = class {
2632
2643
  continue;
2633
2644
  }
2634
2645
  this.usedContent.add(contentKey);
2646
+ const normalizedHeader = section.header.toLowerCase().replace(/[^a-z0-9]/g, "");
2647
+ const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
2648
+ const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2649
+ const normalizedContent = (section.content || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2650
+ const headerMatchesTitle = normalizedHeader === normalizedTitle || normalizedTitle.includes(normalizedHeader) || normalizedHeader.includes(normalizedTitle);
2651
+ const contentMatchesSubtitle = normalizedContent && (normalizedContent === normalizedSubtitle || normalizedSubtitle.includes(normalizedContent) || normalizedContent.includes(normalizedSubtitle));
2652
+ if (headerMatchesTitle && (contentMatchesSubtitle || !section.content || section.content.length < 30)) {
2653
+ logger.warn(`Section "${section.header}" skipped - duplicates title slide`);
2654
+ continue;
2655
+ }
2635
2656
  const headerLower = section.header.toLowerCase();
2636
2657
  const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
2637
2658
  if (isCTASection) {
@@ -2811,47 +2832,69 @@ var SlideFactory = class {
2811
2832
  const scqa = analysis.scqa;
2812
2833
  const titles = this.config.scqaTitles;
2813
2834
  const minBodyLength = 20;
2835
+ const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
2836
+ const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2837
+ const duplicatesHeadline = (body) => {
2838
+ if (!body) return true;
2839
+ const normalizedBody = body.toLowerCase().replace(/[^a-z0-9]/g, "");
2840
+ if (!normalizedBody || normalizedBody.length < 10) return false;
2841
+ const titleMatch = normalizedTitle.length > 5 && (normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle));
2842
+ const subtitleMatch = normalizedSubtitle.length > 5 && (normalizedBody === normalizedSubtitle || normalizedSubtitle.includes(normalizedBody) || normalizedBody.includes(normalizedSubtitle));
2843
+ return titleMatch || subtitleMatch;
2844
+ };
2814
2845
  const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
2815
2846
  if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
2816
- this.usedContent.add("scqa-situation");
2817
- slides.push({
2818
- index: slides.length,
2819
- type: "single-statement",
2820
- data: {
2821
- title: titles.situation,
2822
- // FROM KB - not hardcoded 'Current Situation'
2823
- body: situationBody
2824
- },
2825
- classes: ["situation-slide"]
2826
- });
2847
+ if (duplicatesHeadline(situationBody)) {
2848
+ logger.warn(`SCQA Situation skipped - duplicates title/subtitle: "${situationBody.slice(0, 50)}..."`);
2849
+ } else {
2850
+ this.usedContent.add("scqa-situation");
2851
+ slides.push({
2852
+ index: slides.length,
2853
+ type: "single-statement",
2854
+ data: {
2855
+ title: titles.situation,
2856
+ // FROM KB - not hardcoded 'Current Situation'
2857
+ body: situationBody
2858
+ },
2859
+ classes: ["situation-slide"]
2860
+ });
2861
+ }
2827
2862
  }
2828
2863
  const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
2829
2864
  if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
2830
- this.usedContent.add("scqa-complication");
2831
- slides.push({
2832
- index: slides.length,
2833
- type: "single-statement",
2834
- data: {
2835
- title: titles.complication,
2836
- // FROM KB - not hardcoded 'The Challenge'
2837
- body: complicationBody
2838
- },
2839
- classes: ["complication-slide"]
2840
- });
2865
+ if (duplicatesHeadline(complicationBody)) {
2866
+ logger.warn(`SCQA Complication skipped - duplicates title/subtitle`);
2867
+ } else {
2868
+ this.usedContent.add("scqa-complication");
2869
+ slides.push({
2870
+ index: slides.length,
2871
+ type: "single-statement",
2872
+ data: {
2873
+ title: titles.complication,
2874
+ // FROM KB - not hardcoded 'The Challenge'
2875
+ body: complicationBody
2876
+ },
2877
+ classes: ["complication-slide"]
2878
+ });
2879
+ }
2841
2880
  }
2842
2881
  const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
2843
2882
  if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
2844
- this.usedContent.add("scqa-question");
2845
- slides.push({
2846
- index: slides.length,
2847
- type: "single-statement",
2848
- data: {
2849
- title: titles.question,
2850
- // FROM KB - not hardcoded 'The Question'
2851
- body: questionBody
2852
- },
2853
- classes: ["question-slide"]
2854
- });
2883
+ if (duplicatesHeadline(questionBody)) {
2884
+ logger.warn(`SCQA Question skipped - duplicates title/subtitle`);
2885
+ } else {
2886
+ this.usedContent.add("scqa-question");
2887
+ slides.push({
2888
+ index: slides.length,
2889
+ type: "single-statement",
2890
+ data: {
2891
+ title: titles.question,
2892
+ // FROM KB - not hardcoded 'The Question'
2893
+ body: questionBody
2894
+ },
2895
+ classes: ["question-slide"]
2896
+ });
2897
+ }
2855
2898
  }
2856
2899
  }
2857
2900
  addSparklineSlides(slides, analysis) {
@@ -2904,14 +2947,41 @@ var SlideFactory = class {
2904
2947
  // CONTENT SLIDES - ALL values from KB
2905
2948
  // ===========================================================================
2906
2949
  createBigNumberSlide(index, section, pattern) {
2907
- const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
2908
- const bigNumber = this.classifier.extractBigNumber(fullContent);
2950
+ let bigNumber = null;
2951
+ let sourceBullet = null;
2952
+ for (const bullet of section.bullets) {
2953
+ const extracted = this.classifier.extractBigNumber(bullet);
2954
+ if (extracted) {
2955
+ bigNumber = extracted;
2956
+ sourceBullet = bullet;
2957
+ break;
2958
+ }
2959
+ }
2960
+ if (!bigNumber) {
2961
+ bigNumber = this.classifier.extractBigNumber(section.header);
2962
+ }
2963
+ if (!bigNumber && section.content) {
2964
+ bigNumber = this.classifier.extractBigNumber(section.content);
2965
+ }
2909
2966
  const actualValue = bigNumber?.value || pattern.bigNumberValue;
2910
2967
  if (!actualValue) {
2911
2968
  logger.warn(`No number found for big-number slide, falling back to single-statement`);
2912
2969
  return this.createSingleStatementSlide(index, section);
2913
2970
  }
2914
- const contextContent = bigNumber?.context || section.content;
2971
+ let contextContent = bigNumber?.context || "";
2972
+ if (sourceBullet && !contextContent) {
2973
+ contextContent = sourceBullet.replace(actualValue, "").trim();
2974
+ }
2975
+ if (!contextContent) {
2976
+ contextContent = section.content || section.header;
2977
+ }
2978
+ let cleanTitle = section.header;
2979
+ if (actualValue && cleanTitle.includes(actualValue.replace(/[,$%]/g, ""))) {
2980
+ cleanTitle = cleanTitle.replace(new RegExp(`\\$?${actualValue.replace(/[,$%]/g, "").replace(/\./g, "\\.?")}[KMBkmb]?`, "gi"), "").replace(/\s+/g, " ").trim();
2981
+ }
2982
+ if (!cleanTitle || cleanTitle.length < 5) {
2983
+ cleanTitle = bigNumber?.context?.split(/[.!?]/)[0] || section.header;
2984
+ }
2915
2985
  const craftedContext = this.craftExpertContent(
2916
2986
  contextContent,
2917
2987
  "big_number",
@@ -2921,7 +2991,7 @@ var SlideFactory = class {
2921
2991
  index,
2922
2992
  type: "big-number",
2923
2993
  data: {
2924
- title: this.createTitle(section.header, section),
2994
+ title: cleanTitle,
2925
2995
  keyMessage: actualValue,
2926
2996
  body: craftedContext
2927
2997
  },
@@ -5668,12 +5738,15 @@ var VisualQualityEvaluator = class {
5668
5738
  glanceTest += 1;
5669
5739
  glanceNotes.push("Clean, minimal text - passes glance test easily");
5670
5740
  }
5671
- if (slideData.bullets.length > 5) {
5741
+ const isAgendaSlide = slideType === "agenda" || (slideData.classes || []).includes("agenda-slide");
5742
+ const bulletLimit = isAgendaSlide ? 8 : 5;
5743
+ const bulletWarning = isAgendaSlide ? 6 : 3;
5744
+ if (slideData.bullets.length > bulletLimit) {
5672
5745
  glanceTest -= 4;
5673
5746
  glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
5674
- } else if (slideData.bullets.length > 3) {
5675
- glanceTest -= 2;
5676
- glanceNotes.push("Multiple bullets - needs careful structuring");
5747
+ } else if (slideData.bullets.length > bulletWarning) {
5748
+ glanceTest -= isAgendaSlide ? 1 : 2;
5749
+ glanceNotes.push(isAgendaSlide ? "Agenda with many items" : "Multiple bullets - needs careful structuring");
5677
5750
  }
5678
5751
  if (slideData.hasImage || slideData.hasChart) {
5679
5752
  glanceTest += 1;
package/dist/index.mjs CHANGED
@@ -1117,7 +1117,7 @@ var ContentAnalyzer = class {
1117
1117
  logger.step(`Detected type: ${detectedType}`);
1118
1118
  const sections = this.extractSections(text);
1119
1119
  logger.step(`Found ${sections.length} sections`);
1120
- const scqa = this.extractSCQA(text);
1120
+ const scqa = this.extractSCQA(text, subtitle);
1121
1121
  const sparkline = this.extractSparkline(text);
1122
1122
  const keyMessages = this.extractKeyMessages(text);
1123
1123
  const dataPoints = this.extractDataPoints(text);
@@ -1384,15 +1384,24 @@ var ContentAnalyzer = class {
1384
1384
  /**
1385
1385
  * Extract SCQA structure (Barbara Minto)
1386
1386
  * CRITICAL: Answer must be clean prose, NOT bullet points
1387
+ * @param text - The content text to analyze
1388
+ * @param subtitle - Optional subtitle to exclude (prevents redundancy with title slide)
1387
1389
  */
1388
- extractSCQA(text) {
1390
+ extractSCQA(text, subtitle) {
1389
1391
  const paragraphs = text.split(/\n\n+/).filter((p) => p.trim());
1392
+ const normalizedSubtitle = (subtitle || "").toLowerCase().trim();
1393
+ const isSubtitle = (para) => {
1394
+ if (!normalizedSubtitle) return false;
1395
+ const normalized = para.toLowerCase().trim();
1396
+ return normalized === normalizedSubtitle || normalized.includes(normalizedSubtitle) || normalizedSubtitle.includes(normalized);
1397
+ };
1390
1398
  let situation = "";
1391
1399
  let complication = "";
1392
1400
  let question = "";
1393
1401
  let answer = "";
1394
1402
  for (const para of paragraphs.slice(0, 5)) {
1395
1403
  if (para.startsWith("-") || para.startsWith("#") || para.startsWith("|")) continue;
1404
+ if (isSubtitle(para)) continue;
1396
1405
  if (this.containsSignals(para.toLowerCase(), this.situationSignals)) {
1397
1406
  situation = this.extractFirstSentence(para);
1398
1407
  break;
@@ -1420,6 +1429,7 @@ var ContentAnalyzer = class {
1420
1429
  if (!situation && paragraphs.length > 0) {
1421
1430
  for (const para of paragraphs.slice(0, 5)) {
1422
1431
  if (!para.startsWith("-") && !para.startsWith("#") && para.length > 50) {
1432
+ if (isSubtitle(para)) continue;
1423
1433
  situation = this.extractFirstSentence(para);
1424
1434
  break;
1425
1435
  }
@@ -1866,7 +1876,8 @@ var ContentPatternClassifier = class {
1866
1876
  if (!match || !match[1]) return null;
1867
1877
  const value = match[1];
1868
1878
  const afterNumber = content.slice(content.indexOf(value) + value.length);
1869
- const context = afterNumber.trim().slice(0, 60).split(/[.!?]/)[0]?.trim() ?? "";
1879
+ const contextRaw = afterNumber.trim().split(/[\n\r]|-\s|[.!?]|(?=\d+%)|(?=\$\d)/)[0] || "";
1880
+ const context = contextRaw.trim().slice(0, 60);
1870
1881
  return { value, context };
1871
1882
  }
1872
1883
  /**
@@ -2563,6 +2574,16 @@ var SlideFactory = class {
2563
2574
  continue;
2564
2575
  }
2565
2576
  this.usedContent.add(contentKey);
2577
+ const normalizedHeader = section.header.toLowerCase().replace(/[^a-z0-9]/g, "");
2578
+ const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
2579
+ const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2580
+ const normalizedContent = (section.content || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2581
+ const headerMatchesTitle = normalizedHeader === normalizedTitle || normalizedTitle.includes(normalizedHeader) || normalizedHeader.includes(normalizedTitle);
2582
+ const contentMatchesSubtitle = normalizedContent && (normalizedContent === normalizedSubtitle || normalizedSubtitle.includes(normalizedContent) || normalizedContent.includes(normalizedSubtitle));
2583
+ if (headerMatchesTitle && (contentMatchesSubtitle || !section.content || section.content.length < 30)) {
2584
+ logger.warn(`Section "${section.header}" skipped - duplicates title slide`);
2585
+ continue;
2586
+ }
2566
2587
  const headerLower = section.header.toLowerCase();
2567
2588
  const isCTASection = headerLower.includes("call to action") || headerLower.includes("next step") || headerLower.includes("take action") || headerLower.includes("get started") || headerLower.includes("start today");
2568
2589
  if (isCTASection) {
@@ -2742,47 +2763,69 @@ var SlideFactory = class {
2742
2763
  const scqa = analysis.scqa;
2743
2764
  const titles = this.config.scqaTitles;
2744
2765
  const minBodyLength = 20;
2766
+ const normalizedTitle = analysis.title.toLowerCase().replace(/[^a-z0-9]/g, "");
2767
+ const normalizedSubtitle = (analysis.subtitle || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2768
+ const duplicatesHeadline = (body) => {
2769
+ if (!body) return true;
2770
+ const normalizedBody = body.toLowerCase().replace(/[^a-z0-9]/g, "");
2771
+ if (!normalizedBody || normalizedBody.length < 10) return false;
2772
+ const titleMatch = normalizedTitle.length > 5 && (normalizedBody === normalizedTitle || normalizedTitle.includes(normalizedBody) || normalizedBody.includes(normalizedTitle));
2773
+ const subtitleMatch = normalizedSubtitle.length > 5 && (normalizedBody === normalizedSubtitle || normalizedSubtitle.includes(normalizedBody) || normalizedBody.includes(normalizedSubtitle));
2774
+ return titleMatch || subtitleMatch;
2775
+ };
2745
2776
  const situationBody = scqa?.situation ? this.truncateText(scqa.situation, this.config.rules.wordsPerSlide.max) : "";
2746
2777
  if (situationBody.length >= minBodyLength && !this.usedContent.has("scqa-situation")) {
2747
- this.usedContent.add("scqa-situation");
2748
- slides.push({
2749
- index: slides.length,
2750
- type: "single-statement",
2751
- data: {
2752
- title: titles.situation,
2753
- // FROM KB - not hardcoded 'Current Situation'
2754
- body: situationBody
2755
- },
2756
- classes: ["situation-slide"]
2757
- });
2778
+ if (duplicatesHeadline(situationBody)) {
2779
+ logger.warn(`SCQA Situation skipped - duplicates title/subtitle: "${situationBody.slice(0, 50)}..."`);
2780
+ } else {
2781
+ this.usedContent.add("scqa-situation");
2782
+ slides.push({
2783
+ index: slides.length,
2784
+ type: "single-statement",
2785
+ data: {
2786
+ title: titles.situation,
2787
+ // FROM KB - not hardcoded 'Current Situation'
2788
+ body: situationBody
2789
+ },
2790
+ classes: ["situation-slide"]
2791
+ });
2792
+ }
2758
2793
  }
2759
2794
  const complicationBody = scqa?.complication ? this.truncateText(scqa.complication, this.config.rules.wordsPerSlide.max) : "";
2760
2795
  if (complicationBody.length >= minBodyLength && !this.usedContent.has("scqa-complication")) {
2761
- this.usedContent.add("scqa-complication");
2762
- slides.push({
2763
- index: slides.length,
2764
- type: "single-statement",
2765
- data: {
2766
- title: titles.complication,
2767
- // FROM KB - not hardcoded 'The Challenge'
2768
- body: complicationBody
2769
- },
2770
- classes: ["complication-slide"]
2771
- });
2796
+ if (duplicatesHeadline(complicationBody)) {
2797
+ logger.warn(`SCQA Complication skipped - duplicates title/subtitle`);
2798
+ } else {
2799
+ this.usedContent.add("scqa-complication");
2800
+ slides.push({
2801
+ index: slides.length,
2802
+ type: "single-statement",
2803
+ data: {
2804
+ title: titles.complication,
2805
+ // FROM KB - not hardcoded 'The Challenge'
2806
+ body: complicationBody
2807
+ },
2808
+ classes: ["complication-slide"]
2809
+ });
2810
+ }
2772
2811
  }
2773
2812
  const questionBody = scqa?.question ? this.truncateText(scqa.question, this.config.rules.wordsPerSlide.max) : "";
2774
2813
  if (questionBody.length >= minBodyLength && !this.usedContent.has("scqa-question")) {
2775
- this.usedContent.add("scqa-question");
2776
- slides.push({
2777
- index: slides.length,
2778
- type: "single-statement",
2779
- data: {
2780
- title: titles.question,
2781
- // FROM KB - not hardcoded 'The Question'
2782
- body: questionBody
2783
- },
2784
- classes: ["question-slide"]
2785
- });
2814
+ if (duplicatesHeadline(questionBody)) {
2815
+ logger.warn(`SCQA Question skipped - duplicates title/subtitle`);
2816
+ } else {
2817
+ this.usedContent.add("scqa-question");
2818
+ slides.push({
2819
+ index: slides.length,
2820
+ type: "single-statement",
2821
+ data: {
2822
+ title: titles.question,
2823
+ // FROM KB - not hardcoded 'The Question'
2824
+ body: questionBody
2825
+ },
2826
+ classes: ["question-slide"]
2827
+ });
2828
+ }
2786
2829
  }
2787
2830
  }
2788
2831
  addSparklineSlides(slides, analysis) {
@@ -2835,14 +2878,41 @@ var SlideFactory = class {
2835
2878
  // CONTENT SLIDES - ALL values from KB
2836
2879
  // ===========================================================================
2837
2880
  createBigNumberSlide(index, section, pattern) {
2838
- const fullContent = `${section.header} ${section.content} ${section.bullets.join(" ")}`;
2839
- const bigNumber = this.classifier.extractBigNumber(fullContent);
2881
+ let bigNumber = null;
2882
+ let sourceBullet = null;
2883
+ for (const bullet of section.bullets) {
2884
+ const extracted = this.classifier.extractBigNumber(bullet);
2885
+ if (extracted) {
2886
+ bigNumber = extracted;
2887
+ sourceBullet = bullet;
2888
+ break;
2889
+ }
2890
+ }
2891
+ if (!bigNumber) {
2892
+ bigNumber = this.classifier.extractBigNumber(section.header);
2893
+ }
2894
+ if (!bigNumber && section.content) {
2895
+ bigNumber = this.classifier.extractBigNumber(section.content);
2896
+ }
2840
2897
  const actualValue = bigNumber?.value || pattern.bigNumberValue;
2841
2898
  if (!actualValue) {
2842
2899
  logger.warn(`No number found for big-number slide, falling back to single-statement`);
2843
2900
  return this.createSingleStatementSlide(index, section);
2844
2901
  }
2845
- const contextContent = bigNumber?.context || section.content;
2902
+ let contextContent = bigNumber?.context || "";
2903
+ if (sourceBullet && !contextContent) {
2904
+ contextContent = sourceBullet.replace(actualValue, "").trim();
2905
+ }
2906
+ if (!contextContent) {
2907
+ contextContent = section.content || section.header;
2908
+ }
2909
+ let cleanTitle = section.header;
2910
+ if (actualValue && cleanTitle.includes(actualValue.replace(/[,$%]/g, ""))) {
2911
+ cleanTitle = cleanTitle.replace(new RegExp(`\\$?${actualValue.replace(/[,$%]/g, "").replace(/\./g, "\\.?")}[KMBkmb]?`, "gi"), "").replace(/\s+/g, " ").trim();
2912
+ }
2913
+ if (!cleanTitle || cleanTitle.length < 5) {
2914
+ cleanTitle = bigNumber?.context?.split(/[.!?]/)[0] || section.header;
2915
+ }
2846
2916
  const craftedContext = this.craftExpertContent(
2847
2917
  contextContent,
2848
2918
  "big_number",
@@ -2852,7 +2922,7 @@ var SlideFactory = class {
2852
2922
  index,
2853
2923
  type: "big-number",
2854
2924
  data: {
2855
- title: this.createTitle(section.header, section),
2925
+ title: cleanTitle,
2856
2926
  keyMessage: actualValue,
2857
2927
  body: craftedContext
2858
2928
  },
@@ -5599,12 +5669,15 @@ var VisualQualityEvaluator = class {
5599
5669
  glanceTest += 1;
5600
5670
  glanceNotes.push("Clean, minimal text - passes glance test easily");
5601
5671
  }
5602
- if (slideData.bullets.length > 5) {
5672
+ const isAgendaSlide = slideType === "agenda" || (slideData.classes || []).includes("agenda-slide");
5673
+ const bulletLimit = isAgendaSlide ? 8 : 5;
5674
+ const bulletWarning = isAgendaSlide ? 6 : 3;
5675
+ if (slideData.bullets.length > bulletLimit) {
5603
5676
  glanceTest -= 4;
5604
5677
  glanceNotes.push("Too many bullets - cannot scan in 3 seconds");
5605
- } else if (slideData.bullets.length > 3) {
5606
- glanceTest -= 2;
5607
- glanceNotes.push("Multiple bullets - needs careful structuring");
5678
+ } else if (slideData.bullets.length > bulletWarning) {
5679
+ glanceTest -= isAgendaSlide ? 1 : 2;
5680
+ glanceNotes.push(isAgendaSlide ? "Agenda with many items" : "Multiple bullets - needs careful structuring");
5608
5681
  }
5609
5682
  if (slideData.hasImage || slideData.hasChart) {
5610
5683
  glanceTest += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-presentation-master",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "description": "Generate world-class presentations using expert methodologies from Duarte, Reynolds, Gallo, and Anderson. Enforces rigorous quality standards through real visual validation.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",