{
  "$mulmocast": {
    "version": "1.1"
  },
  "lang": "ja",
  "title": "Three.js 3D Objects Demo",
  "canvasSize": { "width": 1280, "height": 720 },
  "speechParams": {
    "speakers": {
      "narrator": {
        "voiceId": "nova",
        "displayName": { "ja": "ナレーター" }
      }
    }
  },
  "audioParams": {
    "padding": 0,
    "introPadding": 0,
    "closingPadding": 0,
    "outroPadding": 0
  },
  "beats": [
    {
      "id": "cube_rotate",
      "speaker": "narrator",
      "text": "まずは立方体。ゆっくり回転しています。",
      "duration": 6,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div id='three-container' style='position:fixed;inset:0;overflow:hidden;background:#111122'>",
          "  <canvas id='c' style='display:block;width:100%;height:100%'></canvas>",
          "</div>"
        ],
        "script": [
          "import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js';",
          "const canvas = document.getElementById('c');",
          "const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true });",
          "renderer.setClearColor(0x1a1a2e);",
          "renderer.outputColorSpace = THREE.SRGBColorSpace;",
          "",
          "const scene = new THREE.Scene();",
          "const camera = new THREE.PerspectiveCamera(50, 16/9, 0.1, 100);",
          "camera.position.set(0, 1.5, 4);",
          "camera.lookAt(0, 0, 0);",
          "",
          "scene.add(new THREE.AmbientLight(0x404040, 2));",
          "const dirLight = new THREE.DirectionalLight(0xffffff, 3);",
          "dirLight.position.set(3, 5, 4);",
          "scene.add(dirLight);",
          "",
          "const cube = new THREE.Mesh(",
          "  new THREE.BoxGeometry(1.5, 1.5, 1.5),",
          "  new THREE.MeshStandardMaterial({ color: 0x00d2ff, metalness: 0.3, roughness: 0.4 })",
          ");",
          "scene.add(cube);",
          "",
          "let lastW = 0;",
          "let lastH = 0;",
          "const syncSize = () => {",
          "  const w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth));",
          "  const h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight));",
          "  if (w !== lastW || h !== lastH) {",
          "    renderer.setSize(w, h, true);",
          "    camera.aspect = w / h;",
          "    camera.updateProjectionMatrix();",
          "    lastW = w;",
          "    lastH = h;",
          "  }",
          "};",
          "window.render = function(frame, total) {",
          "  document.body.style.zoom = '1';",
          "  syncSize();",
          "  const t = frame / total;",
          "  cube.rotation.x = t * Math.PI * 2;",
          "  cube.rotation.y = t * Math.PI * 3;",
          "  renderer.render(scene, camera);",
          "};"
        ],
        "animation": { "fps": 30 }
      }
    },
    {
      "id": "crystal_spin",
      "speaker": "narrator",
      "text": "次はクリスタル。光を受けてキラキラ輝きます。",
      "duration": 6,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div id='three-container' style='position:fixed;inset:0;overflow:hidden;background:#111122'>",
          "  <canvas id='c' style='display:block;width:100%;height:100%'></canvas>",
          "</div>"
        ],
        "script": [
          "import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js';",
          "const canvas = document.getElementById('c');",
          "const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true });",
          "renderer.setClearColor(0x0d0d1a);",
          "renderer.outputColorSpace = THREE.SRGBColorSpace;",
          "",
          "const scene = new THREE.Scene();",
          "const camera = new THREE.PerspectiveCamera(50, 16/9, 0.1, 100);",
          "camera.position.set(0, 2, 5);",
          "camera.lookAt(0, 0, 0);",
          "",
          "scene.add(new THREE.AmbientLight(0x334455, 3));",
          "const pointLight1 = new THREE.PointLight(0xff66aa, 2, 20);",
          "pointLight1.position.set(3, 3, 3);",
          "scene.add(pointLight1);",
          "const pointLight2 = new THREE.PointLight(0x66aaff, 2, 20);",
          "pointLight2.position.set(-3, 2, -2);",
          "scene.add(pointLight2);",
          "",
          "const crystal = new THREE.Mesh(",
          "  new THREE.OctahedronGeometry(1.2, 0),",
          "  new THREE.MeshStandardMaterial({ color: 0x88ccff, metalness: 0.6, roughness: 0.1 })",
          ");",
          "crystal.scale.set(1, 1.6, 1);",
          "scene.add(crystal);",
          "",
          "let lastW = 0;",
          "let lastH = 0;",
          "const syncSize = () => {",
          "  const w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth));",
          "  const h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight));",
          "  if (w !== lastW || h !== lastH) {",
          "    renderer.setSize(w, h, true);",
          "    camera.aspect = w / h;",
          "    camera.updateProjectionMatrix();",
          "    lastW = w;",
          "    lastH = h;",
          "  }",
          "};",
          "window.render = function(frame, total) {",
          "  document.body.style.zoom = '1';",
          "  syncSize();",
          "  const t = frame / total;",
          "  crystal.rotation.y = t * Math.PI * 4;",
          "  crystal.rotation.x = Math.sin(t * Math.PI * 2) * 0.3;",
          "  pointLight1.position.x = Math.cos(t * Math.PI * 4) * 4;",
          "  pointLight1.position.z = Math.sin(t * Math.PI * 4) * 4;",
          "  pointLight2.position.x = Math.cos(t * Math.PI * 3 + 2) * 3;",
          "  pointLight2.position.z = Math.sin(t * Math.PI * 3 + 2) * 3;",
          "  renderer.render(scene, camera);",
          "};"
        ],
        "animation": { "fps": 30 }
      }
    },
    {
      "id": "both_objects",
      "speaker": "narrator",
      "text": "最後に両方のオブジェクトを並べて。立方体とクリスタルが一緒に回ります。",
      "duration": 8,
      "image": {
        "type": "html_tailwind",
        "html": [
          "<div id='three-container' style='position:fixed;inset:0;overflow:hidden;background:#111122'>",
          "  <canvas id='c' style='display:block;width:100%;height:100%'></canvas>",
          "</div>"
        ],
        "script": [
          "import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.182.0/build/three.module.js';",
          "const canvas = document.getElementById('c');",
          "const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, preserveDrawingBuffer: true });",
          "renderer.setClearColor(0x111122);",
          "renderer.outputColorSpace = THREE.SRGBColorSpace;",
          "",
          "const scene = new THREE.Scene();",
          "const camera = new THREE.PerspectiveCamera(50, 16/9, 0.1, 100);",
          "camera.position.set(0, 2, 6);",
          "camera.lookAt(0, 0, 0);",
          "",
          "scene.add(new THREE.AmbientLight(0x404060, 2));",
          "const dirLight = new THREE.DirectionalLight(0xffffff, 2);",
          "dirLight.position.set(3, 5, 4);",
          "scene.add(dirLight);",
          "const pointLight1 = new THREE.PointLight(0xff6688, 2, 20);",
          "pointLight1.position.set(4, 3, 3);",
          "scene.add(pointLight1);",
          "const pointLight2 = new THREE.PointLight(0x6688ff, 2, 20);",
          "pointLight2.position.set(-4, 2, -2);",
          "scene.add(pointLight2);",
          "",
          "const cube = new THREE.Mesh(",
          "  new THREE.BoxGeometry(1.2, 1.2, 1.2),",
          "  new THREE.MeshStandardMaterial({ color: 0x00d2ff, metalness: 0.3, roughness: 0.4 })",
          ");",
          "cube.position.x = -2;",
          "scene.add(cube);",
          "",
          "const crystal = new THREE.Mesh(",
          "  new THREE.OctahedronGeometry(1.0, 0),",
          "  new THREE.MeshStandardMaterial({ color: 0xcc88ff, metalness: 0.6, roughness: 0.1 })",
          ");",
          "crystal.scale.set(1, 1.5, 1);",
          "crystal.position.x = 2;",
          "scene.add(crystal);",
          "",
          "const grid = new THREE.GridHelper(10, 20, 0x444466, 0x222244);",
          "grid.position.y = -1.5;",
          "scene.add(grid);",
          "",
          "let lastW = 0;",
          "let lastH = 0;",
          "const syncSize = () => {",
          "  const w = Math.max(1, Math.floor(window.innerWidth || document.documentElement.clientWidth));",
          "  const h = Math.max(1, Math.floor(window.innerHeight || document.documentElement.clientHeight));",
          "  if (w !== lastW || h !== lastH) {",
          "    renderer.setSize(w, h, true);",
          "    camera.aspect = w / h;",
          "    camera.updateProjectionMatrix();",
          "    lastW = w;",
          "    lastH = h;",
          "  }",
          "};",
          "window.render = function(frame, total) {",
          "  document.body.style.zoom = '1';",
          "  syncSize();",
          "  const t = frame / total;",
          "  cube.rotation.x = t * Math.PI * 2;",
          "  cube.rotation.y = t * Math.PI * 3;",
          "  cube.position.y = Math.sin(t * Math.PI * 4) * 0.5;",
          "  crystal.rotation.y = t * Math.PI * 4;",
          "  crystal.rotation.x = Math.sin(t * Math.PI * 2) * 0.3;",
          "  crystal.position.y = Math.cos(t * Math.PI * 3) * 0.4 + 0.3;",
          "  pointLight1.position.x = Math.cos(t * Math.PI * 4) * 4;",
          "  pointLight1.position.z = Math.sin(t * Math.PI * 4) * 4;",
          "  pointLight2.position.x = Math.cos(t * Math.PI * 3 + 2) * 3;",
          "  pointLight2.position.z = Math.sin(t * Math.PI * 3 + 2) * 3;",
          "  camera.position.x = Math.sin(t * Math.PI * 2) * 1.5;",
          "  camera.lookAt(0, 0, 0);",
          "  renderer.render(scene, camera);",
          "};"
        ],
        "animation": { "fps": 30 }
      }
    }
  ]
}
