mulmocast 2.1.27 → 2.1.28

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.
@@ -28,6 +28,13 @@
28
28
  const ctx = document.getElementById('myChart');
29
29
  if (!ctx) return false;
30
30
  const chartData = ${chart_data};
31
+
32
+ // Disable animation for static image generation
33
+ if (!chartData.options) chartData.options = {};
34
+ chartData.options.animation = false;
35
+
36
+ // Initialize the chart
37
+
31
38
  new Chart(ctx, chartData);
32
39
  requestAnimationFrame(() => {
33
40
  requestAnimationFrame(() => {
@@ -1,5 +1,6 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <!-- h-full on html and body ensures background fills entire viewport for screenshot rendering -->
3
+ <html lang="en" class="h-full">
3
4
  <head>
4
5
  <meta charset="UTF-8" />
5
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -7,7 +8,7 @@
7
8
  <!-- Tailwind CSS CDN -->
8
9
  <script src="https://cdn.tailwindcss.com"></script>
9
10
  </head>
10
- <body class="bg-white text-gray-800 min-h-screen flex flex-col">
11
+ <body class="bg-white text-gray-800 h-full flex flex-col">
11
12
  ${html_body}
12
13
  </body>
13
14
  </html>
@@ -3,7 +3,7 @@ import { GraphAI, GraphAILogger } from "graphai";
3
3
  import * as agents from "@graphai/vanilla";
4
4
  import { getHTMLFile, getCaptionImagePath, getOutputStudioFilePath } from "../utils/file.js";
5
5
  import { localizedText, processLineBreaks } from "../utils/utils.js";
6
- import { renderHTMLToImage, interpolate } from "../utils/markdown.js";
6
+ import { renderHTMLToImage, interpolate } from "../utils/html_render.js";
7
7
  import { MulmoStudioContextMethods, MulmoPresentationStyleMethods } from "../methods/index.js";
8
8
  import { fileWriteAgent } from "@graphai/vanilla_node_agents";
9
9
  const vanillaAgents = agents.default ?? agents;
@@ -2,7 +2,7 @@ import { GraphAILogger } from "graphai";
2
2
  import { MulmoPresentationStyleMethods, MulmoStudioContextMethods, MulmoBeatMethods, MulmoMediaSourceMethods } from "../methods/index.js";
3
3
  import { getBeatPngImagePath, getBeatMoviePaths, getAudioFilePath } from "../utils/file.js";
4
4
  import { imagePrompt, htmlImageSystemPrompt } from "../utils/prompt.js";
5
- import { renderHTMLToImage } from "../utils/markdown.js";
5
+ import { renderHTMLToImage } from "../utils/html_render.js";
6
6
  import { beatId } from "../utils/utils.js";
7
7
  import { localizedPath } from "./audio.js";
8
8
  const htmlStyle = (context, beat) => {
@@ -5,7 +5,7 @@ import { GraphAILogger, sleep } from "graphai";
5
5
  import { MulmoPresentationStyleMethods } from "../methods/index.js";
6
6
  import { localizedText, isHttp } from "../utils/utils.js";
7
7
  import { getOutputPdfFilePath, writingMessage, getHTMLFile, mulmoCreditPath } from "../utils/file.js";
8
- import { interpolate } from "../utils/markdown.js";
8
+ import { interpolate } from "../utils/html_render.js";
9
9
  import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
10
10
  const isCI = process.env.CI === "true";
11
11
  const getPdfSize = (pdfSize) => {
@@ -0,0 +1,3 @@
1
+ export declare const renderHTMLToImage: (html: string, outputPath: string, width: number, height: number, isMermaid?: boolean, omitBackground?: boolean) => Promise<void>;
2
+ export declare const renderMarkdownToImage: (markdown: string, style: string, outputPath: string, width: number, height: number) => Promise<void>;
3
+ export declare const interpolate: (template: string, data: Record<string, string>) => string;
@@ -0,0 +1,50 @@
1
+ import { marked } from "marked";
2
+ import puppeteer from "puppeteer";
3
+ const isCI = process.env.CI === "true";
4
+ export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
5
+ // Use Puppeteer to render HTML to an image
6
+ const browser = await puppeteer.launch({
7
+ args: isCI ? ["--no-sandbox"] : [],
8
+ });
9
+ const page = await browser.newPage();
10
+ // Set the page content to the HTML generated from the Markdown
11
+ await page.setContent(html);
12
+ // Adjust page settings if needed (like width, height, etc.)
13
+ await page.setViewport({ width, height });
14
+ // height:100% ensures background fills viewport; background:white prevents transparent areas
15
+ await page.addStyleTag({ content: "html,body{height:100%;margin:0;padding:0;overflow:hidden;background:white}" });
16
+ if (isMermaid) {
17
+ await page.waitForFunction(() => {
18
+ const element = document.querySelector(".mermaid");
19
+ return element && element.dataset.ready === "true";
20
+ }, { timeout: 20000 });
21
+ }
22
+ // Wait for Chart.js to finish rendering if this is a chart
23
+ if (html.includes("data-chart-ready")) {
24
+ await page.waitForFunction(() => {
25
+ const canvas = document.querySelector("canvas[data-chart-ready='true']");
26
+ return !!canvas;
27
+ }, { timeout: 20000 });
28
+ }
29
+ // Measure the size of the page and scale the page to the width and height
30
+ await page.evaluate(({ viewportWidth, viewportHeight }) => {
31
+ const docElement = document.documentElement;
32
+ const scrollWidth = Math.max(docElement.scrollWidth, document.body.scrollWidth || 0);
33
+ const scrollHeight = Math.max(docElement.scrollHeight, document.body.scrollHeight || 0);
34
+ const scale = Math.min(viewportWidth / (scrollWidth || viewportWidth), viewportHeight / (scrollHeight || viewportHeight), 1);
35
+ docElement.style.overflow = "hidden";
36
+ document.body.style.zoom = String(scale);
37
+ }, { viewportWidth: width, viewportHeight: height });
38
+ // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
39
+ await page.screenshot({ path: outputPath, omitBackground });
40
+ await browser.close();
41
+ };
42
+ export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
43
+ const header = `<head><style>${style}</style></head>`;
44
+ const body = await marked(markdown);
45
+ const html = `<html>${header}<body>${body}</body></html>`;
46
+ await renderHTMLToImage(html, outputPath, width, height);
47
+ };
48
+ export const interpolate = (template, data) => {
49
+ return template.replace(/\$\{(.*?)\}/g, (_, key) => data[key.trim()] ?? "");
50
+ };
@@ -1,5 +1,5 @@
1
1
  import { getHTMLFile } from "../file.js";
2
- import { renderHTMLToImage, interpolate } from "../markdown.js";
2
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
3
3
  import { parrotingImagePath } from "./utils.js";
4
4
  import nodeProcess from "node:process";
5
5
  export const imageType = "chart";
@@ -1,5 +1,5 @@
1
1
  import { getHTMLFile } from "../file.js";
2
- import { renderHTMLToImage, interpolate } from "../markdown.js";
2
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
3
3
  import { parrotingImagePath } from "./utils.js";
4
4
  export const imageType = "html_tailwind";
5
5
  const processHtmlTailwind = async (params) => {
@@ -1,4 +1,4 @@
1
- import { renderMarkdownToImage } from "../markdown.js";
1
+ import { renderMarkdownToImage } from "../html_render.js";
2
2
  import { parrotingImagePath } from "./utils.js";
3
3
  import { marked } from "marked";
4
4
  export const imageType = "markdown";
@@ -1,6 +1,6 @@
1
1
  import { MulmoMediaSourceMethods } from "../../methods/index.js";
2
2
  import { getHTMLFile } from "../file.js";
3
- import { renderHTMLToImage, interpolate } from "../markdown.js";
3
+ import { renderHTMLToImage, interpolate } from "../html_render.js";
4
4
  import { parrotingImagePath } from "./utils.js";
5
5
  import nodeProcess from "node:process";
6
6
  export const imageType = "mermaid";
@@ -1,4 +1,4 @@
1
- import { renderMarkdownToImage } from "../markdown.js";
1
+ import { renderMarkdownToImage } from "../html_render.js";
2
2
  import { parrotingImagePath } from "./utils.js";
3
3
  import { marked } from "marked";
4
4
  export const imageType = "textSlide";
@@ -1,113 +1,42 @@
1
1
  import { marked } from "marked";
2
2
  import puppeteer from "puppeteer";
3
3
  const isCI = process.env.CI === "true";
4
- const reuseBrowser = process.env.MULMO_PUPPETEER_REUSE !== "0";
5
- const browserLaunchArgs = isCI ? ["--no-sandbox"] : [];
6
- // Shared browser to avoid spawning a new Chromium per render.
7
- let sharedBrowserPromise = null;
8
- let sharedBrowserRefs = 0;
9
- let sharedBrowserCloseTimer = null;
10
- // Acquire a browser instance; reuse a shared one when enabled.
11
- const acquireBrowser = async () => {
12
- if (!reuseBrowser) {
13
- return await puppeteer.launch({ args: browserLaunchArgs });
14
- }
15
- sharedBrowserRefs += 1;
16
- if (sharedBrowserCloseTimer) {
17
- clearTimeout(sharedBrowserCloseTimer);
18
- sharedBrowserCloseTimer = null;
19
- }
20
- if (!sharedBrowserPromise) {
21
- sharedBrowserPromise = puppeteer.launch({ args: browserLaunchArgs });
22
- }
23
- const currentPromise = sharedBrowserPromise;
24
- try {
25
- return await currentPromise;
26
- }
27
- catch (error) {
28
- if (sharedBrowserPromise === currentPromise) {
29
- sharedBrowserPromise = null;
30
- }
31
- sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
32
- throw error;
33
- }
34
- };
35
- // Release the browser; close only after a short idle window.
36
- const releaseBrowser = async (browser) => {
37
- if (!reuseBrowser) {
38
- await browser.close().catch(() => { });
39
- return;
40
- }
41
- sharedBrowserRefs = Math.max(0, sharedBrowserRefs - 1);
42
- if (sharedBrowserRefs > 0 || !sharedBrowserPromise) {
43
- return;
44
- }
45
- // Delay close to allow back-to-back renders to reuse the browser.
46
- sharedBrowserCloseTimer = setTimeout(async () => {
47
- const current = sharedBrowserPromise;
48
- sharedBrowserPromise = null;
49
- sharedBrowserCloseTimer = null;
50
- if (current) {
51
- await (await current).close().catch(() => { });
52
- }
53
- }, 300);
54
- };
55
- // Wait for a single animation frame to let canvas paints settle.
56
- const waitForNextFrame = async (page) => {
57
- await page.evaluate(() => new Promise((resolve) => {
58
- requestAnimationFrame(() => resolve());
59
- }));
60
- };
61
4
  export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
62
- // Charts are rendered in a dedicated browser to avoid shared-page timing issues.
63
- const useSharedBrowser = reuseBrowser && !html.includes("data-chart-ready");
64
- const browser = useSharedBrowser ? await acquireBrowser() : await puppeteer.launch({ args: browserLaunchArgs });
65
- let page = null;
66
- try {
67
- page = await browser.newPage();
68
- // Adjust page settings if needed (like width, height, etc.)
69
- await page.setViewport({ width, height });
70
- // Set the page content to the HTML generated from the Markdown
71
- await page.setContent(html, { waitUntil: "domcontentloaded" });
72
- await page.addStyleTag({ content: "html,body{margin:0;padding:0;overflow:hidden}" });
73
- if (isMermaid) {
74
- await page.waitForFunction(() => {
75
- const element = document.querySelector(".mermaid");
76
- return element && element.dataset.ready === "true";
77
- }, { timeout: 20000 });
78
- }
79
- if (html.includes("data-chart-ready")) {
80
- await page.waitForFunction(() => {
81
- const canvas = document.querySelector("canvas[data-chart-ready='true']");
82
- return !!canvas;
83
- }, { timeout: 20000 });
84
- // Give the browser a couple of frames to paint the canvas.
85
- await waitForNextFrame(page);
86
- await waitForNextFrame(page);
87
- }
88
- // Measure the size of the page and scale the page to the width and height
89
- await page.evaluate(({ vw, vh }) => {
90
- const documentElement = document.documentElement;
91
- const scrollWidth = Math.max(documentElement.scrollWidth, document.body.scrollWidth || 0);
92
- const scrollHeight = Math.max(documentElement.scrollHeight, document.body.scrollHeight || 0);
93
- const scale = Math.min(vw / (scrollWidth || vw), vh / (scrollHeight || vh), 1); // <=1 で縮小のみ
94
- documentElement.style.overflow = "hidden";
95
- document.body.style.zoom = String(scale);
96
- }, { vw: width, vh: height });
97
- // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
98
- await page.screenshot({ path: outputPath, omitBackground });
99
- }
100
- finally {
101
- if (page) {
102
- await page.close().catch(() => { });
103
- }
104
- if (useSharedBrowser) {
105
- await releaseBrowser(browser);
106
- }
107
- else {
108
- await browser.close().catch(() => { });
109
- }
110
- }
5
+ // Use Puppeteer to render HTML to an image
6
+ const browser = await puppeteer.launch({
7
+ args: isCI ? ["--no-sandbox"] : [],
8
+ });
9
+ const page = await browser.newPage();
10
+ // Set the page content to the HTML generated from the Markdown
11
+ await page.setContent(html);
12
+ // Adjust page settings if needed (like width, height, etc.)
13
+ await page.setViewport({ width, height });
14
+ await page.addStyleTag({ content: "html,body{margin:0;padding:0;overflow:hidden}" });
15
+ if (isMermaid) {
16
+ await page.waitForFunction(() => {
17
+ const el = document.querySelector(".mermaid");
18
+ return el && el.dataset.ready === "true";
19
+ }, { timeout: 20000 });
20
+ }
21
+ // Wait for Chart.js to finish rendering if this is a chart
22
+ if (html.includes("data-chart-ready")) {
23
+ await page.waitForFunction(() => {
24
+ const canvas = document.querySelector("canvas[data-chart-ready='true']");
25
+ return !!canvas;
26
+ }, { timeout: 20000 });
27
+ }
28
+ // Measure the size of the page and scale the page to the width and height
29
+ await page.evaluate(({ vw, vh }) => {
30
+ const de = document.documentElement;
31
+ const sw = Math.max(de.scrollWidth, document.body.scrollWidth || 0);
32
+ const sh = Math.max(de.scrollHeight, document.body.scrollHeight || 0);
33
+ const scale = Math.min(vw / (sw || vw), vh / (sh || vh), 1); // <=1 で縮小のみ
34
+ de.style.overflow = "hidden";
35
+ document.body.style.zoom = String(scale);
36
+ }, { vw: width, vh: height });
37
+ // Step 3: Capture screenshot of the page (which contains the Markdown-rendered HTML)
38
+ await page.screenshot({ path: outputPath, omitBackground });
39
+ await browser.close();
111
40
  };
112
41
  export const renderMarkdownToImage = async (markdown, style, outputPath, width, height) => {
113
42
  const header = `<head><style>${style}</style></head>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.1.27",
3
+ "version": "2.1.28",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -0,0 +1,984 @@
1
+ {
2
+ "$mulmocast": {
3
+ "version": "1.1"
4
+ },
5
+ "lang": "en",
6
+ "title": "Render Stress Test",
7
+ "description": "Stress test for parallel HTML/JS rendering. Tests markdown, mermaid, chart, html_tailwind, and textSlide.",
8
+ "audioParams": {
9
+ "introPadding": 0,
10
+ "padding": 0.1,
11
+ "closingPadding": 0,
12
+ "outroPadding": 0
13
+ },
14
+ "beats": [
15
+ {
16
+ "id": "text-slide-1",
17
+ "text": "Text slide with title only",
18
+ "image": {
19
+ "type": "textSlide",
20
+ "slide": {
21
+ "title": "Render Stress Test"
22
+ }
23
+ }
24
+ },
25
+ {
26
+ "id": "text-slide-2",
27
+ "text": "Text slide with bullets",
28
+ "image": {
29
+ "type": "textSlide",
30
+ "slide": {
31
+ "title": "Rendering Types",
32
+ "bullets": [
33
+ "textSlide - Simple text slides",
34
+ "markdown - Markdown to HTML",
35
+ "chart - Chart.js graphs",
36
+ "mermaid - Diagram generation",
37
+ "html_tailwind - Custom HTML with Tailwind"
38
+ ]
39
+ }
40
+ }
41
+ },
42
+ {
43
+ "id": "markdown-table",
44
+ "text": "Markdown table rendering",
45
+ "image": {
46
+ "type": "markdown",
47
+ "markdown": [
48
+ "# Markdown Table Test",
49
+ "",
50
+ "| Renderer | JS Required | Complexity |",
51
+ "| :------- | :---------: | ---------: |",
52
+ "| textSlide | No | Low |",
53
+ "| markdown | No | Medium |",
54
+ "| chart | Yes | High |",
55
+ "| mermaid | Yes | High |",
56
+ "| html_tailwind | Maybe | Variable |"
57
+ ]
58
+ }
59
+ },
60
+ {
61
+ "id": "markdown-code",
62
+ "text": "Markdown code block rendering",
63
+ "image": {
64
+ "type": "markdown",
65
+ "markdown": [
66
+ "# Code Block Test",
67
+ "",
68
+ "```typescript",
69
+ "const renderHTMLToImage = async (",
70
+ " html: string,",
71
+ " outputPath: string,",
72
+ " width: number,",
73
+ " height: number,",
74
+ ") => {",
75
+ " const browser = await acquireBrowser();",
76
+ " const page = await browser.newPage();",
77
+ " await page.setContent(html);",
78
+ " await page.screenshot({ path: outputPath });",
79
+ "};",
80
+ "```"
81
+ ]
82
+ }
83
+ },
84
+ {
85
+ "id": "markdown-styled",
86
+ "text": "Markdown with custom background color using HTML",
87
+ "image": {
88
+ "type": "markdown",
89
+ "markdown": [
90
+ "<div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px; min-height: 100vh; color: white;'>",
91
+ "",
92
+ "# Styled Markdown",
93
+ "",
94
+ "Markdownの中でHTMLを使って背景色を設定できます。",
95
+ "",
96
+ "- **グラデーション背景**",
97
+ "- カスタムパディング",
98
+ "- テキスト色の変更",
99
+ "",
100
+ "</div>"
101
+ ]
102
+ }
103
+ },
104
+ {
105
+ "id": "chart-bar",
106
+ "text": "Bar chart with animation enabled",
107
+ "image": {
108
+ "type": "chart",
109
+ "title": "Monthly Revenue (Bar Chart with Animation)",
110
+ "chartData": {
111
+ "type": "bar",
112
+ "data": {
113
+ "labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
114
+ "datasets": [
115
+ {
116
+ "label": "Revenue 2024 ($K)",
117
+ "data": [120, 135, 180, 155, 170, 190, 210, 195, 220, 240, 260, 300],
118
+ "backgroundColor": "rgba(54, 162, 235, 0.5)",
119
+ "borderColor": "rgba(54, 162, 235, 1)",
120
+ "borderWidth": 2
121
+ },
122
+ {
123
+ "label": "Revenue 2023 ($K)",
124
+ "data": [100, 110, 150, 140, 160, 170, 180, 175, 200, 210, 230, 250],
125
+ "backgroundColor": "rgba(255, 99, 132, 0.5)",
126
+ "borderColor": "rgba(255, 99, 132, 1)",
127
+ "borderWidth": 2
128
+ }
129
+ ]
130
+ },
131
+ "options": {
132
+ "responsive": true,
133
+ "animation": {
134
+ "duration": 1000,
135
+ "easing": "easeOutQuart"
136
+ },
137
+ "plugins": {
138
+ "legend": {
139
+ "position": "top"
140
+ },
141
+ "title": {
142
+ "display": true,
143
+ "text": "Year over Year Comparison"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
150
+ {
151
+ "id": "chart-line",
152
+ "text": "Line chart with multiple datasets",
153
+ "image": {
154
+ "type": "chart",
155
+ "title": "Stock Price Trend (Line Chart)",
156
+ "chartData": {
157
+ "type": "line",
158
+ "data": {
159
+ "labels": ["Week 1", "Week 2", "Week 3", "Week 4", "Week 5", "Week 6", "Week 7", "Week 8"],
160
+ "datasets": [
161
+ {
162
+ "label": "Company A",
163
+ "data": [100, 105, 102, 110, 115, 112, 120, 125],
164
+ "borderColor": "rgba(75, 192, 192, 1)",
165
+ "backgroundColor": "rgba(75, 192, 192, 0.2)",
166
+ "fill": true,
167
+ "tension": 0.4
168
+ },
169
+ {
170
+ "label": "Company B",
171
+ "data": [90, 95, 100, 98, 105, 110, 108, 115],
172
+ "borderColor": "rgba(255, 159, 64, 1)",
173
+ "backgroundColor": "rgba(255, 159, 64, 0.2)",
174
+ "fill": true,
175
+ "tension": 0.4
176
+ },
177
+ {
178
+ "label": "Company C",
179
+ "data": [80, 82, 85, 90, 88, 95, 100, 105],
180
+ "borderColor": "rgba(153, 102, 255, 1)",
181
+ "backgroundColor": "rgba(153, 102, 255, 0.2)",
182
+ "fill": true,
183
+ "tension": 0.4
184
+ }
185
+ ]
186
+ },
187
+ "options": {
188
+ "responsive": true,
189
+ "animation": {
190
+ "duration": 800
191
+ },
192
+ "interaction": {
193
+ "intersect": false,
194
+ "mode": "index"
195
+ }
196
+ }
197
+ }
198
+ }
199
+ },
200
+ {
201
+ "id": "chart-pie",
202
+ "text": "Pie chart test",
203
+ "image": {
204
+ "type": "chart",
205
+ "title": "Market Share (Pie Chart)",
206
+ "chartData": {
207
+ "type": "pie",
208
+ "data": {
209
+ "labels": ["Product A", "Product B", "Product C", "Product D", "Others"],
210
+ "datasets": [
211
+ {
212
+ "data": [35, 25, 20, 12, 8],
213
+ "backgroundColor": [
214
+ "rgba(255, 99, 132, 0.7)",
215
+ "rgba(54, 162, 235, 0.7)",
216
+ "rgba(255, 206, 86, 0.7)",
217
+ "rgba(75, 192, 192, 0.7)",
218
+ "rgba(153, 102, 255, 0.7)"
219
+ ],
220
+ "borderWidth": 2
221
+ }
222
+ ]
223
+ },
224
+ "options": {
225
+ "responsive": true,
226
+ "animation": {
227
+ "animateRotate": true,
228
+ "animateScale": true,
229
+ "duration": 1200
230
+ },
231
+ "plugins": {
232
+ "legend": {
233
+ "position": "right"
234
+ }
235
+ }
236
+ }
237
+ }
238
+ }
239
+ },
240
+ {
241
+ "id": "chart-doughnut",
242
+ "text": "Doughnut chart test",
243
+ "image": {
244
+ "type": "chart",
245
+ "title": "Budget Allocation (Doughnut)",
246
+ "chartData": {
247
+ "type": "doughnut",
248
+ "data": {
249
+ "labels": ["R&D", "Marketing", "Operations", "HR", "IT", "Admin"],
250
+ "datasets": [
251
+ {
252
+ "data": [30, 25, 20, 10, 10, 5],
253
+ "backgroundColor": [
254
+ "rgba(255, 99, 132, 0.8)",
255
+ "rgba(54, 162, 235, 0.8)",
256
+ "rgba(255, 206, 86, 0.8)",
257
+ "rgba(75, 192, 192, 0.8)",
258
+ "rgba(153, 102, 255, 0.8)",
259
+ "rgba(255, 159, 64, 0.8)"
260
+ ]
261
+ }
262
+ ]
263
+ },
264
+ "options": {
265
+ "responsive": true,
266
+ "animation": {
267
+ "duration": 1000
268
+ },
269
+ "cutout": "60%"
270
+ }
271
+ }
272
+ }
273
+ },
274
+ {
275
+ "id": "chart-radar",
276
+ "text": "Radar chart test",
277
+ "image": {
278
+ "type": "chart",
279
+ "title": "Skills Assessment (Radar)",
280
+ "chartData": {
281
+ "type": "radar",
282
+ "data": {
283
+ "labels": ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java"],
284
+ "datasets": [
285
+ {
286
+ "label": "Developer A",
287
+ "data": [90, 85, 70, 60, 40, 75],
288
+ "backgroundColor": "rgba(255, 99, 132, 0.2)",
289
+ "borderColor": "rgba(255, 99, 132, 1)",
290
+ "pointBackgroundColor": "rgba(255, 99, 132, 1)"
291
+ },
292
+ {
293
+ "label": "Developer B",
294
+ "data": [70, 90, 85, 75, 65, 60],
295
+ "backgroundColor": "rgba(54, 162, 235, 0.2)",
296
+ "borderColor": "rgba(54, 162, 235, 1)",
297
+ "pointBackgroundColor": "rgba(54, 162, 235, 1)"
298
+ }
299
+ ]
300
+ },
301
+ "options": {
302
+ "responsive": true,
303
+ "animation": {
304
+ "duration": 1500
305
+ },
306
+ "scales": {
307
+ "r": {
308
+ "beginAtZero": true,
309
+ "max": 100
310
+ }
311
+ }
312
+ }
313
+ }
314
+ }
315
+ },
316
+ {
317
+ "id": "chart-polar",
318
+ "text": "Polar area chart test",
319
+ "image": {
320
+ "type": "chart",
321
+ "title": "Priority Distribution (Polar Area)",
322
+ "chartData": {
323
+ "type": "polarArea",
324
+ "data": {
325
+ "labels": ["Critical", "High", "Medium", "Low", "Trivial"],
326
+ "datasets": [
327
+ {
328
+ "data": [5, 12, 25, 18, 8],
329
+ "backgroundColor": [
330
+ "rgba(255, 99, 132, 0.7)",
331
+ "rgba(255, 159, 64, 0.7)",
332
+ "rgba(255, 206, 86, 0.7)",
333
+ "rgba(75, 192, 192, 0.7)",
334
+ "rgba(153, 102, 255, 0.7)"
335
+ ]
336
+ }
337
+ ]
338
+ },
339
+ "options": {
340
+ "responsive": true,
341
+ "animation": {
342
+ "duration": 1000
343
+ }
344
+ }
345
+ }
346
+ }
347
+ },
348
+ {
349
+ "id": "mermaid-flowchart-lr",
350
+ "text": "Mermaid flowchart left to right",
351
+ "image": {
352
+ "type": "mermaid",
353
+ "title": "CI/CD Pipeline",
354
+ "code": {
355
+ "kind": "text",
356
+ "text": "graph LR\n A[Code Push] --> B[Build]\n B --> C{Tests Pass?}\n C -->|Yes| D[Deploy to Staging]\n C -->|No| E[Fix Issues]\n E --> A\n D --> F{QA Approved?}\n F -->|Yes| G[Deploy to Prod]\n F -->|No| E"
357
+ }
358
+ }
359
+ },
360
+ {
361
+ "id": "mermaid-flowchart-tb",
362
+ "text": "Mermaid flowchart top to bottom",
363
+ "image": {
364
+ "type": "mermaid",
365
+ "title": "Decision Tree",
366
+ "code": {
367
+ "kind": "text",
368
+ "text": "graph TB\n A[Start] --> B{Is it raining?}\n B -->|Yes| C[Take umbrella]\n B -->|No| D{Is it sunny?}\n D -->|Yes| E[Wear sunglasses]\n D -->|No| F[Dress normally]\n C --> G[Go outside]\n E --> G\n F --> G\n G --> H[End]"
369
+ }
370
+ }
371
+ },
372
+ {
373
+ "id": "mermaid-sequence",
374
+ "text": "Mermaid sequence diagram",
375
+ "image": {
376
+ "type": "mermaid",
377
+ "title": "API Request Flow",
378
+ "code": {
379
+ "kind": "text",
380
+ "text": "sequenceDiagram\n participant C as Client\n participant G as API Gateway\n participant A as Auth Service\n participant S as Backend Service\n participant D as Database\n C->>G: HTTP Request\n G->>A: Validate Token\n A-->>G: Token Valid\n G->>S: Forward Request\n S->>D: Query Data\n D-->>S: Return Results\n S-->>G: Response\n G-->>C: HTTP Response"
381
+ }
382
+ }
383
+ },
384
+ {
385
+ "id": "mermaid-class",
386
+ "text": "Mermaid class diagram",
387
+ "image": {
388
+ "type": "mermaid",
389
+ "title": "Class Hierarchy",
390
+ "code": {
391
+ "kind": "text",
392
+ "text": "classDiagram\n class Animal {\n +String name\n +int age\n +makeSound()\n }\n class Dog {\n +String breed\n +bark()\n +fetch()\n }\n class Cat {\n +String color\n +meow()\n +scratch()\n }\n Animal <|-- Dog\n Animal <|-- Cat"
393
+ }
394
+ }
395
+ },
396
+ {
397
+ "id": "mermaid-state",
398
+ "text": "Mermaid state diagram",
399
+ "image": {
400
+ "type": "mermaid",
401
+ "title": "Order State Machine",
402
+ "code": {
403
+ "kind": "text",
404
+ "text": "stateDiagram-v2\n [*] --> Pending\n Pending --> Processing: Payment Received\n Processing --> Shipped: Items Packed\n Shipped --> Delivered: Package Arrived\n Delivered --> [*]\n Processing --> Cancelled: Customer Request\n Pending --> Cancelled: Timeout\n Cancelled --> [*]"
405
+ }
406
+ }
407
+ },
408
+ {
409
+ "id": "mermaid-er",
410
+ "text": "Mermaid ER diagram",
411
+ "image": {
412
+ "type": "mermaid",
413
+ "title": "Database Schema",
414
+ "code": {
415
+ "kind": "text",
416
+ "text": "erDiagram\n USER ||--o{ ORDER : places\n USER {\n int id PK\n string name\n string email\n }\n ORDER ||--|{ ORDER_ITEM : contains\n ORDER {\n int id PK\n date created_at\n string status\n }\n ORDER_ITEM }|--|| PRODUCT : references\n ORDER_ITEM {\n int quantity\n decimal price\n }\n PRODUCT {\n int id PK\n string name\n decimal price\n }"
417
+ }
418
+ }
419
+ },
420
+ {
421
+ "id": "mermaid-gantt",
422
+ "text": "Mermaid Gantt chart",
423
+ "image": {
424
+ "type": "mermaid",
425
+ "title": "Project Timeline",
426
+ "code": {
427
+ "kind": "text",
428
+ "text": "gantt\n title Project Development Schedule\n dateFormat YYYY-MM-DD\n section Planning\n Requirements :a1, 2024-01-01, 14d\n Design :a2, after a1, 21d\n section Development\n Backend :b1, after a2, 30d\n Frontend :b2, after a2, 25d\n Integration :b3, after b1, 14d\n section Testing\n QA Testing :c1, after b3, 14d\n UAT :c2, after c1, 7d\n section Deployment\n Release :d1, after c2, 3d"
429
+ }
430
+ }
431
+ },
432
+ {
433
+ "id": "html-tailwind-1",
434
+ "text": "Tailwind HTML card component",
435
+ "image": {
436
+ "type": "html_tailwind",
437
+ "html": [
438
+ "<div class='flex items-center justify-center h-full bg-gradient-to-br from-blue-500 to-purple-600 p-8'>",
439
+ " <div class='bg-white rounded-2xl shadow-2xl p-8 max-w-md'>",
440
+ " <div class='flex items-center space-x-4 mb-6'>",
441
+ " <div class='w-16 h-16 bg-gradient-to-r from-pink-500 to-yellow-500 rounded-full flex items-center justify-center'>",
442
+ " <span class='text-white text-2xl font-bold'>AI</span>",
443
+ " </div>",
444
+ " <div>",
445
+ " <h2 class='text-2xl font-bold text-gray-800'>MulmoCast</h2>",
446
+ " <p class='text-gray-500'>AI-Native Presentations</p>",
447
+ " </div>",
448
+ " </div>",
449
+ " <p class='text-gray-600 leading-relaxed'>",
450
+ " Transform your content into videos, podcasts, and presentations with the power of AI.",
451
+ " </p>",
452
+ " <div class='mt-6 flex space-x-3'>",
453
+ " <span class='px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm'>Video</span>",
454
+ " <span class='px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm'>Audio</span>",
455
+ " <span class='px-3 py-1 bg-purple-100 text-purple-800 rounded-full text-sm'>PDF</span>",
456
+ " </div>",
457
+ " </div>",
458
+ "</div>"
459
+ ]
460
+ }
461
+ },
462
+ {
463
+ "id": "html-tailwind-2",
464
+ "text": "Tailwind HTML stats dashboard",
465
+ "image": {
466
+ "type": "html_tailwind",
467
+ "html": [
468
+ "<div class='h-full bg-gray-900 p-8 flex flex-col justify-center'>",
469
+ " <h1 class='text-3xl font-bold text-white mb-8 text-center'>Dashboard Stats</h1>",
470
+ " <div class='grid grid-cols-2 gap-6'>",
471
+ " <div class='bg-gradient-to-r from-cyan-500 to-blue-500 rounded-xl p-6 text-white'>",
472
+ " <p class='text-sm opacity-80'>Total Users</p>",
473
+ " <p class='text-4xl font-bold'>12,847</p>",
474
+ " <p class='text-sm mt-2 text-green-200'>+12% from last month</p>",
475
+ " </div>",
476
+ " <div class='bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl p-6 text-white'>",
477
+ " <p class='text-sm opacity-80'>Revenue</p>",
478
+ " <p class='text-4xl font-bold'>$84.2K</p>",
479
+ " <p class='text-sm mt-2 text-green-200'>+8% from last month</p>",
480
+ " </div>",
481
+ " <div class='bg-gradient-to-r from-orange-500 to-red-500 rounded-xl p-6 text-white'>",
482
+ " <p class='text-sm opacity-80'>Active Sessions</p>",
483
+ " <p class='text-4xl font-bold'>2,341</p>",
484
+ " <p class='text-sm mt-2 text-yellow-200'>-3% from last hour</p>",
485
+ " </div>",
486
+ " <div class='bg-gradient-to-r from-green-500 to-teal-500 rounded-xl p-6 text-white'>",
487
+ " <p class='text-sm opacity-80'>Conversion Rate</p>",
488
+ " <p class='text-4xl font-bold'>4.28%</p>",
489
+ " <p class='text-sm mt-2 text-green-200'>+0.5% from last week</p>",
490
+ " </div>",
491
+ " </div>",
492
+ "</div>"
493
+ ]
494
+ }
495
+ },
496
+ {
497
+ "id": "html-tailwind-heavy-js",
498
+ "text": "Tailwind with heavy inline JS animation",
499
+ "image": {
500
+ "type": "html_tailwind",
501
+ "html": [
502
+ "<style>",
503
+ " @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } }",
504
+ " @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }",
505
+ " @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }",
506
+ " .float { animation: float 3s ease-in-out infinite; }",
507
+ " .pulse { animation: pulse 2s ease-in-out infinite; }",
508
+ " .rotate { animation: rotate 4s linear infinite; }",
509
+ "</style>",
510
+ "<div class='h-full bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-800 flex items-center justify-center'>",
511
+ " <div class='text-center'>",
512
+ " <div class='float mb-8'>",
513
+ " <div class='w-32 h-32 mx-auto bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full shadow-2xl flex items-center justify-center'>",
514
+ " <span class='text-white text-4xl font-bold rotate inline-block'>⚡</span>",
515
+ " </div>",
516
+ " </div>",
517
+ " <h1 class='text-5xl font-bold text-white mb-4 pulse'>Heavy Animation Test</h1>",
518
+ " <p class='text-xl text-purple-200'>CSS animations with transforms</p>",
519
+ " <div class='mt-8 flex justify-center gap-4'>",
520
+ " <div class='w-16 h-16 bg-pink-500 rounded-lg float' style='animation-delay: 0s;'></div>",
521
+ " <div class='w-16 h-16 bg-purple-500 rounded-lg float' style='animation-delay: 0.2s;'></div>",
522
+ " <div class='w-16 h-16 bg-indigo-500 rounded-lg float' style='animation-delay: 0.4s;'></div>",
523
+ " <div class='w-16 h-16 bg-blue-500 rounded-lg float' style='animation-delay: 0.6s;'></div>",
524
+ " </div>",
525
+ " </div>",
526
+ "</div>",
527
+ "<script>",
528
+ " // Simulate heavy computation",
529
+ " const start = Date.now();",
530
+ " let sum = 0;",
531
+ " for (let i = 0; i < 100000; i++) { sum += Math.sqrt(i); }",
532
+ " console.log('Computed in', Date.now() - start, 'ms');",
533
+ "</script>"
534
+ ]
535
+ }
536
+ },
537
+ {
538
+ "id": "html-tailwind-complex-grid",
539
+ "text": "Tailwind complex grid layout",
540
+ "image": {
541
+ "type": "html_tailwind",
542
+ "html": [
543
+ "<div class='h-full bg-slate-900 p-4'>",
544
+ " <div class='grid grid-cols-4 grid-rows-3 gap-2 h-full'>",
545
+ " <div class='col-span-2 row-span-2 bg-gradient-to-br from-violet-600 to-indigo-600 rounded-xl p-4 flex flex-col justify-between'>",
546
+ " <h2 class='text-2xl font-bold text-white'>Main Feature</h2>",
547
+ " <p class='text-violet-200'>Large spanning card with important content</p>",
548
+ " </div>",
549
+ " <div class='bg-gradient-to-r from-emerald-500 to-teal-500 rounded-xl p-3 text-white'>",
550
+ " <p class='text-xs opacity-70'>Metric A</p><p class='text-xl font-bold'>1,234</p>",
551
+ " </div>",
552
+ " <div class='bg-gradient-to-r from-amber-500 to-orange-500 rounded-xl p-3 text-white'>",
553
+ " <p class='text-xs opacity-70'>Metric B</p><p class='text-xl font-bold'>5,678</p>",
554
+ " </div>",
555
+ " <div class='bg-gradient-to-r from-rose-500 to-pink-500 rounded-xl p-3 text-white'>",
556
+ " <p class='text-xs opacity-70'>Metric C</p><p class='text-xl font-bold'>91.2%</p>",
557
+ " </div>",
558
+ " <div class='bg-gradient-to-r from-sky-500 to-cyan-500 rounded-xl p-3 text-white'>",
559
+ " <p class='text-xs opacity-70'>Metric D</p><p class='text-xl font-bold'>$42K</p>",
560
+ " </div>",
561
+ " <div class='col-span-2 bg-slate-800 rounded-xl p-3'>",
562
+ " <p class='text-slate-400 text-sm'>Status: <span class='text-emerald-400'>● Online</span></p>",
563
+ " </div>",
564
+ " <div class='col-span-2 bg-slate-800 rounded-xl p-3 flex items-center justify-center'>",
565
+ " <p class='text-slate-300'>Footer Area</p>",
566
+ " </div>",
567
+ " </div>",
568
+ "</div>"
569
+ ]
570
+ }
571
+ },
572
+ {
573
+ "id": "chart-heavy-data",
574
+ "text": "Chart with large dataset (100 points)",
575
+ "image": {
576
+ "type": "chart",
577
+ "title": "Large Dataset Performance Test",
578
+ "chartData": {
579
+ "type": "line",
580
+ "data": {
581
+ "labels": [
582
+ "1",
583
+ "2",
584
+ "3",
585
+ "4",
586
+ "5",
587
+ "6",
588
+ "7",
589
+ "8",
590
+ "9",
591
+ "10",
592
+ "11",
593
+ "12",
594
+ "13",
595
+ "14",
596
+ "15",
597
+ "16",
598
+ "17",
599
+ "18",
600
+ "19",
601
+ "20",
602
+ "21",
603
+ "22",
604
+ "23",
605
+ "24",
606
+ "25",
607
+ "26",
608
+ "27",
609
+ "28",
610
+ "29",
611
+ "30",
612
+ "31",
613
+ "32",
614
+ "33",
615
+ "34",
616
+ "35",
617
+ "36",
618
+ "37",
619
+ "38",
620
+ "39",
621
+ "40",
622
+ "41",
623
+ "42",
624
+ "43",
625
+ "44",
626
+ "45",
627
+ "46",
628
+ "47",
629
+ "48",
630
+ "49",
631
+ "50",
632
+ "51",
633
+ "52",
634
+ "53",
635
+ "54",
636
+ "55",
637
+ "56",
638
+ "57",
639
+ "58",
640
+ "59",
641
+ "60",
642
+ "61",
643
+ "62",
644
+ "63",
645
+ "64",
646
+ "65",
647
+ "66",
648
+ "67",
649
+ "68",
650
+ "69",
651
+ "70",
652
+ "71",
653
+ "72",
654
+ "73",
655
+ "74",
656
+ "75",
657
+ "76",
658
+ "77",
659
+ "78",
660
+ "79",
661
+ "80",
662
+ "81",
663
+ "82",
664
+ "83",
665
+ "84",
666
+ "85",
667
+ "86",
668
+ "87",
669
+ "88",
670
+ "89",
671
+ "90",
672
+ "91",
673
+ "92",
674
+ "93",
675
+ "94",
676
+ "95",
677
+ "96",
678
+ "97",
679
+ "98",
680
+ "99",
681
+ "100"
682
+ ],
683
+ "datasets": [
684
+ {
685
+ "label": "Series A",
686
+ "data": [
687
+ 45, 52, 38, 61, 49, 55, 42, 68, 51, 73, 46, 59, 37, 64, 48, 71, 53, 44, 67, 56, 39, 62, 50, 58, 41, 69, 47, 72, 54, 43, 66, 57, 40, 63, 49,
688
+ 70, 52, 45, 68, 55, 38, 61, 48, 74, 51, 44, 67, 58, 41, 64, 47, 73, 54, 46, 69, 56, 39, 62, 50, 71, 53, 42, 65, 57, 40, 68, 49, 75, 52, 45,
689
+ 66, 59, 38, 63, 48, 72, 55, 44, 67, 58, 41, 70, 47, 76, 54, 43, 69, 56, 39, 64, 50, 73, 53, 46, 68, 57, 40, 61, 49, 74
690
+ ],
691
+ "borderColor": "rgba(99, 102, 241, 1)",
692
+ "backgroundColor": "rgba(99, 102, 241, 0.1)",
693
+ "fill": true,
694
+ "tension": 0.4
695
+ },
696
+ {
697
+ "label": "Series B",
698
+ "data": [
699
+ 32, 41, 55, 28, 47, 36, 59, 44, 51, 33, 62, 48, 39, 56, 45, 31, 58, 42, 53, 35, 64, 49, 38, 57, 46, 30, 61, 43, 54, 34, 63, 50, 37, 60, 47,
700
+ 29, 66, 44, 55, 36, 65, 51, 40, 58, 48, 33, 62, 45, 52, 37, 59, 41, 56, 34, 67, 50, 43, 61, 49, 32, 64, 46, 53, 38, 60, 42, 57, 35, 68, 51,
701
+ 44, 62, 48, 31, 63, 47, 54, 39, 59, 43, 56, 36, 65, 52, 41, 60, 49, 34, 67, 46, 55, 38, 64, 44, 53, 37, 61, 50, 42, 58
702
+ ],
703
+ "borderColor": "rgba(236, 72, 153, 1)",
704
+ "backgroundColor": "rgba(236, 72, 153, 0.1)",
705
+ "fill": true,
706
+ "tension": 0.4
707
+ }
708
+ ]
709
+ },
710
+ "options": {
711
+ "responsive": true,
712
+ "animation": { "duration": 2000 },
713
+ "plugins": { "legend": { "position": "top" } },
714
+ "scales": { "y": { "beginAtZero": true } }
715
+ }
716
+ }
717
+ }
718
+ },
719
+ {
720
+ "id": "chart-stacked-complex",
721
+ "text": "Complex stacked bar chart",
722
+ "image": {
723
+ "type": "chart",
724
+ "title": "Multi-Category Stacked Analysis",
725
+ "chartData": {
726
+ "type": "bar",
727
+ "data": {
728
+ "labels": ["Q1 2023", "Q2 2023", "Q3 2023", "Q4 2023", "Q1 2024", "Q2 2024"],
729
+ "datasets": [
730
+ { "label": "Product A", "data": [120, 150, 180, 200, 220, 250], "backgroundColor": "rgba(59, 130, 246, 0.8)" },
731
+ { "label": "Product B", "data": [80, 100, 120, 140, 160, 180], "backgroundColor": "rgba(16, 185, 129, 0.8)" },
732
+ { "label": "Product C", "data": [60, 70, 90, 100, 120, 140], "backgroundColor": "rgba(245, 158, 11, 0.8)" },
733
+ { "label": "Product D", "data": [40, 50, 60, 80, 90, 110], "backgroundColor": "rgba(239, 68, 68, 0.8)" },
734
+ { "label": "Product E", "data": [20, 30, 40, 50, 60, 70], "backgroundColor": "rgba(139, 92, 246, 0.8)" }
735
+ ]
736
+ },
737
+ "options": {
738
+ "responsive": true,
739
+ "animation": { "duration": 1500 },
740
+ "scales": { "x": { "stacked": true }, "y": { "stacked": true } },
741
+ "plugins": { "legend": { "position": "right" } }
742
+ }
743
+ }
744
+ }
745
+ },
746
+ {
747
+ "id": "mermaid-complex-flowchart",
748
+ "text": "Complex mermaid flowchart with many nodes",
749
+ "image": {
750
+ "type": "mermaid",
751
+ "title": "Complex System Architecture",
752
+ "code": {
753
+ "kind": "text",
754
+ "text": "graph TB\n subgraph Client\n A[Web App] --> B[Mobile App]\n A --> C[Desktop App]\n end\n subgraph Gateway\n D[Load Balancer] --> E[API Gateway]\n E --> F[Auth Service]\n end\n subgraph Services\n G[User Service] --> H[Order Service]\n H --> I[Payment Service]\n I --> J[Notification Service]\n end\n subgraph Data\n K[(PostgreSQL)] --> L[(Redis Cache)]\n L --> M[(Elasticsearch)]\n end\n Client --> Gateway\n Gateway --> Services\n Services --> Data"
755
+ }
756
+ }
757
+ },
758
+ {
759
+ "id": "parallel-chart-1",
760
+ "text": "Parallel test chart 1",
761
+ "image": {
762
+ "type": "chart",
763
+ "title": "Parallel Chart 1",
764
+ "chartData": {
765
+ "type": "bar",
766
+ "data": {
767
+ "labels": ["A", "B", "C", "D", "E"],
768
+ "datasets": [{ "label": "Data", "data": [10, 20, 30, 40, 50], "backgroundColor": "rgba(255,99,132,0.5)" }]
769
+ },
770
+ "options": { "animation": { "duration": 500 } }
771
+ }
772
+ }
773
+ },
774
+ {
775
+ "id": "parallel-chart-2",
776
+ "text": "Parallel test chart 2",
777
+ "image": {
778
+ "type": "chart",
779
+ "title": "Parallel Chart 2",
780
+ "chartData": {
781
+ "type": "line",
782
+ "data": {
783
+ "labels": ["A", "B", "C", "D", "E"],
784
+ "datasets": [{ "label": "Data", "data": [50, 40, 30, 20, 10], "borderColor": "rgba(54,162,235,1)" }]
785
+ },
786
+ "options": { "animation": { "duration": 500 } }
787
+ }
788
+ }
789
+ },
790
+ {
791
+ "id": "parallel-chart-3",
792
+ "text": "Parallel test chart 3",
793
+ "image": {
794
+ "type": "chart",
795
+ "title": "Parallel Chart 3",
796
+ "chartData": {
797
+ "type": "pie",
798
+ "data": {
799
+ "labels": ["X", "Y", "Z"],
800
+ "datasets": [{ "data": [33, 33, 34], "backgroundColor": ["#ff6384", "#36a2eb", "#ffce56"] }]
801
+ },
802
+ "options": { "animation": { "duration": 500 } }
803
+ }
804
+ }
805
+ },
806
+ {
807
+ "id": "parallel-mermaid-1",
808
+ "text": "Parallel mermaid 1",
809
+ "image": {
810
+ "type": "mermaid",
811
+ "title": "Parallel Diagram 1",
812
+ "code": { "kind": "text", "text": "graph LR\n A-->B\n B-->C\n C-->D" }
813
+ }
814
+ },
815
+ {
816
+ "id": "parallel-mermaid-2",
817
+ "text": "Parallel mermaid 2",
818
+ "image": {
819
+ "type": "mermaid",
820
+ "title": "Parallel Diagram 2",
821
+ "code": { "kind": "text", "text": "graph TB\n X-->Y\n Y-->Z\n Z-->X" }
822
+ }
823
+ },
824
+ {
825
+ "id": "vision-section-divider",
826
+ "text": "Vision section divider slide",
827
+ "image": {
828
+ "type": "vision",
829
+ "style": "sectionDividerSlide",
830
+ "data": {
831
+ "heading": "Vision Template Test",
832
+ "subheading": "Testing various vision slide styles"
833
+ }
834
+ }
835
+ },
836
+ {
837
+ "id": "vision-swot",
838
+ "text": "Vision SWOT analysis slide",
839
+ "image": {
840
+ "type": "vision",
841
+ "style": "swotSlide",
842
+ "data": {
843
+ "title": "SWOT Analysis",
844
+ "strengths": ["Strong brand", "Innovative tech", "Skilled team"],
845
+ "weaknesses": ["Limited budget", "Small market share"],
846
+ "opportunities": ["Growing market", "New partnerships"],
847
+ "threats": ["Competition", "Regulation changes"]
848
+ }
849
+ }
850
+ },
851
+ {
852
+ "id": "vision-kpi",
853
+ "text": "Vision KPI highlight slide",
854
+ "image": {
855
+ "type": "vision",
856
+ "style": "kpiHighlightSlide",
857
+ "data": {
858
+ "title": "Key Performance Indicators",
859
+ "kpis": [
860
+ { "label": "Revenue", "value": "$2.4M", "change": "+12%" },
861
+ { "label": "Users", "value": "50K", "change": "+25%" },
862
+ { "label": "NPS", "value": "72", "change": "+5" }
863
+ ]
864
+ }
865
+ }
866
+ },
867
+ {
868
+ "id": "vision-executive-summary",
869
+ "text": "Vision executive summary slide",
870
+ "image": {
871
+ "type": "vision",
872
+ "style": "executiveSummarySlide",
873
+ "data": {
874
+ "title": "Executive Summary",
875
+ "points": [
876
+ "Revenue grew 15% YoY reaching $10M",
877
+ "Successfully launched 3 new products",
878
+ "Expanded to 5 new markets",
879
+ "Customer satisfaction at all-time high"
880
+ ]
881
+ }
882
+ }
883
+ },
884
+ {
885
+ "id": "vision-gantt",
886
+ "text": "Vision Gantt chart slide",
887
+ "image": {
888
+ "type": "vision",
889
+ "style": "ganttSimpleSlide",
890
+ "data": {
891
+ "title": "Project Timeline",
892
+ "tasks": [
893
+ { "name": "Planning", "start": "2024-01", "end": "2024-02" },
894
+ { "name": "Design", "start": "2024-02", "end": "2024-04" },
895
+ { "name": "Development", "start": "2024-03", "end": "2024-07" },
896
+ { "name": "Testing", "start": "2024-06", "end": "2024-08" },
897
+ { "name": "Launch", "start": "2024-08", "end": "2024-09" }
898
+ ]
899
+ }
900
+ }
901
+ },
902
+ {
903
+ "id": "vision-timeline",
904
+ "text": "Vision milestone timeline slide",
905
+ "image": {
906
+ "type": "vision",
907
+ "style": "milestoneTimelineSlide",
908
+ "data": {
909
+ "title": "Product Roadmap",
910
+ "milestones": [
911
+ { "date": "2024 Q1", "title": "Alpha Release", "description": "Internal testing" },
912
+ { "date": "2024 Q2", "title": "Beta Launch", "description": "Public beta" },
913
+ { "date": "2024 Q3", "title": "GA Release", "description": "General availability" },
914
+ { "date": "2024 Q4", "title": "Enterprise", "description": "Enterprise features" }
915
+ ]
916
+ }
917
+ }
918
+ },
919
+ {
920
+ "id": "vision-funnel",
921
+ "text": "Vision funnel slide",
922
+ "image": {
923
+ "type": "vision",
924
+ "style": "funnelSlide",
925
+ "data": {
926
+ "title": "Sales Funnel",
927
+ "stages": [
928
+ { "label": "Awareness", "value": 10000 },
929
+ { "label": "Interest", "value": 5000 },
930
+ { "label": "Consideration", "value": 2000 },
931
+ { "label": "Intent", "value": 500 },
932
+ { "label": "Purchase", "value": 200 }
933
+ ]
934
+ }
935
+ }
936
+ },
937
+ {
938
+ "id": "vision-porter",
939
+ "text": "Vision Porter's Five Forces slide",
940
+ "image": {
941
+ "type": "vision",
942
+ "style": "porterFiveForcesSlide",
943
+ "data": {
944
+ "title": "Industry Analysis",
945
+ "forces": {
946
+ "rivalry": { "level": "High", "description": "Many competitors" },
947
+ "newEntrants": { "level": "Medium", "description": "Moderate barriers" },
948
+ "substitutes": { "level": "Low", "description": "Few alternatives" },
949
+ "buyerPower": { "level": "High", "description": "Price sensitive" },
950
+ "supplierPower": { "level": "Medium", "description": "Multiple suppliers" }
951
+ }
952
+ }
953
+ }
954
+ },
955
+ {
956
+ "id": "vision-bcg-matrix",
957
+ "text": "Vision BCG Matrix slide",
958
+ "image": {
959
+ "type": "vision",
960
+ "style": "bcgMatrixSlide",
961
+ "data": {
962
+ "title": "Product Portfolio",
963
+ "products": [
964
+ { "name": "Product A", "growth": 0.25, "share": 0.4, "quadrant": "star" },
965
+ { "name": "Product B", "growth": 0.05, "share": 0.5, "quadrant": "cashCow" },
966
+ { "name": "Product C", "growth": 0.3, "share": 0.1, "quadrant": "questionMark" },
967
+ { "name": "Product D", "growth": 0.02, "share": 0.08, "quadrant": "dog" }
968
+ ]
969
+ }
970
+ }
971
+ },
972
+ {
973
+ "id": "final",
974
+ "text": "Stress test complete",
975
+ "image": {
976
+ "type": "textSlide",
977
+ "slide": {
978
+ "title": "Stress Test Complete",
979
+ "subtitle": "All rendering types tested"
980
+ }
981
+ }
982
+ }
983
+ ]
984
+ }