claude-presentation-master 3.6.0 → 3.8.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
@@ -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
  /**
@@ -2010,6 +2036,318 @@ async function initSlideGenerator() {
2010
2036
  return generator;
2011
2037
  }
2012
2038
 
2039
+ // src/core/SlideGeneratorV2.ts
2040
+ import { marked } from "marked";
2041
+ var MODE_CONFIGS = {
2042
+ // Consulting decks: 40-80 words per slide is NORMAL
2043
+ consulting_deck: {
2044
+ maxBulletsPerSlide: 7,
2045
+ maxWordsPerSlide: 100,
2046
+ // Not 20!
2047
+ minBulletsToKeep: 3
2048
+ },
2049
+ // Keynotes: fewer words, more impact
2050
+ keynote: {
2051
+ maxBulletsPerSlide: 4,
2052
+ maxWordsPerSlide: 30,
2053
+ minBulletsToKeep: 2
2054
+ },
2055
+ // Default: consulting style
2056
+ default: {
2057
+ maxBulletsPerSlide: 6,
2058
+ maxWordsPerSlide: 80,
2059
+ minBulletsToKeep: 3
2060
+ }
2061
+ };
2062
+ var SlideGeneratorV2 = class {
2063
+ config;
2064
+ presentationType;
2065
+ constructor(presentationType = "consulting_deck") {
2066
+ this.presentationType = presentationType;
2067
+ this.config = MODE_CONFIGS[presentationType] || MODE_CONFIGS.default;
2068
+ }
2069
+ /**
2070
+ * Generate slides from markdown content.
2071
+ * Simple, correct, no garbage.
2072
+ */
2073
+ generate(markdown, title) {
2074
+ const slides = [];
2075
+ let slideIndex = 0;
2076
+ const tokens = marked.lexer(markdown);
2077
+ const detectedTitle = title || this.extractTitle(tokens);
2078
+ slides.push({
2079
+ index: slideIndex++,
2080
+ type: "title",
2081
+ title: detectedTitle,
2082
+ content: {}
2083
+ });
2084
+ const sections = this.splitIntoSections(tokens);
2085
+ for (const section of sections) {
2086
+ const sectionSlides = this.processSectionContent(section, slideIndex);
2087
+ for (const slide of sectionSlides) {
2088
+ slide.index = slideIndex++;
2089
+ slides.push(slide);
2090
+ }
2091
+ }
2092
+ const lastSlide = slides[slides.length - 1];
2093
+ if (lastSlide && lastSlide.type !== "thank_you") {
2094
+ slides.push({
2095
+ index: slideIndex++,
2096
+ type: "thank_you",
2097
+ title: "Thank You",
2098
+ content: { subtext: "Questions?" }
2099
+ });
2100
+ }
2101
+ this.validateSlides(slides);
2102
+ return slides;
2103
+ }
2104
+ /**
2105
+ * Extract title from first H1 token.
2106
+ */
2107
+ extractTitle(tokens) {
2108
+ for (const token of tokens) {
2109
+ if (token.type === "heading" && token.depth === 1) {
2110
+ return token.text;
2111
+ }
2112
+ }
2113
+ return "Presentation";
2114
+ }
2115
+ /**
2116
+ * Split tokens into sections based on H2 headers.
2117
+ */
2118
+ splitIntoSections(tokens) {
2119
+ const sections = [];
2120
+ let currentSection = null;
2121
+ for (const token of tokens) {
2122
+ if (token.type === "heading" && token.depth === 2) {
2123
+ if (currentSection) {
2124
+ sections.push(currentSection);
2125
+ }
2126
+ currentSection = {
2127
+ title: token.text,
2128
+ tokens: []
2129
+ };
2130
+ } else if (token.type === "heading" && token.depth === 1) {
2131
+ continue;
2132
+ } else if (currentSection) {
2133
+ currentSection.tokens.push(token);
2134
+ }
2135
+ }
2136
+ if (currentSection && currentSection.tokens.length > 0) {
2137
+ sections.push(currentSection);
2138
+ }
2139
+ return sections;
2140
+ }
2141
+ /**
2142
+ * Process section content into slides.
2143
+ * Tables become table slides. Lists become bullet slides.
2144
+ */
2145
+ processSectionContent(section, startIndex) {
2146
+ const slides = [];
2147
+ const hasTable = section.tokens.some((t) => t.type === "table");
2148
+ const hasList = section.tokens.some((t) => t.type === "list");
2149
+ const hasSignificantText = this.hasSignificantParagraphs(section.tokens);
2150
+ if (hasTable) {
2151
+ const tableToken = section.tokens.find((t) => t.type === "table");
2152
+ if (tableToken) {
2153
+ slides.push(this.createTableSlide(section.title, tableToken));
2154
+ }
2155
+ }
2156
+ if (hasList) {
2157
+ const listSlides = this.createBulletSlides(section.title, section.tokens);
2158
+ slides.push(...listSlides);
2159
+ }
2160
+ if (hasSignificantText && !hasList && !hasTable) {
2161
+ slides.push(this.createContentSlide(section.title, section.tokens));
2162
+ }
2163
+ if (slides.length === 0 && section.tokens.length > 0) {
2164
+ const statement = this.extractStatement(section.tokens);
2165
+ if (statement) {
2166
+ slides.push({
2167
+ index: startIndex,
2168
+ type: "statement",
2169
+ title: section.title,
2170
+ content: { statement }
2171
+ });
2172
+ }
2173
+ }
2174
+ return slides;
2175
+ }
2176
+ /**
2177
+ * Create a table slide - render the table as-is, no extraction.
2178
+ */
2179
+ createTableSlide(title, tableToken) {
2180
+ const headers = tableToken.header.map((cell) => cell.text);
2181
+ const rows = tableToken.rows.map((row) => row.map((cell) => cell.text));
2182
+ return {
2183
+ index: 0,
2184
+ type: "table",
2185
+ title,
2186
+ content: {
2187
+ table: {
2188
+ headers,
2189
+ rows
2190
+ }
2191
+ }
2192
+ };
2193
+ }
2194
+ /**
2195
+ * Create bullet slides from list content.
2196
+ * NEVER truncate bullets. Split into multiple slides if needed.
2197
+ */
2198
+ createBulletSlides(title, tokens) {
2199
+ const slides = [];
2200
+ const allBullets = [];
2201
+ for (const token of tokens) {
2202
+ if (token.type === "list") {
2203
+ for (const item of token.items) {
2204
+ const bulletText = this.getCompleteItemText(item);
2205
+ if (bulletText.trim()) {
2206
+ allBullets.push(bulletText);
2207
+ }
2208
+ }
2209
+ }
2210
+ }
2211
+ const maxBullets = this.config.maxBulletsPerSlide;
2212
+ const minBullets = this.config.minBulletsToKeep;
2213
+ if (allBullets.length <= maxBullets) {
2214
+ if (allBullets.length >= minBullets || allBullets.length === 0) {
2215
+ slides.push({
2216
+ index: 0,
2217
+ type: "bullets",
2218
+ title,
2219
+ content: { bullets: allBullets }
2220
+ });
2221
+ } else {
2222
+ slides.push({
2223
+ index: 0,
2224
+ type: "bullets",
2225
+ title,
2226
+ content: { bullets: allBullets },
2227
+ notes: "Consider combining with adjacent content"
2228
+ });
2229
+ }
2230
+ } else {
2231
+ for (let i = 0; i < allBullets.length; i += maxBullets) {
2232
+ const chunk = allBullets.slice(i, i + maxBullets);
2233
+ const isFirst = i === 0;
2234
+ const slideTitle = isFirst ? title : `${title} (continued)`;
2235
+ slides.push({
2236
+ index: 0,
2237
+ type: "bullets",
2238
+ title: slideTitle,
2239
+ content: { bullets: chunk }
2240
+ });
2241
+ }
2242
+ }
2243
+ return slides;
2244
+ }
2245
+ /**
2246
+ * Get complete text from a list item, including nested content.
2247
+ * NEVER truncate.
2248
+ */
2249
+ getCompleteItemText(item) {
2250
+ let text = "";
2251
+ for (const token of item.tokens) {
2252
+ if (token.type === "text") {
2253
+ text += token.text;
2254
+ } else if (token.type === "paragraph") {
2255
+ const paraToken = token;
2256
+ text += paraToken.text;
2257
+ } else if (token.type === "strong") {
2258
+ text += token.text;
2259
+ } else if (token.type === "em") {
2260
+ text += token.text;
2261
+ }
2262
+ }
2263
+ return text.trim();
2264
+ }
2265
+ /**
2266
+ * Check if section has significant paragraph content.
2267
+ */
2268
+ hasSignificantParagraphs(tokens) {
2269
+ let wordCount = 0;
2270
+ for (const token of tokens) {
2271
+ if (token.type === "paragraph") {
2272
+ wordCount += token.text.split(/\s+/).length;
2273
+ }
2274
+ }
2275
+ return wordCount > 20;
2276
+ }
2277
+ /**
2278
+ * Create a content slide from paragraphs.
2279
+ */
2280
+ createContentSlide(title, tokens) {
2281
+ const paragraphs = [];
2282
+ for (const token of tokens) {
2283
+ if (token.type === "paragraph") {
2284
+ paragraphs.push(token.text);
2285
+ }
2286
+ }
2287
+ return {
2288
+ index: 0,
2289
+ type: "bullets",
2290
+ title,
2291
+ content: {
2292
+ body: paragraphs.join("\n\n")
2293
+ }
2294
+ };
2295
+ }
2296
+ /**
2297
+ * Extract a key statement from tokens.
2298
+ */
2299
+ extractStatement(tokens) {
2300
+ for (const token of tokens) {
2301
+ if (token.type === "paragraph" && token.text.length > 10) {
2302
+ return token.text;
2303
+ }
2304
+ }
2305
+ return null;
2306
+ }
2307
+ /**
2308
+ * VALIDATE ALL SLIDES - Fail loudly on garbage.
2309
+ */
2310
+ validateSlides(slides) {
2311
+ const errors = [];
2312
+ for (const slide of slides) {
2313
+ if (slide.content.bullets) {
2314
+ for (const bullet of slide.content.bullets) {
2315
+ if (bullet.endsWith("\u2192") || bullet.endsWith("...") || bullet.endsWith("-")) {
2316
+ errors.push(`Slide "${slide.title}": Truncated bullet detected: "${bullet}"`);
2317
+ }
2318
+ if (bullet.length < 5) {
2319
+ errors.push(`Slide "${slide.title}": Suspiciously short bullet: "${bullet}"`);
2320
+ }
2321
+ }
2322
+ }
2323
+ if (slide.content.metrics) {
2324
+ for (const metric of slide.content.metrics) {
2325
+ if (metric.label.length < 3) {
2326
+ errors.push(`Slide "${slide.title}": Garbage metric label: "${metric.label}"`);
2327
+ }
2328
+ if (metric.label.includes("|") || metric.label.includes("---")) {
2329
+ errors.push(`Slide "${slide.title}": Table syntax in label: "${metric.label}"`);
2330
+ }
2331
+ }
2332
+ }
2333
+ const hasContent = slide.content.bullets && slide.content.bullets.length > 0 || slide.content.table || slide.content.metrics || slide.content.statement || slide.content.body || slide.type === "title" || slide.type === "section" || slide.type === "thank_you";
2334
+ if (!hasContent) {
2335
+ errors.push(`Slide "${slide.title}": Empty slide with no content`);
2336
+ }
2337
+ }
2338
+ if (errors.length > 0) {
2339
+ console.warn("\n\u26A0\uFE0F QUALITY WARNINGS:");
2340
+ for (const error of errors) {
2341
+ console.warn(` - ${error}`);
2342
+ }
2343
+ console.warn("");
2344
+ }
2345
+ }
2346
+ };
2347
+ function createSlideGeneratorV2(type = "consulting_deck") {
2348
+ return new SlideGeneratorV2(type);
2349
+ }
2350
+
2013
2351
  // src/qa/SlideQualityReviewer.ts
2014
2352
  var ASSESSMENT_WEIGHTS = {
2015
2353
  visualImpact: 0.25,
@@ -3674,194 +4012,196 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
3674
4012
  /* CSS Variables from Knowledge Base - ALL styling driven by KB */
3675
4013
  :root {
3676
4014
  ${cssVariables}
4015
+ /* Professional color scheme - McKinsey/BCG style */
4016
+ --slide-bg-primary: #1a1a2e;
4017
+ --slide-bg-secondary: #16213e;
4018
+ --slide-bg-accent: #0f3460;
4019
+ --text-primary: #ffffff;
4020
+ --text-secondary: rgba(255, 255, 255, 0.85);
4021
+ --text-muted: rgba(255, 255, 255, 0.6);
4022
+ --accent-blue: #4a9eff;
4023
+ --accent-green: #00d4aa;
4024
+ --accent-orange: #ff9f43;
3677
4025
  }
3678
4026
 
3679
- /* Premium Slide Styling - Using KB variables */
4027
+ /* Premium Slide Styling - Clean, Professional */
3680
4028
  .reveal {
3681
- font-family: var(--font-body, system-ui, sans-serif);
3682
- background: var(--color-background);
4029
+ font-family: var(--font-body, 'Inter', system-ui, sans-serif);
3683
4030
  }
3684
4031
 
3685
4032
  .reveal .slides {
3686
4033
  text-align: left;
3687
4034
  }
3688
4035
 
4036
+ /* All slides get a professional dark gradient background */
3689
4037
  .reveal .slides section {
3690
- padding: 48px !important;
4038
+ padding: 60px 80px !important;
3691
4039
  box-sizing: border-box !important;
3692
- position: relative;
4040
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, var(--slide-bg-secondary) 50%, var(--slide-bg-accent) 100%);
3693
4041
  }
3694
4042
 
3695
- /* Dark gradient overlay for ALL slides - ensures text is always readable */
3696
- .reveal .slides section::before {
4043
+ /* When slide has a background image, add overlay for text readability */
4044
+ .reveal .slides section[data-background-image]::before {
3697
4045
  content: '';
3698
4046
  position: absolute;
3699
4047
  top: 0;
3700
4048
  left: 0;
3701
4049
  right: 0;
3702
4050
  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%);
4051
+ background: rgba(0, 0, 0, 0.5);
3707
4052
  z-index: 0;
3708
- pointer-events: none;
3709
4053
  }
3710
4054
 
3711
- /* Ensure all slide content is above the overlay */
3712
- .reveal .slides section > * {
4055
+ .reveal .slides section[data-background-image] > * {
3713
4056
  position: relative;
3714
4057
  z-index: 1;
3715
4058
  }
3716
4059
 
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
-
4060
+ /* Typography - Clean, readable, professional */
3740
4061
  .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);
4062
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
4063
+ font-size: 56px;
4064
+ font-weight: 700;
4065
+ color: var(--text-primary);
4066
+ margin-bottom: 24px;
4067
+ line-height: 1.1;
4068
+ letter-spacing: -0.02em;
3747
4069
  }
3748
4070
 
3749
4071
  .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);
4072
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
4073
+ font-size: 36px;
4074
+ font-weight: 600;
4075
+ color: var(--text-secondary);
4076
+ margin-bottom: 32px;
4077
+ line-height: 1.2;
3756
4078
  }
3757
4079
 
3758
4080
  .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);
4081
+ font-size: 28px;
4082
+ font-weight: 600;
4083
+ color: var(--text-primary);
4084
+ margin-bottom: 16px;
3764
4085
  }
3765
4086
 
3766
4087
  .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);
4088
+ font-size: 22px;
4089
+ color: var(--text-secondary);
4090
+ line-height: 1.6;
4091
+ margin-bottom: 24px;
3771
4092
  }
3772
4093
 
3773
- /* Bullet Points - Using KB spacing */
4094
+ /* Bullet Points - Professional styling */
3774
4095
  .reveal ul, .reveal ol {
3775
- margin-left: var(--spacing-lg);
4096
+ margin-left: 0;
4097
+ padding-left: 0;
4098
+ list-style: none;
3776
4099
  }
3777
4100
 
3778
4101
  .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);
4102
+ font-size: 22px;
4103
+ color: var(--text-secondary);
4104
+ line-height: 1.5;
4105
+ margin-bottom: 16px;
4106
+ padding-left: 28px;
4107
+ position: relative;
3783
4108
  }
3784
4109
 
3785
- .reveal li::marker {
3786
- color: var(--color-primary);
4110
+ .reveal li::before {
4111
+ content: '';
4112
+ position: absolute;
4113
+ left: 0;
4114
+ top: 10px;
4115
+ width: 8px;
4116
+ height: 8px;
4117
+ background: var(--accent-blue);
4118
+ border-radius: 50%;
3787
4119
  }
3788
4120
 
3789
4121
  /* Slide Type Specific Styling */
3790
4122
 
3791
- /* Title Slides */
4123
+ /* Title Slides - Bold and centered */
3792
4124
  .slide-title, .slide-title_impact {
3793
4125
  display: flex;
3794
4126
  flex-direction: column;
3795
4127
  justify-content: center;
3796
4128
  align-items: center;
3797
4129
  text-align: center;
4130
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, #0a0a14 100%);
3798
4131
  }
3799
4132
 
3800
4133
  .slide-title h1, .slide-title_impact h1 {
3801
- font-size: var(--font-size-6xl);
3802
- margin-bottom: var(--spacing-sm);
4134
+ font-size: 72px;
4135
+ margin-bottom: 16px;
3803
4136
  }
3804
4137
 
3805
4138
  .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);
4139
+ font-size: 28px;
4140
+ font-weight: 400;
4141
+ color: var(--text-muted);
3809
4142
  }
3810
4143
 
3811
- /* Section Dividers - Dark overlay for guaranteed contrast */
4144
+ /* Section Dividers - Bold accent background */
3812
4145
  .slide-section_divider {
3813
4146
  display: flex;
3814
4147
  flex-direction: column;
3815
4148
  justify-content: center;
3816
4149
  align-items: center;
3817
4150
  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;
4151
+ background: linear-gradient(135deg, #0f3460 0%, #1a1a2e 100%);
3820
4152
  }
3821
4153
 
3822
4154
  .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);
4155
+ color: #ffffff;
4156
+ font-size: 64px;
4157
+ font-weight: 700;
3826
4158
  }
3827
4159
 
3828
- /* Big Number / Metrics - Using KB glass effects */
4160
+ /* Metrics Grid - McKinsey/BCG style cards */
3829
4161
  .slide-big_number, .slide-metrics_grid {
3830
4162
  text-align: center;
3831
4163
  }
3832
4164
 
4165
+ .metrics-container {
4166
+ display: flex;
4167
+ justify-content: center;
4168
+ gap: 32px;
4169
+ flex-wrap: wrap;
4170
+ margin-top: 40px;
4171
+ }
4172
+
3833
4173
  .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));
4174
+ display: flex;
4175
+ flex-direction: column;
4176
+ align-items: center;
4177
+ padding: 32px 48px;
4178
+ background: rgba(255, 255, 255, 0.05);
4179
+ border: 1px solid rgba(255, 255, 255, 0.1);
4180
+ border-radius: 12px;
4181
+ min-width: 200px;
3843
4182
  }
3844
4183
 
3845
4184
  .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);
4185
+ font-size: 48px;
4186
+ font-weight: 700;
4187
+ color: var(--accent-blue);
4188
+ line-height: 1.1;
4189
+ margin-bottom: 8px;
3850
4190
  }
3851
4191
 
3852
4192
  .metric-label {
3853
- font-size: var(--font-size-lg);
3854
- color: var(--color-text);
3855
- opacity: 0.8;
3856
- margin-top: var(--spacing-sm);
4193
+ font-size: 16px;
4194
+ color: var(--text-secondary);
4195
+ text-transform: uppercase;
4196
+ letter-spacing: 0.05em;
3857
4197
  }
3858
4198
 
3859
- .metric-trend-up {
3860
- color: var(--color-success);
4199
+ .metric-trend-up .metric-value {
4200
+ color: var(--accent-green);
3861
4201
  }
3862
4202
 
3863
- .metric-trend-down {
3864
- color: var(--color-danger);
4203
+ .metric-trend-down .metric-value {
4204
+ color: var(--accent-orange);
3865
4205
  }
3866
4206
 
3867
4207
  /* Quote Slides - Using KB typography */
@@ -4159,9 +4499,15 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
4159
4499
  <div class="source">Source: ${this.renderMarkdown(data.source)}</div>`;
4160
4500
  }
4161
4501
  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">
4502
+ if (bgImageUrl) {
4503
+ return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4163
4504
  ${content}
4164
4505
  </section>`;
4506
+ } else {
4507
+ return ` <section class="${slideClass}">
4508
+ ${content}
4509
+ </section>`;
4510
+ }
4165
4511
  }
4166
4512
  renderTitleSlide(data) {
4167
4513
  let html = "";
@@ -4366,7 +4712,13 @@ ${content}
4366
4712
  }
4367
4713
  /**
4368
4714
  * Generate a background image URL based on slide type and content.
4369
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
4715
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
4716
+ *
4717
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
4718
+ * a financial slide makes you look incompetent. Only use images that are:
4719
+ * - Explicitly provided by the user
4720
+ * - Actually relevant to the content
4721
+ * Otherwise, rely on clean gradients via CSS.
4370
4722
  */
4371
4723
  getBackgroundImageUrl(slideType, slideData, slideIndex, localImages, imageBasePath) {
4372
4724
  if (slideData.image) {
@@ -4379,7 +4731,7 @@ ${content}
4379
4731
  return this.resolveImagePath(localImage, imageBasePath);
4380
4732
  }
4381
4733
  }
4382
- return this.getFallbackImageUrl(slideType, slideData, slideIndex);
4734
+ return "";
4383
4735
  }
4384
4736
  /**
4385
4737
  * Resolve an image path - handles relative paths, absolute paths, and URLs.
@@ -4530,6 +4882,459 @@ async function initRenderer() {
4530
4882
  return renderer;
4531
4883
  }
4532
4884
 
4885
+ // src/output/RendererV2.ts
4886
+ var RendererV2 = class {
4887
+ /**
4888
+ * Render slides to complete HTML document.
4889
+ */
4890
+ render(slides, title = "Presentation") {
4891
+ const slidesHtml = slides.map((slide) => this.renderSlide(slide)).join("\n");
4892
+ return `<!DOCTYPE html>
4893
+ <html lang="en">
4894
+ <head>
4895
+ <meta charset="UTF-8">
4896
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4897
+ <title>${this.escapeHtml(title)}</title>
4898
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/reveal.css">
4899
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/theme/black.css">
4900
+ <style>
4901
+ ${this.getCSS()}
4902
+ </style>
4903
+ </head>
4904
+ <body>
4905
+ <div class="reveal">
4906
+ <div class="slides">
4907
+ ${slidesHtml}
4908
+ </div>
4909
+ </div>
4910
+ <script src="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/reveal.js"></script>
4911
+ <script>
4912
+ Reveal.initialize({
4913
+ hash: true,
4914
+ transition: 'fade',
4915
+ transitionSpeed: 'default',
4916
+ center: false,
4917
+ width: 1920,
4918
+ height: 1080,
4919
+ margin: 0.04,
4920
+ });
4921
+ </script>
4922
+ </body>
4923
+ </html>`;
4924
+ }
4925
+ /**
4926
+ * Render a single slide.
4927
+ */
4928
+ renderSlide(slide) {
4929
+ const slideClass = `slide-${slide.type}`;
4930
+ let content = "";
4931
+ switch (slide.type) {
4932
+ case "title":
4933
+ content = this.renderTitleSlide(slide);
4934
+ break;
4935
+ case "section":
4936
+ content = this.renderSectionSlide(slide);
4937
+ break;
4938
+ case "bullets":
4939
+ content = this.renderBulletSlide(slide);
4940
+ break;
4941
+ case "table":
4942
+ content = this.renderTableSlide(slide);
4943
+ break;
4944
+ case "metrics":
4945
+ content = this.renderMetricsSlide(slide);
4946
+ break;
4947
+ case "statement":
4948
+ content = this.renderStatementSlide(slide);
4949
+ break;
4950
+ case "call_to_action":
4951
+ content = this.renderCTASlide(slide);
4952
+ break;
4953
+ case "thank_you":
4954
+ content = this.renderThankYouSlide(slide);
4955
+ break;
4956
+ default:
4957
+ content = this.renderBulletSlide(slide);
4958
+ }
4959
+ return ` <section class="${slideClass}">
4960
+ ${content}
4961
+ </section>`;
4962
+ }
4963
+ /**
4964
+ * Title slide - big, bold, centered.
4965
+ */
4966
+ renderTitleSlide(slide) {
4967
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
4968
+ ${slide.content.subtext ? `<h2>${this.escapeHtml(slide.content.subtext)}</h2>` : ""}`;
4969
+ }
4970
+ /**
4971
+ * Section divider - transition slide.
4972
+ */
4973
+ renderSectionSlide(slide) {
4974
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>`;
4975
+ }
4976
+ /**
4977
+ * Bullet slide - the workhorse.
4978
+ */
4979
+ renderBulletSlide(slide) {
4980
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
4981
+ `;
4982
+ if (slide.content.body) {
4983
+ const paragraphs = slide.content.body.split("\n\n");
4984
+ for (const p of paragraphs) {
4985
+ html += ` <p>${this.escapeHtml(p)}</p>
4986
+ `;
4987
+ }
4988
+ }
4989
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
4990
+ html += " <ul>\n";
4991
+ for (const bullet of slide.content.bullets) {
4992
+ const formattedBullet = this.formatBulletText(bullet);
4993
+ html += ` <li>${formattedBullet}</li>
4994
+ `;
4995
+ }
4996
+ html += " </ul>\n";
4997
+ }
4998
+ if (slide.content.source) {
4999
+ html += ` <div class="source">Source: ${this.escapeHtml(slide.content.source)}</div>
5000
+ `;
5001
+ }
5002
+ return html;
5003
+ }
5004
+ /**
5005
+ * Table slide - render tables properly.
5006
+ */
5007
+ renderTableSlide(slide) {
5008
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5009
+ `;
5010
+ if (slide.content.table) {
5011
+ html += this.renderTable(slide.content.table);
5012
+ }
5013
+ return html;
5014
+ }
5015
+ /**
5016
+ * Render a table as clean HTML.
5017
+ */
5018
+ renderTable(table) {
5019
+ let html = ' <table class="data-table">\n';
5020
+ html += " <thead>\n <tr>\n";
5021
+ for (const header of table.headers) {
5022
+ html += ` <th>${this.escapeHtml(header)}</th>
5023
+ `;
5024
+ }
5025
+ html += " </tr>\n </thead>\n";
5026
+ html += " <tbody>\n";
5027
+ for (const row of table.rows) {
5028
+ html += " <tr>\n";
5029
+ for (let i = 0; i < row.length; i++) {
5030
+ const cell = row[i] || "";
5031
+ const isNumber = /^[\d$%,.\-+]+[KMB]?$/.test(cell.trim());
5032
+ const align = isNumber ? ' class="number"' : "";
5033
+ html += ` <td${align}>${this.escapeHtml(cell)}</td>
5034
+ `;
5035
+ }
5036
+ html += " </tr>\n";
5037
+ }
5038
+ html += " </tbody>\n";
5039
+ html += " </table>\n";
5040
+ return html;
5041
+ }
5042
+ /**
5043
+ * Metrics slide - big numbers with labels.
5044
+ */
5045
+ renderMetricsSlide(slide) {
5046
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5047
+ `;
5048
+ html += ' <div class="metrics-container">\n';
5049
+ if (slide.content.metrics) {
5050
+ for (const metric of slide.content.metrics) {
5051
+ const trendClass = metric.trend ? ` metric-trend-${metric.trend}` : "";
5052
+ html += ` <div class="metric${trendClass}">
5053
+ <div class="metric-value">${this.escapeHtml(metric.value)}</div>
5054
+ <div class="metric-label">${this.escapeHtml(metric.label)}</div>
5055
+ </div>
5056
+ `;
5057
+ }
5058
+ }
5059
+ html += " </div>\n";
5060
+ return html;
5061
+ }
5062
+ /**
5063
+ * Statement slide - single powerful message.
5064
+ */
5065
+ renderStatementSlide(slide) {
5066
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5067
+ `;
5068
+ if (slide.content.statement) {
5069
+ html += ` <p class="statement">${this.escapeHtml(slide.content.statement)}</p>
5070
+ `;
5071
+ }
5072
+ if (slide.content.subtext) {
5073
+ html += ` <p class="subtext">${this.escapeHtml(slide.content.subtext)}</p>
5074
+ `;
5075
+ }
5076
+ return html;
5077
+ }
5078
+ /**
5079
+ * Call to action slide.
5080
+ */
5081
+ renderCTASlide(slide) {
5082
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5083
+ `;
5084
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
5085
+ html += " <ul>\n";
5086
+ for (const bullet of slide.content.bullets) {
5087
+ html += ` <li>${this.formatBulletText(bullet)}</li>
5088
+ `;
5089
+ }
5090
+ html += " </ul>\n";
5091
+ }
5092
+ return html;
5093
+ }
5094
+ /**
5095
+ * Thank you slide.
5096
+ */
5097
+ renderThankYouSlide(slide) {
5098
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
5099
+ <h2>Questions?</h2>`;
5100
+ }
5101
+ /**
5102
+ * Format bullet text - handle bold, arrows, etc.
5103
+ */
5104
+ formatBulletText(text) {
5105
+ let formatted = this.escapeHtml(text);
5106
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
5107
+ formatted = formatted.replace(/\*(.+?)\*/g, "<em>$1</em>");
5108
+ formatted = formatted.replace(/→/g, " \u2192 ");
5109
+ formatted = formatted.replace(/->/g, " \u2192 ");
5110
+ return formatted;
5111
+ }
5112
+ /**
5113
+ * Escape HTML special characters.
5114
+ */
5115
+ escapeHtml(text) {
5116
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5117
+ }
5118
+ /**
5119
+ * Professional CSS - McKinsey/BCG style.
5120
+ */
5121
+ getCSS() {
5122
+ return `
5123
+ /* Professional Dark Theme - No Random Images */
5124
+ :root {
5125
+ --bg-primary: #1a1a2e;
5126
+ --bg-secondary: #16213e;
5127
+ --bg-accent: #0f3460;
5128
+ --text-primary: #ffffff;
5129
+ --text-secondary: rgba(255, 255, 255, 0.85);
5130
+ --text-muted: rgba(255, 255, 255, 0.6);
5131
+ --accent-blue: #4a9eff;
5132
+ --accent-green: #00d4aa;
5133
+ --accent-orange: #ff9f43;
5134
+ --border-color: rgba(255, 255, 255, 0.1);
5135
+ }
5136
+
5137
+ .reveal {
5138
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5139
+ }
5140
+
5141
+ .reveal .slides {
5142
+ text-align: left;
5143
+ }
5144
+
5145
+ .reveal .slides section {
5146
+ padding: 60px 80px;
5147
+ background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-accent) 100%);
5148
+ height: 100%;
5149
+ box-sizing: border-box;
5150
+ }
5151
+
5152
+ /* Typography */
5153
+ .reveal h1 {
5154
+ font-size: 52px;
5155
+ font-weight: 700;
5156
+ color: var(--text-primary);
5157
+ margin-bottom: 32px;
5158
+ line-height: 1.1;
5159
+ letter-spacing: -0.02em;
5160
+ }
5161
+
5162
+ .reveal h2 {
5163
+ font-size: 32px;
5164
+ font-weight: 500;
5165
+ color: var(--text-secondary);
5166
+ margin-bottom: 24px;
5167
+ }
5168
+
5169
+ .reveal p {
5170
+ font-size: 24px;
5171
+ color: var(--text-secondary);
5172
+ line-height: 1.6;
5173
+ margin-bottom: 20px;
5174
+ }
5175
+
5176
+ /* Bullets - Complete, readable */
5177
+ .reveal ul {
5178
+ margin: 0;
5179
+ padding: 0;
5180
+ list-style: none;
5181
+ }
5182
+
5183
+ .reveal li {
5184
+ font-size: 24px;
5185
+ color: var(--text-secondary);
5186
+ line-height: 1.5;
5187
+ margin-bottom: 20px;
5188
+ padding-left: 32px;
5189
+ position: relative;
5190
+ }
5191
+
5192
+ .reveal li::before {
5193
+ content: '';
5194
+ position: absolute;
5195
+ left: 0;
5196
+ top: 12px;
5197
+ width: 10px;
5198
+ height: 10px;
5199
+ background: var(--accent-blue);
5200
+ border-radius: 50%;
5201
+ }
5202
+
5203
+ .reveal li strong {
5204
+ color: var(--text-primary);
5205
+ font-weight: 600;
5206
+ }
5207
+
5208
+ /* Tables - Clean, professional */
5209
+ .data-table {
5210
+ width: 100%;
5211
+ border-collapse: collapse;
5212
+ margin-top: 24px;
5213
+ font-size: 20px;
5214
+ }
5215
+
5216
+ .data-table th {
5217
+ background: rgba(255, 255, 255, 0.08);
5218
+ color: var(--text-primary);
5219
+ font-weight: 600;
5220
+ text-align: left;
5221
+ padding: 16px 20px;
5222
+ border-bottom: 2px solid var(--border-color);
5223
+ }
5224
+
5225
+ .data-table td {
5226
+ color: var(--text-secondary);
5227
+ padding: 14px 20px;
5228
+ border-bottom: 1px solid var(--border-color);
5229
+ }
5230
+
5231
+ .data-table td.number {
5232
+ text-align: right;
5233
+ font-family: 'SF Mono', 'Monaco', monospace;
5234
+ font-weight: 500;
5235
+ color: var(--accent-blue);
5236
+ }
5237
+
5238
+ .data-table tbody tr:hover td {
5239
+ background: rgba(255, 255, 255, 0.04);
5240
+ }
5241
+
5242
+ /* Metrics */
5243
+ .metrics-container {
5244
+ display: flex;
5245
+ justify-content: flex-start;
5246
+ gap: 40px;
5247
+ flex-wrap: wrap;
5248
+ margin-top: 40px;
5249
+ }
5250
+
5251
+ .metric {
5252
+ background: rgba(255, 255, 255, 0.05);
5253
+ border: 1px solid var(--border-color);
5254
+ border-radius: 12px;
5255
+ padding: 32px 48px;
5256
+ text-align: center;
5257
+ min-width: 200px;
5258
+ }
5259
+
5260
+ .metric-value {
5261
+ font-size: 56px;
5262
+ font-weight: 700;
5263
+ color: var(--accent-blue);
5264
+ line-height: 1;
5265
+ margin-bottom: 12px;
5266
+ }
5267
+
5268
+ .metric-trend-up .metric-value {
5269
+ color: var(--accent-green);
5270
+ }
5271
+
5272
+ .metric-trend-down .metric-value {
5273
+ color: var(--accent-orange);
5274
+ }
5275
+
5276
+ .metric-label {
5277
+ font-size: 16px;
5278
+ color: var(--text-muted);
5279
+ text-transform: uppercase;
5280
+ letter-spacing: 0.1em;
5281
+ }
5282
+
5283
+ /* Statement slides */
5284
+ .statement {
5285
+ font-size: 36px;
5286
+ color: var(--text-primary);
5287
+ line-height: 1.4;
5288
+ max-width: 80%;
5289
+ }
5290
+
5291
+ .subtext {
5292
+ font-size: 24px;
5293
+ color: var(--text-muted);
5294
+ }
5295
+
5296
+ /* Title slide */
5297
+ .slide-title {
5298
+ display: flex;
5299
+ flex-direction: column;
5300
+ justify-content: center;
5301
+ align-items: center;
5302
+ text-align: center;
5303
+ }
5304
+
5305
+ .slide-title h1 {
5306
+ font-size: 72px;
5307
+ }
5308
+
5309
+ /* Thank you slide */
5310
+ .slide-thank_you {
5311
+ display: flex;
5312
+ flex-direction: column;
5313
+ justify-content: center;
5314
+ align-items: center;
5315
+ text-align: center;
5316
+ }
5317
+
5318
+ .slide-thank_you h1 {
5319
+ font-size: 72px;
5320
+ margin-bottom: 16px;
5321
+ }
5322
+
5323
+ /* Source citation */
5324
+ .source {
5325
+ position: absolute;
5326
+ bottom: 24px;
5327
+ left: 80px;
5328
+ font-size: 14px;
5329
+ color: var(--text-muted);
5330
+ }
5331
+ `;
5332
+ }
5333
+ };
5334
+ function createRendererV2() {
5335
+ return new RendererV2();
5336
+ }
5337
+
4533
5338
  // src/image/NanoBananaProvider.ts
4534
5339
  var NanoBananaProvider = class {
4535
5340
  name = "NanoBanana Pro";
@@ -5572,11 +6377,15 @@ export {
5572
6377
  NanoBananaProvider,
5573
6378
  Remediator,
5574
6379
  Renderer,
6380
+ RendererV2,
5575
6381
  SlideGenerator,
6382
+ SlideGeneratorV2,
5576
6383
  SlideQualityReviewer,
5577
6384
  VERSION,
5578
6385
  VisualDesignSystem,
5579
6386
  createNanoBananaProvider,
6387
+ createRendererV2,
6388
+ createSlideGeneratorV2,
5580
6389
  generate,
5581
6390
  getContentAnalyzer,
5582
6391
  getDeckQualityReviewer,