claude-presentation-master 3.7.0 → 3.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +169 -5
- package/dist/index.d.ts +169 -5
- package/dist/index.js +773 -0
- package/dist/index.mjs +769 -0
- package/package.json +1 -1
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,
|
|
@@ -2102,6 +2106,318 @@ async function initSlideGenerator() {
|
|
|
2102
2106
|
return generator;
|
|
2103
2107
|
}
|
|
2104
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
|
+
|
|
2105
2421
|
// src/qa/SlideQualityReviewer.ts
|
|
2106
2422
|
var ASSESSMENT_WEIGHTS = {
|
|
2107
2423
|
visualImpact: 0.25,
|
|
@@ -4636,6 +4952,459 @@ async function initRenderer() {
|
|
|
4636
4952
|
return renderer;
|
|
4637
4953
|
}
|
|
4638
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
+
|
|
4639
5408
|
// src/image/NanoBananaProvider.ts
|
|
4640
5409
|
var NanoBananaProvider = class {
|
|
4641
5410
|
name = "NanoBanana Pro";
|
|
@@ -5679,11 +6448,15 @@ function countWords(slide) {
|
|
|
5679
6448
|
NanoBananaProvider,
|
|
5680
6449
|
Remediator,
|
|
5681
6450
|
Renderer,
|
|
6451
|
+
RendererV2,
|
|
5682
6452
|
SlideGenerator,
|
|
6453
|
+
SlideGeneratorV2,
|
|
5683
6454
|
SlideQualityReviewer,
|
|
5684
6455
|
VERSION,
|
|
5685
6456
|
VisualDesignSystem,
|
|
5686
6457
|
createNanoBananaProvider,
|
|
6458
|
+
createRendererV2,
|
|
6459
|
+
createSlideGeneratorV2,
|
|
5687
6460
|
generate,
|
|
5688
6461
|
getContentAnalyzer,
|
|
5689
6462
|
getDeckQualityReviewer,
|