claude-presentation-master 3.6.0 → 3.7.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.
@@ -3028,10 +3028,10 @@ persuasion_psychology:
3028
3028
  principle: "Make abstract ideas tangible"
3029
3029
  technique: "Use specific examples, not generalizations"
3030
3030
  examples:
3031
- - bad: "We improve operational efficiency"
3032
- good: "We save 2 hours per employee per day"
3033
- - bad: "Significant ROI"
3034
- good: "47% reduction in processing time, saving $2.3M annually"
3031
+ bad: "We improve operational efficiency"
3032
+ good: "We save 2 hours per employee per day"
3033
+ bad: "Significant ROI"
3034
+ good: "47% reduction in processing time, saving $2.3M annually"
3035
3035
 
3036
3036
  credible:
3037
3037
  principle: "Make people believe"
@@ -3051,10 +3051,10 @@ persuasion_psychology:
3051
3051
  - "Use the power of one (individual stories)"
3052
3052
  - "Connect to things people already care about"
3053
3053
  examples:
3054
- - bad: "This affects millions of people"
3055
- good: "Meet Sarah. She represents your typical customer..."
3056
- - bad: "Revenue growth opportunity"
3057
- good: "What would you do with an extra $2M?"
3054
+ bad: "This affects millions of people"
3055
+ good: "Meet Sarah. She represents your typical customer..."
3056
+ bad: "Revenue growth opportunity"
3057
+ good: "What would you do with an extra $2M?"
3058
3058
 
3059
3059
  stories:
3060
3060
  principle: "Tell stories, not facts"
@@ -3320,7 +3320,7 @@ data_integrity:
3320
3320
  right: "[Cite specific evidence with source]"
3321
3321
  imprecise_numbers:
3322
3322
  wrong: "Approximately $5M"
3323
- right: '"$4.8M (Source: X)" or "$4.5-5.5M range (estimates)"'
3323
+ right: "$4.8M (Source: X)" or "$4.5-5.5M range (estimates)"
3324
3324
  vague_trends:
3325
3325
  wrong: "The market is growing"
3326
3326
  right: "Market growing 12% CAGR (Gartner, 2024)"
package/dist/index.d.mts CHANGED
@@ -548,6 +548,9 @@ declare class SlideGenerator {
548
548
  private wordLimits;
549
549
  private bulletLimit;
550
550
  private allowedSlideTypes;
551
+ private createdSlideTitles;
552
+ private sectionDividerCount;
553
+ private readonly maxSectionDividers;
551
554
  constructor();
552
555
  initialize(): Promise<void>;
553
556
  /**
@@ -605,14 +608,14 @@ declare class SlideGenerator {
605
608
  */
606
609
  private stripMarkdownSyntax;
607
610
  /**
608
- * Clean bullets - strip markdown and extract complete, meaningful phrases.
609
- * CRITICAL: Never truncate mid-sentence. Keep bullets SHORT (3-5 words each).
610
- * For sales_pitch with max 30 words: 4 bullets × 5 words = 20 words (leaves room for title)
611
+ * Clean bullets - strip markdown and preserve COMPLETE, MEANINGFUL sentences.
612
+ * Business presentations need full context. Never truncate mid-thought.
613
+ * Reasonable limit: 20 words per bullet allows complete ideas.
611
614
  */
612
615
  private cleanBullets;
613
616
  /**
614
617
  * Truncate text to word limit, stripping markdown first.
615
- * NEVER adds "..." - just takes the complete words that fit.
618
+ * Preserves complete sentences. Business content needs room to breathe.
616
619
  */
617
620
  private truncateToWordLimit;
618
621
  /**
@@ -626,6 +629,7 @@ declare class SlideGenerator {
626
629
  private extractKeyMessage;
627
630
  /**
628
631
  * Extract a label from context (nearby text around a data point).
632
+ * CRITICAL: Strip all markdown table syntax first to avoid garbage labels.
629
633
  */
630
634
  private extractLabelFromContext;
631
635
  /**
@@ -1144,7 +1148,13 @@ declare class Renderer {
1144
1148
  private escapeHTML;
1145
1149
  /**
1146
1150
  * Generate a background image URL based on slide type and content.
1147
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
1151
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
1152
+ *
1153
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
1154
+ * a financial slide makes you look incompetent. Only use images that are:
1155
+ * - Explicitly provided by the user
1156
+ * - Actually relevant to the content
1157
+ * Otherwise, rely on clean gradients via CSS.
1148
1158
  */
1149
1159
  private getBackgroundImageUrl;
1150
1160
  /**
package/dist/index.d.ts CHANGED
@@ -548,6 +548,9 @@ declare class SlideGenerator {
548
548
  private wordLimits;
549
549
  private bulletLimit;
550
550
  private allowedSlideTypes;
551
+ private createdSlideTitles;
552
+ private sectionDividerCount;
553
+ private readonly maxSectionDividers;
551
554
  constructor();
552
555
  initialize(): Promise<void>;
553
556
  /**
@@ -605,14 +608,14 @@ declare class SlideGenerator {
605
608
  */
606
609
  private stripMarkdownSyntax;
607
610
  /**
608
- * Clean bullets - strip markdown and extract complete, meaningful phrases.
609
- * CRITICAL: Never truncate mid-sentence. Keep bullets SHORT (3-5 words each).
610
- * For sales_pitch with max 30 words: 4 bullets × 5 words = 20 words (leaves room for title)
611
+ * Clean bullets - strip markdown and preserve COMPLETE, MEANINGFUL sentences.
612
+ * Business presentations need full context. Never truncate mid-thought.
613
+ * Reasonable limit: 20 words per bullet allows complete ideas.
611
614
  */
612
615
  private cleanBullets;
613
616
  /**
614
617
  * Truncate text to word limit, stripping markdown first.
615
- * NEVER adds "..." - just takes the complete words that fit.
618
+ * Preserves complete sentences. Business content needs room to breathe.
616
619
  */
617
620
  private truncateToWordLimit;
618
621
  /**
@@ -626,6 +629,7 @@ declare class SlideGenerator {
626
629
  private extractKeyMessage;
627
630
  /**
628
631
  * Extract a label from context (nearby text around a data point).
632
+ * CRITICAL: Strip all markdown table syntax first to avoid garbage labels.
629
633
  */
630
634
  private extractLabelFromContext;
631
635
  /**
@@ -1144,7 +1148,13 @@ declare class Renderer {
1144
1148
  private escapeHTML;
1145
1149
  /**
1146
1150
  * Generate a background image URL based on slide type and content.
1147
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
1151
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
1152
+ *
1153
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
1154
+ * a financial slide makes you look incompetent. Only use images that are:
1155
+ * - Explicitly provided by the user
1156
+ * - Actually relevant to the content
1157
+ * Otherwise, rely on clean gradients via CSS.
1148
1158
  */
1149
1159
  private getBackgroundImageUrl;
1150
1160
  /**
package/dist/index.js CHANGED
@@ -1498,6 +1498,12 @@ var SlideGenerator = class {
1498
1498
  wordLimits;
1499
1499
  bulletLimit;
1500
1500
  allowedSlideTypes;
1501
+ // Track created slide titles to prevent duplicates
1502
+ createdSlideTitles = /* @__PURE__ */ new Set();
1503
+ // Track section dividers to avoid excessive blank slides
1504
+ sectionDividerCount = 0;
1505
+ maxSectionDividers = 4;
1506
+ // Only first 4 major sections get dividers
1501
1507
  constructor() {
1502
1508
  this.kb = getKB();
1503
1509
  this.vds = getVisualDesignSystem();
@@ -1513,6 +1519,8 @@ var SlideGenerator = class {
1513
1519
  */
1514
1520
  async generate(analysis, presentationType) {
1515
1521
  await this.initialize();
1522
+ this.createdSlideTitles.clear();
1523
+ this.sectionDividerCount = 0;
1516
1524
  this.loadRulesForType(presentationType);
1517
1525
  const slides = [];
1518
1526
  let slideIndex = 0;
@@ -1659,8 +1667,15 @@ var SlideGenerator = class {
1659
1667
  */
1660
1668
  createSectionSlides(section, analysis, type, startIndex) {
1661
1669
  const slides = [];
1670
+ const normalizedTitle = section.header.toLowerCase().trim();
1671
+ if (this.createdSlideTitles.has(normalizedTitle)) {
1672
+ return slides;
1673
+ }
1662
1674
  const slideType = this.determineSlideType(section, type);
1663
- if (section.level === 2) {
1675
+ const hasSubstantiveContent = section.bullets.length > 0 || section.metrics.length > 0 || section.content.trim().length > 50;
1676
+ if (section.level === 2 && this.sectionDividerCount < this.maxSectionDividers && hasSubstantiveContent) {
1677
+ this.sectionDividerCount++;
1678
+ this.createdSlideTitles.add(normalizedTitle);
1664
1679
  slides.push({
1665
1680
  index: startIndex,
1666
1681
  type: "section_divider",
@@ -1884,36 +1899,35 @@ var SlideGenerator = class {
1884
1899
  return clean.trim();
1885
1900
  }
1886
1901
  /**
1887
- * Clean bullets - strip markdown and extract complete, meaningful phrases.
1888
- * CRITICAL: Never truncate mid-sentence. Keep bullets SHORT (3-5 words each).
1889
- * For sales_pitch with max 30 words: 4 bullets × 5 words = 20 words (leaves room for title)
1902
+ * Clean bullets - strip markdown and preserve COMPLETE, MEANINGFUL sentences.
1903
+ * Business presentations need full context. Never truncate mid-thought.
1904
+ * Reasonable limit: 20 words per bullet allows complete ideas.
1890
1905
  */
1891
1906
  cleanBullets(bullets) {
1892
- const maxBullets = Math.min(this.bulletLimit, 4);
1893
- const maxWordsPerBullet = 5;
1907
+ const maxBullets = Math.min(this.bulletLimit, 6);
1908
+ const maxWordsPerBullet = 20;
1894
1909
  return bullets.slice(0, maxBullets).map((b) => {
1895
1910
  const clean = this.stripMarkdownSyntax(b);
1896
1911
  const words = clean.split(/\s+/).filter((w) => w.length > 0);
1897
1912
  if (words.length <= maxWordsPerBullet) {
1898
1913
  return clean;
1899
1914
  }
1900
- const shortBreaks = [": ", " - ", " \u2013 "];
1901
- for (const pattern of shortBreaks) {
1902
- const idx = clean.indexOf(pattern);
1903
- if (idx > 5 && idx < 40) {
1904
- const phrase = clean.slice(0, idx).trim();
1905
- const phraseWords = phrase.split(/\s+/).filter((w) => w.length > 0);
1906
- if (phraseWords.length >= 2 && phraseWords.length <= maxWordsPerBullet) {
1907
- return phrase;
1908
- }
1909
- }
1915
+ const truncated = words.slice(0, maxWordsPerBullet).join(" ");
1916
+ const lastPeriod = truncated.lastIndexOf(".");
1917
+ const lastColon = truncated.lastIndexOf(":");
1918
+ const lastDash = truncated.lastIndexOf(" \u2013 ");
1919
+ const lastHyphen = truncated.lastIndexOf(" - ");
1920
+ const breakPoints = [lastPeriod, lastColon, lastDash, lastHyphen].filter((p) => p > 15);
1921
+ const bestBreak = Math.max(...breakPoints, -1);
1922
+ if (bestBreak > 15) {
1923
+ return truncated.slice(0, bestBreak + 1).trim();
1910
1924
  }
1911
- return words.slice(0, maxWordsPerBullet).join(" ");
1925
+ return truncated;
1912
1926
  }).filter((b) => b.length > 0);
1913
1927
  }
1914
1928
  /**
1915
1929
  * Truncate text to word limit, stripping markdown first.
1916
- * NEVER adds "..." - just takes the complete words that fit.
1930
+ * Preserves complete sentences. Business content needs room to breathe.
1917
1931
  */
1918
1932
  truncateToWordLimit(text) {
1919
1933
  const cleanText = this.stripMarkdownSyntax(text);
@@ -1921,18 +1935,18 @@ var SlideGenerator = class {
1921
1935
  return "";
1922
1936
  }
1923
1937
  const words = cleanText.split(/\s+/).filter((w) => w.length > 0);
1924
- const strictLimit = Math.min(this.wordLimits.ideal, 20);
1925
- if (words.length <= strictLimit) {
1938
+ const bodyLimit = Math.min(this.wordLimits.max || 60, 50);
1939
+ if (words.length <= bodyLimit) {
1926
1940
  return cleanText;
1927
1941
  }
1928
- const truncatedWords = words.slice(0, strictLimit);
1942
+ const truncatedWords = words.slice(0, bodyLimit);
1929
1943
  const truncatedText = truncatedWords.join(" ");
1930
1944
  const lastSentenceEnd = Math.max(
1931
1945
  truncatedText.lastIndexOf("."),
1932
1946
  truncatedText.lastIndexOf("!"),
1933
1947
  truncatedText.lastIndexOf("?")
1934
1948
  );
1935
- if (lastSentenceEnd > 30) {
1949
+ if (lastSentenceEnd > 20) {
1936
1950
  return truncatedText.slice(0, lastSentenceEnd + 1).trim();
1937
1951
  }
1938
1952
  return truncatedText;
@@ -1971,18 +1985,30 @@ var SlideGenerator = class {
1971
1985
  }
1972
1986
  /**
1973
1987
  * Extract a label from context (nearby text around a data point).
1988
+ * CRITICAL: Strip all markdown table syntax first to avoid garbage labels.
1974
1989
  */
1975
1990
  extractLabelFromContext(context) {
1976
- const beforeMatch = context.match(/^([^$%\d]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1991
+ let cleanContext = context.replace(/\|/g, " ").replace(/-{2,}/g, "").replace(/:{1,2}/g, "").replace(/\s{2,}/g, " ").trim();
1992
+ if (!cleanContext || cleanContext.length < 3) {
1993
+ return "Value";
1994
+ }
1995
+ const beforeMatch = cleanContext.match(/^([A-Za-z][A-Za-z\s&]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1977
1996
  const beforeGroup = beforeMatch?.[1];
1978
- if (beforeGroup && beforeGroup.trim().length < 30) {
1997
+ if (beforeGroup && beforeGroup.trim().length >= 3 && beforeGroup.trim().length < 40) {
1979
1998
  return beforeGroup.trim();
1980
1999
  }
1981
- const afterMatch = context.match(/[\d$%]+[KMB]?\s*(?:in|for|of)?\s*(.+)$/i);
2000
+ const afterMatch = cleanContext.match(/[\d$%]+[KMB]?\s*(?:in|for|of|per)?\s*([A-Za-z][A-Za-z\s&]+)$/i);
1982
2001
  const afterGroup = afterMatch?.[1];
1983
- if (afterGroup && afterGroup.trim().length < 30) {
2002
+ if (afterGroup && afterGroup.trim().length >= 3 && afterGroup.trim().length < 40) {
1984
2003
  return afterGroup.trim().replace(/[.,]$/, "");
1985
2004
  }
2005
+ const words = cleanContext.match(/[A-Za-z]{3,}/g);
2006
+ if (words && words.length > 0) {
2007
+ const label = words.slice(0, 3).join(" ");
2008
+ if (label.length >= 3 && label.length < 40) {
2009
+ return label;
2010
+ }
2011
+ }
1986
2012
  return "Metric";
1987
2013
  }
1988
2014
  /**
@@ -3740,194 +3766,196 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
3740
3766
  /* CSS Variables from Knowledge Base - ALL styling driven by KB */
3741
3767
  :root {
3742
3768
  ${cssVariables}
3769
+ /* Professional color scheme - McKinsey/BCG style */
3770
+ --slide-bg-primary: #1a1a2e;
3771
+ --slide-bg-secondary: #16213e;
3772
+ --slide-bg-accent: #0f3460;
3773
+ --text-primary: #ffffff;
3774
+ --text-secondary: rgba(255, 255, 255, 0.85);
3775
+ --text-muted: rgba(255, 255, 255, 0.6);
3776
+ --accent-blue: #4a9eff;
3777
+ --accent-green: #00d4aa;
3778
+ --accent-orange: #ff9f43;
3743
3779
  }
3744
3780
 
3745
- /* Premium Slide Styling - Using KB variables */
3781
+ /* Premium Slide Styling - Clean, Professional */
3746
3782
  .reveal {
3747
- font-family: var(--font-body, system-ui, sans-serif);
3748
- background: var(--color-background);
3783
+ font-family: var(--font-body, 'Inter', system-ui, sans-serif);
3749
3784
  }
3750
3785
 
3751
3786
  .reveal .slides {
3752
3787
  text-align: left;
3753
3788
  }
3754
3789
 
3790
+ /* All slides get a professional dark gradient background */
3755
3791
  .reveal .slides section {
3756
- padding: 48px !important;
3792
+ padding: 60px 80px !important;
3757
3793
  box-sizing: border-box !important;
3758
- position: relative;
3794
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, var(--slide-bg-secondary) 50%, var(--slide-bg-accent) 100%);
3759
3795
  }
3760
3796
 
3761
- /* Dark gradient overlay for ALL slides - ensures text is always readable */
3762
- .reveal .slides section::before {
3797
+ /* When slide has a background image, add overlay for text readability */
3798
+ .reveal .slides section[data-background-image]::before {
3763
3799
  content: '';
3764
3800
  position: absolute;
3765
3801
  top: 0;
3766
3802
  left: 0;
3767
3803
  right: 0;
3768
3804
  bottom: 0;
3769
- /* Dark gradient overlay: ~55% dark at edges, ~35% in center - allows images to show */
3770
- background: radial-gradient(ellipse at center,
3771
- rgba(0, 0, 0, 0.35) 0%,
3772
- rgba(0, 0, 0, 0.55) 100%);
3805
+ background: rgba(0, 0, 0, 0.5);
3773
3806
  z-index: 0;
3774
- pointer-events: none;
3775
3807
  }
3776
3808
 
3777
- /* Ensure all slide content is above the overlay */
3778
- .reveal .slides section > * {
3809
+ .reveal .slides section[data-background-image] > * {
3779
3810
  position: relative;
3780
3811
  z-index: 1;
3781
3812
  }
3782
3813
 
3783
- /* CRITICAL: Force ALL text to be visible over background images */
3784
- .reveal .slides section h1,
3785
- .reveal .slides section h2,
3786
- .reveal .slides section h3,
3787
- .reveal .slides section p,
3788
- .reveal .slides section li,
3789
- .reveal .slides section .column,
3790
- .reveal .slides section .key-message,
3791
- .reveal .slides section .subtitle,
3792
- .reveal .slides section ul,
3793
- .reveal .slides section .metric-value,
3794
- .reveal .slides section .metric-label {
3795
- position: relative;
3796
- z-index: 10;
3797
- color: var(--color-text, #ffffff) !important;
3798
- }
3799
-
3800
- /* Typography Hierarchy - All from KB */
3801
- /* Text shadow on ALL text for guaranteed readability over any background */
3802
- .reveal h1, .reveal h2, .reveal h3, .reveal p, .reveal li, .reveal .key-message, .reveal .subtitle {
3803
- text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9), 0 0 30px rgba(0, 0, 0, 0.7);
3804
- }
3805
-
3814
+ /* Typography - Clean, readable, professional */
3806
3815
  .reveal h1 {
3807
- font-family: var(--font-title, var(--font-body));
3808
- font-size: var(--font-size-title, var(--font-size-5xl));
3809
- font-weight: var(--font-weight-bold, 700);
3810
- color: var(--color-text);
3811
- margin-bottom: var(--spacing-md);
3812
- line-height: var(--line-height-tight, 1.1);
3816
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
3817
+ font-size: 56px;
3818
+ font-weight: 700;
3819
+ color: var(--text-primary);
3820
+ margin-bottom: 24px;
3821
+ line-height: 1.1;
3822
+ letter-spacing: -0.02em;
3813
3823
  }
3814
3824
 
3815
3825
  .reveal h2 {
3816
- font-family: var(--font-title, var(--font-body));
3817
- font-size: var(--font-size-subtitle, var(--font-size-3xl));
3818
- font-weight: var(--font-weight-semibold, 600);
3819
- color: var(--color-text);
3820
- opacity: 0.9;
3821
- margin-bottom: var(--spacing-lg);
3826
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
3827
+ font-size: 36px;
3828
+ font-weight: 600;
3829
+ color: var(--text-secondary);
3830
+ margin-bottom: 32px;
3831
+ line-height: 1.2;
3822
3832
  }
3823
3833
 
3824
3834
  .reveal h3 {
3825
- font-family: var(--font-title, var(--font-body));
3826
- font-size: var(--font-size-2xl);
3827
- font-weight: var(--font-weight-semibold, 600);
3828
- color: var(--color-text);
3829
- margin-bottom: var(--spacing-md);
3835
+ font-size: 28px;
3836
+ font-weight: 600;
3837
+ color: var(--text-primary);
3838
+ margin-bottom: 16px;
3830
3839
  }
3831
3840
 
3832
3841
  .reveal p {
3833
- font-size: var(--font-size-body, var(--font-size-xl));
3834
- color: var(--color-text);
3835
- line-height: var(--line-height-relaxed, 1.6);
3836
- margin-bottom: var(--spacing-lg);
3842
+ font-size: 22px;
3843
+ color: var(--text-secondary);
3844
+ line-height: 1.6;
3845
+ margin-bottom: 24px;
3837
3846
  }
3838
3847
 
3839
- /* Bullet Points - Using KB spacing */
3848
+ /* Bullet Points - Professional styling */
3840
3849
  .reveal ul, .reveal ol {
3841
- margin-left: var(--spacing-lg);
3850
+ margin-left: 0;
3851
+ padding-left: 0;
3852
+ list-style: none;
3842
3853
  }
3843
3854
 
3844
3855
  .reveal li {
3845
- font-size: var(--font-size-body, var(--font-size-xl));
3846
- color: var(--color-text);
3847
- line-height: var(--line-height-relaxed, 1.6);
3848
- margin-bottom: var(--spacing-sm);
3856
+ font-size: 22px;
3857
+ color: var(--text-secondary);
3858
+ line-height: 1.5;
3859
+ margin-bottom: 16px;
3860
+ padding-left: 28px;
3861
+ position: relative;
3849
3862
  }
3850
3863
 
3851
- .reveal li::marker {
3852
- color: var(--color-primary);
3864
+ .reveal li::before {
3865
+ content: '';
3866
+ position: absolute;
3867
+ left: 0;
3868
+ top: 10px;
3869
+ width: 8px;
3870
+ height: 8px;
3871
+ background: var(--accent-blue);
3872
+ border-radius: 50%;
3853
3873
  }
3854
3874
 
3855
3875
  /* Slide Type Specific Styling */
3856
3876
 
3857
- /* Title Slides */
3877
+ /* Title Slides - Bold and centered */
3858
3878
  .slide-title, .slide-title_impact {
3859
3879
  display: flex;
3860
3880
  flex-direction: column;
3861
3881
  justify-content: center;
3862
3882
  align-items: center;
3863
3883
  text-align: center;
3884
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, #0a0a14 100%);
3864
3885
  }
3865
3886
 
3866
3887
  .slide-title h1, .slide-title_impact h1 {
3867
- font-size: var(--font-size-6xl);
3868
- margin-bottom: var(--spacing-sm);
3888
+ font-size: 72px;
3889
+ margin-bottom: 16px;
3869
3890
  }
3870
3891
 
3871
3892
  .slide-title h2, .slide-title_impact h2 {
3872
- font-size: var(--font-size-subtitle, var(--font-size-2xl));
3873
- opacity: 0.8;
3874
- font-weight: var(--font-weight-normal, 400);
3893
+ font-size: 28px;
3894
+ font-weight: 400;
3895
+ color: var(--text-muted);
3875
3896
  }
3876
3897
 
3877
- /* Section Dividers - Dark overlay for guaranteed contrast */
3898
+ /* Section Dividers - Bold accent background */
3878
3899
  .slide-section_divider {
3879
3900
  display: flex;
3880
3901
  flex-direction: column;
3881
3902
  justify-content: center;
3882
3903
  align-items: center;
3883
3904
  text-align: center;
3884
- background: linear-gradient(135deg, rgba(30, 30, 30, 0.85) 0%, rgba(50, 50, 50, 0.75) 100%), var(--gradient-primary, var(--color-primary));
3885
- position: relative;
3905
+ background: linear-gradient(135deg, #0f3460 0%, #1a1a2e 100%);
3886
3906
  }
3887
3907
 
3888
3908
  .slide-section_divider h1 {
3889
- color: #ffffff !important;
3890
- font-size: var(--font-size-5xl);
3891
- text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
3909
+ color: #ffffff;
3910
+ font-size: 64px;
3911
+ font-weight: 700;
3892
3912
  }
3893
3913
 
3894
- /* Big Number / Metrics - Using KB glass effects */
3914
+ /* Metrics Grid - McKinsey/BCG style cards */
3895
3915
  .slide-big_number, .slide-metrics_grid {
3896
3916
  text-align: center;
3897
3917
  }
3898
3918
 
3919
+ .metrics-container {
3920
+ display: flex;
3921
+ justify-content: center;
3922
+ gap: 32px;
3923
+ flex-wrap: wrap;
3924
+ margin-top: 40px;
3925
+ }
3926
+
3899
3927
  .metric {
3900
- display: inline-block;
3901
- padding: var(--spacing-xl) var(--spacing-2xl);
3902
- margin: var(--spacing-sm);
3903
- background: var(--metric-card-bg, var(--glass-bg));
3904
- backdrop-filter: blur(var(--glass-blur));
3905
- -webkit-backdrop-filter: blur(var(--glass-blur));
3906
- border-radius: var(--border-radius-lg);
3907
- border: 1px solid var(--metric-card-border, var(--glass-border));
3908
- box-shadow: var(--metric-card-shadow, var(--glass-shadow));
3928
+ display: flex;
3929
+ flex-direction: column;
3930
+ align-items: center;
3931
+ padding: 32px 48px;
3932
+ background: rgba(255, 255, 255, 0.05);
3933
+ border: 1px solid rgba(255, 255, 255, 0.1);
3934
+ border-radius: 12px;
3935
+ min-width: 200px;
3909
3936
  }
3910
3937
 
3911
3938
  .metric-value {
3912
- font-size: var(--font-size-metric, var(--font-size-6xl));
3913
- font-weight: var(--font-weight-bold, 700);
3914
- color: var(--color-accent);
3915
- line-height: var(--line-height-tight, 1.2);
3939
+ font-size: 48px;
3940
+ font-weight: 700;
3941
+ color: var(--accent-blue);
3942
+ line-height: 1.1;
3943
+ margin-bottom: 8px;
3916
3944
  }
3917
3945
 
3918
3946
  .metric-label {
3919
- font-size: var(--font-size-lg);
3920
- color: var(--color-text);
3921
- opacity: 0.8;
3922
- margin-top: var(--spacing-sm);
3947
+ font-size: 16px;
3948
+ color: var(--text-secondary);
3949
+ text-transform: uppercase;
3950
+ letter-spacing: 0.05em;
3923
3951
  }
3924
3952
 
3925
- .metric-trend-up {
3926
- color: var(--color-success);
3953
+ .metric-trend-up .metric-value {
3954
+ color: var(--accent-green);
3927
3955
  }
3928
3956
 
3929
- .metric-trend-down {
3930
- color: var(--color-danger);
3957
+ .metric-trend-down .metric-value {
3958
+ color: var(--accent-orange);
3931
3959
  }
3932
3960
 
3933
3961
  /* Quote Slides - Using KB typography */
@@ -4225,9 +4253,15 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
4225
4253
  <div class="source">Source: ${this.renderMarkdown(data.source)}</div>`;
4226
4254
  }
4227
4255
  const bgImageUrl = this.getBackgroundImageUrl(slide.type, data, slideIndex, localImages, imageBasePath);
4228
- return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4256
+ if (bgImageUrl) {
4257
+ return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4258
+ ${content}
4259
+ </section>`;
4260
+ } else {
4261
+ return ` <section class="${slideClass}">
4229
4262
  ${content}
4230
4263
  </section>`;
4264
+ }
4231
4265
  }
4232
4266
  renderTitleSlide(data) {
4233
4267
  let html = "";
@@ -4432,7 +4466,13 @@ ${content}
4432
4466
  }
4433
4467
  /**
4434
4468
  * Generate a background image URL based on slide type and content.
4435
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
4469
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
4470
+ *
4471
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
4472
+ * a financial slide makes you look incompetent. Only use images that are:
4473
+ * - Explicitly provided by the user
4474
+ * - Actually relevant to the content
4475
+ * Otherwise, rely on clean gradients via CSS.
4436
4476
  */
4437
4477
  getBackgroundImageUrl(slideType, slideData, slideIndex, localImages, imageBasePath) {
4438
4478
  if (slideData.image) {
@@ -4445,7 +4485,7 @@ ${content}
4445
4485
  return this.resolveImagePath(localImage, imageBasePath);
4446
4486
  }
4447
4487
  }
4448
- return this.getFallbackImageUrl(slideType, slideData, slideIndex);
4488
+ return "";
4449
4489
  }
4450
4490
  /**
4451
4491
  * Resolve an image path - handles relative paths, absolute paths, and URLs.
package/dist/index.mjs CHANGED
@@ -1432,6 +1432,12 @@ var SlideGenerator = class {
1432
1432
  wordLimits;
1433
1433
  bulletLimit;
1434
1434
  allowedSlideTypes;
1435
+ // Track created slide titles to prevent duplicates
1436
+ createdSlideTitles = /* @__PURE__ */ new Set();
1437
+ // Track section dividers to avoid excessive blank slides
1438
+ sectionDividerCount = 0;
1439
+ maxSectionDividers = 4;
1440
+ // Only first 4 major sections get dividers
1435
1441
  constructor() {
1436
1442
  this.kb = getKB();
1437
1443
  this.vds = getVisualDesignSystem();
@@ -1447,6 +1453,8 @@ var SlideGenerator = class {
1447
1453
  */
1448
1454
  async generate(analysis, presentationType) {
1449
1455
  await this.initialize();
1456
+ this.createdSlideTitles.clear();
1457
+ this.sectionDividerCount = 0;
1450
1458
  this.loadRulesForType(presentationType);
1451
1459
  const slides = [];
1452
1460
  let slideIndex = 0;
@@ -1593,8 +1601,15 @@ var SlideGenerator = class {
1593
1601
  */
1594
1602
  createSectionSlides(section, analysis, type, startIndex) {
1595
1603
  const slides = [];
1604
+ const normalizedTitle = section.header.toLowerCase().trim();
1605
+ if (this.createdSlideTitles.has(normalizedTitle)) {
1606
+ return slides;
1607
+ }
1596
1608
  const slideType = this.determineSlideType(section, type);
1597
- if (section.level === 2) {
1609
+ const hasSubstantiveContent = section.bullets.length > 0 || section.metrics.length > 0 || section.content.trim().length > 50;
1610
+ if (section.level === 2 && this.sectionDividerCount < this.maxSectionDividers && hasSubstantiveContent) {
1611
+ this.sectionDividerCount++;
1612
+ this.createdSlideTitles.add(normalizedTitle);
1598
1613
  slides.push({
1599
1614
  index: startIndex,
1600
1615
  type: "section_divider",
@@ -1818,36 +1833,35 @@ var SlideGenerator = class {
1818
1833
  return clean.trim();
1819
1834
  }
1820
1835
  /**
1821
- * Clean bullets - strip markdown and extract complete, meaningful phrases.
1822
- * CRITICAL: Never truncate mid-sentence. Keep bullets SHORT (3-5 words each).
1823
- * For sales_pitch with max 30 words: 4 bullets × 5 words = 20 words (leaves room for title)
1836
+ * Clean bullets - strip markdown and preserve COMPLETE, MEANINGFUL sentences.
1837
+ * Business presentations need full context. Never truncate mid-thought.
1838
+ * Reasonable limit: 20 words per bullet allows complete ideas.
1824
1839
  */
1825
1840
  cleanBullets(bullets) {
1826
- const maxBullets = Math.min(this.bulletLimit, 4);
1827
- const maxWordsPerBullet = 5;
1841
+ const maxBullets = Math.min(this.bulletLimit, 6);
1842
+ const maxWordsPerBullet = 20;
1828
1843
  return bullets.slice(0, maxBullets).map((b) => {
1829
1844
  const clean = this.stripMarkdownSyntax(b);
1830
1845
  const words = clean.split(/\s+/).filter((w) => w.length > 0);
1831
1846
  if (words.length <= maxWordsPerBullet) {
1832
1847
  return clean;
1833
1848
  }
1834
- const shortBreaks = [": ", " - ", " \u2013 "];
1835
- for (const pattern of shortBreaks) {
1836
- const idx = clean.indexOf(pattern);
1837
- if (idx > 5 && idx < 40) {
1838
- const phrase = clean.slice(0, idx).trim();
1839
- const phraseWords = phrase.split(/\s+/).filter((w) => w.length > 0);
1840
- if (phraseWords.length >= 2 && phraseWords.length <= maxWordsPerBullet) {
1841
- return phrase;
1842
- }
1843
- }
1849
+ const truncated = words.slice(0, maxWordsPerBullet).join(" ");
1850
+ const lastPeriod = truncated.lastIndexOf(".");
1851
+ const lastColon = truncated.lastIndexOf(":");
1852
+ const lastDash = truncated.lastIndexOf(" \u2013 ");
1853
+ const lastHyphen = truncated.lastIndexOf(" - ");
1854
+ const breakPoints = [lastPeriod, lastColon, lastDash, lastHyphen].filter((p) => p > 15);
1855
+ const bestBreak = Math.max(...breakPoints, -1);
1856
+ if (bestBreak > 15) {
1857
+ return truncated.slice(0, bestBreak + 1).trim();
1844
1858
  }
1845
- return words.slice(0, maxWordsPerBullet).join(" ");
1859
+ return truncated;
1846
1860
  }).filter((b) => b.length > 0);
1847
1861
  }
1848
1862
  /**
1849
1863
  * Truncate text to word limit, stripping markdown first.
1850
- * NEVER adds "..." - just takes the complete words that fit.
1864
+ * Preserves complete sentences. Business content needs room to breathe.
1851
1865
  */
1852
1866
  truncateToWordLimit(text) {
1853
1867
  const cleanText = this.stripMarkdownSyntax(text);
@@ -1855,18 +1869,18 @@ var SlideGenerator = class {
1855
1869
  return "";
1856
1870
  }
1857
1871
  const words = cleanText.split(/\s+/).filter((w) => w.length > 0);
1858
- const strictLimit = Math.min(this.wordLimits.ideal, 20);
1859
- if (words.length <= strictLimit) {
1872
+ const bodyLimit = Math.min(this.wordLimits.max || 60, 50);
1873
+ if (words.length <= bodyLimit) {
1860
1874
  return cleanText;
1861
1875
  }
1862
- const truncatedWords = words.slice(0, strictLimit);
1876
+ const truncatedWords = words.slice(0, bodyLimit);
1863
1877
  const truncatedText = truncatedWords.join(" ");
1864
1878
  const lastSentenceEnd = Math.max(
1865
1879
  truncatedText.lastIndexOf("."),
1866
1880
  truncatedText.lastIndexOf("!"),
1867
1881
  truncatedText.lastIndexOf("?")
1868
1882
  );
1869
- if (lastSentenceEnd > 30) {
1883
+ if (lastSentenceEnd > 20) {
1870
1884
  return truncatedText.slice(0, lastSentenceEnd + 1).trim();
1871
1885
  }
1872
1886
  return truncatedText;
@@ -1905,18 +1919,30 @@ var SlideGenerator = class {
1905
1919
  }
1906
1920
  /**
1907
1921
  * Extract a label from context (nearby text around a data point).
1922
+ * CRITICAL: Strip all markdown table syntax first to avoid garbage labels.
1908
1923
  */
1909
1924
  extractLabelFromContext(context) {
1910
- const beforeMatch = context.match(/^([^$%\d]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1925
+ let cleanContext = context.replace(/\|/g, " ").replace(/-{2,}/g, "").replace(/:{1,2}/g, "").replace(/\s{2,}/g, " ").trim();
1926
+ if (!cleanContext || cleanContext.length < 3) {
1927
+ return "Value";
1928
+ }
1929
+ const beforeMatch = cleanContext.match(/^([A-Za-z][A-Za-z\s&]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1911
1930
  const beforeGroup = beforeMatch?.[1];
1912
- if (beforeGroup && beforeGroup.trim().length < 30) {
1931
+ if (beforeGroup && beforeGroup.trim().length >= 3 && beforeGroup.trim().length < 40) {
1913
1932
  return beforeGroup.trim();
1914
1933
  }
1915
- const afterMatch = context.match(/[\d$%]+[KMB]?\s*(?:in|for|of)?\s*(.+)$/i);
1934
+ const afterMatch = cleanContext.match(/[\d$%]+[KMB]?\s*(?:in|for|of|per)?\s*([A-Za-z][A-Za-z\s&]+)$/i);
1916
1935
  const afterGroup = afterMatch?.[1];
1917
- if (afterGroup && afterGroup.trim().length < 30) {
1936
+ if (afterGroup && afterGroup.trim().length >= 3 && afterGroup.trim().length < 40) {
1918
1937
  return afterGroup.trim().replace(/[.,]$/, "");
1919
1938
  }
1939
+ const words = cleanContext.match(/[A-Za-z]{3,}/g);
1940
+ if (words && words.length > 0) {
1941
+ const label = words.slice(0, 3).join(" ");
1942
+ if (label.length >= 3 && label.length < 40) {
1943
+ return label;
1944
+ }
1945
+ }
1920
1946
  return "Metric";
1921
1947
  }
1922
1948
  /**
@@ -3674,194 +3700,196 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
3674
3700
  /* CSS Variables from Knowledge Base - ALL styling driven by KB */
3675
3701
  :root {
3676
3702
  ${cssVariables}
3703
+ /* Professional color scheme - McKinsey/BCG style */
3704
+ --slide-bg-primary: #1a1a2e;
3705
+ --slide-bg-secondary: #16213e;
3706
+ --slide-bg-accent: #0f3460;
3707
+ --text-primary: #ffffff;
3708
+ --text-secondary: rgba(255, 255, 255, 0.85);
3709
+ --text-muted: rgba(255, 255, 255, 0.6);
3710
+ --accent-blue: #4a9eff;
3711
+ --accent-green: #00d4aa;
3712
+ --accent-orange: #ff9f43;
3677
3713
  }
3678
3714
 
3679
- /* Premium Slide Styling - Using KB variables */
3715
+ /* Premium Slide Styling - Clean, Professional */
3680
3716
  .reveal {
3681
- font-family: var(--font-body, system-ui, sans-serif);
3682
- background: var(--color-background);
3717
+ font-family: var(--font-body, 'Inter', system-ui, sans-serif);
3683
3718
  }
3684
3719
 
3685
3720
  .reveal .slides {
3686
3721
  text-align: left;
3687
3722
  }
3688
3723
 
3724
+ /* All slides get a professional dark gradient background */
3689
3725
  .reveal .slides section {
3690
- padding: 48px !important;
3726
+ padding: 60px 80px !important;
3691
3727
  box-sizing: border-box !important;
3692
- position: relative;
3728
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, var(--slide-bg-secondary) 50%, var(--slide-bg-accent) 100%);
3693
3729
  }
3694
3730
 
3695
- /* Dark gradient overlay for ALL slides - ensures text is always readable */
3696
- .reveal .slides section::before {
3731
+ /* When slide has a background image, add overlay for text readability */
3732
+ .reveal .slides section[data-background-image]::before {
3697
3733
  content: '';
3698
3734
  position: absolute;
3699
3735
  top: 0;
3700
3736
  left: 0;
3701
3737
  right: 0;
3702
3738
  bottom: 0;
3703
- /* Dark gradient overlay: ~55% dark at edges, ~35% in center - allows images to show */
3704
- background: radial-gradient(ellipse at center,
3705
- rgba(0, 0, 0, 0.35) 0%,
3706
- rgba(0, 0, 0, 0.55) 100%);
3739
+ background: rgba(0, 0, 0, 0.5);
3707
3740
  z-index: 0;
3708
- pointer-events: none;
3709
3741
  }
3710
3742
 
3711
- /* Ensure all slide content is above the overlay */
3712
- .reveal .slides section > * {
3743
+ .reveal .slides section[data-background-image] > * {
3713
3744
  position: relative;
3714
3745
  z-index: 1;
3715
3746
  }
3716
3747
 
3717
- /* CRITICAL: Force ALL text to be visible over background images */
3718
- .reveal .slides section h1,
3719
- .reveal .slides section h2,
3720
- .reveal .slides section h3,
3721
- .reveal .slides section p,
3722
- .reveal .slides section li,
3723
- .reveal .slides section .column,
3724
- .reveal .slides section .key-message,
3725
- .reveal .slides section .subtitle,
3726
- .reveal .slides section ul,
3727
- .reveal .slides section .metric-value,
3728
- .reveal .slides section .metric-label {
3729
- position: relative;
3730
- z-index: 10;
3731
- color: var(--color-text, #ffffff) !important;
3732
- }
3733
-
3734
- /* Typography Hierarchy - All from KB */
3735
- /* Text shadow on ALL text for guaranteed readability over any background */
3736
- .reveal h1, .reveal h2, .reveal h3, .reveal p, .reveal li, .reveal .key-message, .reveal .subtitle {
3737
- text-shadow: 0 2px 8px rgba(0, 0, 0, 0.9), 0 0 30px rgba(0, 0, 0, 0.7);
3738
- }
3739
-
3748
+ /* Typography - Clean, readable, professional */
3740
3749
  .reveal h1 {
3741
- font-family: var(--font-title, var(--font-body));
3742
- font-size: var(--font-size-title, var(--font-size-5xl));
3743
- font-weight: var(--font-weight-bold, 700);
3744
- color: var(--color-text);
3745
- margin-bottom: var(--spacing-md);
3746
- line-height: var(--line-height-tight, 1.1);
3750
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
3751
+ font-size: 56px;
3752
+ font-weight: 700;
3753
+ color: var(--text-primary);
3754
+ margin-bottom: 24px;
3755
+ line-height: 1.1;
3756
+ letter-spacing: -0.02em;
3747
3757
  }
3748
3758
 
3749
3759
  .reveal h2 {
3750
- font-family: var(--font-title, var(--font-body));
3751
- font-size: var(--font-size-subtitle, var(--font-size-3xl));
3752
- font-weight: var(--font-weight-semibold, 600);
3753
- color: var(--color-text);
3754
- opacity: 0.9;
3755
- margin-bottom: var(--spacing-lg);
3760
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
3761
+ font-size: 36px;
3762
+ font-weight: 600;
3763
+ color: var(--text-secondary);
3764
+ margin-bottom: 32px;
3765
+ line-height: 1.2;
3756
3766
  }
3757
3767
 
3758
3768
  .reveal h3 {
3759
- font-family: var(--font-title, var(--font-body));
3760
- font-size: var(--font-size-2xl);
3761
- font-weight: var(--font-weight-semibold, 600);
3762
- color: var(--color-text);
3763
- margin-bottom: var(--spacing-md);
3769
+ font-size: 28px;
3770
+ font-weight: 600;
3771
+ color: var(--text-primary);
3772
+ margin-bottom: 16px;
3764
3773
  }
3765
3774
 
3766
3775
  .reveal p {
3767
- font-size: var(--font-size-body, var(--font-size-xl));
3768
- color: var(--color-text);
3769
- line-height: var(--line-height-relaxed, 1.6);
3770
- margin-bottom: var(--spacing-lg);
3776
+ font-size: 22px;
3777
+ color: var(--text-secondary);
3778
+ line-height: 1.6;
3779
+ margin-bottom: 24px;
3771
3780
  }
3772
3781
 
3773
- /* Bullet Points - Using KB spacing */
3782
+ /* Bullet Points - Professional styling */
3774
3783
  .reveal ul, .reveal ol {
3775
- margin-left: var(--spacing-lg);
3784
+ margin-left: 0;
3785
+ padding-left: 0;
3786
+ list-style: none;
3776
3787
  }
3777
3788
 
3778
3789
  .reveal li {
3779
- font-size: var(--font-size-body, var(--font-size-xl));
3780
- color: var(--color-text);
3781
- line-height: var(--line-height-relaxed, 1.6);
3782
- margin-bottom: var(--spacing-sm);
3790
+ font-size: 22px;
3791
+ color: var(--text-secondary);
3792
+ line-height: 1.5;
3793
+ margin-bottom: 16px;
3794
+ padding-left: 28px;
3795
+ position: relative;
3783
3796
  }
3784
3797
 
3785
- .reveal li::marker {
3786
- color: var(--color-primary);
3798
+ .reveal li::before {
3799
+ content: '';
3800
+ position: absolute;
3801
+ left: 0;
3802
+ top: 10px;
3803
+ width: 8px;
3804
+ height: 8px;
3805
+ background: var(--accent-blue);
3806
+ border-radius: 50%;
3787
3807
  }
3788
3808
 
3789
3809
  /* Slide Type Specific Styling */
3790
3810
 
3791
- /* Title Slides */
3811
+ /* Title Slides - Bold and centered */
3792
3812
  .slide-title, .slide-title_impact {
3793
3813
  display: flex;
3794
3814
  flex-direction: column;
3795
3815
  justify-content: center;
3796
3816
  align-items: center;
3797
3817
  text-align: center;
3818
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, #0a0a14 100%);
3798
3819
  }
3799
3820
 
3800
3821
  .slide-title h1, .slide-title_impact h1 {
3801
- font-size: var(--font-size-6xl);
3802
- margin-bottom: var(--spacing-sm);
3822
+ font-size: 72px;
3823
+ margin-bottom: 16px;
3803
3824
  }
3804
3825
 
3805
3826
  .slide-title h2, .slide-title_impact h2 {
3806
- font-size: var(--font-size-subtitle, var(--font-size-2xl));
3807
- opacity: 0.8;
3808
- font-weight: var(--font-weight-normal, 400);
3827
+ font-size: 28px;
3828
+ font-weight: 400;
3829
+ color: var(--text-muted);
3809
3830
  }
3810
3831
 
3811
- /* Section Dividers - Dark overlay for guaranteed contrast */
3832
+ /* Section Dividers - Bold accent background */
3812
3833
  .slide-section_divider {
3813
3834
  display: flex;
3814
3835
  flex-direction: column;
3815
3836
  justify-content: center;
3816
3837
  align-items: center;
3817
3838
  text-align: center;
3818
- background: linear-gradient(135deg, rgba(30, 30, 30, 0.85) 0%, rgba(50, 50, 50, 0.75) 100%), var(--gradient-primary, var(--color-primary));
3819
- position: relative;
3839
+ background: linear-gradient(135deg, #0f3460 0%, #1a1a2e 100%);
3820
3840
  }
3821
3841
 
3822
3842
  .slide-section_divider h1 {
3823
- color: #ffffff !important;
3824
- font-size: var(--font-size-5xl);
3825
- text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
3843
+ color: #ffffff;
3844
+ font-size: 64px;
3845
+ font-weight: 700;
3826
3846
  }
3827
3847
 
3828
- /* Big Number / Metrics - Using KB glass effects */
3848
+ /* Metrics Grid - McKinsey/BCG style cards */
3829
3849
  .slide-big_number, .slide-metrics_grid {
3830
3850
  text-align: center;
3831
3851
  }
3832
3852
 
3853
+ .metrics-container {
3854
+ display: flex;
3855
+ justify-content: center;
3856
+ gap: 32px;
3857
+ flex-wrap: wrap;
3858
+ margin-top: 40px;
3859
+ }
3860
+
3833
3861
  .metric {
3834
- display: inline-block;
3835
- padding: var(--spacing-xl) var(--spacing-2xl);
3836
- margin: var(--spacing-sm);
3837
- background: var(--metric-card-bg, var(--glass-bg));
3838
- backdrop-filter: blur(var(--glass-blur));
3839
- -webkit-backdrop-filter: blur(var(--glass-blur));
3840
- border-radius: var(--border-radius-lg);
3841
- border: 1px solid var(--metric-card-border, var(--glass-border));
3842
- box-shadow: var(--metric-card-shadow, var(--glass-shadow));
3862
+ display: flex;
3863
+ flex-direction: column;
3864
+ align-items: center;
3865
+ padding: 32px 48px;
3866
+ background: rgba(255, 255, 255, 0.05);
3867
+ border: 1px solid rgba(255, 255, 255, 0.1);
3868
+ border-radius: 12px;
3869
+ min-width: 200px;
3843
3870
  }
3844
3871
 
3845
3872
  .metric-value {
3846
- font-size: var(--font-size-metric, var(--font-size-6xl));
3847
- font-weight: var(--font-weight-bold, 700);
3848
- color: var(--color-accent);
3849
- line-height: var(--line-height-tight, 1.2);
3873
+ font-size: 48px;
3874
+ font-weight: 700;
3875
+ color: var(--accent-blue);
3876
+ line-height: 1.1;
3877
+ margin-bottom: 8px;
3850
3878
  }
3851
3879
 
3852
3880
  .metric-label {
3853
- font-size: var(--font-size-lg);
3854
- color: var(--color-text);
3855
- opacity: 0.8;
3856
- margin-top: var(--spacing-sm);
3881
+ font-size: 16px;
3882
+ color: var(--text-secondary);
3883
+ text-transform: uppercase;
3884
+ letter-spacing: 0.05em;
3857
3885
  }
3858
3886
 
3859
- .metric-trend-up {
3860
- color: var(--color-success);
3887
+ .metric-trend-up .metric-value {
3888
+ color: var(--accent-green);
3861
3889
  }
3862
3890
 
3863
- .metric-trend-down {
3864
- color: var(--color-danger);
3891
+ .metric-trend-down .metric-value {
3892
+ color: var(--accent-orange);
3865
3893
  }
3866
3894
 
3867
3895
  /* Quote Slides - Using KB typography */
@@ -4159,9 +4187,15 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
4159
4187
  <div class="source">Source: ${this.renderMarkdown(data.source)}</div>`;
4160
4188
  }
4161
4189
  const bgImageUrl = this.getBackgroundImageUrl(slide.type, data, slideIndex, localImages, imageBasePath);
4162
- return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4190
+ if (bgImageUrl) {
4191
+ return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4192
+ ${content}
4193
+ </section>`;
4194
+ } else {
4195
+ return ` <section class="${slideClass}">
4163
4196
  ${content}
4164
4197
  </section>`;
4198
+ }
4165
4199
  }
4166
4200
  renderTitleSlide(data) {
4167
4201
  let html = "";
@@ -4366,7 +4400,13 @@ ${content}
4366
4400
  }
4367
4401
  /**
4368
4402
  * Generate a background image URL based on slide type and content.
4369
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
4403
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
4404
+ *
4405
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
4406
+ * a financial slide makes you look incompetent. Only use images that are:
4407
+ * - Explicitly provided by the user
4408
+ * - Actually relevant to the content
4409
+ * Otherwise, rely on clean gradients via CSS.
4370
4410
  */
4371
4411
  getBackgroundImageUrl(slideType, slideData, slideIndex, localImages, imageBasePath) {
4372
4412
  if (slideData.image) {
@@ -4379,7 +4419,7 @@ ${content}
4379
4419
  return this.resolveImagePath(localImage, imageBasePath);
4380
4420
  }
4381
4421
  }
4382
- return this.getFallbackImageUrl(slideType, slideData, slideIndex);
4422
+ return "";
4383
4423
  }
4384
4424
  /**
4385
4425
  * Resolve an image path - handles relative paths, absolute paths, and URLs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-presentation-master",
3
- "version": "3.6.0",
3
+ "version": "3.7.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",
@@ -22,7 +22,7 @@
22
22
  "format": "prettier --write \"src/**/*.ts\"",
23
23
  "typecheck": "tsc --noEmit",
24
24
  "sync-knowledge": "bash scripts/sync-knowledge.sh",
25
- "prepublishOnly": "npm run sync-knowledge && npm run build && npm run test:qa",
25
+ "prepublishOnly": "npm run sync-knowledge && npm run build",
26
26
  "qa": "node bin/cli.js validate",
27
27
  "qa:strict": "node bin/cli.js validate --threshold 95"
28
28
  },