claude-presentation-master 3.8.5 → 3.8.7

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
@@ -1,3 +1,5 @@
1
+ import "./chunk-HEBXNMVQ.mjs";
2
+
1
3
  // src/kb/KnowledgeGateway.ts
2
4
  import { readFileSync } from "fs";
3
5
  import { join } from "path";
@@ -2140,29 +2142,29 @@ var SlideGeneratorV2 = class {
2140
2142
  }
2141
2143
  /**
2142
2144
  * Process section content into slides.
2143
- * Tables become table slides. Lists become bullet slides.
2145
+ * RULES:
2146
+ * 1. Tables ALWAYS become table slides - NEVER extract to metrics
2147
+ * 2. Empty sections are SKIPPED - no blank divider slides
2148
+ * 3. Sections with minimal content are combined into statement slides
2144
2149
  */
2145
2150
  processSectionContent(section, startIndex) {
2146
2151
  const slides = [];
2147
- const hasTable = section.tokens.some((t) => t.type === "table");
2152
+ const tableTokens = section.tokens.filter((t) => t.type === "table");
2148
2153
  const hasList = section.tokens.some((t) => t.type === "list");
2149
2154
  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
+ for (const tableToken of tableTokens) {
2156
+ slides.push(this.createTableSlide(section.title, tableToken));
2155
2157
  }
2156
2158
  if (hasList) {
2157
2159
  const listSlides = this.createBulletSlides(section.title, section.tokens);
2158
2160
  slides.push(...listSlides);
2159
2161
  }
2160
- if (hasSignificantText && !hasList && !hasTable) {
2162
+ if (hasSignificantText && !hasList && tableTokens.length === 0) {
2161
2163
  slides.push(this.createContentSlide(section.title, section.tokens));
2162
2164
  }
2163
2165
  if (slides.length === 0 && section.tokens.length > 0) {
2164
2166
  const statement = this.extractStatement(section.tokens);
2165
- if (statement) {
2167
+ if (statement && statement.length > 20) {
2166
2168
  slides.push({
2167
2169
  index: startIndex,
2168
2170
  type: "statement",
@@ -2193,7 +2195,10 @@ var SlideGeneratorV2 = class {
2193
2195
  }
2194
2196
  /**
2195
2197
  * Create bullet slides from list content.
2196
- * NEVER truncate bullets. Split into multiple slides if needed.
2198
+ * RULES:
2199
+ * 1. NEVER truncate bullets
2200
+ * 2. NEVER create slides with only 1-2 bullets (orphan slides)
2201
+ * 3. If there are fewer than minBulletsToKeep bullets, combine them into a statement slide
2197
2202
  */
2198
2203
  createBulletSlides(title, tokens) {
2199
2204
  const slides = [];
@@ -2208,40 +2213,61 @@ var SlideGeneratorV2 = class {
2208
2213
  }
2209
2214
  }
2210
2215
  }
2216
+ if (allBullets.length === 0) {
2217
+ return slides;
2218
+ }
2211
2219
  const maxBullets = this.config.maxBulletsPerSlide;
2212
2220
  const minBullets = this.config.minBulletsToKeep;
2221
+ if (allBullets.length < minBullets) {
2222
+ const statement = allBullets.join(" \u2022 ");
2223
+ slides.push({
2224
+ index: 0,
2225
+ type: "statement",
2226
+ title,
2227
+ content: { statement }
2228
+ });
2229
+ return slides;
2230
+ }
2213
2231
  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
- }
2232
+ slides.push({
2233
+ index: 0,
2234
+ type: "bullets",
2235
+ title,
2236
+ content: { bullets: allBullets }
2237
+ });
2230
2238
  } else {
2231
- for (let i = 0; i < allBullets.length; i += maxBullets) {
2232
- const chunk = allBullets.slice(i, i + maxBullets);
2239
+ const chunks = this.splitBulletsWithoutOrphans(allBullets, maxBullets, minBullets);
2240
+ for (let i = 0; i < chunks.length; i++) {
2233
2241
  const isFirst = i === 0;
2234
2242
  const slideTitle = isFirst ? title : `${title} (continued)`;
2235
2243
  slides.push({
2236
2244
  index: 0,
2237
2245
  type: "bullets",
2238
2246
  title: slideTitle,
2239
- content: { bullets: chunk }
2247
+ content: { bullets: chunks[i] }
2240
2248
  });
2241
2249
  }
2242
2250
  }
2243
2251
  return slides;
2244
2252
  }
2253
+ /**
2254
+ * Split bullets into chunks without creating orphan slides.
2255
+ * If the last chunk would have fewer than minBullets, steal from previous chunk.
2256
+ */
2257
+ splitBulletsWithoutOrphans(bullets, maxBullets, minBullets) {
2258
+ const chunks = [];
2259
+ for (let i = 0; i < bullets.length; i += maxBullets) {
2260
+ chunks.push(bullets.slice(i, i + maxBullets));
2261
+ }
2262
+ const lastChunk = chunks[chunks.length - 1];
2263
+ if (lastChunk && lastChunk.length < minBullets && chunks.length > 1) {
2264
+ const prevChunk = chunks[chunks.length - 2];
2265
+ const bulletsToMove = minBullets - lastChunk.length;
2266
+ const movedBullets = prevChunk.splice(-bulletsToMove);
2267
+ chunks[chunks.length - 1] = [...movedBullets, ...lastChunk];
2268
+ }
2269
+ return chunks;
2270
+ }
2245
2271
  /**
2246
2272
  * Get complete text from a list item, including nested content.
2247
2273
  * NEVER truncate.
@@ -4883,7 +4909,71 @@ async function initRenderer() {
4883
4909
  }
4884
4910
 
4885
4911
  // src/output/RendererV2.ts
4912
+ import { writeFileSync } from "fs";
4913
+ var THEMES = {
4914
+ mckinsey: {
4915
+ style: "mckinsey",
4916
+ primaryColor: "#003366",
4917
+ // McKinsey blue
4918
+ accentColor: "#0066cc",
4919
+ description: "White background, professional hierarchy - for consulting/analysis decks"
4920
+ },
4921
+ dark: {
4922
+ style: "dark",
4923
+ primaryColor: "#1a1a2e",
4924
+ accentColor: "#4a9eff",
4925
+ description: "Dark dramatic theme - for keynotes and tech presentations"
4926
+ },
4927
+ minimal: {
4928
+ style: "minimal",
4929
+ primaryColor: "#333333",
4930
+ accentColor: "#0066cc",
4931
+ description: "Clean minimal white - for training and documentation"
4932
+ },
4933
+ corporate: {
4934
+ style: "mckinsey",
4935
+ // Uses McKinsey CSS but with different branding potential
4936
+ primaryColor: "#1a365d",
4937
+ accentColor: "#3182ce",
4938
+ description: "Professional corporate - for executive presentations"
4939
+ },
4940
+ startup: {
4941
+ style: "dark",
4942
+ primaryColor: "#0d1117",
4943
+ accentColor: "#58a6ff",
4944
+ description: "Modern dark - for pitch decks and product demos"
4945
+ }
4946
+ };
4947
+ var PRESENTATION_TYPE_TO_THEME = {
4948
+ // Keynotes → Dark dramatic style (TED talks, big stage)
4949
+ ted_keynote: "dark",
4950
+ // Sales → Dark dramatic style (persuasion, impact)
4951
+ sales_pitch: "dark",
4952
+ // Consulting/Analysis decks → McKinsey white style (data-heavy, professional)
4953
+ consulting_deck: "mckinsey",
4954
+ // Investment Banking → McKinsey style (financial, dense data)
4955
+ investment_banking: "mckinsey",
4956
+ // Investor Pitch → Startup dark style (modern, VC audiences)
4957
+ investor_pitch: "startup",
4958
+ // Technical → Dark style (engineering audiences like dark mode)
4959
+ technical_presentation: "dark",
4960
+ // All Hands → Minimal clean style (readable, accessible)
4961
+ all_hands: "minimal"
4962
+ };
4886
4963
  var RendererV2 = class {
4964
+ theme;
4965
+ presentationType;
4966
+ /**
4967
+ * Create renderer with theme based on presentation type.
4968
+ * @param presentationType - The type of deck being created (determines theme)
4969
+ * @param themeOverride - Optional explicit theme override
4970
+ */
4971
+ constructor(presentationType = "consulting_deck", themeOverride) {
4972
+ this.presentationType = presentationType;
4973
+ const themeStyle = themeOverride || PRESENTATION_TYPE_TO_THEME[presentationType] || "mckinsey";
4974
+ this.theme = THEMES[themeStyle] || THEMES.mckinsey;
4975
+ console.log(`[RendererV2] Using "${this.theme.style}" theme for "${presentationType}" deck`);
4976
+ }
4887
4977
  /**
4888
4978
  * Render slides to complete HTML document.
4889
4979
  */
@@ -4923,202 +5013,1020 @@ ${slidesHtml}
4923
5013
  </html>`;
4924
5014
  }
4925
5015
  /**
4926
- * Render a single slide.
5016
+ * Render slides to both HTML and PDF files.
5017
+ * Always generates both formats for easy sharing.
4927
5018
  */
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>`;
5019
+ async renderToFiles(slides, outputPath, title = "Presentation") {
5020
+ const html = this.render(slides, title);
5021
+ const htmlPath = outputPath.replace(/\.pdf$/i, "") + ".html";
5022
+ const pdfPath = outputPath.replace(/\.html$/i, "") + ".pdf";
5023
+ writeFileSync(htmlPath, html);
5024
+ console.log(`\u2705 HTML saved: ${htmlPath}`);
5025
+ await this.renderToPDF(slides, pdfPath, title);
5026
+ console.log(`\u2705 PDF saved: ${pdfPath}`);
5027
+ return { htmlPath, pdfPath };
4962
5028
  }
4963
5029
  /**
4964
- * Title slide - big, bold, centered.
5030
+ * Render slides directly to PDF using Puppeteer.
5031
+ * Creates a printable PDF with one slide per page.
4965
5032
  */
4966
- renderTitleSlide(slide) {
4967
- return ` <h1>${this.escapeHtml(slide.title)}</h1>
4968
- ${slide.content.subtext ? `<h2>${this.escapeHtml(slide.content.subtext)}</h2>` : ""}`;
5033
+ async renderToPDF(slides, outputPath, title = "Presentation") {
5034
+ const puppeteer = await import("./puppeteer-EG4MVFUF.mjs");
5035
+ const printHtml = this.renderForPrint(slides, title);
5036
+ const browser = await puppeteer.default.launch({
5037
+ headless: true,
5038
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
5039
+ });
5040
+ try {
5041
+ const page = await browser.newPage();
5042
+ await page.setViewport({ width: 1920, height: 1080 });
5043
+ await page.setContent(printHtml, { waitUntil: "networkidle0" });
5044
+ const pdfBuffer = await page.pdf({
5045
+ path: outputPath,
5046
+ width: "1920px",
5047
+ height: "1080px",
5048
+ printBackground: true,
5049
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
5050
+ preferCSSPageSize: true
5051
+ });
5052
+ return pdfBuffer;
5053
+ } finally {
5054
+ await browser.close();
5055
+ }
4969
5056
  }
4970
5057
  /**
4971
- * Section divider - transition slide.
5058
+ * Render HTML optimized for PDF printing (no reveal.js, static pages).
4972
5059
  */
4973
- renderSectionSlide(slide) {
4974
- return ` <h1>${this.escapeHtml(slide.title)}</h1>`;
5060
+ renderForPrint(slides, title) {
5061
+ const slidesHtml = slides.map((slide) => {
5062
+ const content = this.renderSlideContent(slide);
5063
+ return `<div class="slide slide-${slide.type}">${content}</div>`;
5064
+ }).join("\n");
5065
+ if (this.theme.style === "mckinsey") {
5066
+ return this.getMcKinseyPrintHTML(title, slidesHtml);
5067
+ }
5068
+ return this.getDarkPrintHTML(title, slidesHtml);
4975
5069
  }
4976
5070
  /**
4977
- * Bullet slide - the workhorse.
5071
+ * McKinsey-style print HTML (white background, professional).
4978
5072
  */
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
- }
5073
+ getMcKinseyPrintHTML(title, slidesHtml) {
5074
+ return `<!DOCTYPE html>
5075
+ <html lang="en">
5076
+ <head>
5077
+ <meta charset="UTF-8">
5078
+ <title>${this.escapeHtml(title)}</title>
5079
+ <style>
5080
+ @page {
5081
+ size: 1920px 1080px;
5082
+ margin: 0;
4988
5083
  }
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";
5084
+
5085
+ * {
5086
+ box-sizing: border-box;
5087
+ margin: 0;
5088
+ padding: 0;
4997
5089
  }
4998
- if (slide.content.source) {
4999
- html += ` <div class="source">Source: ${this.escapeHtml(slide.content.source)}</div>
5000
- `;
5090
+
5091
+ body {
5092
+ font-family: 'Arial', 'Helvetica', sans-serif;
5093
+ background: #ffffff;
5094
+ color: #1a1a1a;
5001
5095
  }
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);
5096
+
5097
+ .slide {
5098
+ width: 1920px;
5099
+ height: 1080px;
5100
+ padding: 0;
5101
+ background: #ffffff;
5102
+ page-break-after: always;
5103
+ page-break-inside: avoid;
5104
+ position: relative;
5105
+ display: flex;
5106
+ flex-direction: column;
5107
+ overflow: hidden;
5012
5108
  }
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
- `;
5109
+
5110
+ /* McKinsey header bar */
5111
+ .slide::before {
5112
+ content: '';
5113
+ display: block;
5114
+ height: 8px;
5115
+ background: #003366;
5116
+ width: 100%;
5117
+ flex-shrink: 0;
5024
5118
  }
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";
5119
+
5120
+ .slide-content-area {
5121
+ padding: 60px 80px;
5122
+ flex: 1;
5123
+ display: flex;
5124
+ flex-direction: column;
5037
5125
  }
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
- }
5126
+
5127
+ .slide:last-child {
5128
+ page-break-after: auto;
5058
5129
  }
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
- `;
5130
+
5131
+ h1 {
5132
+ font-size: 44px;
5133
+ font-weight: 700;
5134
+ color: #003366;
5135
+ margin-bottom: 24px;
5136
+ line-height: 1.2;
5137
+ border-bottom: 3px solid #003366;
5138
+ padding-bottom: 16px;
5071
5139
  }
5072
- if (slide.content.subtext) {
5073
- html += ` <p class="subtext">${this.escapeHtml(slide.content.subtext)}</p>
5074
- `;
5140
+
5141
+ h2 {
5142
+ font-size: 28px;
5143
+ font-weight: 600;
5144
+ color: #333333;
5145
+ margin-bottom: 20px;
5075
5146
  }
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";
5147
+
5148
+ p {
5149
+ font-size: 22px;
5150
+ color: #333333;
5151
+ line-height: 1.6;
5152
+ margin-bottom: 16px;
5091
5153
  }
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.
5154
+
5155
+ ul {
5156
+ margin: 20px 0 0 0;
5157
+ padding: 0;
5158
+ list-style: none;
5159
+ flex: 1;
5160
+ }
5161
+
5162
+ li {
5163
+ font-size: 22px;
5164
+ color: #333333;
5165
+ line-height: 1.5;
5166
+ margin-bottom: 16px;
5167
+ padding-left: 28px;
5168
+ position: relative;
5169
+ }
5170
+
5171
+ li::before {
5172
+ content: '\u25A0';
5173
+ position: absolute;
5174
+ left: 0;
5175
+ top: 0;
5176
+ color: #003366;
5177
+ font-size: 12px;
5178
+ }
5179
+
5180
+ li strong {
5181
+ color: #1a1a1a;
5182
+ font-weight: 700;
5183
+ }
5184
+
5185
+ .data-table {
5186
+ width: 100%;
5187
+ border-collapse: collapse;
5188
+ margin-top: 24px;
5189
+ font-size: 18px;
5190
+ }
5191
+
5192
+ .data-table th {
5193
+ background: #003366;
5194
+ color: #ffffff;
5195
+ font-weight: 600;
5196
+ text-align: left;
5197
+ padding: 14px 16px;
5198
+ border: 1px solid #003366;
5199
+ }
5200
+
5201
+ .data-table td {
5202
+ color: #333333;
5203
+ padding: 12px 16px;
5204
+ border: 1px solid #dee2e6;
5205
+ background: #ffffff;
5206
+ }
5207
+
5208
+ .data-table tbody tr:nth-child(even) td {
5209
+ background: #f8f9fa;
5210
+ }
5211
+
5212
+ .data-table td.number {
5213
+ text-align: right;
5214
+ font-weight: 600;
5215
+ color: #003366;
5216
+ }
5217
+
5218
+ .metrics-container {
5219
+ display: flex;
5220
+ justify-content: flex-start;
5221
+ gap: 32px;
5222
+ flex-wrap: wrap;
5223
+ margin-top: 32px;
5224
+ }
5225
+
5226
+ .metric {
5227
+ background: #f8f9fa;
5228
+ border: 2px solid #dee2e6;
5229
+ border-top: 4px solid #003366;
5230
+ padding: 24px 32px;
5231
+ text-align: center;
5232
+ min-width: 180px;
5233
+ }
5234
+
5235
+ .metric-value {
5236
+ font-size: 48px;
5237
+ font-weight: 700;
5238
+ color: #003366;
5239
+ line-height: 1;
5240
+ margin-bottom: 8px;
5241
+ }
5242
+
5243
+ .metric-label {
5244
+ font-size: 14px;
5245
+ color: #666666;
5246
+ text-transform: uppercase;
5247
+ letter-spacing: 0.05em;
5248
+ }
5249
+
5250
+ .statement {
5251
+ font-size: 32px;
5252
+ color: #1a1a1a;
5253
+ line-height: 1.5;
5254
+ max-width: 85%;
5255
+ font-style: italic;
5256
+ }
5257
+
5258
+ .subtext {
5259
+ font-size: 20px;
5260
+ color: #666666;
5261
+ }
5262
+
5263
+ .slide-title, .slide-thank_you {
5264
+ justify-content: center;
5265
+ align-items: center;
5266
+ text-align: center;
5267
+ background: #003366 !important;
5268
+ }
5269
+
5270
+ .slide-title::before, .slide-thank_you::before {
5271
+ display: none !important;
5272
+ }
5273
+
5274
+ .slide-title h1, .slide-thank_you h1 {
5275
+ font-size: 56px;
5276
+ color: #ffffff;
5277
+ border-bottom: none;
5278
+ padding-bottom: 0;
5279
+ }
5280
+
5281
+ .slide-title h2, .slide-thank_you h2 {
5282
+ color: rgba(255, 255, 255, 0.8);
5283
+ }
5284
+
5285
+ .source {
5286
+ position: absolute;
5287
+ bottom: 24px;
5288
+ left: 80px;
5289
+ font-size: 12px;
5290
+ color: #666666;
5291
+ font-style: italic;
5292
+ }
5293
+ </style>
5294
+ </head>
5295
+ <body>
5296
+ ${slidesHtml}
5297
+ </body>
5298
+ </html>`;
5299
+ }
5300
+ /**
5301
+ * Dark theme print HTML (legacy).
5302
+ */
5303
+ getDarkPrintHTML(title, slidesHtml) {
5304
+ return `<!DOCTYPE html>
5305
+ <html lang="en">
5306
+ <head>
5307
+ <meta charset="UTF-8">
5308
+ <title>${this.escapeHtml(title)}</title>
5309
+ <style>
5310
+ @page {
5311
+ size: 1920px 1080px;
5312
+ margin: 0;
5313
+ }
5314
+
5315
+ * {
5316
+ box-sizing: border-box;
5317
+ margin: 0;
5318
+ padding: 0;
5319
+ }
5320
+
5321
+ body {
5322
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5323
+ background: #1a1a2e;
5324
+ color: #ffffff;
5325
+ }
5326
+
5327
+ .slide {
5328
+ width: 1920px;
5329
+ height: 1080px;
5330
+ padding: 80px 100px;
5331
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
5332
+ page-break-after: always;
5333
+ page-break-inside: avoid;
5334
+ position: relative;
5335
+ display: flex;
5336
+ flex-direction: column;
5337
+ justify-content: flex-start;
5338
+ overflow: hidden;
5339
+ }
5340
+
5341
+ /* Content area takes full available height */
5342
+ .slide ul,
5343
+ .slide .data-table,
5344
+ .slide .statement {
5345
+ flex: 1;
5346
+ display: flex;
5347
+ flex-direction: column;
5348
+ justify-content: flex-start;
5349
+ }
5350
+
5351
+ .slide ul {
5352
+ margin-top: 20px;
5353
+ }
5354
+
5355
+ .slide:last-child {
5356
+ page-break-after: auto;
5357
+ }
5358
+
5359
+ h1 {
5360
+ font-size: 52px;
5361
+ font-weight: 700;
5362
+ color: #ffffff;
5363
+ margin-bottom: 32px;
5364
+ line-height: 1.1;
5365
+ letter-spacing: -0.02em;
5366
+ }
5367
+
5368
+ h2 {
5369
+ font-size: 32px;
5370
+ font-weight: 500;
5371
+ color: rgba(255, 255, 255, 0.85);
5372
+ margin-bottom: 24px;
5373
+ }
5374
+
5375
+ p {
5376
+ font-size: 24px;
5377
+ color: rgba(255, 255, 255, 0.85);
5378
+ line-height: 1.6;
5379
+ margin-bottom: 20px;
5380
+ }
5381
+
5382
+ ul {
5383
+ margin: 0;
5384
+ padding: 0;
5385
+ list-style: none;
5386
+ }
5387
+
5388
+ li {
5389
+ font-size: 24px;
5390
+ color: rgba(255, 255, 255, 0.85);
5391
+ line-height: 1.5;
5392
+ margin-bottom: 20px;
5393
+ padding-left: 32px;
5394
+ position: relative;
5395
+ }
5396
+
5397
+ li::before {
5398
+ content: '';
5399
+ position: absolute;
5400
+ left: 0;
5401
+ top: 12px;
5402
+ width: 10px;
5403
+ height: 10px;
5404
+ background: #4a9eff;
5405
+ border-radius: 50%;
5406
+ }
5407
+
5408
+ li strong {
5409
+ color: #ffffff;
5410
+ font-weight: 600;
5411
+ }
5412
+
5413
+ .data-table {
5414
+ width: 100%;
5415
+ border-collapse: collapse;
5416
+ margin-top: 24px;
5417
+ font-size: 20px;
5418
+ }
5419
+
5420
+ .data-table th {
5421
+ background: rgba(255, 255, 255, 0.08);
5422
+ color: #ffffff;
5423
+ font-weight: 600;
5424
+ text-align: left;
5425
+ padding: 16px 20px;
5426
+ border-bottom: 2px solid rgba(255, 255, 255, 0.1);
5427
+ }
5428
+
5429
+ .data-table td {
5430
+ color: rgba(255, 255, 255, 0.85);
5431
+ padding: 14px 20px;
5432
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
5433
+ }
5434
+
5435
+ .data-table td.number {
5436
+ text-align: right;
5437
+ font-family: 'SF Mono', 'Monaco', monospace;
5438
+ font-weight: 500;
5439
+ color: #4a9eff;
5440
+ }
5441
+
5442
+ .metrics-container {
5443
+ display: flex;
5444
+ justify-content: flex-start;
5445
+ gap: 40px;
5446
+ flex-wrap: wrap;
5447
+ margin-top: 40px;
5448
+ }
5449
+
5450
+ .metric {
5451
+ background: rgba(255, 255, 255, 0.05);
5452
+ border: 1px solid rgba(255, 255, 255, 0.1);
5453
+ border-radius: 12px;
5454
+ padding: 32px 48px;
5455
+ text-align: center;
5456
+ min-width: 200px;
5457
+ }
5458
+
5459
+ .metric-value {
5460
+ font-size: 56px;
5461
+ font-weight: 700;
5462
+ color: #4a9eff;
5463
+ line-height: 1;
5464
+ margin-bottom: 12px;
5465
+ }
5466
+
5467
+ .metric-label {
5468
+ font-size: 16px;
5469
+ color: rgba(255, 255, 255, 0.6);
5470
+ text-transform: uppercase;
5471
+ letter-spacing: 0.1em;
5472
+ }
5473
+
5474
+ .statement {
5475
+ font-size: 36px;
5476
+ color: #ffffff;
5477
+ line-height: 1.4;
5478
+ max-width: 80%;
5479
+ }
5480
+
5481
+ .subtext {
5482
+ font-size: 24px;
5483
+ color: rgba(255, 255, 255, 0.6);
5484
+ }
5485
+
5486
+ .slide-title, .slide-thank_you {
5487
+ justify-content: center;
5488
+ align-items: center;
5489
+ text-align: center;
5490
+ }
5491
+
5492
+ .slide-title h1, .slide-thank_you h1 {
5493
+ font-size: 72px;
5494
+ }
5495
+
5496
+ .source {
5497
+ position: absolute;
5498
+ bottom: 24px;
5499
+ left: 80px;
5500
+ font-size: 14px;
5501
+ color: rgba(255, 255, 255, 0.6);
5502
+ }
5503
+ </style>
5504
+ </head>
5505
+ <body>
5506
+ ${slidesHtml}
5507
+ </body>
5508
+ </html>`;
5509
+ }
5510
+ /**
5511
+ * Render slide content (shared between HTML and PDF renderers).
5512
+ */
5513
+ renderSlideContent(slide) {
5514
+ switch (slide.type) {
5515
+ case "title":
5516
+ return this.renderTitleSlide(slide);
5517
+ case "section":
5518
+ return this.renderSectionSlide(slide);
5519
+ case "bullets":
5520
+ return this.renderBulletSlide(slide);
5521
+ case "table":
5522
+ return this.renderTableSlide(slide);
5523
+ case "metrics":
5524
+ return this.renderMetricsSlide(slide);
5525
+ case "statement":
5526
+ return this.renderStatementSlide(slide);
5527
+ case "call_to_action":
5528
+ return this.renderCTASlide(slide);
5529
+ case "thank_you":
5530
+ return this.renderThankYouSlide(slide);
5531
+ default:
5532
+ return this.renderBulletSlide(slide);
5533
+ }
5534
+ }
5535
+ /**
5536
+ * Render a single slide.
5537
+ */
5538
+ renderSlide(slide) {
5539
+ const slideClass = `slide-${slide.type}`;
5540
+ let content = "";
5541
+ switch (slide.type) {
5542
+ case "title":
5543
+ content = this.renderTitleSlide(slide);
5544
+ break;
5545
+ case "section":
5546
+ content = this.renderSectionSlide(slide);
5547
+ break;
5548
+ case "bullets":
5549
+ content = this.renderBulletSlide(slide);
5550
+ break;
5551
+ case "table":
5552
+ content = this.renderTableSlide(slide);
5553
+ break;
5554
+ case "metrics":
5555
+ content = this.renderMetricsSlide(slide);
5556
+ break;
5557
+ case "statement":
5558
+ content = this.renderStatementSlide(slide);
5559
+ break;
5560
+ case "call_to_action":
5561
+ content = this.renderCTASlide(slide);
5562
+ break;
5563
+ case "thank_you":
5564
+ content = this.renderThankYouSlide(slide);
5565
+ break;
5566
+ default:
5567
+ content = this.renderBulletSlide(slide);
5568
+ }
5569
+ return ` <section class="${slideClass}">
5570
+ ${content}
5571
+ </section>`;
5572
+ }
5573
+ /**
5574
+ * Title slide - big, bold, centered.
5575
+ */
5576
+ renderTitleSlide(slide) {
5577
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
5578
+ ${slide.content.subtext ? `<h2>${this.escapeHtml(slide.content.subtext)}</h2>` : ""}`;
5579
+ }
5580
+ /**
5581
+ * Section divider - transition slide.
5582
+ */
5583
+ renderSectionSlide(slide) {
5584
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>`;
5585
+ }
5586
+ /**
5587
+ * Bullet slide - the workhorse.
5588
+ */
5589
+ renderBulletSlide(slide) {
5590
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5591
+ `;
5592
+ if (slide.content.body) {
5593
+ const paragraphs = slide.content.body.split("\n\n");
5594
+ for (const p of paragraphs) {
5595
+ html += ` <p>${this.escapeHtml(p)}</p>
5596
+ `;
5597
+ }
5598
+ }
5599
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
5600
+ html += " <ul>\n";
5601
+ for (const bullet of slide.content.bullets) {
5602
+ const formattedBullet = this.formatBulletText(bullet);
5603
+ html += ` <li>${formattedBullet}</li>
5604
+ `;
5605
+ }
5606
+ html += " </ul>\n";
5607
+ }
5608
+ if (slide.content.source) {
5609
+ html += ` <div class="source">Source: ${this.escapeHtml(slide.content.source)}</div>
5610
+ `;
5611
+ }
5612
+ return html;
5613
+ }
5614
+ /**
5615
+ * Table slide - render tables properly.
5616
+ */
5617
+ renderTableSlide(slide) {
5618
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5619
+ `;
5620
+ if (slide.content.table) {
5621
+ html += this.renderTable(slide.content.table);
5622
+ }
5623
+ return html;
5624
+ }
5625
+ /**
5626
+ * Render a table as clean HTML.
5627
+ */
5628
+ renderTable(table) {
5629
+ let html = ' <table class="data-table">\n';
5630
+ html += " <thead>\n <tr>\n";
5631
+ for (const header of table.headers) {
5632
+ html += ` <th>${this.escapeHtml(header)}</th>
5633
+ `;
5634
+ }
5635
+ html += " </tr>\n </thead>\n";
5636
+ html += " <tbody>\n";
5637
+ for (const row of table.rows) {
5638
+ html += " <tr>\n";
5639
+ for (let i = 0; i < row.length; i++) {
5640
+ const cell = row[i] || "";
5641
+ const isNumber = /^[\d$%,.\-+]+[KMB]?$/.test(cell.trim());
5642
+ const align = isNumber ? ' class="number"' : "";
5643
+ html += ` <td${align}>${this.escapeHtml(cell)}</td>
5644
+ `;
5645
+ }
5646
+ html += " </tr>\n";
5647
+ }
5648
+ html += " </tbody>\n";
5649
+ html += " </table>\n";
5650
+ return html;
5651
+ }
5652
+ /**
5653
+ * Metrics slide - big numbers with labels.
5654
+ */
5655
+ renderMetricsSlide(slide) {
5656
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5657
+ `;
5658
+ html += ' <div class="metrics-container">\n';
5659
+ if (slide.content.metrics) {
5660
+ for (const metric of slide.content.metrics) {
5661
+ const trendClass = metric.trend ? ` metric-trend-${metric.trend}` : "";
5662
+ html += ` <div class="metric${trendClass}">
5663
+ <div class="metric-value">${this.escapeHtml(metric.value)}</div>
5664
+ <div class="metric-label">${this.escapeHtml(metric.label)}</div>
5665
+ </div>
5666
+ `;
5667
+ }
5668
+ }
5669
+ html += " </div>\n";
5670
+ return html;
5671
+ }
5672
+ /**
5673
+ * Statement slide - single powerful message.
5674
+ */
5675
+ renderStatementSlide(slide) {
5676
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5677
+ `;
5678
+ if (slide.content.statement) {
5679
+ html += ` <p class="statement">${this.escapeHtml(slide.content.statement)}</p>
5680
+ `;
5681
+ }
5682
+ if (slide.content.subtext) {
5683
+ html += ` <p class="subtext">${this.escapeHtml(slide.content.subtext)}</p>
5684
+ `;
5685
+ }
5686
+ return html;
5687
+ }
5688
+ /**
5689
+ * Call to action slide.
5690
+ */
5691
+ renderCTASlide(slide) {
5692
+ let html = ` <h1>${this.escapeHtml(slide.title)}</h1>
5693
+ `;
5694
+ if (slide.content.bullets && slide.content.bullets.length > 0) {
5695
+ html += " <ul>\n";
5696
+ for (const bullet of slide.content.bullets) {
5697
+ html += ` <li>${this.formatBulletText(bullet)}</li>
5698
+ `;
5699
+ }
5700
+ html += " </ul>\n";
5701
+ }
5702
+ return html;
5703
+ }
5704
+ /**
5705
+ * Thank you slide.
5706
+ */
5707
+ renderThankYouSlide(slide) {
5708
+ return ` <h1>${this.escapeHtml(slide.title)}</h1>
5709
+ <h2>Questions?</h2>`;
5710
+ }
5711
+ /**
5712
+ * Format bullet text - handle bold, arrows, etc.
5713
+ */
5714
+ formatBulletText(text) {
5715
+ let formatted = this.escapeHtml(text);
5716
+ formatted = formatted.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
5717
+ formatted = formatted.replace(/\*(.+?)\*/g, "<em>$1</em>");
5718
+ formatted = formatted.replace(/→/g, " \u2192 ");
5719
+ formatted = formatted.replace(/->/g, " \u2192 ");
5720
+ return formatted;
5721
+ }
5722
+ /**
5723
+ * Escape HTML special characters.
5724
+ */
5725
+ escapeHtml(text) {
5726
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
5727
+ }
5728
+ /**
5729
+ * Professional CSS - McKinsey/BCG consulting style.
5120
5730
  */
5121
5731
  getCSS() {
5732
+ if (this.theme.style === "mckinsey") {
5733
+ return this.getMcKinseyCSS();
5734
+ }
5735
+ return this.getDarkCSS();
5736
+ }
5737
+ /**
5738
+ * McKinsey/BCG Consulting Style - WHITE background, professional hierarchy.
5739
+ */
5740
+ getMcKinseyCSS() {
5741
+ return `
5742
+ /* McKinsey Consulting Theme - Clean White, Strong Hierarchy */
5743
+ :root {
5744
+ --bg-primary: #ffffff;
5745
+ --bg-secondary: #f8f9fa;
5746
+ --bg-accent: #e9ecef;
5747
+ --text-primary: #1a1a1a;
5748
+ --text-secondary: #333333;
5749
+ --text-muted: #666666;
5750
+ --mckinsey-blue: #003366;
5751
+ --accent-blue: #0066cc;
5752
+ --accent-green: #28a745;
5753
+ --accent-red: #dc3545;
5754
+ --border-color: #dee2e6;
5755
+ --header-bar: #003366;
5756
+ }
5757
+
5758
+ .reveal {
5759
+ font-family: 'Georgia', 'Times New Roman', serif;
5760
+ }
5761
+
5762
+ .reveal .slides {
5763
+ text-align: left;
5764
+ }
5765
+
5766
+ .reveal .slides section {
5767
+ padding: 0;
5768
+ background: var(--bg-primary);
5769
+ height: 100%;
5770
+ box-sizing: border-box;
5771
+ display: flex !important;
5772
+ flex-direction: column;
5773
+ justify-content: flex-start;
5774
+ align-items: stretch;
5775
+ }
5776
+
5777
+ /* McKinsey-style header bar */
5778
+ .reveal .slides section::before {
5779
+ content: '';
5780
+ display: block;
5781
+ height: 8px;
5782
+ background: var(--header-bar);
5783
+ width: 100%;
5784
+ flex-shrink: 0;
5785
+ }
5786
+
5787
+ .reveal .slides section .slide-content {
5788
+ padding: 60px 80px;
5789
+ flex: 1;
5790
+ display: flex;
5791
+ flex-direction: column;
5792
+ }
5793
+
5794
+ .reveal .slides section > ul,
5795
+ .reveal .slides section > .data-table {
5796
+ flex: 1;
5797
+ display: flex;
5798
+ flex-direction: column;
5799
+ justify-content: flex-start;
5800
+ }
5801
+
5802
+ /* Typography - Strong Hierarchy */
5803
+ .reveal h1 {
5804
+ font-size: 44px;
5805
+ font-weight: 700;
5806
+ color: var(--mckinsey-blue);
5807
+ margin-bottom: 24px;
5808
+ line-height: 1.2;
5809
+ letter-spacing: -0.01em;
5810
+ font-family: 'Arial', 'Helvetica', sans-serif;
5811
+ border-bottom: 3px solid var(--mckinsey-blue);
5812
+ padding-bottom: 16px;
5813
+ }
5814
+
5815
+ .reveal h2 {
5816
+ font-size: 28px;
5817
+ font-weight: 600;
5818
+ color: var(--text-secondary);
5819
+ margin-bottom: 20px;
5820
+ font-family: 'Arial', 'Helvetica', sans-serif;
5821
+ }
5822
+
5823
+ .reveal p {
5824
+ font-size: 22px;
5825
+ color: var(--text-secondary);
5826
+ line-height: 1.6;
5827
+ margin-bottom: 16px;
5828
+ }
5829
+
5830
+ /* Bullets - Clear, readable */
5831
+ .reveal ul {
5832
+ margin: 20px 0 0 0;
5833
+ padding: 0;
5834
+ list-style: none;
5835
+ }
5836
+
5837
+ .reveal li {
5838
+ font-size: 22px;
5839
+ color: var(--text-secondary);
5840
+ line-height: 1.5;
5841
+ margin-bottom: 16px;
5842
+ padding-left: 28px;
5843
+ position: relative;
5844
+ }
5845
+
5846
+ .reveal li::before {
5847
+ content: '\u25A0';
5848
+ position: absolute;
5849
+ left: 0;
5850
+ top: 0;
5851
+ color: var(--mckinsey-blue);
5852
+ font-size: 12px;
5853
+ }
5854
+
5855
+ .reveal li strong {
5856
+ color: var(--text-primary);
5857
+ font-weight: 700;
5858
+ }
5859
+
5860
+ /* Tables - Clean consulting style */
5861
+ .data-table {
5862
+ width: 100%;
5863
+ border-collapse: collapse;
5864
+ margin-top: 24px;
5865
+ font-size: 18px;
5866
+ font-family: 'Arial', sans-serif;
5867
+ }
5868
+
5869
+ .data-table th {
5870
+ background: var(--mckinsey-blue);
5871
+ color: #ffffff;
5872
+ font-weight: 600;
5873
+ text-align: left;
5874
+ padding: 14px 16px;
5875
+ border: 1px solid var(--mckinsey-blue);
5876
+ }
5877
+
5878
+ .data-table td {
5879
+ color: var(--text-secondary);
5880
+ padding: 12px 16px;
5881
+ border: 1px solid var(--border-color);
5882
+ background: var(--bg-primary);
5883
+ }
5884
+
5885
+ .data-table tbody tr:nth-child(even) td {
5886
+ background: var(--bg-secondary);
5887
+ }
5888
+
5889
+ .data-table td.number {
5890
+ text-align: right;
5891
+ font-family: 'Arial', sans-serif;
5892
+ font-weight: 600;
5893
+ color: var(--mckinsey-blue);
5894
+ }
5895
+
5896
+ .data-table tbody tr:hover td {
5897
+ background: #e3f2fd;
5898
+ }
5899
+
5900
+ /* Metrics - Clean boxes */
5901
+ .metrics-container {
5902
+ display: flex;
5903
+ justify-content: flex-start;
5904
+ gap: 32px;
5905
+ flex-wrap: wrap;
5906
+ margin-top: 32px;
5907
+ }
5908
+
5909
+ .metric {
5910
+ background: var(--bg-secondary);
5911
+ border: 2px solid var(--border-color);
5912
+ border-top: 4px solid var(--mckinsey-blue);
5913
+ padding: 24px 32px;
5914
+ text-align: center;
5915
+ min-width: 180px;
5916
+ }
5917
+
5918
+ .metric-value {
5919
+ font-size: 48px;
5920
+ font-weight: 700;
5921
+ color: var(--mckinsey-blue);
5922
+ line-height: 1;
5923
+ margin-bottom: 8px;
5924
+ font-family: 'Arial', sans-serif;
5925
+ }
5926
+
5927
+ .metric-trend-up .metric-value {
5928
+ color: var(--accent-green);
5929
+ }
5930
+
5931
+ .metric-trend-down .metric-value {
5932
+ color: var(--accent-red);
5933
+ }
5934
+
5935
+ .metric-label {
5936
+ font-size: 14px;
5937
+ color: var(--text-muted);
5938
+ text-transform: uppercase;
5939
+ letter-spacing: 0.05em;
5940
+ font-family: 'Arial', sans-serif;
5941
+ }
5942
+
5943
+ /* Statement slides */
5944
+ .statement {
5945
+ font-size: 32px;
5946
+ color: var(--text-primary);
5947
+ line-height: 1.5;
5948
+ max-width: 85%;
5949
+ font-style: italic;
5950
+ }
5951
+
5952
+ .subtext {
5953
+ font-size: 20px;
5954
+ color: var(--text-muted);
5955
+ }
5956
+
5957
+ /* Title slide */
5958
+ .slide-title {
5959
+ display: flex;
5960
+ flex-direction: column;
5961
+ justify-content: center;
5962
+ align-items: center;
5963
+ text-align: center;
5964
+ background: var(--mckinsey-blue) !important;
5965
+ }
5966
+
5967
+ .slide-title::before {
5968
+ display: none !important;
5969
+ }
5970
+
5971
+ .slide-title h1 {
5972
+ font-size: 56px;
5973
+ color: #ffffff;
5974
+ border-bottom: none;
5975
+ padding-bottom: 0;
5976
+ }
5977
+
5978
+ .slide-title h2 {
5979
+ color: rgba(255, 255, 255, 0.8);
5980
+ }
5981
+
5982
+ /* Thank you slide */
5983
+ .slide-thank_you {
5984
+ display: flex;
5985
+ flex-direction: column;
5986
+ justify-content: center;
5987
+ align-items: center;
5988
+ text-align: center;
5989
+ background: var(--mckinsey-blue) !important;
5990
+ }
5991
+
5992
+ .slide-thank_you::before {
5993
+ display: none !important;
5994
+ }
5995
+
5996
+ .slide-thank_you h1 {
5997
+ font-size: 56px;
5998
+ color: #ffffff;
5999
+ border-bottom: none;
6000
+ margin-bottom: 16px;
6001
+ }
6002
+
6003
+ .slide-thank_you h2 {
6004
+ color: rgba(255, 255, 255, 0.8);
6005
+ }
6006
+
6007
+ /* Source citation */
6008
+ .source {
6009
+ position: absolute;
6010
+ bottom: 24px;
6011
+ left: 80px;
6012
+ font-size: 12px;
6013
+ color: var(--text-muted);
6014
+ font-style: italic;
6015
+ }
6016
+
6017
+ /* Page number style footer */
6018
+ .reveal .slide-number {
6019
+ background: var(--mckinsey-blue);
6020
+ color: #ffffff;
6021
+ font-size: 14px;
6022
+ padding: 8px 12px;
6023
+ }
6024
+ `;
6025
+ }
6026
+ /**
6027
+ * Dark theme CSS (legacy).
6028
+ */
6029
+ getDarkCSS() {
5122
6030
  return `
5123
6031
  /* Professional Dark Theme - No Random Images */
5124
6032
  :root {
@@ -5143,10 +6051,22 @@ ${content}
5143
6051
  }
5144
6052
 
5145
6053
  .reveal .slides section {
5146
- padding: 60px 80px;
6054
+ padding: 80px 100px;
5147
6055
  background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-accent) 100%);
5148
6056
  height: 100%;
5149
6057
  box-sizing: border-box;
6058
+ display: flex !important;
6059
+ flex-direction: column;
6060
+ justify-content: flex-start;
6061
+ align-items: stretch;
6062
+ }
6063
+
6064
+ .reveal .slides section > ul,
6065
+ .reveal .slides section > .data-table {
6066
+ flex: 1;
6067
+ display: flex;
6068
+ flex-direction: column;
6069
+ justify-content: flex-start;
5150
6070
  }
5151
6071
 
5152
6072
  /* Typography */
@@ -5331,8 +6251,8 @@ ${content}
5331
6251
  `;
5332
6252
  }
5333
6253
  };
5334
- function createRendererV2() {
5335
- return new RendererV2();
6254
+ function createRendererV2(presentationType = "consulting_deck", themeOverride) {
6255
+ return new RendererV2(presentationType, themeOverride);
5336
6256
  }
5337
6257
 
5338
6258
  // src/image/NanoBananaProvider.ts