mulmocast 2.3.2 → 2.4.1
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/html/tailwind.html +1 -0
- package/assets/html/tailwind_animated.html +259 -0
- package/lib/actions/image_agents.d.ts +1 -0
- package/lib/actions/image_agents.js +24 -2
- package/lib/actions/images.d.ts +3 -0
- package/lib/actions/images.js +4 -3
- package/lib/agents/combine_audio_files_agent.js +5 -0
- package/lib/methods/mulmo_beat.d.ts +7 -0
- package/lib/methods/mulmo_beat.js +18 -0
- package/lib/types/schema.d.ts +23 -0
- package/lib/types/schema.js +10 -0
- package/lib/utils/context.d.ts +8 -0
- package/lib/utils/ffmpeg_utils.d.ts +11 -1
- package/lib/utils/ffmpeg_utils.js +33 -2
- package/lib/utils/file.d.ts +1 -0
- package/lib/utils/file.js +6 -0
- package/lib/utils/html_render.d.ts +11 -0
- package/lib/utils/html_render.js +84 -34
- package/lib/utils/image_plugins/html_tailwind.js +78 -6
- package/package.json +3 -3
- package/scripts/test/test_html_animation.json +563 -0
- package/scripts/test/test_vocab_animation.json +226 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$mulmocast": {
|
|
3
|
+
"version": "1.1"
|
|
4
|
+
},
|
|
5
|
+
"lang": "en",
|
|
6
|
+
"title": "MulmoCast HTML Animation Guide",
|
|
7
|
+
"speechParams": {
|
|
8
|
+
"speakers": {
|
|
9
|
+
"Presenter": {
|
|
10
|
+
"voiceId": "shimmer",
|
|
11
|
+
"displayName": {
|
|
12
|
+
"en": "Presenter"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"beats": [
|
|
18
|
+
{
|
|
19
|
+
"id": "intro_title",
|
|
20
|
+
"speaker": "Presenter",
|
|
21
|
+
"duration": 3,
|
|
22
|
+
"image": {
|
|
23
|
+
"type": "html_tailwind",
|
|
24
|
+
"html": [
|
|
25
|
+
"<div class='h-full flex flex-col items-center justify-center bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900'>",
|
|
26
|
+
" <h1 id='title' class='text-5xl font-bold text-white mb-6' style='opacity:0'>MulmoCast Animation</h1>",
|
|
27
|
+
" <p id='subtitle' class='text-xl text-blue-300' style='opacity:0'>Frame-based HTML animation for deterministic video</p>",
|
|
28
|
+
" <div id='line' class='h-1 bg-gradient-to-r from-cyan-400 to-purple-500 mt-8 rounded' style='width:0'></div>",
|
|
29
|
+
"</div>"
|
|
30
|
+
],
|
|
31
|
+
"script": [
|
|
32
|
+
"const animation = new MulmoAnimation();",
|
|
33
|
+
"animation.animate('#title', { opacity: [0, 1], translateY: [30, 0] }, { start: 0, end: 0.5, easing: 'easeOut' });",
|
|
34
|
+
"animation.animate('#subtitle', { opacity: [0, 1], translateY: [20, 0] }, { start: 0.3, end: 0.8, easing: 'easeOut' });",
|
|
35
|
+
"animation.animate('#line', { width: [0, 400, 'px'] }, { start: 0.5, end: 1.5, easing: 'easeInOut' });"
|
|
36
|
+
],
|
|
37
|
+
"animation": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "explain_basics",
|
|
42
|
+
"speaker": "Presenter",
|
|
43
|
+
"duration": 4,
|
|
44
|
+
"image": {
|
|
45
|
+
"type": "html_tailwind",
|
|
46
|
+
"html": [
|
|
47
|
+
"<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
|
|
48
|
+
" <h2 id='heading' class='text-3xl font-bold text-white mb-8' style='opacity:0'>How It Works</h2>",
|
|
49
|
+
" <div class='bg-slate-800 rounded-xl p-6 mb-6'>",
|
|
50
|
+
" <p class='text-sm text-slate-400 mb-2 font-mono'>MulmoScript (JSON)</p>",
|
|
51
|
+
" <pre id='code' class='text-lg font-mono text-green-400 leading-relaxed'></pre>",
|
|
52
|
+
" </div>",
|
|
53
|
+
" <div id='note' class='text-base text-slate-400 leading-relaxed' style='opacity:0'>",
|
|
54
|
+
" <p>Add <code class='text-cyan-400'>\"animation\": true</code> to enable frame-based rendering.</p>",
|
|
55
|
+
" <p>Define a <code class='text-cyan-400'>render(frame, totalFrames, fps)</code> function in your HTML.</p>",
|
|
56
|
+
" <p>Each frame is captured by Puppeteer, then combined into video by FFmpeg.</p>",
|
|
57
|
+
" </div>",
|
|
58
|
+
"</div>"
|
|
59
|
+
],
|
|
60
|
+
"script": [
|
|
61
|
+
"const codeLines = [",
|
|
62
|
+
" '{',",
|
|
63
|
+
" ' \"image\": {',",
|
|
64
|
+
" ' \"type\": \"html_tailwind\",',",
|
|
65
|
+
" ' \"html\": \"<div>...</div>\",',",
|
|
66
|
+
" ' \"animation\": true',",
|
|
67
|
+
" ' },',",
|
|
68
|
+
" ' \"duration\": 3',",
|
|
69
|
+
" '}'",
|
|
70
|
+
"];",
|
|
71
|
+
"const animation = new MulmoAnimation();",
|
|
72
|
+
"animation.animate('#heading', { opacity: [0, 1] }, { start: 0, end: 0.3 });",
|
|
73
|
+
"animation.codeReveal('#code', codeLines, { start: 0.3, end: 2.5 });",
|
|
74
|
+
"animation.animate('#note', { opacity: [0, 1] }, { start: 2.5, end: 3 });"
|
|
75
|
+
],
|
|
76
|
+
"animation": true
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "explain_render",
|
|
81
|
+
"speaker": "Presenter",
|
|
82
|
+
"duration": 4,
|
|
83
|
+
"image": {
|
|
84
|
+
"type": "html_tailwind",
|
|
85
|
+
"html": [
|
|
86
|
+
"<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
|
|
87
|
+
" <h2 class='text-3xl font-bold text-white mb-6'>The render() Function</h2>",
|
|
88
|
+
" <div class='bg-slate-800 rounded-xl p-6 mb-6'>",
|
|
89
|
+
" <pre id='fn-code' class='text-base font-mono text-green-400 leading-relaxed'></pre>",
|
|
90
|
+
" </div>",
|
|
91
|
+
" <div class='flex gap-8 mt-4'>",
|
|
92
|
+
" <div id='param0' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
|
|
93
|
+
" <p class='text-cyan-400 font-mono font-bold mb-1'>frame</p>",
|
|
94
|
+
" <p class='text-slate-300 text-sm'>Current frame number (0-based)</p>",
|
|
95
|
+
" <p id='frame-val' class='text-2xl font-mono text-white mt-2'>0</p>",
|
|
96
|
+
" </div>",
|
|
97
|
+
" <div id='param1' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
|
|
98
|
+
" <p class='text-purple-400 font-mono font-bold mb-1'>totalFrames</p>",
|
|
99
|
+
" <p class='text-slate-300 text-sm'>Total frame count = floor(duration * fps)</p>",
|
|
100
|
+
" <p id='total-val' class='text-2xl font-mono text-white mt-2'>120</p>",
|
|
101
|
+
" </div>",
|
|
102
|
+
" <div id='param2' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
|
|
103
|
+
" <p class='text-yellow-400 font-mono font-bold mb-1'>fps</p>",
|
|
104
|
+
" <p class='text-slate-300 text-sm'>Frames per second (default: 30)</p>",
|
|
105
|
+
" <p class='text-2xl font-mono text-white mt-2'>30</p>",
|
|
106
|
+
" </div>",
|
|
107
|
+
" </div>",
|
|
108
|
+
"</div>"
|
|
109
|
+
],
|
|
110
|
+
"script": [
|
|
111
|
+
"const fnLines = [",
|
|
112
|
+
" 'function render(frame, totalFrames, fps) {',",
|
|
113
|
+
" ' // Called once per frame by Puppeteer',",
|
|
114
|
+
" ' // Manipulate DOM based on frame number',",
|
|
115
|
+
" ' element.style.opacity = frame / totalFrames;',",
|
|
116
|
+
" '}'",
|
|
117
|
+
"];",
|
|
118
|
+
"const animation = new MulmoAnimation();",
|
|
119
|
+
"animation.codeReveal('#fn-code', fnLines, { start: 0, end: 1.0 });",
|
|
120
|
+
"animation.stagger('#param{i}', 3, { opacity: [0, 1], translateY: [15, 0] }, { start: 1.2, stagger: 0.4, duration: 0.3, easing: 'easeOut' });",
|
|
121
|
+
"function render(frame, totalFrames, fps) {",
|
|
122
|
+
" animation.update(frame, fps);",
|
|
123
|
+
" document.getElementById('frame-val').textContent = Math.floor(interpolate(frame, { input: { inMin: fps * 2.5, inMax: totalFrames }, output: { outMin: 0, outMax: totalFrames } }));",
|
|
124
|
+
"}"
|
|
125
|
+
],
|
|
126
|
+
"animation": true
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "explain_interpolate",
|
|
131
|
+
"speaker": "Presenter",
|
|
132
|
+
"duration": 5,
|
|
133
|
+
"image": {
|
|
134
|
+
"type": "html_tailwind",
|
|
135
|
+
"html": [
|
|
136
|
+
"<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
|
|
137
|
+
" <h2 class='text-3xl font-bold text-white mb-6'>Helper: interpolate()</h2>",
|
|
138
|
+
" <div class='bg-slate-800 rounded-xl p-4 mb-6'>",
|
|
139
|
+
" <pre class='text-base font-mono text-green-400'>interpolate(frame, { input: { inMin, inMax }, output: { outMin, outMax }, easing? })</pre>",
|
|
140
|
+
" </div>",
|
|
141
|
+
" <p class='text-slate-400 mb-6'>Linear interpolation with clamping. Maps frame range to value range.</p>",
|
|
142
|
+
" <div class='flex gap-8'>",
|
|
143
|
+
" <div class='flex-1'>",
|
|
144
|
+
" <p class='text-white font-semibold mb-3'>Live Demo: opacity & position</p>",
|
|
145
|
+
" <div class='bg-slate-800 rounded-lg h-40 relative overflow-hidden'>",
|
|
146
|
+
" <div id='demo-ball' class='w-12 h-12 bg-cyan-400 rounded-full absolute top-14' style='left:10px; opacity:0'></div>",
|
|
147
|
+
" <div class='absolute bottom-2 left-2 right-2 h-1 bg-slate-700 rounded'></div>",
|
|
148
|
+
" </div>",
|
|
149
|
+
" <p id='demo-label' class='text-sm font-mono text-slate-400 mt-2'></p>",
|
|
150
|
+
" </div>",
|
|
151
|
+
" <div class='flex-1'>",
|
|
152
|
+
" <p class='text-white font-semibold mb-3'>Code</p>",
|
|
153
|
+
" <div class='bg-slate-800 rounded-lg p-4'>",
|
|
154
|
+
" <pre id='demo-code' class='text-sm font-mono text-green-400 leading-relaxed'></pre>",
|
|
155
|
+
" </div>",
|
|
156
|
+
" </div>",
|
|
157
|
+
" </div>",
|
|
158
|
+
"</div>"
|
|
159
|
+
],
|
|
160
|
+
"script": [
|
|
161
|
+
"const demoCode = [",
|
|
162
|
+
" '// Fade in: 0→1 over first second',",
|
|
163
|
+
" 'interpolate(frame, {',",
|
|
164
|
+
" ' input: { inMin: 0, inMax: fps },',",
|
|
165
|
+
" ' output: { outMin: 0, outMax: 1 }',",
|
|
166
|
+
" '})',",
|
|
167
|
+
" '',",
|
|
168
|
+
" '// Move: 10→350 over full duration',",
|
|
169
|
+
" 'interpolate(frame, {',",
|
|
170
|
+
" ' input: { inMin: 0, inMax: totalFrames },',",
|
|
171
|
+
" ' output: { outMin: 10, outMax: 350 }',",
|
|
172
|
+
" '})'",
|
|
173
|
+
"];",
|
|
174
|
+
"const animation = new MulmoAnimation();",
|
|
175
|
+
"animation.codeReveal('#demo-code', demoCode, { start: 0.5, end: 2 });",
|
|
176
|
+
"function render(frame, totalFrames, fps) {",
|
|
177
|
+
" animation.update(frame, fps);",
|
|
178
|
+
" const ball = document.getElementById('demo-ball');",
|
|
179
|
+
" const opacity = interpolate(frame, { input: { inMin: 0, inMax: fps }, output: { outMin: 0, outMax: 1 } });",
|
|
180
|
+
" const x = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 10, outMax: 350 } });",
|
|
181
|
+
" ball.style.opacity = opacity;",
|
|
182
|
+
" ball.style.left = x + 'px';",
|
|
183
|
+
" document.getElementById('demo-label').textContent = 'frame=' + frame + ' opacity=' + opacity.toFixed(2) + ' x=' + Math.round(x);",
|
|
184
|
+
"}"
|
|
185
|
+
],
|
|
186
|
+
"animation": true
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "explain_easing",
|
|
191
|
+
"speaker": "Presenter",
|
|
192
|
+
"duration": 5,
|
|
193
|
+
"image": {
|
|
194
|
+
"type": "html_tailwind",
|
|
195
|
+
"html": [
|
|
196
|
+
"<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
|
|
197
|
+
" <h2 class='text-3xl font-bold text-white mb-2'>Easing Functions</h2>",
|
|
198
|
+
" <p class='text-slate-400 mb-6'>interpolate(frame, { ..., easing: Easing.xxx })</p>",
|
|
199
|
+
" <div class='flex gap-6'>",
|
|
200
|
+
" <div class='flex-1 text-center'>",
|
|
201
|
+
" <p class='text-cyan-400 font-mono mb-2'>Easing.linear</p>",
|
|
202
|
+
" <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
|
|
203
|
+
" <div id='e-linear' class='w-8 h-8 bg-cyan-400 rounded-full absolute left-4' style='bottom:10px'></div>",
|
|
204
|
+
" </div>",
|
|
205
|
+
" </div>",
|
|
206
|
+
" <div class='flex-1 text-center'>",
|
|
207
|
+
" <p class='text-green-400 font-mono mb-2'>Easing.easeIn</p>",
|
|
208
|
+
" <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
|
|
209
|
+
" <div id='e-in' class='w-8 h-8 bg-green-400 rounded-full absolute left-4' style='bottom:10px'></div>",
|
|
210
|
+
" </div>",
|
|
211
|
+
" </div>",
|
|
212
|
+
" <div class='flex-1 text-center'>",
|
|
213
|
+
" <p class='text-yellow-400 font-mono mb-2'>Easing.easeOut</p>",
|
|
214
|
+
" <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
|
|
215
|
+
" <div id='e-out' class='w-8 h-8 bg-yellow-400 rounded-full absolute left-4' style='bottom:10px'></div>",
|
|
216
|
+
" </div>",
|
|
217
|
+
" </div>",
|
|
218
|
+
" <div class='flex-1 text-center'>",
|
|
219
|
+
" <p class='text-rose-400 font-mono mb-2'>Easing.easeInOut</p>",
|
|
220
|
+
" <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
|
|
221
|
+
" <div id='e-inout' class='w-8 h-8 bg-rose-400 rounded-full absolute left-4' style='bottom:10px'></div>",
|
|
222
|
+
" </div>",
|
|
223
|
+
" </div>",
|
|
224
|
+
" </div>",
|
|
225
|
+
" <p id='easing-frame' class='text-sm font-mono text-slate-500 mt-4 text-center'></p>",
|
|
226
|
+
"</div>"
|
|
227
|
+
],
|
|
228
|
+
"script": [
|
|
229
|
+
"function render(frame, totalFrames, fps) {",
|
|
230
|
+
" const start = fps * 0.5;",
|
|
231
|
+
" const end = totalFrames - fps * 0.5;",
|
|
232
|
+
" const maxY = 160;",
|
|
233
|
+
" document.getElementById('e-linear').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.linear })) + 'px';",
|
|
234
|
+
" document.getElementById('e-in').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeIn })) + 'px';",
|
|
235
|
+
" document.getElementById('e-out').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeOut })) + 'px';",
|
|
236
|
+
" document.getElementById('e-inout').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeInOut })) + 'px';",
|
|
237
|
+
" document.getElementById('easing-frame').textContent = 'frame ' + frame + ' / ' + totalFrames + ' | progress: ' + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: 100 } }).toFixed(0) + '%';",
|
|
238
|
+
"}"
|
|
239
|
+
],
|
|
240
|
+
"animation": true
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"id": "explain_fps",
|
|
245
|
+
"speaker": "Presenter",
|
|
246
|
+
"duration": 3,
|
|
247
|
+
"image": {
|
|
248
|
+
"type": "html_tailwind",
|
|
249
|
+
"html": [
|
|
250
|
+
"<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
|
|
251
|
+
" <h2 class='text-3xl font-bold text-white mb-6'>Custom FPS</h2>",
|
|
252
|
+
" <div class='bg-slate-800 rounded-xl p-4 mb-6'>",
|
|
253
|
+
" <pre class='text-base font-mono text-green-400'>\"animation\": true // default 30fps</pre>",
|
|
254
|
+
" <pre class='text-base font-mono text-yellow-400'>\"animation\": { \"fps\": 15 } // custom fps</pre>",
|
|
255
|
+
" </div>",
|
|
256
|
+
" <p class='text-slate-400 mb-8'>Lower fps = fewer frames = faster render. Higher fps = smoother motion.</p>",
|
|
257
|
+
" <div class='flex gap-8'>",
|
|
258
|
+
" <div class='flex-1 text-center'>",
|
|
259
|
+
" <p class='text-slate-300 font-semibold mb-3'>15 fps (this beat uses 15fps)</p>",
|
|
260
|
+
" <div class='bg-slate-800 rounded-lg h-24 relative overflow-hidden'>",
|
|
261
|
+
" <div id='ball15' class='w-10 h-10 bg-yellow-400 rounded-full absolute top-7' style='left:10px'></div>",
|
|
262
|
+
" </div>",
|
|
263
|
+
" </div>",
|
|
264
|
+
" <div class='flex-1 text-center'>",
|
|
265
|
+
" <p class='text-slate-300 font-semibold mb-3'>Simulated 30fps motion</p>",
|
|
266
|
+
" <div class='bg-slate-800 rounded-lg h-24 relative overflow-hidden'>",
|
|
267
|
+
" <div id='ball30' class='w-10 h-10 bg-cyan-400 rounded-full absolute top-7' style='left:10px'></div>",
|
|
268
|
+
" </div>",
|
|
269
|
+
" </div>",
|
|
270
|
+
" </div>",
|
|
271
|
+
" <p id='fps-info' class='text-sm font-mono text-slate-500 mt-4 text-center'></p>",
|
|
272
|
+
"</div>"
|
|
273
|
+
],
|
|
274
|
+
"script": [
|
|
275
|
+
"function render(frame, totalFrames, fps) {",
|
|
276
|
+
" document.getElementById('ball15').style.left = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 10, outMax: 700 }, easing: Easing.easeInOut }) + 'px';",
|
|
277
|
+
" document.getElementById('ball30').style.left = interpolate(frame * 2, { input: { inMin: 0, inMax: totalFrames * 2 }, output: { outMin: 10, outMax: 700 }, easing: Easing.easeInOut }) + 'px';",
|
|
278
|
+
" document.getElementById('fps-info').textContent = 'frame ' + frame + ' / ' + totalFrames + ' | fps=' + fps + ' | totalFrames = floor(' + (totalFrames/fps).toFixed(0) + 's * ' + fps + 'fps)';",
|
|
279
|
+
"}"
|
|
280
|
+
],
|
|
281
|
+
"animation": { "fps": 15 }
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"id": "demo_fade_in",
|
|
286
|
+
"speaker": "Presenter",
|
|
287
|
+
"duration": 2,
|
|
288
|
+
"image": {
|
|
289
|
+
"type": "html_tailwind",
|
|
290
|
+
"html": [
|
|
291
|
+
"<div class='h-full flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600'>",
|
|
292
|
+
" <h1 id='title' class='text-6xl font-bold text-white'>Fade In Title</h1>",
|
|
293
|
+
"</div>"
|
|
294
|
+
],
|
|
295
|
+
"script": [
|
|
296
|
+
"const animation = new MulmoAnimation();",
|
|
297
|
+
"animation.animate('#title', { opacity: [0, 1], scale: [0.8, 1] }, { start: 0, end: 1, easing: 'easeOut' });"
|
|
298
|
+
],
|
|
299
|
+
"animation": true
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"speaker": "Presenter",
|
|
304
|
+
"duration": 3,
|
|
305
|
+
"image": {
|
|
306
|
+
"type": "html_tailwind",
|
|
307
|
+
"html": [
|
|
308
|
+
"<div class='h-full flex items-center justify-center bg-gray-900 overflow-hidden'>",
|
|
309
|
+
" <div id='box' class='w-40 h-40 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-2xl'></div>",
|
|
310
|
+
"</div>"
|
|
311
|
+
],
|
|
312
|
+
"script": [
|
|
313
|
+
"function render(frame, totalFrames, fps) {",
|
|
314
|
+
" const rotation = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: 360 } });",
|
|
315
|
+
" const scale = 0.5 + 0.5 * Math.sin(frame / fps * Math.PI);",
|
|
316
|
+
" document.getElementById('box').style.transform = 'rotate(' + rotation + 'deg) scale(' + scale + ')';",
|
|
317
|
+
"}"
|
|
318
|
+
],
|
|
319
|
+
"animation": { "fps": 30 }
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "progress_bars",
|
|
324
|
+
"speaker": "Presenter",
|
|
325
|
+
"duration": 2,
|
|
326
|
+
"image": {
|
|
327
|
+
"type": "html_tailwind",
|
|
328
|
+
"html": [
|
|
329
|
+
"<div class='h-full flex flex-col justify-center bg-white px-12'>",
|
|
330
|
+
" <div id='bar1' class='h-8 bg-blue-500 rounded-r mb-4'></div>",
|
|
331
|
+
" <div id='bar2' class='h-8 bg-green-500 rounded-r mb-4'></div>",
|
|
332
|
+
" <div id='bar3' class='h-8 bg-red-500 rounded-r mb-4'></div>",
|
|
333
|
+
" <p id='label' class='text-gray-600 text-lg mt-8'>Loading progress...</p>",
|
|
334
|
+
"</div>"
|
|
335
|
+
],
|
|
336
|
+
"script": [
|
|
337
|
+
"const animation = new MulmoAnimation();",
|
|
338
|
+
"animation.animate('#bar1', { width: [0, 80, '%'] }, { start: 0, end: 1 });",
|
|
339
|
+
"animation.animate('#bar2', { width: [0, 60, '%'] }, { start: 0.3, end: 1.4 });",
|
|
340
|
+
"animation.animate('#bar3', { width: [0, 90, '%'] }, { start: 0.6, end: 2 });",
|
|
341
|
+
"animation.counter('#label', [0, 100], { start: 0, end: 2, prefix: 'Progress: ', suffix: '%' });"
|
|
342
|
+
],
|
|
343
|
+
"animation": true
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
"speaker": "Presenter",
|
|
348
|
+
"duration": 3,
|
|
349
|
+
"image": {
|
|
350
|
+
"type": "html_tailwind",
|
|
351
|
+
"html": [
|
|
352
|
+
"<div class='h-full flex items-center justify-center bg-slate-900'>",
|
|
353
|
+
" <svg id='svg-canvas' class='w-full h-full' viewBox='0 0 400 400'>",
|
|
354
|
+
" <circle id='circle1' cx='200' cy='200' r='0' fill='none' stroke='#06b6d4' stroke-width='3' />",
|
|
355
|
+
" <circle id='circle2' cx='200' cy='200' r='0' fill='none' stroke='#8b5cf6' stroke-width='3' />",
|
|
356
|
+
" <circle id='circle3' cx='200' cy='200' r='0' fill='none' stroke='#f43f5e' stroke-width='3' />",
|
|
357
|
+
" <circle id='dot' cx='200' cy='200' r='8' fill='#fbbf24' />",
|
|
358
|
+
" </svg>",
|
|
359
|
+
"</div>"
|
|
360
|
+
],
|
|
361
|
+
"script": [
|
|
362
|
+
"function render(frame, totalFrames, fps) {",
|
|
363
|
+
" const c1 = document.getElementById('circle1');",
|
|
364
|
+
" const c2 = document.getElementById('circle2');",
|
|
365
|
+
" const c3 = document.getElementById('circle3');",
|
|
366
|
+
" const dot = document.getElementById('dot');",
|
|
367
|
+
" c1.setAttribute('r', interpolate(frame, { input: { inMin: 0, inMax: totalFrames * 0.6 }, output: { outMin: 0, outMax: 150 } }));",
|
|
368
|
+
" c1.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.4, inMax: totalFrames }, output: { outMin: 1, outMax: 0.2 } }));",
|
|
369
|
+
" c2.setAttribute('r', interpolate(frame, { input: { inMin: fps * 0.3, inMax: totalFrames * 0.7 }, output: { outMin: 0, outMax: 120 } }));",
|
|
370
|
+
" c2.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.5, inMax: totalFrames }, output: { outMin: 1, outMax: 0.2 } }));",
|
|
371
|
+
" c3.setAttribute('r', interpolate(frame, { input: { inMin: fps * 0.6, inMax: totalFrames * 0.8 }, output: { outMin: 0, outMax: 90 } }));",
|
|
372
|
+
" const angle = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: Math.PI * 4 } });",
|
|
373
|
+
" const orbitR = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: 160 }, easing: Easing.easeOut });",
|
|
374
|
+
" dot.setAttribute('cx', 200 + Math.cos(angle) * orbitR);",
|
|
375
|
+
" dot.setAttribute('cy', 200 + Math.sin(angle) * orbitR);",
|
|
376
|
+
"}"
|
|
377
|
+
],
|
|
378
|
+
"animation": true
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"speaker": "Presenter",
|
|
383
|
+
"duration": 3,
|
|
384
|
+
"image": {
|
|
385
|
+
"type": "html_tailwind",
|
|
386
|
+
"html": [
|
|
387
|
+
"<div class='h-full flex items-center justify-center bg-gray-950'>",
|
|
388
|
+
" <svg id='wave-svg' class='w-full' style='max-height:400px' viewBox='0 0 600 200'>",
|
|
389
|
+
" <path id='wave-path' fill='none' stroke='url(#grad)' stroke-width='4' stroke-linecap='round' />",
|
|
390
|
+
" <defs>",
|
|
391
|
+
" <linearGradient id='grad' x1='0%' y1='0%' x2='100%' y2='0%'>",
|
|
392
|
+
" <stop offset='0%' stop-color='#06b6d4' />",
|
|
393
|
+
" <stop offset='50%' stop-color='#8b5cf6' />",
|
|
394
|
+
" <stop offset='100%' stop-color='#f43f5e' />",
|
|
395
|
+
" </linearGradient>",
|
|
396
|
+
" </defs>",
|
|
397
|
+
" </svg>",
|
|
398
|
+
"</div>"
|
|
399
|
+
],
|
|
400
|
+
"script": [
|
|
401
|
+
"function render(frame, totalFrames, fps) {",
|
|
402
|
+
" const phase = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: Math.PI * 6 } });",
|
|
403
|
+
" const amp = interpolate(frame, { input: { inMin: 0, inMax: fps }, output: { outMin: 0, outMax: 60 }, easing: Easing.easeOut });",
|
|
404
|
+
" const points = [];",
|
|
405
|
+
" for (let x = 0; x <= 600; x += 4) {",
|
|
406
|
+
" const y = 100 + Math.sin(x * 0.02 + phase) * amp * Math.sin(x * 0.005 + phase * 0.3);",
|
|
407
|
+
" points.push((x === 0 ? 'M' : 'L') + x + ' ' + y);",
|
|
408
|
+
" }",
|
|
409
|
+
" document.getElementById('wave-path').setAttribute('d', points.join(' '));",
|
|
410
|
+
"}"
|
|
411
|
+
],
|
|
412
|
+
"animation": { "fps": 30 }
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"id": "typewriter_demo",
|
|
417
|
+
"speaker": "Presenter",
|
|
418
|
+
"duration": 4,
|
|
419
|
+
"image": {
|
|
420
|
+
"type": "html_tailwind",
|
|
421
|
+
"html": [
|
|
422
|
+
"<div class='h-full flex flex-col items-center justify-center bg-gray-900 px-16'>",
|
|
423
|
+
" <p id='typewriter' class='text-2xl font-mono text-green-400 leading-relaxed max-w-2xl'></p>",
|
|
424
|
+
" <span id='cursor' class='text-2xl font-mono text-green-400'>|</span>",
|
|
425
|
+
"</div>"
|
|
426
|
+
],
|
|
427
|
+
"script": [
|
|
428
|
+
"const animation = new MulmoAnimation();",
|
|
429
|
+
"animation.typewriter('#typewriter', 'MulmoCast transforms your ideas into multi-modal presentations. With frame-based animation, you can create smooth, deterministic visuals powered by HTML and Tailwind CSS.', { start: 0, end: 3.4 });",
|
|
430
|
+
"animation.blink('#cursor', { interval: 0.35 });"
|
|
431
|
+
],
|
|
432
|
+
"animation": true
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"speaker": "Presenter",
|
|
437
|
+
"duration": 3,
|
|
438
|
+
"image": {
|
|
439
|
+
"type": "html_tailwind",
|
|
440
|
+
"html": [
|
|
441
|
+
"<div class='h-full flex items-center justify-center bg-gradient-to-b from-gray-900 to-gray-800'>",
|
|
442
|
+
" <svg id='pie-svg' class='h-full' viewBox='0 0 300 300'></svg>",
|
|
443
|
+
"</div>"
|
|
444
|
+
],
|
|
445
|
+
"script": [
|
|
446
|
+
"const data = [",
|
|
447
|
+
" { value: 35, color: '#3b82f6', label: 'Blue' },",
|
|
448
|
+
" { value: 25, color: '#10b981', label: 'Green' },",
|
|
449
|
+
" { value: 20, color: '#f59e0b', label: 'Yellow' },",
|
|
450
|
+
" { value: 20, color: '#ef4444', label: 'Red' }",
|
|
451
|
+
"];",
|
|
452
|
+
"function render(frame, totalFrames, fps) {",
|
|
453
|
+
" const svg = document.getElementById('pie-svg');",
|
|
454
|
+
" svg.innerHTML = '';",
|
|
455
|
+
" const cx = 150, cy = 150, r = 120;",
|
|
456
|
+
" const progress = interpolate(frame, { input: { inMin: 0, inMax: totalFrames * 0.7 }, output: { outMin: 0, outMax: 1 }, easing: Easing.easeInOut });",
|
|
457
|
+
" const totalAngle = progress * Math.PI * 2;",
|
|
458
|
+
" let startAngle = -Math.PI / 2;",
|
|
459
|
+
" const total = data.reduce(function(s, d) { return s + d.value; }, 0);",
|
|
460
|
+
" for (let i = 0; i < data.length; i++) {",
|
|
461
|
+
" const sliceAngle = (data[i].value / total) * totalAngle;",
|
|
462
|
+
" const endAngle = startAngle + sliceAngle;",
|
|
463
|
+
" const x1 = cx + r * Math.cos(startAngle);",
|
|
464
|
+
" const y1 = cy + r * Math.sin(startAngle);",
|
|
465
|
+
" const x2 = cx + r * Math.cos(endAngle);",
|
|
466
|
+
" const y2 = cy + r * Math.sin(endAngle);",
|
|
467
|
+
" const large = sliceAngle > Math.PI ? 1 : 0;",
|
|
468
|
+
" const d = 'M' + cx + ' ' + cy + ' L' + x1 + ' ' + y1 + ' A' + r + ' ' + r + ' 0 ' + large + ' 1 ' + x2 + ' ' + y2 + ' Z';",
|
|
469
|
+
" const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');",
|
|
470
|
+
" path.setAttribute('d', d);",
|
|
471
|
+
" path.setAttribute('fill', data[i].color);",
|
|
472
|
+
" svg.appendChild(path);",
|
|
473
|
+
" startAngle = endAngle;",
|
|
474
|
+
" }",
|
|
475
|
+
"}"
|
|
476
|
+
],
|
|
477
|
+
"animation": true
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"id": "stagger_items",
|
|
482
|
+
"speaker": "Presenter",
|
|
483
|
+
"duration": 3,
|
|
484
|
+
"image": {
|
|
485
|
+
"type": "html_tailwind",
|
|
486
|
+
"html": [
|
|
487
|
+
"<div class='h-full flex flex-col items-center justify-center bg-white gap-6 px-16'>",
|
|
488
|
+
" <div id='item0' class='w-full max-w-3xl p-4 bg-blue-50 rounded-lg border-l-4 border-blue-500' style='opacity:0; transform: translateX(-40px)'>",
|
|
489
|
+
" <p class='text-lg font-semibold text-blue-800'>1. Schema Design</p>",
|
|
490
|
+
" </div>",
|
|
491
|
+
" <div id='item1' class='w-full max-w-3xl p-4 bg-green-50 rounded-lg border-l-4 border-green-500' style='opacity:0; transform: translateX(-40px)'>",
|
|
492
|
+
" <p class='text-lg font-semibold text-green-800'>2. Frame Renderer</p>",
|
|
493
|
+
" </div>",
|
|
494
|
+
" <div id='item2' class='w-full max-w-3xl p-4 bg-purple-50 rounded-lg border-l-4 border-purple-500' style='opacity:0; transform: translateX(-40px)'>",
|
|
495
|
+
" <p class='text-lg font-semibold text-purple-800'>3. FFmpeg Pipeline</p>",
|
|
496
|
+
" </div>",
|
|
497
|
+
" <div id='item3' class='w-full max-w-3xl p-4 bg-orange-50 rounded-lg border-l-4 border-orange-500' style='opacity:0; transform: translateX(-40px)'>",
|
|
498
|
+
" <p class='text-lg font-semibold text-orange-800'>4. Integration Test</p>",
|
|
499
|
+
" </div>",
|
|
500
|
+
"</div>"
|
|
501
|
+
],
|
|
502
|
+
"script": [
|
|
503
|
+
"const animation = new MulmoAnimation();",
|
|
504
|
+
"animation.stagger('#item{i}', 4, { opacity: [0, 1], translateX: [-40, 0] }, { start: 0, stagger: 0.4, duration: 0.5, easing: 'easeOut' });"
|
|
505
|
+
],
|
|
506
|
+
"animation": true
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"speaker": "Presenter",
|
|
511
|
+
"duration": 3,
|
|
512
|
+
"image": {
|
|
513
|
+
"type": "html_tailwind",
|
|
514
|
+
"html": [
|
|
515
|
+
"<div class='h-full flex items-center justify-center bg-indigo-950 overflow-hidden'>",
|
|
516
|
+
" <svg id='particle-svg' class='w-full h-full' viewBox='0 0 800 450'></svg>",
|
|
517
|
+
"</div>"
|
|
518
|
+
],
|
|
519
|
+
"script": [
|
|
520
|
+
"const particles = [];",
|
|
521
|
+
"for (let i = 0; i < 40; i++) {",
|
|
522
|
+
" particles.push({",
|
|
523
|
+
" x: 400, y: 225,",
|
|
524
|
+
" vx: (Math.cos(i * 0.157) * (2 + (i % 5))),",
|
|
525
|
+
" vy: (Math.sin(i * 0.157) * (2 + (i % 5))),",
|
|
526
|
+
" size: 3 + (i % 4) * 2,",
|
|
527
|
+
" hue: (i * 9) % 360",
|
|
528
|
+
" });",
|
|
529
|
+
"}",
|
|
530
|
+
"function render(frame, totalFrames, fps) {",
|
|
531
|
+
" const svg = document.getElementById('particle-svg');",
|
|
532
|
+
" svg.innerHTML = '';",
|
|
533
|
+
" for (let i = 0; i < particles.length; i++) {",
|
|
534
|
+
" const p = particles[i];",
|
|
535
|
+
" const t = Math.min(frame / fps, 2);",
|
|
536
|
+
" const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');",
|
|
537
|
+
" c.setAttribute('cx', p.x + p.vx * t * 60);",
|
|
538
|
+
" c.setAttribute('cy', p.y + p.vy * t * 60);",
|
|
539
|
+
" c.setAttribute('r', p.size * interpolate(frame, { input: { inMin: 0, inMax: fps * 0.5 }, output: { outMin: 0, outMax: 1 }, easing: Easing.easeOut }));",
|
|
540
|
+
" c.setAttribute('fill', 'hsl(' + p.hue + ', 80%, 65%)');",
|
|
541
|
+
" c.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.6, inMax: totalFrames }, output: { outMin: 1, outMax: 0 } }));",
|
|
542
|
+
" svg.appendChild(c);",
|
|
543
|
+
" }",
|
|
544
|
+
"}"
|
|
545
|
+
],
|
|
546
|
+
"animation": { "fps": 24 }
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"speaker": "Presenter",
|
|
551
|
+
"duration": 2,
|
|
552
|
+
"image": {
|
|
553
|
+
"type": "html_tailwind",
|
|
554
|
+
"html": [
|
|
555
|
+
"<div class='h-full flex flex-col items-center justify-center bg-slate-50'>",
|
|
556
|
+
" <h2 class='text-3xl font-bold text-gray-800 mb-8'>Static Slide (No Animation)</h2>",
|
|
557
|
+
" <p class='text-xl text-gray-500'>This beat uses html_tailwind without animation.</p>",
|
|
558
|
+
"</div>"
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
]
|
|
563
|
+
}
|