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.js CHANGED
@@ -36,11 +36,15 @@ __export(index_exports, {
36
36
  NanoBananaProvider: () => NanoBananaProvider,
37
37
  Remediator: () => Remediator,
38
38
  Renderer: () => Renderer,
39
+ RendererV2: () => RendererV2,
39
40
  SlideGenerator: () => SlideGenerator,
41
+ SlideGeneratorV2: () => SlideGeneratorV2,
40
42
  SlideQualityReviewer: () => SlideQualityReviewer,
41
43
  VERSION: () => VERSION,
42
44
  VisualDesignSystem: () => VisualDesignSystem,
43
45
  createNanoBananaProvider: () => createNanoBananaProvider,
46
+ createRendererV2: () => createRendererV2,
47
+ createSlideGeneratorV2: () => createSlideGeneratorV2,
44
48
  generate: () => generate,
45
49
  getContentAnalyzer: () => getContentAnalyzer,
46
50
  getDeckQualityReviewer: () => getDeckQualityReviewer,
@@ -1498,6 +1502,12 @@ var SlideGenerator = class {
1498
1502
  wordLimits;
1499
1503
  bulletLimit;
1500
1504
  allowedSlideTypes;
1505
+ // Track created slide titles to prevent duplicates
1506
+ createdSlideTitles = /* @__PURE__ */ new Set();
1507
+ // Track section dividers to avoid excessive blank slides
1508
+ sectionDividerCount = 0;
1509
+ maxSectionDividers = 4;
1510
+ // Only first 4 major sections get dividers
1501
1511
  constructor() {
1502
1512
  this.kb = getKB();
1503
1513
  this.vds = getVisualDesignSystem();
@@ -1513,6 +1523,8 @@ var SlideGenerator = class {
1513
1523
  */
1514
1524
  async generate(analysis, presentationType) {
1515
1525
  await this.initialize();
1526
+ this.createdSlideTitles.clear();
1527
+ this.sectionDividerCount = 0;
1516
1528
  this.loadRulesForType(presentationType);
1517
1529
  const slides = [];
1518
1530
  let slideIndex = 0;
@@ -1659,8 +1671,15 @@ var SlideGenerator = class {
1659
1671
  */
1660
1672
  createSectionSlides(section, analysis, type, startIndex) {
1661
1673
  const slides = [];
1674
+ const normalizedTitle = section.header.toLowerCase().trim();
1675
+ if (this.createdSlideTitles.has(normalizedTitle)) {
1676
+ return slides;
1677
+ }
1662
1678
  const slideType = this.determineSlideType(section, type);
1663
- if (section.level === 2) {
1679
+ const hasSubstantiveContent = section.bullets.length > 0 || section.metrics.length > 0 || section.content.trim().length > 50;
1680
+ if (section.level === 2 && this.sectionDividerCount < this.maxSectionDividers && hasSubstantiveContent) {
1681
+ this.sectionDividerCount++;
1682
+ this.createdSlideTitles.add(normalizedTitle);
1664
1683
  slides.push({
1665
1684
  index: startIndex,
1666
1685
  type: "section_divider",
@@ -1884,36 +1903,35 @@ var SlideGenerator = class {
1884
1903
  return clean.trim();
1885
1904
  }
1886
1905
  /**
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)
1906
+ * Clean bullets - strip markdown and preserve COMPLETE, MEANINGFUL sentences.
1907
+ * Business presentations need full context. Never truncate mid-thought.
1908
+ * Reasonable limit: 20 words per bullet allows complete ideas.
1890
1909
  */
1891
1910
  cleanBullets(bullets) {
1892
- const maxBullets = Math.min(this.bulletLimit, 4);
1893
- const maxWordsPerBullet = 5;
1911
+ const maxBullets = Math.min(this.bulletLimit, 6);
1912
+ const maxWordsPerBullet = 20;
1894
1913
  return bullets.slice(0, maxBullets).map((b) => {
1895
1914
  const clean = this.stripMarkdownSyntax(b);
1896
1915
  const words = clean.split(/\s+/).filter((w) => w.length > 0);
1897
1916
  if (words.length <= maxWordsPerBullet) {
1898
1917
  return clean;
1899
1918
  }
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
- }
1919
+ const truncated = words.slice(0, maxWordsPerBullet).join(" ");
1920
+ const lastPeriod = truncated.lastIndexOf(".");
1921
+ const lastColon = truncated.lastIndexOf(":");
1922
+ const lastDash = truncated.lastIndexOf(" \u2013 ");
1923
+ const lastHyphen = truncated.lastIndexOf(" - ");
1924
+ const breakPoints = [lastPeriod, lastColon, lastDash, lastHyphen].filter((p) => p > 15);
1925
+ const bestBreak = Math.max(...breakPoints, -1);
1926
+ if (bestBreak > 15) {
1927
+ return truncated.slice(0, bestBreak + 1).trim();
1910
1928
  }
1911
- return words.slice(0, maxWordsPerBullet).join(" ");
1929
+ return truncated;
1912
1930
  }).filter((b) => b.length > 0);
1913
1931
  }
1914
1932
  /**
1915
1933
  * Truncate text to word limit, stripping markdown first.
1916
- * NEVER adds "..." - just takes the complete words that fit.
1934
+ * Preserves complete sentences. Business content needs room to breathe.
1917
1935
  */
1918
1936
  truncateToWordLimit(text) {
1919
1937
  const cleanText = this.stripMarkdownSyntax(text);
@@ -1921,18 +1939,18 @@ var SlideGenerator = class {
1921
1939
  return "";
1922
1940
  }
1923
1941
  const words = cleanText.split(/\s+/).filter((w) => w.length > 0);
1924
- const strictLimit = Math.min(this.wordLimits.ideal, 20);
1925
- if (words.length <= strictLimit) {
1942
+ const bodyLimit = Math.min(this.wordLimits.max || 60, 50);
1943
+ if (words.length <= bodyLimit) {
1926
1944
  return cleanText;
1927
1945
  }
1928
- const truncatedWords = words.slice(0, strictLimit);
1946
+ const truncatedWords = words.slice(0, bodyLimit);
1929
1947
  const truncatedText = truncatedWords.join(" ");
1930
1948
  const lastSentenceEnd = Math.max(
1931
1949
  truncatedText.lastIndexOf("."),
1932
1950
  truncatedText.lastIndexOf("!"),
1933
1951
  truncatedText.lastIndexOf("?")
1934
1952
  );
1935
- if (lastSentenceEnd > 30) {
1953
+ if (lastSentenceEnd > 20) {
1936
1954
  return truncatedText.slice(0, lastSentenceEnd + 1).trim();
1937
1955
  }
1938
1956
  return truncatedText;
@@ -1971,18 +1989,30 @@ var SlideGenerator = class {
1971
1989
  }
1972
1990
  /**
1973
1991
  * Extract a label from context (nearby text around a data point).
1992
+ * CRITICAL: Strip all markdown table syntax first to avoid garbage labels.
1974
1993
  */
1975
1994
  extractLabelFromContext(context) {
1976
- const beforeMatch = context.match(/^([^$%\d]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1995
+ let cleanContext = context.replace(/\|/g, " ").replace(/-{2,}/g, "").replace(/:{1,2}/g, "").replace(/\s{2,}/g, " ").trim();
1996
+ if (!cleanContext || cleanContext.length < 3) {
1997
+ return "Value";
1998
+ }
1999
+ const beforeMatch = cleanContext.match(/^([A-Za-z][A-Za-z\s&]+?)(?:\s*[:=]?\s*)?[\d$%]/);
1977
2000
  const beforeGroup = beforeMatch?.[1];
1978
- if (beforeGroup && beforeGroup.trim().length < 30) {
2001
+ if (beforeGroup && beforeGroup.trim().length >= 3 && beforeGroup.trim().length < 40) {
1979
2002
  return beforeGroup.trim();
1980
2003
  }
1981
- const afterMatch = context.match(/[\d$%]+[KMB]?\s*(?:in|for|of)?\s*(.+)$/i);
2004
+ const afterMatch = cleanContext.match(/[\d$%]+[KMB]?\s*(?:in|for|of|per)?\s*([A-Za-z][A-Za-z\s&]+)$/i);
1982
2005
  const afterGroup = afterMatch?.[1];
1983
- if (afterGroup && afterGroup.trim().length < 30) {
2006
+ if (afterGroup && afterGroup.trim().length >= 3 && afterGroup.trim().length < 40) {
1984
2007
  return afterGroup.trim().replace(/[.,]$/, "");
1985
2008
  }
2009
+ const words = cleanContext.match(/[A-Za-z]{3,}/g);
2010
+ if (words && words.length > 0) {
2011
+ const label = words.slice(0, 3).join(" ");
2012
+ if (label.length >= 3 && label.length < 40) {
2013
+ return label;
2014
+ }
2015
+ }
1986
2016
  return "Metric";
1987
2017
  }
1988
2018
  /**
@@ -2076,6 +2106,318 @@ async function initSlideGenerator() {
2076
2106
  return generator;
2077
2107
  }
2078
2108
 
2109
+ // src/core/SlideGeneratorV2.ts
2110
+ var import_marked = require("marked");
2111
+ var MODE_CONFIGS = {
2112
+ // Consulting decks: 40-80 words per slide is NORMAL
2113
+ consulting_deck: {
2114
+ maxBulletsPerSlide: 7,
2115
+ maxWordsPerSlide: 100,
2116
+ // Not 20!
2117
+ minBulletsToKeep: 3
2118
+ },
2119
+ // Keynotes: fewer words, more impact
2120
+ keynote: {
2121
+ maxBulletsPerSlide: 4,
2122
+ maxWordsPerSlide: 30,
2123
+ minBulletsToKeep: 2
2124
+ },
2125
+ // Default: consulting style
2126
+ default: {
2127
+ maxBulletsPerSlide: 6,
2128
+ maxWordsPerSlide: 80,
2129
+ minBulletsToKeep: 3
2130
+ }
2131
+ };
2132
+ var SlideGeneratorV2 = class {
2133
+ config;
2134
+ presentationType;
2135
+ constructor(presentationType = "consulting_deck") {
2136
+ this.presentationType = presentationType;
2137
+ this.config = MODE_CONFIGS[presentationType] || MODE_CONFIGS.default;
2138
+ }
2139
+ /**
2140
+ * Generate slides from markdown content.
2141
+ * Simple, correct, no garbage.
2142
+ */
2143
+ generate(markdown, title) {
2144
+ const slides = [];
2145
+ let slideIndex = 0;
2146
+ const tokens = import_marked.marked.lexer(markdown);
2147
+ const detectedTitle = title || this.extractTitle(tokens);
2148
+ slides.push({
2149
+ index: slideIndex++,
2150
+ type: "title",
2151
+ title: detectedTitle,
2152
+ content: {}
2153
+ });
2154
+ const sections = this.splitIntoSections(tokens);
2155
+ for (const section of sections) {
2156
+ const sectionSlides = this.processSectionContent(section, slideIndex);
2157
+ for (const slide of sectionSlides) {
2158
+ slide.index = slideIndex++;
2159
+ slides.push(slide);
2160
+ }
2161
+ }
2162
+ const lastSlide = slides[slides.length - 1];
2163
+ if (lastSlide && lastSlide.type !== "thank_you") {
2164
+ slides.push({
2165
+ index: slideIndex++,
2166
+ type: "thank_you",
2167
+ title: "Thank You",
2168
+ content: { subtext: "Questions?" }
2169
+ });
2170
+ }
2171
+ this.validateSlides(slides);
2172
+ return slides;
2173
+ }
2174
+ /**
2175
+ * Extract title from first H1 token.
2176
+ */
2177
+ extractTitle(tokens) {
2178
+ for (const token of tokens) {
2179
+ if (token.type === "heading" && token.depth === 1) {
2180
+ return token.text;
2181
+ }
2182
+ }
2183
+ return "Presentation";
2184
+ }
2185
+ /**
2186
+ * Split tokens into sections based on H2 headers.
2187
+ */
2188
+ splitIntoSections(tokens) {
2189
+ const sections = [];
2190
+ let currentSection = null;
2191
+ for (const token of tokens) {
2192
+ if (token.type === "heading" && token.depth === 2) {
2193
+ if (currentSection) {
2194
+ sections.push(currentSection);
2195
+ }
2196
+ currentSection = {
2197
+ title: token.text,
2198
+ tokens: []
2199
+ };
2200
+ } else if (token.type === "heading" && token.depth === 1) {
2201
+ continue;
2202
+ } else if (currentSection) {
2203
+ currentSection.tokens.push(token);
2204
+ }
2205
+ }
2206
+ if (currentSection && currentSection.tokens.length > 0) {
2207
+ sections.push(currentSection);
2208
+ }
2209
+ return sections;
2210
+ }
2211
+ /**
2212
+ * Process section content into slides.
2213
+ * Tables become table slides. Lists become bullet slides.
2214
+ */
2215
+ processSectionContent(section, startIndex) {
2216
+ const slides = [];
2217
+ const hasTable = section.tokens.some((t) => t.type === "table");
2218
+ const hasList = section.tokens.some((t) => t.type === "list");
2219
+ const hasSignificantText = this.hasSignificantParagraphs(section.tokens);
2220
+ if (hasTable) {
2221
+ const tableToken = section.tokens.find((t) => t.type === "table");
2222
+ if (tableToken) {
2223
+ slides.push(this.createTableSlide(section.title, tableToken));
2224
+ }
2225
+ }
2226
+ if (hasList) {
2227
+ const listSlides = this.createBulletSlides(section.title, section.tokens);
2228
+ slides.push(...listSlides);
2229
+ }
2230
+ if (hasSignificantText && !hasList && !hasTable) {
2231
+ slides.push(this.createContentSlide(section.title, section.tokens));
2232
+ }
2233
+ if (slides.length === 0 && section.tokens.length > 0) {
2234
+ const statement = this.extractStatement(section.tokens);
2235
+ if (statement) {
2236
+ slides.push({
2237
+ index: startIndex,
2238
+ type: "statement",
2239
+ title: section.title,
2240
+ content: { statement }
2241
+ });
2242
+ }
2243
+ }
2244
+ return slides;
2245
+ }
2246
+ /**
2247
+ * Create a table slide - render the table as-is, no extraction.
2248
+ */
2249
+ createTableSlide(title, tableToken) {
2250
+ const headers = tableToken.header.map((cell) => cell.text);
2251
+ const rows = tableToken.rows.map((row) => row.map((cell) => cell.text));
2252
+ return {
2253
+ index: 0,
2254
+ type: "table",
2255
+ title,
2256
+ content: {
2257
+ table: {
2258
+ headers,
2259
+ rows
2260
+ }
2261
+ }
2262
+ };
2263
+ }
2264
+ /**
2265
+ * Create bullet slides from list content.
2266
+ * NEVER truncate bullets. Split into multiple slides if needed.
2267
+ */
2268
+ createBulletSlides(title, tokens) {
2269
+ const slides = [];
2270
+ const allBullets = [];
2271
+ for (const token of tokens) {
2272
+ if (token.type === "list") {
2273
+ for (const item of token.items) {
2274
+ const bulletText = this.getCompleteItemText(item);
2275
+ if (bulletText.trim()) {
2276
+ allBullets.push(bulletText);
2277
+ }
2278
+ }
2279
+ }
2280
+ }
2281
+ const maxBullets = this.config.maxBulletsPerSlide;
2282
+ const minBullets = this.config.minBulletsToKeep;
2283
+ if (allBullets.length <= maxBullets) {
2284
+ if (allBullets.length >= minBullets || allBullets.length === 0) {
2285
+ slides.push({
2286
+ index: 0,
2287
+ type: "bullets",
2288
+ title,
2289
+ content: { bullets: allBullets }
2290
+ });
2291
+ } else {
2292
+ slides.push({
2293
+ index: 0,
2294
+ type: "bullets",
2295
+ title,
2296
+ content: { bullets: allBullets },
2297
+ notes: "Consider combining with adjacent content"
2298
+ });
2299
+ }
2300
+ } else {
2301
+ for (let i = 0; i < allBullets.length; i += maxBullets) {
2302
+ const chunk = allBullets.slice(i, i + maxBullets);
2303
+ const isFirst = i === 0;
2304
+ const slideTitle = isFirst ? title : `${title} (continued)`;
2305
+ slides.push({
2306
+ index: 0,
2307
+ type: "bullets",
2308
+ title: slideTitle,
2309
+ content: { bullets: chunk }
2310
+ });
2311
+ }
2312
+ }
2313
+ return slides;
2314
+ }
2315
+ /**
2316
+ * Get complete text from a list item, including nested content.
2317
+ * NEVER truncate.
2318
+ */
2319
+ getCompleteItemText(item) {
2320
+ let text = "";
2321
+ for (const token of item.tokens) {
2322
+ if (token.type === "text") {
2323
+ text += token.text;
2324
+ } else if (token.type === "paragraph") {
2325
+ const paraToken = token;
2326
+ text += paraToken.text;
2327
+ } else if (token.type === "strong") {
2328
+ text += token.text;
2329
+ } else if (token.type === "em") {
2330
+ text += token.text;
2331
+ }
2332
+ }
2333
+ return text.trim();
2334
+ }
2335
+ /**
2336
+ * Check if section has significant paragraph content.
2337
+ */
2338
+ hasSignificantParagraphs(tokens) {
2339
+ let wordCount = 0;
2340
+ for (const token of tokens) {
2341
+ if (token.type === "paragraph") {
2342
+ wordCount += token.text.split(/\s+/).length;
2343
+ }
2344
+ }
2345
+ return wordCount > 20;
2346
+ }
2347
+ /**
2348
+ * Create a content slide from paragraphs.
2349
+ */
2350
+ createContentSlide(title, tokens) {
2351
+ const paragraphs = [];
2352
+ for (const token of tokens) {
2353
+ if (token.type === "paragraph") {
2354
+ paragraphs.push(token.text);
2355
+ }
2356
+ }
2357
+ return {
2358
+ index: 0,
2359
+ type: "bullets",
2360
+ title,
2361
+ content: {
2362
+ body: paragraphs.join("\n\n")
2363
+ }
2364
+ };
2365
+ }
2366
+ /**
2367
+ * Extract a key statement from tokens.
2368
+ */
2369
+ extractStatement(tokens) {
2370
+ for (const token of tokens) {
2371
+ if (token.type === "paragraph" && token.text.length > 10) {
2372
+ return token.text;
2373
+ }
2374
+ }
2375
+ return null;
2376
+ }
2377
+ /**
2378
+ * VALIDATE ALL SLIDES - Fail loudly on garbage.
2379
+ */
2380
+ validateSlides(slides) {
2381
+ const errors = [];
2382
+ for (const slide of slides) {
2383
+ if (slide.content.bullets) {
2384
+ for (const bullet of slide.content.bullets) {
2385
+ if (bullet.endsWith("\u2192") || bullet.endsWith("...") || bullet.endsWith("-")) {
2386
+ errors.push(`Slide "${slide.title}": Truncated bullet detected: "${bullet}"`);
2387
+ }
2388
+ if (bullet.length < 5) {
2389
+ errors.push(`Slide "${slide.title}": Suspiciously short bullet: "${bullet}"`);
2390
+ }
2391
+ }
2392
+ }
2393
+ if (slide.content.metrics) {
2394
+ for (const metric of slide.content.metrics) {
2395
+ if (metric.label.length < 3) {
2396
+ errors.push(`Slide "${slide.title}": Garbage metric label: "${metric.label}"`);
2397
+ }
2398
+ if (metric.label.includes("|") || metric.label.includes("---")) {
2399
+ errors.push(`Slide "${slide.title}": Table syntax in label: "${metric.label}"`);
2400
+ }
2401
+ }
2402
+ }
2403
+ 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";
2404
+ if (!hasContent) {
2405
+ errors.push(`Slide "${slide.title}": Empty slide with no content`);
2406
+ }
2407
+ }
2408
+ if (errors.length > 0) {
2409
+ console.warn("\n\u26A0\uFE0F QUALITY WARNINGS:");
2410
+ for (const error of errors) {
2411
+ console.warn(` - ${error}`);
2412
+ }
2413
+ console.warn("");
2414
+ }
2415
+ }
2416
+ };
2417
+ function createSlideGeneratorV2(type = "consulting_deck") {
2418
+ return new SlideGeneratorV2(type);
2419
+ }
2420
+
2079
2421
  // src/qa/SlideQualityReviewer.ts
2080
2422
  var ASSESSMENT_WEIGHTS = {
2081
2423
  visualImpact: 0.25,
@@ -3740,194 +4082,196 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
3740
4082
  /* CSS Variables from Knowledge Base - ALL styling driven by KB */
3741
4083
  :root {
3742
4084
  ${cssVariables}
4085
+ /* Professional color scheme - McKinsey/BCG style */
4086
+ --slide-bg-primary: #1a1a2e;
4087
+ --slide-bg-secondary: #16213e;
4088
+ --slide-bg-accent: #0f3460;
4089
+ --text-primary: #ffffff;
4090
+ --text-secondary: rgba(255, 255, 255, 0.85);
4091
+ --text-muted: rgba(255, 255, 255, 0.6);
4092
+ --accent-blue: #4a9eff;
4093
+ --accent-green: #00d4aa;
4094
+ --accent-orange: #ff9f43;
3743
4095
  }
3744
4096
 
3745
- /* Premium Slide Styling - Using KB variables */
4097
+ /* Premium Slide Styling - Clean, Professional */
3746
4098
  .reveal {
3747
- font-family: var(--font-body, system-ui, sans-serif);
3748
- background: var(--color-background);
4099
+ font-family: var(--font-body, 'Inter', system-ui, sans-serif);
3749
4100
  }
3750
4101
 
3751
4102
  .reveal .slides {
3752
4103
  text-align: left;
3753
4104
  }
3754
4105
 
4106
+ /* All slides get a professional dark gradient background */
3755
4107
  .reveal .slides section {
3756
- padding: 48px !important;
4108
+ padding: 60px 80px !important;
3757
4109
  box-sizing: border-box !important;
3758
- position: relative;
4110
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, var(--slide-bg-secondary) 50%, var(--slide-bg-accent) 100%);
3759
4111
  }
3760
4112
 
3761
- /* Dark gradient overlay for ALL slides - ensures text is always readable */
3762
- .reveal .slides section::before {
4113
+ /* When slide has a background image, add overlay for text readability */
4114
+ .reveal .slides section[data-background-image]::before {
3763
4115
  content: '';
3764
4116
  position: absolute;
3765
4117
  top: 0;
3766
4118
  left: 0;
3767
4119
  right: 0;
3768
4120
  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%);
4121
+ background: rgba(0, 0, 0, 0.5);
3773
4122
  z-index: 0;
3774
- pointer-events: none;
3775
4123
  }
3776
4124
 
3777
- /* Ensure all slide content is above the overlay */
3778
- .reveal .slides section > * {
4125
+ .reveal .slides section[data-background-image] > * {
3779
4126
  position: relative;
3780
4127
  z-index: 1;
3781
4128
  }
3782
4129
 
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
-
4130
+ /* Typography - Clean, readable, professional */
3806
4131
  .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);
4132
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
4133
+ font-size: 56px;
4134
+ font-weight: 700;
4135
+ color: var(--text-primary);
4136
+ margin-bottom: 24px;
4137
+ line-height: 1.1;
4138
+ letter-spacing: -0.02em;
3813
4139
  }
3814
4140
 
3815
4141
  .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);
4142
+ font-family: var(--font-title, 'Inter', system-ui, sans-serif);
4143
+ font-size: 36px;
4144
+ font-weight: 600;
4145
+ color: var(--text-secondary);
4146
+ margin-bottom: 32px;
4147
+ line-height: 1.2;
3822
4148
  }
3823
4149
 
3824
4150
  .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);
4151
+ font-size: 28px;
4152
+ font-weight: 600;
4153
+ color: var(--text-primary);
4154
+ margin-bottom: 16px;
3830
4155
  }
3831
4156
 
3832
4157
  .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);
4158
+ font-size: 22px;
4159
+ color: var(--text-secondary);
4160
+ line-height: 1.6;
4161
+ margin-bottom: 24px;
3837
4162
  }
3838
4163
 
3839
- /* Bullet Points - Using KB spacing */
4164
+ /* Bullet Points - Professional styling */
3840
4165
  .reveal ul, .reveal ol {
3841
- margin-left: var(--spacing-lg);
4166
+ margin-left: 0;
4167
+ padding-left: 0;
4168
+ list-style: none;
3842
4169
  }
3843
4170
 
3844
4171
  .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);
4172
+ font-size: 22px;
4173
+ color: var(--text-secondary);
4174
+ line-height: 1.5;
4175
+ margin-bottom: 16px;
4176
+ padding-left: 28px;
4177
+ position: relative;
3849
4178
  }
3850
4179
 
3851
- .reveal li::marker {
3852
- color: var(--color-primary);
4180
+ .reveal li::before {
4181
+ content: '';
4182
+ position: absolute;
4183
+ left: 0;
4184
+ top: 10px;
4185
+ width: 8px;
4186
+ height: 8px;
4187
+ background: var(--accent-blue);
4188
+ border-radius: 50%;
3853
4189
  }
3854
4190
 
3855
4191
  /* Slide Type Specific Styling */
3856
4192
 
3857
- /* Title Slides */
4193
+ /* Title Slides - Bold and centered */
3858
4194
  .slide-title, .slide-title_impact {
3859
4195
  display: flex;
3860
4196
  flex-direction: column;
3861
4197
  justify-content: center;
3862
4198
  align-items: center;
3863
4199
  text-align: center;
4200
+ background: linear-gradient(135deg, var(--slide-bg-primary) 0%, #0a0a14 100%);
3864
4201
  }
3865
4202
 
3866
4203
  .slide-title h1, .slide-title_impact h1 {
3867
- font-size: var(--font-size-6xl);
3868
- margin-bottom: var(--spacing-sm);
4204
+ font-size: 72px;
4205
+ margin-bottom: 16px;
3869
4206
  }
3870
4207
 
3871
4208
  .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);
4209
+ font-size: 28px;
4210
+ font-weight: 400;
4211
+ color: var(--text-muted);
3875
4212
  }
3876
4213
 
3877
- /* Section Dividers - Dark overlay for guaranteed contrast */
4214
+ /* Section Dividers - Bold accent background */
3878
4215
  .slide-section_divider {
3879
4216
  display: flex;
3880
4217
  flex-direction: column;
3881
4218
  justify-content: center;
3882
4219
  align-items: center;
3883
4220
  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;
4221
+ background: linear-gradient(135deg, #0f3460 0%, #1a1a2e 100%);
3886
4222
  }
3887
4223
 
3888
4224
  .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);
4225
+ color: #ffffff;
4226
+ font-size: 64px;
4227
+ font-weight: 700;
3892
4228
  }
3893
4229
 
3894
- /* Big Number / Metrics - Using KB glass effects */
4230
+ /* Metrics Grid - McKinsey/BCG style cards */
3895
4231
  .slide-big_number, .slide-metrics_grid {
3896
4232
  text-align: center;
3897
4233
  }
3898
4234
 
4235
+ .metrics-container {
4236
+ display: flex;
4237
+ justify-content: center;
4238
+ gap: 32px;
4239
+ flex-wrap: wrap;
4240
+ margin-top: 40px;
4241
+ }
4242
+
3899
4243
  .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));
4244
+ display: flex;
4245
+ flex-direction: column;
4246
+ align-items: center;
4247
+ padding: 32px 48px;
4248
+ background: rgba(255, 255, 255, 0.05);
4249
+ border: 1px solid rgba(255, 255, 255, 0.1);
4250
+ border-radius: 12px;
4251
+ min-width: 200px;
3909
4252
  }
3910
4253
 
3911
4254
  .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);
4255
+ font-size: 48px;
4256
+ font-weight: 700;
4257
+ color: var(--accent-blue);
4258
+ line-height: 1.1;
4259
+ margin-bottom: 8px;
3916
4260
  }
3917
4261
 
3918
4262
  .metric-label {
3919
- font-size: var(--font-size-lg);
3920
- color: var(--color-text);
3921
- opacity: 0.8;
3922
- margin-top: var(--spacing-sm);
4263
+ font-size: 16px;
4264
+ color: var(--text-secondary);
4265
+ text-transform: uppercase;
4266
+ letter-spacing: 0.05em;
3923
4267
  }
3924
4268
 
3925
- .metric-trend-up {
3926
- color: var(--color-success);
4269
+ .metric-trend-up .metric-value {
4270
+ color: var(--accent-green);
3927
4271
  }
3928
4272
 
3929
- .metric-trend-down {
3930
- color: var(--color-danger);
4273
+ .metric-trend-down .metric-value {
4274
+ color: var(--accent-orange);
3931
4275
  }
3932
4276
 
3933
4277
  /* Quote Slides - Using KB typography */
@@ -4225,9 +4569,15 @@ ${slides.map((slide, index) => this.renderSlide(slide, options.presentationType,
4225
4569
  <div class="source">Source: ${this.renderMarkdown(data.source)}</div>`;
4226
4570
  }
4227
4571
  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">
4572
+ if (bgImageUrl) {
4573
+ return ` <section class="${slideClass}" data-background-image="${bgImageUrl}" data-background-size="cover" data-background-position="center" data-background-opacity="0.4">
4229
4574
  ${content}
4230
4575
  </section>`;
4576
+ } else {
4577
+ return ` <section class="${slideClass}">
4578
+ ${content}
4579
+ </section>`;
4580
+ }
4231
4581
  }
4232
4582
  renderTitleSlide(data) {
4233
4583
  let html = "";
@@ -4432,7 +4782,13 @@ ${content}
4432
4782
  }
4433
4783
  /**
4434
4784
  * Generate a background image URL based on slide type and content.
4435
- * Priority: 1) slide.data.image, 2) local images array, 3) Picsum fallback
4785
+ * Priority: 1) slide.data.image, 2) local images array, 3) NO RANDOM STOCK PHOTOS
4786
+ *
4787
+ * IMPORTANT: Random stock photos destroy credibility. A photo of flowers behind
4788
+ * a financial slide makes you look incompetent. Only use images that are:
4789
+ * - Explicitly provided by the user
4790
+ * - Actually relevant to the content
4791
+ * Otherwise, rely on clean gradients via CSS.
4436
4792
  */
4437
4793
  getBackgroundImageUrl(slideType, slideData, slideIndex, localImages, imageBasePath) {
4438
4794
  if (slideData.image) {
@@ -4445,7 +4801,7 @@ ${content}
4445
4801
  return this.resolveImagePath(localImage, imageBasePath);
4446
4802
  }
4447
4803
  }
4448
- return this.getFallbackImageUrl(slideType, slideData, slideIndex);
4804
+ return "";
4449
4805
  }
4450
4806
  /**
4451
4807
  * Resolve an image path - handles relative paths, absolute paths, and URLs.
@@ -4596,6 +4952,459 @@ async function initRenderer() {
4596
4952
  return renderer;
4597
4953
  }
4598
4954
 
4955
+ // src/output/RendererV2.ts
4956
+ var RendererV2 = class {
4957
+ /**
4958
+ * Render slides to complete HTML document.
4959
+ */
4960
+ render(slides, title = "Presentation") {
4961
+ const slidesHtml = slides.map((slide) => this.renderSlide(slide)).join("\n");
4962
+ return `<!DOCTYPE html>
4963
+ <html lang="en">
4964
+ <head>
4965
+ <meta charset="UTF-8">
4966
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4967
+ <title>${this.escapeHtml(title)}</title>
4968
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/reveal.css">
4969
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/theme/black.css">
4970
+ <style>
4971
+ ${this.getCSS()}
4972
+ </style>
4973
+ </head>
4974
+ <body>
4975
+ <div class="reveal">
4976
+ <div class="slides">
4977
+ ${slidesHtml}
4978
+ </div>
4979
+ </div>
4980
+ <script src="https://cdn.jsdelivr.net/npm/reveal.js@4.5.0/dist/reveal.js"></script>
4981
+ <script>
4982
+ Reveal.initialize({
4983
+ hash: true,
4984
+ transition: 'fade',
4985
+ transitionSpeed: 'default',
4986
+ center: false,
4987
+ width: 1920,
4988
+ height: 1080,
4989
+ margin: 0.04,
4990
+ });
4991
+ </script>
4992
+ </body>
4993
+ </html>`;
4994
+ }
4995
+ /**
4996
+ * Render a single slide.
4997
+ */
4998
+ renderSlide(slide) {
4999
+ const slideClass = `slide-${slide.type}`;
5000
+ let content = "";
5001
+ switch (slide.type) {
5002
+ case "title":
5003
+ content = this.renderTitleSlide(slide);
5004
+ break;
5005
+ case "section":
5006
+ content = this.renderSectionSlide(slide);
5007
+ break;
5008
+ case "bullets":
5009
+ content = this.renderBulletSlide(slide);
5010
+ break;
5011
+ case "table":
5012
+ content = this.renderTableSlide(slide);
5013
+ break;
5014
+ case "metrics":
5015
+ content = this.renderMetricsSlide(slide);
5016
+ break;
5017
+ case "statement":
5018
+ content = this.renderStatementSlide(slide);
5019
+ break;
5020
+ case "call_to_action":
5021
+ content = this.renderCTASlide(slide);
5022
+ break;
5023
+ case "thank_you":
5024
+ content = this.renderThankYouSlide(slide);
5025
+ break;
5026
+ default:
5027
+ content = this.renderBulletSlide(slide);
5028
+ }
5029
+ return ` <section class="${slideClass}">
5030
+ ${content}
5031
+ </section>`;
5032
+ }
5033
+ /**
5034
+ * Title slide - big, bold, centered.
5035
+ */
5036
+ renderTitleSlide(slide) {
5037
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
5038
+ ${slide.content.subtext ? `<h2>${this.escapeHtml(slide.content.subtext)}</h2>` : ""}`;
5039
+ }
5040
+ /**
5041
+ * Section divider - transition slide.
5042
+ */
5043
+ renderSectionSlide(slide) {
5044
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>`;
5045
+ }
5046
+ /**
5047
+ * Bullet slide - the workhorse.
5048
+ */
5049
+ renderBulletSlide(slide) {
5050
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5051
+ `;
5052
+ if (slide.content.body) {
5053
+ const paragraphs = slide.content.body.split("\n\n");
5054
+ for (const p of paragraphs) {
5055
+ html += ` <p>${this.escapeHtml(p)}</p>
5056
+ `;
5057
+ }
5058
+ }
5059
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
5060
+ html += " <ul>\n";
5061
+ for (const bullet of slide.content.bullets) {
5062
+ const formattedBullet = this.formatBulletText(bullet);
5063
+ html += ` <li>${formattedBullet}</li>
5064
+ `;
5065
+ }
5066
+ html += " </ul>\n";
5067
+ }
5068
+ if (slide.content.source) {
5069
+ html += ` <div class="source">Source: ${this.escapeHtml(slide.content.source)}</div>
5070
+ `;
5071
+ }
5072
+ return html;
5073
+ }
5074
+ /**
5075
+ * Table slide - render tables properly.
5076
+ */
5077
+ renderTableSlide(slide) {
5078
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5079
+ `;
5080
+ if (slide.content.table) {
5081
+ html += this.renderTable(slide.content.table);
5082
+ }
5083
+ return html;
5084
+ }
5085
+ /**
5086
+ * Render a table as clean HTML.
5087
+ */
5088
+ renderTable(table) {
5089
+ let html = ' <table class="data-table">\n';
5090
+ html += " <thead>\n <tr>\n";
5091
+ for (const header of table.headers) {
5092
+ html += ` <th>${this.escapeHtml(header)}</th>
5093
+ `;
5094
+ }
5095
+ html += " </tr>\n </thead>\n";
5096
+ html += " <tbody>\n";
5097
+ for (const row of table.rows) {
5098
+ html += " <tr>\n";
5099
+ for (let i = 0; i < row.length; i++) {
5100
+ const cell = row[i] || "";
5101
+ const isNumber = /^[\d$%,.\-+]+[KMB]?$/.test(cell.trim());
5102
+ const align = isNumber ? ' class="number"' : "";
5103
+ html += ` <td${align}>${this.escapeHtml(cell)}</td>
5104
+ `;
5105
+ }
5106
+ html += " </tr>\n";
5107
+ }
5108
+ html += " </tbody>\n";
5109
+ html += " </table>\n";
5110
+ return html;
5111
+ }
5112
+ /**
5113
+ * Metrics slide - big numbers with labels.
5114
+ */
5115
+ renderMetricsSlide(slide) {
5116
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5117
+ `;
5118
+ html += ' <div class="metrics-container">\n';
5119
+ if (slide.content.metrics) {
5120
+ for (const metric of slide.content.metrics) {
5121
+ const trendClass = metric.trend ? ` metric-trend-${metric.trend}` : "";
5122
+ html += ` <div class="metric${trendClass}">
5123
+ <div class="metric-value">${this.escapeHtml(metric.value)}</div>
5124
+ <div class="metric-label">${this.escapeHtml(metric.label)}</div>
5125
+ </div>
5126
+ `;
5127
+ }
5128
+ }
5129
+ html += " </div>\n";
5130
+ return html;
5131
+ }
5132
+ /**
5133
+ * Statement slide - single powerful message.
5134
+ */
5135
+ renderStatementSlide(slide) {
5136
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5137
+ `;
5138
+ if (slide.content.statement) {
5139
+ html += ` <p class="statement">${this.escapeHtml(slide.content.statement)}</p>
5140
+ `;
5141
+ }
5142
+ if (slide.content.subtext) {
5143
+ html += ` <p class="subtext">${this.escapeHtml(slide.content.subtext)}</p>
5144
+ `;
5145
+ }
5146
+ return html;
5147
+ }
5148
+ /**
5149
+ * Call to action slide.
5150
+ */
5151
+ renderCTASlide(slide) {
5152
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5153
+ `;
5154
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
5155
+ html += " <ul>\n";
5156
+ for (const bullet of slide.content.bullets) {
5157
+ html += ` <li>${this.formatBulletText(bullet)}</li>
5158
+ `;
5159
+ }
5160
+ html += " </ul>\n";
5161
+ }
5162
+ return html;
5163
+ }
5164
+ /**
5165
+ * Thank you slide.
5166
+ */
5167
+ renderThankYouSlide(slide) {
5168
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
5169
+ <h2>Questions?</h2>`;
5170
+ }
5171
+ /**
5172
+ * Format bullet text - handle bold, arrows, etc.
5173
+ */
5174
+ formatBulletText(text) {
5175
+ let formatted = this.escapeHtml(text);
5176
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
5177
+ formatted = formatted.replace(/\*(.+?)\*/g, "<em>$1</em>");
5178
+ formatted = formatted.replace(/→/g, " \u2192 ");
5179
+ formatted = formatted.replace(/->/g, " \u2192 ");
5180
+ return formatted;
5181
+ }
5182
+ /**
5183
+ * Escape HTML special characters.
5184
+ */
5185
+ escapeHtml(text) {
5186
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5187
+ }
5188
+ /**
5189
+ * Professional CSS - McKinsey/BCG style.
5190
+ */
5191
+ getCSS() {
5192
+ return `
5193
+ /* Professional Dark Theme - No Random Images */
5194
+ :root {
5195
+ --bg-primary: #1a1a2e;
5196
+ --bg-secondary: #16213e;
5197
+ --bg-accent: #0f3460;
5198
+ --text-primary: #ffffff;
5199
+ --text-secondary: rgba(255, 255, 255, 0.85);
5200
+ --text-muted: rgba(255, 255, 255, 0.6);
5201
+ --accent-blue: #4a9eff;
5202
+ --accent-green: #00d4aa;
5203
+ --accent-orange: #ff9f43;
5204
+ --border-color: rgba(255, 255, 255, 0.1);
5205
+ }
5206
+
5207
+ .reveal {
5208
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5209
+ }
5210
+
5211
+ .reveal .slides {
5212
+ text-align: left;
5213
+ }
5214
+
5215
+ .reveal .slides section {
5216
+ padding: 60px 80px;
5217
+ background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-accent) 100%);
5218
+ height: 100%;
5219
+ box-sizing: border-box;
5220
+ }
5221
+
5222
+ /* Typography */
5223
+ .reveal h1 {
5224
+ font-size: 52px;
5225
+ font-weight: 700;
5226
+ color: var(--text-primary);
5227
+ margin-bottom: 32px;
5228
+ line-height: 1.1;
5229
+ letter-spacing: -0.02em;
5230
+ }
5231
+
5232
+ .reveal h2 {
5233
+ font-size: 32px;
5234
+ font-weight: 500;
5235
+ color: var(--text-secondary);
5236
+ margin-bottom: 24px;
5237
+ }
5238
+
5239
+ .reveal p {
5240
+ font-size: 24px;
5241
+ color: var(--text-secondary);
5242
+ line-height: 1.6;
5243
+ margin-bottom: 20px;
5244
+ }
5245
+
5246
+ /* Bullets - Complete, readable */
5247
+ .reveal ul {
5248
+ margin: 0;
5249
+ padding: 0;
5250
+ list-style: none;
5251
+ }
5252
+
5253
+ .reveal li {
5254
+ font-size: 24px;
5255
+ color: var(--text-secondary);
5256
+ line-height: 1.5;
5257
+ margin-bottom: 20px;
5258
+ padding-left: 32px;
5259
+ position: relative;
5260
+ }
5261
+
5262
+ .reveal li::before {
5263
+ content: '';
5264
+ position: absolute;
5265
+ left: 0;
5266
+ top: 12px;
5267
+ width: 10px;
5268
+ height: 10px;
5269
+ background: var(--accent-blue);
5270
+ border-radius: 50%;
5271
+ }
5272
+
5273
+ .reveal li strong {
5274
+ color: var(--text-primary);
5275
+ font-weight: 600;
5276
+ }
5277
+
5278
+ /* Tables - Clean, professional */
5279
+ .data-table {
5280
+ width: 100%;
5281
+ border-collapse: collapse;
5282
+ margin-top: 24px;
5283
+ font-size: 20px;
5284
+ }
5285
+
5286
+ .data-table th {
5287
+ background: rgba(255, 255, 255, 0.08);
5288
+ color: var(--text-primary);
5289
+ font-weight: 600;
5290
+ text-align: left;
5291
+ padding: 16px 20px;
5292
+ border-bottom: 2px solid var(--border-color);
5293
+ }
5294
+
5295
+ .data-table td {
5296
+ color: var(--text-secondary);
5297
+ padding: 14px 20px;
5298
+ border-bottom: 1px solid var(--border-color);
5299
+ }
5300
+
5301
+ .data-table td.number {
5302
+ text-align: right;
5303
+ font-family: 'SF Mono', 'Monaco', monospace;
5304
+ font-weight: 500;
5305
+ color: var(--accent-blue);
5306
+ }
5307
+
5308
+ .data-table tbody tr:hover td {
5309
+ background: rgba(255, 255, 255, 0.04);
5310
+ }
5311
+
5312
+ /* Metrics */
5313
+ .metrics-container {
5314
+ display: flex;
5315
+ justify-content: flex-start;
5316
+ gap: 40px;
5317
+ flex-wrap: wrap;
5318
+ margin-top: 40px;
5319
+ }
5320
+
5321
+ .metric {
5322
+ background: rgba(255, 255, 255, 0.05);
5323
+ border: 1px solid var(--border-color);
5324
+ border-radius: 12px;
5325
+ padding: 32px 48px;
5326
+ text-align: center;
5327
+ min-width: 200px;
5328
+ }
5329
+
5330
+ .metric-value {
5331
+ font-size: 56px;
5332
+ font-weight: 700;
5333
+ color: var(--accent-blue);
5334
+ line-height: 1;
5335
+ margin-bottom: 12px;
5336
+ }
5337
+
5338
+ .metric-trend-up .metric-value {
5339
+ color: var(--accent-green);
5340
+ }
5341
+
5342
+ .metric-trend-down .metric-value {
5343
+ color: var(--accent-orange);
5344
+ }
5345
+
5346
+ .metric-label {
5347
+ font-size: 16px;
5348
+ color: var(--text-muted);
5349
+ text-transform: uppercase;
5350
+ letter-spacing: 0.1em;
5351
+ }
5352
+
5353
+ /* Statement slides */
5354
+ .statement {
5355
+ font-size: 36px;
5356
+ color: var(--text-primary);
5357
+ line-height: 1.4;
5358
+ max-width: 80%;
5359
+ }
5360
+
5361
+ .subtext {
5362
+ font-size: 24px;
5363
+ color: var(--text-muted);
5364
+ }
5365
+
5366
+ /* Title slide */
5367
+ .slide-title {
5368
+ display: flex;
5369
+ flex-direction: column;
5370
+ justify-content: center;
5371
+ align-items: center;
5372
+ text-align: center;
5373
+ }
5374
+
5375
+ .slide-title h1 {
5376
+ font-size: 72px;
5377
+ }
5378
+
5379
+ /* Thank you slide */
5380
+ .slide-thank_you {
5381
+ display: flex;
5382
+ flex-direction: column;
5383
+ justify-content: center;
5384
+ align-items: center;
5385
+ text-align: center;
5386
+ }
5387
+
5388
+ .slide-thank_you h1 {
5389
+ font-size: 72px;
5390
+ margin-bottom: 16px;
5391
+ }
5392
+
5393
+ /* Source citation */
5394
+ .source {
5395
+ position: absolute;
5396
+ bottom: 24px;
5397
+ left: 80px;
5398
+ font-size: 14px;
5399
+ color: var(--text-muted);
5400
+ }
5401
+ `;
5402
+ }
5403
+ };
5404
+ function createRendererV2() {
5405
+ return new RendererV2();
5406
+ }
5407
+
4599
5408
  // src/image/NanoBananaProvider.ts
4600
5409
  var NanoBananaProvider = class {
4601
5410
  name = "NanoBanana Pro";
@@ -5639,11 +6448,15 @@ function countWords(slide) {
5639
6448
  NanoBananaProvider,
5640
6449
  Remediator,
5641
6450
  Renderer,
6451
+ RendererV2,
5642
6452
  SlideGenerator,
6453
+ SlideGeneratorV2,
5643
6454
  SlideQualityReviewer,
5644
6455
  VERSION,
5645
6456
  VisualDesignSystem,
5646
6457
  createNanoBananaProvider,
6458
+ createRendererV2,
6459
+ createSlideGeneratorV2,
5647
6460
  generate,
5648
6461
  getContentAnalyzer,
5649
6462
  getDeckQualityReviewer,