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/assets/presentation-knowledge.yaml +9 -9
- package/dist/index.d.mts +184 -10
- package/dist/index.d.ts +184 -10
- package/dist/index.js +942 -129
- package/dist/index.mjs +938 -129
- package/package.json +2 -2
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
|
-
|
|
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
|
|
1888
|
-
*
|
|
1889
|
-
*
|
|
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,
|
|
1893
|
-
const maxWordsPerBullet =
|
|
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
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
1925
|
-
if (words.length <=
|
|
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,
|
|
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 >
|
|
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
|
-
|
|
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 <
|
|
2001
|
+
if (beforeGroup && beforeGroup.trim().length >= 3 && beforeGroup.trim().length < 40) {
|
|
1979
2002
|
return beforeGroup.trim();
|
|
1980
2003
|
}
|
|
1981
|
-
const afterMatch =
|
|
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 <
|
|
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 -
|
|
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:
|
|
4108
|
+
padding: 60px 80px !important;
|
|
3757
4109
|
box-sizing: border-box !important;
|
|
3758
|
-
|
|
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
|
-
/*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
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,
|
|
3808
|
-
font-size:
|
|
3809
|
-
font-weight:
|
|
3810
|
-
color: var(--
|
|
3811
|
-
margin-bottom:
|
|
3812
|
-
line-height:
|
|
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,
|
|
3817
|
-
font-size:
|
|
3818
|
-
font-weight:
|
|
3819
|
-
color: var(--
|
|
3820
|
-
|
|
3821
|
-
|
|
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-
|
|
3826
|
-
font-
|
|
3827
|
-
|
|
3828
|
-
|
|
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:
|
|
3834
|
-
color: var(--
|
|
3835
|
-
line-height:
|
|
3836
|
-
margin-bottom:
|
|
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 -
|
|
4164
|
+
/* Bullet Points - Professional styling */
|
|
3840
4165
|
.reveal ul, .reveal ol {
|
|
3841
|
-
margin-left:
|
|
4166
|
+
margin-left: 0;
|
|
4167
|
+
padding-left: 0;
|
|
4168
|
+
list-style: none;
|
|
3842
4169
|
}
|
|
3843
4170
|
|
|
3844
4171
|
.reveal li {
|
|
3845
|
-
font-size:
|
|
3846
|
-
color: var(--
|
|
3847
|
-
line-height:
|
|
3848
|
-
margin-bottom:
|
|
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::
|
|
3852
|
-
|
|
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:
|
|
3868
|
-
margin-bottom:
|
|
4204
|
+
font-size: 72px;
|
|
4205
|
+
margin-bottom: 16px;
|
|
3869
4206
|
}
|
|
3870
4207
|
|
|
3871
4208
|
.slide-title h2, .slide-title_impact h2 {
|
|
3872
|
-
font-size:
|
|
3873
|
-
|
|
3874
|
-
|
|
4209
|
+
font-size: 28px;
|
|
4210
|
+
font-weight: 400;
|
|
4211
|
+
color: var(--text-muted);
|
|
3875
4212
|
}
|
|
3876
4213
|
|
|
3877
|
-
/* Section Dividers -
|
|
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,
|
|
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
|
|
3890
|
-
font-size:
|
|
3891
|
-
|
|
4225
|
+
color: #ffffff;
|
|
4226
|
+
font-size: 64px;
|
|
4227
|
+
font-weight: 700;
|
|
3892
4228
|
}
|
|
3893
4229
|
|
|
3894
|
-
/*
|
|
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:
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
border-radius:
|
|
3907
|
-
|
|
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:
|
|
3913
|
-
font-weight:
|
|
3914
|
-
color: var(--
|
|
3915
|
-
line-height:
|
|
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:
|
|
3920
|
-
color: var(--
|
|
3921
|
-
|
|
3922
|
-
|
|
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(--
|
|
4269
|
+
.metric-trend-up .metric-value {
|
|
4270
|
+
color: var(--accent-green);
|
|
3927
4271
|
}
|
|
3928
4272
|
|
|
3929
|
-
.metric-trend-down {
|
|
3930
|
-
color: var(--
|
|
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
|
-
|
|
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)
|
|
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
|
|
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, "&").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
|
+
|
|
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,
|