mulmocast 2.1.39 → 2.2.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.
Files changed (68) hide show
  1. package/README.md +43 -0
  2. package/lib/actions/image_agents.d.ts +2 -1
  3. package/lib/actions/image_agents.js +3 -3
  4. package/lib/actions/images.d.ts +3 -1
  5. package/lib/actions/images.js +1 -0
  6. package/lib/actions/pdf.js +18 -15
  7. package/lib/methods/mulmo_media_source.d.ts +1 -0
  8. package/lib/methods/mulmo_media_source.js +17 -3
  9. package/lib/slide/blocks.d.ts +7 -0
  10. package/lib/slide/blocks.js +149 -0
  11. package/lib/slide/index.d.ts +5 -0
  12. package/lib/slide/index.js +7 -0
  13. package/lib/slide/layouts/big_quote.d.ts +2 -0
  14. package/lib/slide/layouts/big_quote.js +19 -0
  15. package/lib/slide/layouts/columns.d.ts +2 -0
  16. package/lib/slide/layouts/columns.js +56 -0
  17. package/lib/slide/layouts/comparison.d.ts +2 -0
  18. package/lib/slide/layouts/comparison.js +29 -0
  19. package/lib/slide/layouts/funnel.d.ts +2 -0
  20. package/lib/slide/layouts/funnel.js +27 -0
  21. package/lib/slide/layouts/grid.d.ts +2 -0
  22. package/lib/slide/layouts/grid.js +43 -0
  23. package/lib/slide/layouts/index.d.ts +3 -0
  24. package/lib/slide/layouts/index.js +43 -0
  25. package/lib/slide/layouts/matrix.d.ts +2 -0
  26. package/lib/slide/layouts/matrix.js +53 -0
  27. package/lib/slide/layouts/split.d.ts +2 -0
  28. package/lib/slide/layouts/split.js +38 -0
  29. package/lib/slide/layouts/stats.d.ts +2 -0
  30. package/lib/slide/layouts/stats.js +23 -0
  31. package/lib/slide/layouts/table.d.ts +2 -0
  32. package/lib/slide/layouts/table.js +46 -0
  33. package/lib/slide/layouts/timeline.d.ts +2 -0
  34. package/lib/slide/layouts/timeline.js +24 -0
  35. package/lib/slide/layouts/title.d.ts +2 -0
  36. package/lib/slide/layouts/title.js +17 -0
  37. package/lib/slide/render.d.ts +3 -0
  38. package/lib/slide/render.js +52 -0
  39. package/lib/slide/schema.d.ts +4463 -0
  40. package/lib/slide/schema.js +349 -0
  41. package/lib/slide/utils.d.ts +43 -0
  42. package/lib/slide/utils.js +165 -0
  43. package/lib/types/schema.d.ts +4935 -38
  44. package/lib/types/schema.js +11 -0
  45. package/lib/types/slide.d.ts +4463 -0
  46. package/lib/types/slide.js +349 -0
  47. package/lib/types/type.d.ts +1 -0
  48. package/lib/utils/context.d.ts +1351 -9
  49. package/lib/utils/html_render.js +44 -6
  50. package/lib/utils/image_plugins/index.js +14 -1
  51. package/lib/utils/image_plugins/slide.d.ts +19 -0
  52. package/lib/utils/image_plugins/slide.js +134 -0
  53. package/package.json +8 -8
  54. package/scripts/test/golden_age_of_discovery.json +270 -0
  55. package/scripts/test/img_detector.png +0 -0
  56. package/scripts/test/img_higgs.png +0 -0
  57. package/scripts/test/img_lhc.png +0 -0
  58. package/scripts/test/test_slide_01.json +105 -0
  59. package/scripts/test/test_slide_11.json +144 -0
  60. package/scripts/test/test_slide_12.json +887 -0
  61. package/scripts/test/test_slide_chart_mermaid.json +148 -0
  62. package/scripts/test/test_slide_image_ref.json +261 -0
  63. package/scripts/test/test_slide_image_ref_en.json +287 -0
  64. package/scripts/test/test_slide_showcase_corporate.json +497 -0
  65. package/scripts/test/test_slide_showcase_creative.json +545 -0
  66. package/scripts/test/test_slide_showcase_minimal.json +501 -0
  67. package/scripts/test/test_slide_showcase_pop.json +547 -0
  68. package/scripts/test/test_slide_showcase_warm.json +486 -0
@@ -1,17 +1,55 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import nodePath from "node:path";
4
+ import crypto from "node:crypto";
1
5
  import { marked } from "marked";
2
6
  import puppeteer from "puppeteer";
3
7
  const isCI = process.env.CI === "true";
8
+ /** Determine the appropriate waitUntil strategy based on HTML content */
9
+ const resolveWaitUntil = (html) => {
10
+ const hasExternalImages = html.includes("<img") && /src=["']https?:\/\//.test(html);
11
+ const hasLocalImages = html.includes("<img") && /src=["']file:\/\//.test(html);
12
+ if (hasExternalImages)
13
+ return "networkidle0";
14
+ if (hasLocalImages)
15
+ return "load";
16
+ return "domcontentloaded";
17
+ };
18
+ /**
19
+ * Load HTML into a Puppeteer page.
20
+ * When the HTML references file:// URLs, write it to a temp file
21
+ * and navigate via page.goto (setContent uses about:blank origin
22
+ * which blocks file:// loading).
23
+ */
24
+ const loadHtmlIntoPage = async (page, html, timeout_ms) => {
25
+ const waitUntil = resolveWaitUntil(html);
26
+ const hasFileUrls = /file:\/\//.test(html);
27
+ if (hasFileUrls) {
28
+ const tmpFile = nodePath.join(os.tmpdir(), `mulmocast_render_${crypto.randomUUID()}.html`);
29
+ fs.writeFileSync(tmpFile, html);
30
+ try {
31
+ await page.goto(`file://${tmpFile}`, { waitUntil, timeout: timeout_ms });
32
+ }
33
+ finally {
34
+ try {
35
+ fs.unlinkSync(tmpFile);
36
+ }
37
+ catch {
38
+ /* ignore cleanup errors */
39
+ }
40
+ }
41
+ }
42
+ else {
43
+ await page.setContent(html, { waitUntil, timeout: timeout_ms });
44
+ }
45
+ };
4
46
  export const renderHTMLToImage = async (html, outputPath, width, height, isMermaid = false, omitBackground = false) => {
5
47
  // Use Puppeteer to render HTML to an image
6
48
  const browser = await puppeteer.launch({
7
- args: isCI ? ["--no-sandbox"] : [],
49
+ args: isCI ? ["--no-sandbox", "--allow-file-access-from-files"] : ["--allow-file-access-from-files"],
8
50
  });
9
51
  const page = await browser.newPage();
10
- // Set the page content to the HTML generated from the Markdown
11
- // Use networkidle0 only for external images, otherwise use domcontentloaded for faster rendering
12
- const hasExternalImages = html.includes("<img") && /src=["']https?:\/\//.test(html);
13
- const waitUntil = hasExternalImages ? "networkidle0" : "domcontentloaded";
14
- await page.setContent(html, { waitUntil, timeout: 30000 });
52
+ await loadHtmlIntoPage(page, html, 30000);
15
53
  // Adjust page settings if needed (like width, height, etc.)
16
54
  await page.setViewport({ width, height });
17
55
  // height:100% ensures background fills viewport; only reset html, let body styles come from custom CSS
@@ -8,7 +8,20 @@ import * as pluginMovie from "./movie.js";
8
8
  import * as pluginBeat from "./beat.js";
9
9
  import * as pluginVoiceOver from "./voice_over.js";
10
10
  import * as pluginVision from "./vision.js";
11
- const imagePlugins = [pluginTextSlide, pluginMarkdown, pluginImage, pluginChart, pluginMermaid, pluginMovie, pluginHtmlTailwind, pluginBeat, pluginVoiceOver, pluginVision];
11
+ import * as pluginSlide from "./slide.js";
12
+ const imagePlugins = [
13
+ pluginTextSlide,
14
+ pluginMarkdown,
15
+ pluginImage,
16
+ pluginChart,
17
+ pluginMermaid,
18
+ pluginMovie,
19
+ pluginHtmlTailwind,
20
+ pluginBeat,
21
+ pluginVoiceOver,
22
+ pluginVision,
23
+ pluginSlide,
24
+ ];
12
25
  export const findImagePlugin = (imageType) => {
13
26
  return imagePlugins.find((plugin) => plugin.imageType === imageType);
14
27
  };
@@ -0,0 +1,19 @@
1
+ import { ImageProcessorParams } from "../../types/index.js";
2
+ import type { SlideLayout, ContentBlock } from "../../slide/index.js";
3
+ export declare const imageType = "slide";
4
+ /**
5
+ * Collect all content block arrays from a slide layout.
6
+ * Only layouts that embed ContentBlock[] are handled:
7
+ * columns, comparison, grid, split, matrix
8
+ */
9
+ export declare const collectContentArrays: (slide: SlideLayout) => ContentBlock[][];
10
+ /**
11
+ * Deep-clone a slide layout and resolve `imageRef` content blocks
12
+ * into `image` blocks using the resolved imageRefs map and a path-to-URL converter.
13
+ * Default converter produces data URLs (for self-contained HTML).
14
+ * Pass `toFileUrl` for Puppeteer rendering (avoids huge inline base64).
15
+ */
16
+ export declare const resolveSlideImageRefs: (slide: SlideLayout, imageRefs: Record<string, string>, converter?: (filePath: string) => string) => SlideLayout;
17
+ export declare const process: (params: ImageProcessorParams) => Promise<string | undefined>;
18
+ export declare const path: (params: ImageProcessorParams) => string;
19
+ export declare const html: (params: ImageProcessorParams) => Promise<string | undefined>;
@@ -0,0 +1,134 @@
1
+ import nodePath from "node:path";
2
+ import { pathToFileURL } from "node:url";
3
+ import { generateSlideHTML } from "../../slide/index.js";
4
+ import { renderHTMLToImage } from "../html_render.js";
5
+ import { parrotingImagePath } from "./utils.js";
6
+ import { pathToDataUrl } from "../../methods/mulmo_media_source.js";
7
+ import { imageAction, imageFileTarget, unknownMediaType } from "../error_cause.js";
8
+ export const imageType = "slide";
9
+ const slideImageRefError = (refKey) => ({
10
+ type: unknownMediaType,
11
+ action: imageAction,
12
+ target: imageFileTarget,
13
+ agentName: "slidePlugin",
14
+ refKey,
15
+ });
16
+ /** Convert a file path to a file:// URL string */
17
+ const toFileUrl = (filePath) => {
18
+ return pathToFileURL(nodePath.resolve(filePath)).href;
19
+ };
20
+ /**
21
+ * Collect all content block arrays from a slide layout.
22
+ * Only layouts that embed ContentBlock[] are handled:
23
+ * columns, comparison, grid, split, matrix
24
+ */
25
+ export const collectContentArrays = (slide) => {
26
+ const result = [];
27
+ switch (slide.layout) {
28
+ case "columns":
29
+ slide.columns.forEach((col) => {
30
+ if (col.content)
31
+ result.push(col.content);
32
+ });
33
+ break;
34
+ case "comparison":
35
+ if (slide.left.content)
36
+ result.push(slide.left.content);
37
+ if (slide.right.content)
38
+ result.push(slide.right.content);
39
+ break;
40
+ case "grid":
41
+ slide.items.forEach((item) => {
42
+ if (item.content)
43
+ result.push(item.content);
44
+ });
45
+ break;
46
+ case "split":
47
+ if (slide.left?.content)
48
+ result.push(slide.left.content);
49
+ if (slide.right?.content)
50
+ result.push(slide.right.content);
51
+ break;
52
+ case "matrix":
53
+ slide.cells.forEach((cell) => {
54
+ if (cell.content)
55
+ result.push(cell.content);
56
+ });
57
+ break;
58
+ default:
59
+ // title, bigQuote, stats, timeline, table, funnel — no content blocks
60
+ break;
61
+ }
62
+ return result;
63
+ };
64
+ /**
65
+ * Deep-clone a slide layout and resolve `imageRef` content blocks
66
+ * into `image` blocks using the resolved imageRefs map and a path-to-URL converter.
67
+ * Default converter produces data URLs (for self-contained HTML).
68
+ * Pass `toFileUrl` for Puppeteer rendering (avoids huge inline base64).
69
+ */
70
+ export const resolveSlideImageRefs = (slide, imageRefs, converter = pathToDataUrl) => {
71
+ const cloned = JSON.parse(JSON.stringify(slide));
72
+ const contentArrays = collectContentArrays(cloned);
73
+ contentArrays.forEach((blocks) => {
74
+ blocks.forEach((block, index) => {
75
+ if (block.type !== "imageRef")
76
+ return;
77
+ const filePath = imageRefs[block.ref];
78
+ if (!filePath) {
79
+ throw new Error(`Unknown image ref key: "${block.ref}"`, { cause: slideImageRefError(block.ref) });
80
+ }
81
+ blocks[index] = {
82
+ type: "image",
83
+ src: converter(filePath),
84
+ ...(block.alt !== undefined && { alt: block.alt }),
85
+ ...(block.fit !== undefined && { fit: block.fit }),
86
+ };
87
+ });
88
+ });
89
+ return cloned;
90
+ };
91
+ const resolveTheme = (params) => {
92
+ const { beat, context } = params;
93
+ if (!beat.image || beat.image.type !== imageType) {
94
+ throw new Error("resolveTheme called on non-slide beat");
95
+ }
96
+ const defaultTheme = context.presentationStyle.slideParams?.theme;
97
+ const theme = beat.image.theme ?? defaultTheme;
98
+ if (!theme) {
99
+ throw new Error("Slide theme is required: set slideParams.theme or beat.image.theme");
100
+ }
101
+ return theme;
102
+ };
103
+ const resolveSlide = (params, converter = pathToDataUrl) => {
104
+ const { beat, imageRefs } = params;
105
+ if (!beat.image || beat.image.type !== imageType) {
106
+ throw new Error("resolveSlide called on non-slide beat");
107
+ }
108
+ const slide = beat.image.slide;
109
+ if (imageRefs && Object.keys(imageRefs).length > 0) {
110
+ return resolveSlideImageRefs(slide, imageRefs, converter);
111
+ }
112
+ return slide;
113
+ };
114
+ const processSlide = async (params) => {
115
+ const { beat, imagePath, canvasSize } = params;
116
+ if (!beat.image || beat.image.type !== imageType)
117
+ return;
118
+ const theme = resolveTheme(params);
119
+ const slide = resolveSlide(params, toFileUrl);
120
+ const html = generateSlideHTML(theme, slide);
121
+ await renderHTMLToImage(html, imagePath, canvasSize.width, canvasSize.height);
122
+ return imagePath;
123
+ };
124
+ const dumpHtml = async (params) => {
125
+ const { beat } = params;
126
+ if (!beat.image || beat.image.type !== imageType)
127
+ return;
128
+ const theme = resolveTheme(params);
129
+ const slide = resolveSlide(params);
130
+ return generateSlideHTML(theme, slide);
131
+ };
132
+ export const process = processSlide;
133
+ export const path = parrotingImagePath;
134
+ export const html = dumpHtml;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mulmocast",
3
- "version": "2.1.39",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.node.js",
@@ -86,8 +86,8 @@
86
86
  "@graphai/stream_agent_filter": "^2.0.3",
87
87
  "@graphai/vanilla": "^2.0.12",
88
88
  "@graphai/vanilla_node_agents": "^2.0.4",
89
- "@inquirer/input": "^5.0.4",
90
- "@inquirer/select": "^5.0.4",
89
+ "@inquirer/input": "^5.0.7",
90
+ "@inquirer/select": "^5.0.7",
91
91
  "@modelcontextprotocol/sdk": "^1.26.0",
92
92
  "@mozilla/readability": "^0.6.0",
93
93
  "@tavily/core": "^0.5.11",
@@ -96,11 +96,11 @@
96
96
  "dotenv": "^17.3.1",
97
97
  "fluent-ffmpeg": "^2.1.3",
98
98
  "graphai": "^2.0.16",
99
- "jsdom": "^28.0.0",
100
- "marked": "^17.0.2",
99
+ "jsdom": "^28.1.0",
100
+ "marked": "^17.0.3",
101
101
  "mulmocast-vision": "^1.0.8",
102
102
  "ora": "^9.3.0",
103
- "puppeteer": "^24.37.2",
103
+ "puppeteer": "^24.37.4",
104
104
  "replicate": "^1.4.0",
105
105
  "yaml": "^2.8.2",
106
106
  "yargs": "^18.0.0",
@@ -117,12 +117,12 @@
117
117
  "eslint": "^10.0.0",
118
118
  "eslint-config-prettier": "^10.1.8",
119
119
  "eslint-plugin-prettier": "^5.5.5",
120
- "eslint-plugin-sonarjs": "^3.0.7",
120
+ "eslint-plugin-sonarjs": "^4.0.0",
121
121
  "globals": "^17.3.0",
122
122
  "prettier": "^3.8.1",
123
123
  "tsx": "^4.21.0",
124
124
  "typescript": "^5.9.3",
125
- "typescript-eslint": "^8.55.0"
125
+ "typescript-eslint": "^8.56.0"
126
126
  },
127
127
  "engines": {
128
128
  "node": ">=22.0.0"
@@ -0,0 +1,270 @@
1
+ {
2
+ "$mulmocast": { "version": "1.1" },
3
+ "lang": "en",
4
+ "title": "We Are Entering a Golden Age of Discovery",
5
+ "canvasSize": { "width": 1280, "height": 720 },
6
+ "speechParams": {
7
+ "speakers": {
8
+ "Presenter": {
9
+ "provider": "openai",
10
+ "voiceId": "shimmer",
11
+ "displayName": { "en": "Presenter" }
12
+ }
13
+ }
14
+ },
15
+ "imageParams": { "provider": "openai", "images": {} },
16
+ "movieParams": { "provider": "replicate" },
17
+ "soundEffectParams": { "provider": "replicate" },
18
+ "slideParams": {
19
+ "theme": {
20
+ "colors": {
21
+ "bg": "1C1917",
22
+ "bgCard": "292524",
23
+ "bgCardAlt": "3D3733",
24
+ "text": "FAFAF9",
25
+ "textMuted": "D6D3D1",
26
+ "textDim": "A8A29E",
27
+ "primary": "F43F5E",
28
+ "accent": "FB923C",
29
+ "success": "4ADE80",
30
+ "warning": "FBBF24",
31
+ "danger": "EF4444",
32
+ "info": "38BDF8",
33
+ "highlight": "E879F9"
34
+ },
35
+ "fonts": { "title": "Georgia", "body": "Helvetica", "mono": "Consolas" }
36
+ }
37
+ },
38
+ "audioParams": {
39
+ "introPadding": 1,
40
+ "padding": 0.3,
41
+ "closingPadding": 0.8,
42
+ "outroPadding": 1,
43
+ "bgmVolume": 0.2,
44
+ "audioVolume": 1,
45
+ "suppressSpeech": false
46
+ },
47
+ "beats": [
48
+ {
49
+ "text": "We are entering a golden age of discovery. Nobel laureate Demis Hassabis believes the next ten to fifteen years will be the most transformative period in human history.",
50
+ "image": {
51
+ "type": "slide",
52
+ "slide": {
53
+ "layout": "title",
54
+ "title": "A Golden Age of Discovery",
55
+ "subtitle": "How AI Will Transform Science and Society",
56
+ "author": "Based on Demis Hassabis via Garry's List",
57
+ "note": "February 2026"
58
+ }
59
+ }
60
+ },
61
+ {
62
+ "text": "Hassabis envisions a future of radical abundance. AI will solve our hardest problems: personalized medicine replacing one-size-fits-all healthcare, breakthroughs in renewable energy like fusion, solar, and batteries, and eventually enabling humanity to explore the galaxy.",
63
+ "image": {
64
+ "type": "slide",
65
+ "slide": {
66
+ "layout": "grid",
67
+ "title": "The Vision of Radical Abundance",
68
+ "subtitle": "AI solving humanity's hardest challenges",
69
+ "gridColumns": 3,
70
+ "items": [
71
+ {
72
+ "title": "Personalized Medicine",
73
+ "icon": "💊",
74
+ "accentColor": "primary",
75
+ "content": [{ "type": "text", "value": "Replace one-size-fits-all healthcare with treatments tailored to each individual" }]
76
+ },
77
+ {
78
+ "title": "Clean Energy",
79
+ "icon": "⚡",
80
+ "accentColor": "success",
81
+ "content": [{ "type": "text", "value": "Breakthroughs in fusion, solar, and battery technology for limitless energy" }]
82
+ },
83
+ {
84
+ "title": "Space Exploration",
85
+ "icon": "🚀",
86
+ "accentColor": "accent",
87
+ "content": [{ "type": "text", "value": "Radical abundance enabling humanity to venture beyond Earth and explore the galaxy" }]
88
+ }
89
+ ]
90
+ }
91
+ }
92
+ },
93
+ {
94
+ "text": "In drug discovery, AI is already compressing development timelines dramatically. What used to take four and a half years can now be done in nine to eighteen months. And AI agents could dissolve healthcare's massive administrative inefficiencies.",
95
+ "image": {
96
+ "type": "slide",
97
+ "slide": {
98
+ "layout": "stats",
99
+ "title": "AI Transforms Drug Discovery",
100
+ "subtitle": "Compressing timelines and eliminating bureaucracy",
101
+ "stats": [
102
+ {
103
+ "value": "4.5 yrs",
104
+ "label": "Traditional Drug Development",
105
+ "color": "danger",
106
+ "change": "Slow"
107
+ },
108
+ {
109
+ "value": "9-18 mo",
110
+ "label": "AI-Accelerated Development",
111
+ "color": "success",
112
+ "change": "3-6x faster"
113
+ }
114
+ ],
115
+ "callout": {
116
+ "text": "AI agents can manage insurance bureaucracy and claims processing, dissolving administrative inefficiencies",
117
+ "label": "Beyond Discovery",
118
+ "color": "info"
119
+ }
120
+ }
121
+ }
122
+ },
123
+ {
124
+ "text": "The key insight is that the constraint was never biology. It was computing power and data. Now that we have both, the doors to discovery are wide open.",
125
+ "image": {
126
+ "type": "slide",
127
+ "slide": {
128
+ "layout": "bigQuote",
129
+ "quote": "The constraint was never biology. It was computing power and data.",
130
+ "author": "Demis Hassabis",
131
+ "role": "Nobel Laureate, CEO of Google DeepMind"
132
+ }
133
+ }
134
+ },
135
+ {
136
+ "text": "But there are two competing worldviews about the future. Pessimists think in terms of scarcity: same resources with fewer people leads to despair. Builders see infinite human needs, where solving one problem reveals ten more worth solving.",
137
+ "image": {
138
+ "type": "slide",
139
+ "slide": {
140
+ "layout": "comparison",
141
+ "title": "Two Competing Worldviews",
142
+ "subtitle": "A fundamental philosophical split",
143
+ "left": {
144
+ "title": "Pessimist",
145
+ "accentColor": "danger",
146
+ "content": [
147
+ { "type": "text", "value": "Scarcity Mindset", "bold": true, "color": "danger", "fontSize": 22 },
148
+ { "type": "bullets", "items": ["Same with less people", "Zero-sum thinking", "Leads to despair", "Progress as threat"] }
149
+ ]
150
+ },
151
+ "right": {
152
+ "title": "Builder",
153
+ "accentColor": "success",
154
+ "content": [
155
+ { "type": "text", "value": "Abundance Mindset", "bold": true, "color": "success", "fontSize": 22 },
156
+ { "type": "bullets", "items": ["Infinite human needs", "Solving 1 problem reveals 10 more", "Boundless opportunity", "Progress as gift"] }
157
+ ]
158
+ }
159
+ }
160
+ }
161
+ },
162
+ {
163
+ "text": "Innovation is inherently generous. Creators capture just two percent of the value they create. Ninety-eight percent flows to society. Building is the most philanthropic act there is.",
164
+ "image": {
165
+ "type": "slide",
166
+ "slide": {
167
+ "layout": "split",
168
+ "left": {
169
+ "title": "Value Distribution",
170
+ "dark": true,
171
+ "ratio": 40,
172
+ "accentColor": "accent",
173
+ "content": [
174
+ { "type": "metric", "value": "2%", "label": "Captured by Creators", "color": "accent" },
175
+ { "type": "divider", "color": "accent" },
176
+ { "type": "metric", "value": "98%", "label": "Flows to Society", "color": "success" }
177
+ ]
178
+ },
179
+ "right": {
180
+ "title": "Innovation is Philanthropy",
181
+ "ratio": 60,
182
+ "content": [
183
+ { "type": "text", "value": "Founders and builders create outsized value for the world, keeping only a tiny fraction for themselves." },
184
+ { "type": "callout", "text": "Building is the most philanthropic act there is", "color": "accent", "style": "quote" }
185
+ ]
186
+ }
187
+ }
188
+ }
189
+ },
190
+ {
191
+ "text": "But there's a danger. The article argues that the only way to create suffering at scale is to outlaw progress. The Precautionary Principle, when taken too far, becomes the enemy of discovery.",
192
+ "image": {
193
+ "type": "slide",
194
+ "slide": {
195
+ "layout": "comparison",
196
+ "title": "The Regulatory Dilemma",
197
+ "left": {
198
+ "title": "Precautionary Principle",
199
+ "accentColor": "warning",
200
+ "content": [
201
+ {
202
+ "type": "bullets",
203
+ "items": ["Prove safety before deploying", "Err on the side of caution", "Regulate first, innovate later", "Minimize any possible risk"]
204
+ },
205
+ { "type": "callout", "text": "Can become an obstacle to all progress", "color": "warning", "style": "warning" }
206
+ ]
207
+ },
208
+ "right": {
209
+ "title": "Pro-Innovation View",
210
+ "accentColor": "success",
211
+ "content": [
212
+ { "type": "bullets", "items": ["Build, test, iterate", "Benefits compound over time", "Inaction has its own risks", "Progress lifts everyone"] },
213
+ { "type": "callout", "text": "The only way to create suffering at scale is to outlaw progress", "color": "danger", "style": "warning" }
214
+ ]
215
+ }
216
+ }
217
+ }
218
+ },
219
+ {
220
+ "text": "Founders intuitively understand what economists formally prove: human wants are boundless. Every problem solved creates new frontiers. Whether we accelerate into this golden age depends entirely on whether builders choose action over regulatory caution.",
221
+ "image": {
222
+ "type": "slide",
223
+ "slide": {
224
+ "layout": "timeline",
225
+ "title": "The Path to the Golden Age",
226
+ "subtitle": "Progress acceleration depends on builder's choices",
227
+ "items": [
228
+ {
229
+ "date": "Now",
230
+ "title": "AI Breakthrough",
231
+ "description": "Computing power and data unlock biology, physics, chemistry",
232
+ "color": "info",
233
+ "done": true
234
+ },
235
+ {
236
+ "date": "Near Term",
237
+ "title": "Applied Discovery",
238
+ "description": "Drug discovery in months, renewable energy breakthroughs",
239
+ "color": "accent"
240
+ },
241
+ {
242
+ "date": "10-15 Years",
243
+ "title": "Radical Abundance",
244
+ "description": "Personalized medicine, limitless energy, space exploration",
245
+ "color": "success"
246
+ },
247
+ {
248
+ "date": "Beyond",
249
+ "title": "Golden Age",
250
+ "description": "Humanity's most transformative era of scientific discovery",
251
+ "color": "highlight"
252
+ }
253
+ ]
254
+ }
255
+ }
256
+ },
257
+ {
258
+ "text": "We stand at the threshold of the most transformative era in human history. The golden age of discovery isn't a distant dream. It's already beginning. The question is whether we choose to build it.",
259
+ "image": {
260
+ "type": "slide",
261
+ "slide": {
262
+ "layout": "bigQuote",
263
+ "quote": "Human wants are boundless. Every problem solved creates new frontiers. The golden age of discovery isn't a distant dream — it's already beginning.",
264
+ "author": "Demis Hassabis",
265
+ "role": "Nobel Laureate"
266
+ }
267
+ }
268
+ }
269
+ ]
270
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,105 @@
1
+ {
2
+ "$mulmocast": { "version": "1.1" },
3
+ "lang": "en",
4
+ "title": "Slide Plugin Test - Dark Theme",
5
+ "beats": [
6
+ {
7
+ "text": "TechVault Series A - title slide",
8
+ "image": {
9
+ "type": "slide",
10
+ "theme": {
11
+ "colors": {
12
+ "bg": "0F172A",
13
+ "bgCard": "1E293B",
14
+ "bgCardAlt": "334155",
15
+ "text": "FFFFFF",
16
+ "textMuted": "CBD5E1",
17
+ "textDim": "64748B",
18
+ "primary": "3B82F6",
19
+ "accent": "8B5CF6",
20
+ "success": "22C55E",
21
+ "warning": "F97316",
22
+ "danger": "EF4444",
23
+ "info": "14B8A6",
24
+ "highlight": "EC4899"
25
+ },
26
+ "fonts": { "title": "Georgia", "body": "Calibri", "mono": "Consolas" }
27
+ },
28
+ "slide": {
29
+ "layout": "title",
30
+ "title": "TechVault Series A",
31
+ "subtitle": "Redefining Cloud Security",
32
+ "author": "Sarah Chen, CEO"
33
+ }
34
+ }
35
+ },
36
+ {
37
+ "text": "Key metrics showing strong growth",
38
+ "image": {
39
+ "type": "slide",
40
+ "theme": {
41
+ "colors": {
42
+ "bg": "0F172A",
43
+ "bgCard": "1E293B",
44
+ "bgCardAlt": "334155",
45
+ "text": "FFFFFF",
46
+ "textMuted": "CBD5E1",
47
+ "textDim": "64748B",
48
+ "primary": "3B82F6",
49
+ "accent": "8B5CF6",
50
+ "success": "22C55E",
51
+ "warning": "F97316",
52
+ "danger": "EF4444",
53
+ "info": "14B8A6",
54
+ "highlight": "EC4899"
55
+ },
56
+ "fonts": { "title": "Georgia", "body": "Calibri", "mono": "Consolas" }
57
+ },
58
+ "slide": {
59
+ "layout": "stats",
60
+ "title": "Key Metrics",
61
+ "stats": [
62
+ { "value": "$4.2M", "label": "ARR", "change": "+127%" },
63
+ { "value": "12,500", "label": "Active Users", "change": "+45%" },
64
+ { "value": "99.9%", "label": "Uptime" },
65
+ { "value": "4.8", "label": "NPS Score" }
66
+ ]
67
+ }
68
+ }
69
+ },
70
+ {
71
+ "text": "2026 roadmap with quarterly milestones",
72
+ "image": {
73
+ "type": "slide",
74
+ "theme": {
75
+ "colors": {
76
+ "bg": "0F172A",
77
+ "bgCard": "1E293B",
78
+ "bgCardAlt": "334155",
79
+ "text": "FFFFFF",
80
+ "textMuted": "CBD5E1",
81
+ "textDim": "64748B",
82
+ "primary": "3B82F6",
83
+ "accent": "8B5CF6",
84
+ "success": "22C55E",
85
+ "warning": "F97316",
86
+ "danger": "EF4444",
87
+ "info": "14B8A6",
88
+ "highlight": "EC4899"
89
+ },
90
+ "fonts": { "title": "Georgia", "body": "Calibri", "mono": "Consolas" }
91
+ },
92
+ "slide": {
93
+ "layout": "timeline",
94
+ "title": "2026 Roadmap",
95
+ "items": [
96
+ { "date": "Q1 2026", "title": "Alpha", "done": true },
97
+ { "date": "Q2 2026", "title": "Beta", "done": true },
98
+ { "date": "Q3 2026", "title": "Public Launch", "done": false },
99
+ { "date": "Q4 2026", "title": "Enterprise", "done": false }
100
+ ]
101
+ }
102
+ }
103
+ }
104
+ ]
105
+ }