{
  "$mulmocast": {
    "version": "1.1"
  },
  "lang": "en",
  "title": "MulmoCast HTML Animation Guide",
  "speechParams": {
    "speakers": {
      "Presenter": {
        "voiceId": "shimmer",
        "displayName": {
          "en": "Presenter"
        }
      }
    }
  },
  "beats": [
    {
      "id": "intro_title",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col items-center justify-center bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900'>",
          "  <h1 id='title' class='text-5xl font-bold text-white mb-6' style='opacity:0'>MulmoCast Animation</h1>",
          "  <p id='subtitle' class='text-xl text-blue-300' style='opacity:0'>Frame-based HTML animation for deterministic video</p>",
          "  <div id='line' class='h-1 bg-gradient-to-r from-cyan-400 to-purple-500 mt-8 rounded' style='width:0'></div>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#title', { opacity: [0, 1], translateY: [30, 0] }, { start: 0, end: 0.5, easing: 'easeOut' });",
          "animation.animate('#subtitle', { opacity: [0, 1], translateY: [20, 0] }, { start: 0.3, end: 0.8, easing: 'easeOut' });",
          "animation.animate('#line', { width: [0, 400, 'px'] }, { start: 0.5, end: 1.5, easing: 'easeInOut' });"
        ],
        "animation": true
      }
    },
    {
      "id": "explain_basics",
      "speaker": "Presenter",
      "duration": 4,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
          "  <h2 id='heading' class='text-3xl font-bold text-white mb-8' style='opacity:0'>How It Works</h2>",
          "  <div class='bg-slate-800 rounded-xl p-6 mb-6'>",
          "    <p class='text-sm text-slate-400 mb-2 font-mono'>MulmoScript (JSON)</p>",
          "    <pre id='code' class='text-lg font-mono text-green-400 leading-relaxed'></pre>",
          "  </div>",
          "  <div id='note' class='text-base text-slate-400 leading-relaxed' style='opacity:0'>",
          "    <p>Add <code class='text-cyan-400'>\"animation\": true</code> to enable frame-based rendering.</p>",
          "    <p>Define a <code class='text-cyan-400'>render(frame, totalFrames, fps)</code> function in your HTML.</p>",
          "    <p>Each frame is captured by Puppeteer, then combined into video by FFmpeg.</p>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const codeLines = [",
          "  '{',",
          "  '  \"image\": {',",
          "  '    \"type\": \"html_tailwind\",',",
          "  '    \"html\": \"<div>...</div>\",',",
          "  '    \"animation\": true',",
          "  '  },',",
          "  '  \"duration\": 3',",
          "  '}'",
          "];",
          "const animation = new MulmoAnimation();",
          "animation.animate('#heading', { opacity: [0, 1] }, { start: 0, end: 0.3 });",
          "animation.codeReveal('#code', codeLines, { start: 0.3, end: 2.5 });",
          "animation.animate('#note', { opacity: [0, 1] }, { start: 2.5, end: 3 });"
        ],
        "animation": true
      }
    },
    {
      "id": "explain_render",
      "speaker": "Presenter",
      "duration": 4,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
          "  <h2 class='text-3xl font-bold text-white mb-6'>The render() Function</h2>",
          "  <div class='bg-slate-800 rounded-xl p-6 mb-6'>",
          "    <pre id='fn-code' class='text-base font-mono text-green-400 leading-relaxed'></pre>",
          "  </div>",
          "  <div class='flex gap-8 mt-4'>",
          "    <div id='param0' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
          "      <p class='text-cyan-400 font-mono font-bold mb-1'>frame</p>",
          "      <p class='text-slate-300 text-sm'>Current frame number (0-based)</p>",
          "      <p id='frame-val' class='text-2xl font-mono text-white mt-2'>0</p>",
          "    </div>",
          "    <div id='param1' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
          "      <p class='text-purple-400 font-mono font-bold mb-1'>totalFrames</p>",
          "      <p class='text-slate-300 text-sm'>Total frame count = floor(duration * fps)</p>",
          "      <p id='total-val' class='text-2xl font-mono text-white mt-2'>120</p>",
          "    </div>",
          "    <div id='param2' class='flex-1 bg-slate-800 rounded-lg p-4' style='opacity:0'>",
          "      <p class='text-yellow-400 font-mono font-bold mb-1'>fps</p>",
          "      <p class='text-slate-300 text-sm'>Frames per second (default: 30)</p>",
          "      <p class='text-2xl font-mono text-white mt-2'>30</p>",
          "    </div>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const fnLines = [",
          "  'function render(frame, totalFrames, fps) {',",
          "  '  // Called once per frame by Puppeteer',",
          "  '  // Manipulate DOM based on frame number',",
          "  '  element.style.opacity = frame / totalFrames;',",
          "  '}'",
          "];",
          "const animation = new MulmoAnimation();",
          "animation.codeReveal('#fn-code', fnLines, { start: 0, end: 1.0 });",
          "animation.stagger('#param{i}', 3, { opacity: [0, 1], translateY: [15, 0] }, { start: 1.2, stagger: 0.4, duration: 0.3, easing: 'easeOut' });",
          "function render(frame, totalFrames, fps) {",
          "  animation.update(frame, fps);",
          "  document.getElementById('frame-val').textContent = Math.floor(interpolate(frame, { input: { inMin: fps * 2.5, inMax: totalFrames }, output: { outMin: 0, outMax: totalFrames } }));",
          "}"
        ],
        "animation": true
      }
    },
    {
      "id": "explain_interpolate",
      "speaker": "Presenter",
      "duration": 5,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
          "  <h2 class='text-3xl font-bold text-white mb-6'>Helper: interpolate()</h2>",
          "  <div class='bg-slate-800 rounded-xl p-4 mb-6'>",
          "    <pre class='text-base font-mono text-green-400'>interpolate(frame, { input: { inMin, inMax }, output: { outMin, outMax }, easing? })</pre>",
          "  </div>",
          "  <p class='text-slate-400 mb-6'>Linear interpolation with clamping. Maps frame range to value range.</p>",
          "  <div class='flex gap-8'>",
          "    <div class='flex-1'>",
          "      <p class='text-white font-semibold mb-3'>Live Demo: opacity & position</p>",
          "      <div class='bg-slate-800 rounded-lg h-40 relative overflow-hidden'>",
          "        <div id='demo-ball' class='w-12 h-12 bg-cyan-400 rounded-full absolute top-14' style='left:10px; opacity:0'></div>",
          "        <div class='absolute bottom-2 left-2 right-2 h-1 bg-slate-700 rounded'></div>",
          "      </div>",
          "      <p id='demo-label' class='text-sm font-mono text-slate-400 mt-2'></p>",
          "    </div>",
          "    <div class='flex-1'>",
          "      <p class='text-white font-semibold mb-3'>Code</p>",
          "      <div class='bg-slate-800 rounded-lg p-4'>",
          "        <pre id='demo-code' class='text-sm font-mono text-green-400 leading-relaxed'></pre>",
          "      </div>",
          "    </div>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const demoCode = [",
          "  '// Fade in: 0→1 over first second',",
          "  'interpolate(frame, {',",
          "  '  input: { inMin: 0, inMax: fps },',",
          "  '  output: { outMin: 0, outMax: 1 }',",
          "  '})',",
          "  '',",
          "  '// Move: 10→350 over full duration',",
          "  'interpolate(frame, {',",
          "  '  input: { inMin: 0, inMax: totalFrames },',",
          "  '  output: { outMin: 10, outMax: 350 }',",
          "  '})'",
          "];",
          "const animation = new MulmoAnimation();",
          "animation.codeReveal('#demo-code', demoCode, { start: 0.5, end: 2 });",
          "function render(frame, totalFrames, fps) {",
          "  animation.update(frame, fps);",
          "  const ball = document.getElementById('demo-ball');",
          "  const opacity = interpolate(frame, { input: { inMin: 0, inMax: fps }, output: { outMin: 0, outMax: 1 } });",
          "  const x = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 10, outMax: 350 } });",
          "  ball.style.opacity = opacity;",
          "  ball.style.left = x + 'px';",
          "  document.getElementById('demo-label').textContent = 'frame=' + frame + '  opacity=' + opacity.toFixed(2) + '  x=' + Math.round(x);",
          "}"
        ],
        "animation": true
      }
    },
    {
      "id": "explain_easing",
      "speaker": "Presenter",
      "duration": 5,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
          "  <h2 class='text-3xl font-bold text-white mb-2'>Easing Functions</h2>",
          "  <p class='text-slate-400 mb-6'>interpolate(frame, { ..., easing: Easing.xxx })</p>",
          "  <div class='flex gap-6'>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-cyan-400 font-mono mb-2'>Easing.linear</p>",
          "      <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
          "        <div id='e-linear' class='w-8 h-8 bg-cyan-400 rounded-full absolute left-4' style='bottom:10px'></div>",
          "      </div>",
          "    </div>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-green-400 font-mono mb-2'>Easing.easeIn</p>",
          "      <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
          "        <div id='e-in' class='w-8 h-8 bg-green-400 rounded-full absolute left-4' style='bottom:10px'></div>",
          "      </div>",
          "    </div>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-yellow-400 font-mono mb-2'>Easing.easeOut</p>",
          "      <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
          "        <div id='e-out' class='w-8 h-8 bg-yellow-400 rounded-full absolute left-4' style='bottom:10px'></div>",
          "      </div>",
          "    </div>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-rose-400 font-mono mb-2'>Easing.easeInOut</p>",
          "      <div class='bg-slate-800 rounded-lg h-48 relative overflow-hidden'>",
          "        <div id='e-inout' class='w-8 h-8 bg-rose-400 rounded-full absolute left-4' style='bottom:10px'></div>",
          "      </div>",
          "    </div>",
          "  </div>",
          "  <p id='easing-frame' class='text-sm font-mono text-slate-500 mt-4 text-center'></p>",
          "</div>"
        ],
        "script": [
          "function render(frame, totalFrames, fps) {",
          "  const start = fps * 0.5;",
          "  const end = totalFrames - fps * 0.5;",
          "  const maxY = 160;",
          "  document.getElementById('e-linear').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.linear })) + 'px';",
          "  document.getElementById('e-in').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeIn })) + 'px';",
          "  document.getElementById('e-out').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeOut })) + 'px';",
          "  document.getElementById('e-inout').style.bottom = (10 + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: maxY }, easing: Easing.easeInOut })) + 'px';",
          "  document.getElementById('easing-frame').textContent = 'frame ' + frame + ' / ' + totalFrames + '  |  progress: ' + interpolate(frame, { input: { inMin: start, inMax: end }, output: { outMin: 0, outMax: 100 } }).toFixed(0) + '%';",
          "}"
        ],
        "animation": true
      }
    },
    {
      "id": "explain_fps",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-start pt-12 bg-slate-900 px-16'>",
          "  <h2 class='text-3xl font-bold text-white mb-6'>Custom FPS</h2>",
          "  <div class='bg-slate-800 rounded-xl p-4 mb-6'>",
          "    <pre class='text-base font-mono text-green-400'>\"animation\": true          // default 30fps</pre>",
          "    <pre class='text-base font-mono text-yellow-400'>\"animation\": { \"fps\": 15 } // custom fps</pre>",
          "  </div>",
          "  <p class='text-slate-400 mb-8'>Lower fps = fewer frames = faster render. Higher fps = smoother motion.</p>",
          "  <div class='flex gap-8'>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-slate-300 font-semibold mb-3'>15 fps (this beat uses 15fps)</p>",
          "      <div class='bg-slate-800 rounded-lg h-24 relative overflow-hidden'>",
          "        <div id='ball15' class='w-10 h-10 bg-yellow-400 rounded-full absolute top-7' style='left:10px'></div>",
          "      </div>",
          "    </div>",
          "    <div class='flex-1 text-center'>",
          "      <p class='text-slate-300 font-semibold mb-3'>Simulated 30fps motion</p>",
          "      <div class='bg-slate-800 rounded-lg h-24 relative overflow-hidden'>",
          "        <div id='ball30' class='w-10 h-10 bg-cyan-400 rounded-full absolute top-7' style='left:10px'></div>",
          "      </div>",
          "    </div>",
          "  </div>",
          "  <p id='fps-info' class='text-sm font-mono text-slate-500 mt-4 text-center'></p>",
          "</div>"
        ],
        "script": [
          "function render(frame, totalFrames, fps) {",
          "  document.getElementById('ball15').style.left = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 10, outMax: 700 }, easing: Easing.easeInOut }) + 'px';",
          "  document.getElementById('ball30').style.left = interpolate(frame * 2, { input: { inMin: 0, inMax: totalFrames * 2 }, output: { outMin: 10, outMax: 700 }, easing: Easing.easeInOut }) + 'px';",
          "  document.getElementById('fps-info').textContent = 'frame ' + frame + ' / ' + totalFrames + '  |  fps=' + fps + '  |  totalFrames = floor(' + (totalFrames/fps).toFixed(0) + 's * ' + fps + 'fps)';",
          "}"
        ],
        "animation": { "fps": 15 }
      }
    },
    {
      "id": "demo_fade_in",
      "speaker": "Presenter",
      "duration": 2,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600'>",
          "  <h1 id='title' class='text-6xl font-bold text-white'>Fade In Title</h1>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#title', { opacity: [0, 1], scale: [0.8, 1] }, { start: 0, end: 1, easing: 'easeOut' });"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-gray-900 overflow-hidden'>",
          "  <div id='box' class='w-40 h-40 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-2xl'></div>",
          "</div>"
        ],
        "script": [
          "function render(frame, totalFrames, fps) {",
          "  const rotation = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: 360 } });",
          "  const scale = 0.5 + 0.5 * Math.sin(frame / fps * Math.PI);",
          "  document.getElementById('box').style.transform = 'rotate(' + rotation + 'deg) scale(' + scale + ')';",
          "}"
        ],
        "animation": { "fps": 30 }
      }
    },
    {
      "id": "progress_bars",
      "speaker": "Presenter",
      "duration": 2,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col justify-center bg-white px-12'>",
          "  <div id='bar1' class='h-8 bg-blue-500 rounded-r mb-4'></div>",
          "  <div id='bar2' class='h-8 bg-green-500 rounded-r mb-4'></div>",
          "  <div id='bar3' class='h-8 bg-red-500 rounded-r mb-4'></div>",
          "  <p id='label' class='text-gray-600 text-lg mt-8'>Loading progress...</p>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#bar1', { width: [0, 80, '%'] }, { start: 0, end: 1 });",
          "animation.animate('#bar2', { width: [0, 60, '%'] }, { start: 0.3, end: 1.4 });",
          "animation.animate('#bar3', { width: [0, 90, '%'] }, { start: 0.6, end: 2 });",
          "animation.counter('#label', [0, 100], { start: 0, end: 2, prefix: 'Progress: ', suffix: '%' });"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-slate-900'>",
          "  <svg id='svg-canvas' class='w-full h-full' viewBox='0 0 400 400'>",
          "    <circle id='circle1' cx='200' cy='200' r='0' fill='none' stroke='#06b6d4' stroke-width='3' />",
          "    <circle id='circle2' cx='200' cy='200' r='0' fill='none' stroke='#8b5cf6' stroke-width='3' />",
          "    <circle id='circle3' cx='200' cy='200' r='0' fill='none' stroke='#f43f5e' stroke-width='3' />",
          "    <circle id='dot' cx='200' cy='200' r='8' fill='#fbbf24' />",
          "  </svg>",
          "</div>"
        ],
        "script": [
          "function render(frame, totalFrames, fps) {",
          "  const c1 = document.getElementById('circle1');",
          "  const c2 = document.getElementById('circle2');",
          "  const c3 = document.getElementById('circle3');",
          "  const dot = document.getElementById('dot');",
          "  c1.setAttribute('r', interpolate(frame, { input: { inMin: 0, inMax: totalFrames * 0.6 }, output: { outMin: 0, outMax: 150 } }));",
          "  c1.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.4, inMax: totalFrames }, output: { outMin: 1, outMax: 0.2 } }));",
          "  c2.setAttribute('r', interpolate(frame, { input: { inMin: fps * 0.3, inMax: totalFrames * 0.7 }, output: { outMin: 0, outMax: 120 } }));",
          "  c2.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.5, inMax: totalFrames }, output: { outMin: 1, outMax: 0.2 } }));",
          "  c3.setAttribute('r', interpolate(frame, { input: { inMin: fps * 0.6, inMax: totalFrames * 0.8 }, output: { outMin: 0, outMax: 90 } }));",
          "  const angle = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: Math.PI * 4 } });",
          "  const orbitR = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: 160 }, easing: Easing.easeOut });",
          "  dot.setAttribute('cx', 200 + Math.cos(angle) * orbitR);",
          "  dot.setAttribute('cy', 200 + Math.sin(angle) * orbitR);",
          "}"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-gray-950'>",
          "  <svg id='wave-svg' class='w-full' style='max-height:400px' viewBox='0 0 600 200'>",
          "    <path id='wave-path' fill='none' stroke='url(#grad)' stroke-width='4' stroke-linecap='round' />",
          "    <defs>",
          "      <linearGradient id='grad' x1='0%' y1='0%' x2='100%' y2='0%'>",
          "        <stop offset='0%' stop-color='#06b6d4' />",
          "        <stop offset='50%' stop-color='#8b5cf6' />",
          "        <stop offset='100%' stop-color='#f43f5e' />",
          "      </linearGradient>",
          "    </defs>",
          "  </svg>",
          "</div>"
        ],
        "script": [
          "function render(frame, totalFrames, fps) {",
          "  const phase = interpolate(frame, { input: { inMin: 0, inMax: totalFrames }, output: { outMin: 0, outMax: Math.PI * 6 } });",
          "  const amp = interpolate(frame, { input: { inMin: 0, inMax: fps }, output: { outMin: 0, outMax: 60 }, easing: Easing.easeOut });",
          "  const points = [];",
          "  for (let x = 0; x <= 600; x += 4) {",
          "    const y = 100 + Math.sin(x * 0.02 + phase) * amp * Math.sin(x * 0.005 + phase * 0.3);",
          "    points.push((x === 0 ? 'M' : 'L') + x + ' ' + y);",
          "  }",
          "  document.getElementById('wave-path').setAttribute('d', points.join(' '));",
          "}"
        ],
        "animation": { "fps": 30 }
      }
    },
    {
      "id": "typewriter_demo",
      "speaker": "Presenter",
      "duration": 4,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col items-center justify-center bg-gray-900 px-16'>",
          "  <p id='typewriter' class='text-2xl font-mono text-green-400 leading-relaxed max-w-2xl'></p>",
          "  <span id='cursor' class='text-2xl font-mono text-green-400'>|</span>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "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 });",
          "animation.blink('#cursor', { interval: 0.35 });"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-gradient-to-b from-gray-900 to-gray-800'>",
          "  <svg id='pie-svg' class='h-full' viewBox='0 0 300 300'></svg>",
          "</div>"
        ],
        "script": [
          "const data = [",
          "  { value: 35, color: '#3b82f6', label: 'Blue' },",
          "  { value: 25, color: '#10b981', label: 'Green' },",
          "  { value: 20, color: '#f59e0b', label: 'Yellow' },",
          "  { value: 20, color: '#ef4444', label: 'Red' }",
          "];",
          "function render(frame, totalFrames, fps) {",
          "  const svg = document.getElementById('pie-svg');",
          "  svg.innerHTML = '';",
          "  const cx = 150, cy = 150, r = 120;",
          "  const progress = interpolate(frame, { input: { inMin: 0, inMax: totalFrames * 0.7 }, output: { outMin: 0, outMax: 1 }, easing: Easing.easeInOut });",
          "  const totalAngle = progress * Math.PI * 2;",
          "  let startAngle = -Math.PI / 2;",
          "  const total = data.reduce(function(s, d) { return s + d.value; }, 0);",
          "  for (let i = 0; i < data.length; i++) {",
          "    const sliceAngle = (data[i].value / total) * totalAngle;",
          "    const endAngle = startAngle + sliceAngle;",
          "    const x1 = cx + r * Math.cos(startAngle);",
          "    const y1 = cy + r * Math.sin(startAngle);",
          "    const x2 = cx + r * Math.cos(endAngle);",
          "    const y2 = cy + r * Math.sin(endAngle);",
          "    const large = sliceAngle > Math.PI ? 1 : 0;",
          "    const d = 'M' + cx + ' ' + cy + ' L' + x1 + ' ' + y1 + ' A' + r + ' ' + r + ' 0 ' + large + ' 1 ' + x2 + ' ' + y2 + ' Z';",
          "    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');",
          "    path.setAttribute('d', d);",
          "    path.setAttribute('fill', data[i].color);",
          "    svg.appendChild(path);",
          "    startAngle = endAngle;",
          "  }",
          "}"
        ],
        "animation": true
      }
    },
    {
      "id": "stagger_items",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col items-center justify-center bg-white gap-6 px-16'>",
          "  <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)'>",
          "    <p class='text-lg font-semibold text-blue-800'>1. Schema Design</p>",
          "  </div>",
          "  <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)'>",
          "    <p class='text-lg font-semibold text-green-800'>2. Frame Renderer</p>",
          "  </div>",
          "  <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)'>",
          "    <p class='text-lg font-semibold text-purple-800'>3. FFmpeg Pipeline</p>",
          "  </div>",
          "  <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)'>",
          "    <p class='text-lg font-semibold text-orange-800'>4. Integration Test</p>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.stagger('#item{i}', 4, { opacity: [0, 1], translateX: [-40, 0] }, { start: 0, stagger: 0.4, duration: 0.5, easing: 'easeOut' });"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-indigo-950 overflow-hidden'>",
          "  <svg id='particle-svg' class='w-full h-full' viewBox='0 0 800 450'></svg>",
          "</div>"
        ],
        "script": [
          "const particles = [];",
          "for (let i = 0; i < 40; i++) {",
          "  particles.push({",
          "    x: 400, y: 225,",
          "    vx: (Math.cos(i * 0.157) * (2 + (i % 5))),",
          "    vy: (Math.sin(i * 0.157) * (2 + (i % 5))),",
          "    size: 3 + (i % 4) * 2,",
          "    hue: (i * 9) % 360",
          "  });",
          "}",
          "function render(frame, totalFrames, fps) {",
          "  const svg = document.getElementById('particle-svg');",
          "  svg.innerHTML = '';",
          "  for (let i = 0; i < particles.length; i++) {",
          "    const p = particles[i];",
          "    const t = Math.min(frame / fps, 2);",
          "    const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');",
          "    c.setAttribute('cx', p.x + p.vx * t * 60);",
          "    c.setAttribute('cy', p.y + p.vy * t * 60);",
          "    c.setAttribute('r', p.size * interpolate(frame, { input: { inMin: 0, inMax: fps * 0.5 }, output: { outMin: 0, outMax: 1 }, easing: Easing.easeOut }));",
          "    c.setAttribute('fill', 'hsl(' + p.hue + ', 80%, 65%)');",
          "    c.setAttribute('opacity', interpolate(frame, { input: { inMin: totalFrames * 0.6, inMax: totalFrames }, output: { outMin: 1, outMax: 0 } }));",
          "    svg.appendChild(c);",
          "  }",
          "}"
        ],
        "animation": { "fps": 24 }
      }
    },
    {
      "id": "demo_3d_card_flip",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex items-center justify-center bg-gradient-to-br from-slate-900 to-indigo-950'>",
          "  <div style='perspective:1000px'>",
          "    <div id='card' class='relative' style='width:340px;height:200px;transform-style:preserve-3d'>",
          "      <div class='absolute inset-0 rounded-2xl flex flex-col items-center justify-center' style='backface-visibility:hidden;background:linear-gradient(135deg,#3b82f6,#06b6d4);box-shadow:0 20px 60px rgba(6,182,212,0.3)'>",
          "        <p class='text-white text-3xl font-bold tracking-wide'>MulmoCast</p>",
          "        <p class='text-blue-200 text-sm mt-2 tracking-wider'>FRONT SIDE</p>",
          "      </div>",
          "      <div class='absolute inset-0 rounded-2xl flex flex-col items-center justify-center' style='backface-visibility:hidden;transform:rotateY(180deg);background:linear-gradient(135deg,#8b5cf6,#ec4899);box-shadow:0 20px 60px rgba(139,92,246,0.3)'>",
          "        <p class='text-white text-3xl font-bold tracking-wide'>AI-Native</p>",
          "        <p class='text-purple-200 text-sm mt-2 tracking-wider'>BACK SIDE</p>",
          "      </div>",
          "    </div>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#card', { rotateY: [0, 180] }, { start: 0.5, end: 2.5, easing: 'easeInOut' });"
        ],
        "animation": true
      }
    },
    {
      "id": "demo_3d_title_reveal",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col items-center justify-center bg-black' style='perspective:800px'>",
          "  <h1 id='title' class='text-7xl font-bold tracking-wider' style='opacity:0;font-family:Impact,sans-serif;color:white;text-shadow:0 0 40px rgba(6,182,212,0.5)'>CINEMATIC</h1>",
          "  <div id='line' class='h-0.5 mt-6 rounded' style='width:0;background:linear-gradient(90deg,transparent,#06b6d4,transparent)'></div>",
          "  <p id='sub' class='text-lg mt-6 tracking-[0.4em]' style='opacity:0;color:#64748b;font-family:monospace'>3D PERSPECTIVE REVEAL</p>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#title', { opacity: [0, 1], rotateX: [90, 0] }, { start: 0.2, end: 1.2, easing: 'easeOut' });",
          "animation.animate('#line', { width: [0, 400, 'px'] }, { start: 1.0, end: 1.8, easing: 'easeOut' });",
          "animation.animate('#sub', { opacity: [0, 1] }, { start: 1.5, end: 2.2, easing: 'easeOut' });"
        ],
        "animation": true
      }
    },
    {
      "id": "demo_split_reveal",
      "speaker": "Presenter",
      "duration": 3,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex bg-black overflow-hidden'>",
          "  <div id='left' class='flex-1 flex items-center justify-center' style='background:linear-gradient(135deg,#1e3a5f,#0f172a);opacity:0'>",
          "    <p class='text-6xl font-bold text-white' style='font-family:Georgia,serif'>Create</p>",
          "  </div>",
          "  <div id='divider' class='w-1' style='background:linear-gradient(to bottom,transparent,#06b6d4,transparent);opacity:0'></div>",
          "  <div id='right' class='flex-1 flex items-center justify-center' style='background:linear-gradient(225deg,#4c1d95,#0f172a);opacity:0'>",
          "    <p class='text-6xl font-bold text-white' style='font-family:Georgia,serif'>Inspire</p>",
          "  </div>",
          "</div>"
        ],
        "script": [
          "const animation = new MulmoAnimation();",
          "animation.animate('#left', { translateX: [-640, 0], opacity: [0, 1] }, { start: 0, end: 1.0, easing: 'easeOut' });",
          "animation.animate('#right', { translateX: [640, 0], opacity: [0, 1] }, { start: 0.3, end: 1.3, easing: 'easeOut' });",
          "animation.animate('#divider', { opacity: [0, 1] }, { start: 1.2, end: 1.8 });"
        ],
        "animation": true
      }
    },
    {
      "speaker": "Presenter",
      "duration": 2,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div class='h-full flex flex-col items-center justify-center bg-slate-50'>",
          "  <h2 class='text-3xl font-bold text-gray-800 mb-8'>Static Slide (No Animation)</h2>",
          "  <p class='text-xl text-gray-500'>This beat uses html_tailwind without animation.</p>",
          "</div>"
        ]
      }
    }
  ]
}
