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