@pixerate/flair 0.25.0 → 0.26.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 (32) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -0
  3. package/dist/catalog/index.js +1 -1
  4. package/dist/{chunk-WAMA45G3.js → chunk-5USSDEQL.js} +77 -21
  5. package/dist/chunk-5USSDEQL.js.map +1 -0
  6. package/dist/{chunk-GTIAOYOK.js → chunk-74ZLCZOG.js} +13 -10
  7. package/dist/chunk-74ZLCZOG.js.map +1 -0
  8. package/dist/{chunk-O44ZJTGG.js → chunk-CK6EV37I.js} +15 -11
  9. package/dist/chunk-CK6EV37I.js.map +1 -0
  10. package/dist/{chunk-OKYMKSXK.js → chunk-G2B5L234.js} +3 -3
  11. package/dist/{chunk-PYDN5G2P.js → chunk-HVXWNGTW.js} +79 -2
  12. package/dist/{chunk-PYDN5G2P.js.map → chunk-HVXWNGTW.js.map} +1 -1
  13. package/dist/{chunk-S2RGHHKZ.js → chunk-UCZ43ARE.js} +2 -2
  14. package/dist/cli.js +9 -9
  15. package/dist/editor/index.js +1 -1
  16. package/dist/{effect-26CPF5HS.js → effect-GGH3OSOT.js} +4 -4
  17. package/dist/embed/index.js +2 -2
  18. package/dist/{export-DH2HMACO.js → export-QVW474KM.js} +3 -3
  19. package/dist/index.d.ts +42 -2
  20. package/dist/index.js +18 -4
  21. package/dist/index.js.map +1 -1
  22. package/dist/node.js +3 -3
  23. package/dist/react/index.js +2 -2
  24. package/dist/svelte/index.js +2 -2
  25. package/package.json +1 -1
  26. package/dist/chunk-GTIAOYOK.js.map +0 -1
  27. package/dist/chunk-O44ZJTGG.js.map +0 -1
  28. package/dist/chunk-WAMA45G3.js.map +0 -1
  29. /package/dist/{chunk-OKYMKSXK.js.map → chunk-G2B5L234.js.map} +0 -0
  30. /package/dist/{chunk-S2RGHHKZ.js.map → chunk-UCZ43ARE.js.map} +0 -0
  31. /package/dist/{effect-26CPF5HS.js.map → effect-GGH3OSOT.js.map} +0 -0
  32. /package/dist/{export-DH2HMACO.js.map → export-QVW474KM.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/export/motn.ts","../src/export/lottie.ts","../src/export/interchange.ts"],"sourcesContent":["import type { Scene, Shot } from '../scene/types.js';\nimport { DEFAULT_STAGE, CAMERA_MS, DEFAULT_PERSPECTIVE, centreOf, isAngled, type View } from '../scene/camera.js';\nimport { cameraSamples, sceneStage, type CameraSample } from '../scene/cameratrack.js';\nimport { isElementCommand } from '../scene/types.js';\n\nexport interface MotnExportOptions {\n frameRate?: number;\n}\n\nfunction escapeXml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\nconst r3 = (n: number) => Math.round(n * 1000) / 1000;\n\n/** A parameter, keyframed (linear, at frames) when the samples change and static when they do not. */\nfunction curveParameter(name: string, id: number, samples: readonly CameraSample[], value: (v: View) => number, indent: string): string[] {\n const values = samples.map((x) => r3(value(x.view)));\n if (values.every((v) => v === values[0])) return [`${indent}<parameter name=\"${name}\" id=\"${id}\" value=\"${values[0]}\"/>`];\n return [\n `${indent}<parameter name=\"${name}\" id=\"${id}\" value=\"${values[0]}\">`,\n `${indent} <curve type=\"1\" default=\"${values[0]}\" value=\"${values[0]}\">`,\n ...samples.map((x, i) => `${indent} <keypoint interpolation=\"1\"><time>${x.frame}</time><value>${values[i]}</value></keypoint>`),\n `${indent} </curve>`,\n `${indent}</parameter>`,\n ];\n}\n\n/**\n * The scene's camera as a group the shot groups sit in: its anchor point is\n * the camera's centre, its scale the zoom and its rotations the angle\n * (Motion's y points up, so tilt and roll turn the other way), keyframed\n * every other frame through each move. The perspective distance and each\n * move's motion blur are noted in comments: set them on a Motion camera and\n * in the render settings.\n */\nfunction cameraGroupOpen(scene: Scene, frameRate: number, totalFrames: number, id: number): string[] {\n const stage = sceneStage(scene);\n const samples = cameraSamples(scene, frameRate);\n const angled = samples.some((x) => isAngled(x.view));\n const lines = [` <group name=\"Camera\" id=\"${id}\">`];\n if (angled) {\n const distance = r3((samples.find((x) => x.view.perspective)?.view.perspective ?? DEFAULT_PERSPECTIVE) * stage.width);\n lines.push(` <!-- 3D group: view it through a camera ${distance}px from the canvas -->`);\n }\n for (const shot of scene.shots) {\n if (shot.camera?.motionBlur) lines.push(` <!-- ${escapeXml(shot.title ?? 'Shot')}: motion blur ${shot.camera.motionBlur} on the move in -->`);\n }\n lines.push(` <timing in=\"0\" out=\"${totalFrames - 1}\" offset=\"0\"/>`);\n lines.push(' <parameter name=\"Properties\" id=\"1\">');\n lines.push(' <parameter name=\"Transform\" id=\"100\">');\n lines.push(' <parameter name=\"Position\" id=\"101\">');\n lines.push(' <parameter name=\"X\" id=\"1\" value=\"0\"/>');\n lines.push(' <parameter name=\"Y\" id=\"2\" value=\"0\"/>');\n lines.push(' </parameter>');\n lines.push(' <parameter name=\"Scale\" id=\"102\">');\n lines.push(...curveParameter('X', 1, samples, (v) => v.zoom * 100, ' '));\n lines.push(...curveParameter('Y', 2, samples, (v) => v.zoom * 100, ' '));\n lines.push(' </parameter>');\n lines.push(' <parameter name=\"Rotation\" id=\"103\">');\n lines.push(...curveParameter('X', 1, samples, (v) => 0 - (v.tilt ?? 0), ' '));\n lines.push(...curveParameter('Y', 2, samples, (v) => v.turn ?? 0, ' '));\n lines.push(...curveParameter('Z', 3, samples, (v) => 0 - (v.roll ?? 0), ' '));\n lines.push(' </parameter>');\n lines.push(' <parameter name=\"Anchor Point\" id=\"104\">');\n lines.push(...curveParameter('X', 1, samples, (v) => centreOf(v, stage)[0], ' '));\n lines.push(...curveParameter('Y', 2, samples, (v) => 0 - centreOf(v, stage)[1], ' '));\n lines.push(' </parameter>');\n lines.push(' </parameter>');\n lines.push(' </parameter>');\n return lines;\n}\n\n/**\n * Serializes a Flair Scene into Apple Motion ozml 4.0 XML document format\n * (.motn). A scene with a camera puts its shots in a \"Camera\" group that\n * moves, zooms and turns as the camera does.\n */\nexport function exportMotn(scene: Scene, options: MotnExportOptions = {}): string {\n const frameRate = options.frameRate ?? 30;\n const width = scene.stage?.width ?? DEFAULT_STAGE.width;\n const height = scene.stage?.height ?? DEFAULT_STAGE.height;\n\n // Calculate shot frame ranges\n let currentMs = 0;\n const shotTimings = scene.shots.map((shot, idx) => {\n const camMs = idx > 0 && shot.camera ? CAMERA_MS : 0;\n const holdMs = shot.hold ?? 800;\n const durationMs = camMs + holdMs;\n const startMs = currentMs;\n currentMs += durationMs;\n\n const startFrame = Math.round((startMs / 1000) * frameRate);\n const durationFrames = Math.max(1, Math.round((durationMs / 1000) * frameRate));\n const endFrame = startFrame + durationFrames - 1;\n\n return {\n shot,\n startFrame,\n durationFrames,\n endFrame,\n };\n });\n\n const totalFrames = Math.max(1, Math.round((currentMs / 1000) * frameRate));\n\n let nodeId = 10000;\n const lines: string[] = [];\n\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<!DOCTYPE ozxmlscene>');\n lines.push('<ozml version=\"4.0\">');\n if (scene.name) {\n lines.push(` <!-- Scene: ${escapeXml(scene.name)} -->`);\n }\n lines.push('');\n lines.push(' <displayversion>5.0</displayversion>');\n lines.push('');\n lines.push(' <factory id=\"1\" uuid=\"044beba5ad3211d7ac9b000393833f6a\">');\n lines.push(' <description>Style</description>');\n lines.push(' <manufacturer>Apple</manufacturer>');\n lines.push(' <version>1</version>');\n lines.push(' </factory>');\n lines.push(' <factory id=\"2\" uuid=\"babfc7777f4711d7aaa7000393833f6a\">');\n lines.push(' <description>Text</description>');\n lines.push(' <manufacturer>Apple</manufacturer>');\n lines.push(' <version>1</version>');\n lines.push(' </factory>');\n lines.push(' <factory id=\"3\" uuid=\"712462a4323911d78f8400039389b702\">');\n lines.push(' <description>Shape</description>');\n lines.push(' <manufacturer>Apple</manufacturer>');\n lines.push(' <version>1</version>');\n lines.push(' </factory>');\n lines.push(' <factory id=\"4\" uuid=\"65cb4dc9d4504fa281921f5f751fba06\">');\n lines.push(' <description>Widget</description>');\n lines.push(' <manufacturer>Apple</manufacturer>');\n lines.push(' <version>1</version>');\n lines.push(' </factory>');\n lines.push('');\n lines.push(' <scene>');\n lines.push(' <sceneSettings>');\n lines.push(` <width>${width}</width>`);\n lines.push(` <height>${height}</height>`);\n lines.push(` <duration>${totalFrames}</duration>`);\n lines.push(` <frameRate>${frameRate}</frameRate>`);\n lines.push(' <NTSC>0</NTSC>');\n lines.push(' <channels>4</channels>');\n lines.push(' <channelDepth>8</channelDepth>');\n lines.push(' <pixelAspectRatio>1</pixelAspectRatio>');\n lines.push(' <backgroundColor red=\"0\" green=\"0\" blue=\"0\" alpha=\"1\"/>');\n lines.push(' </sceneSettings>');\n lines.push(' <currentFrame>0</currentFrame>');\n lines.push(` <timeRange offset=\"0\" duration=\"${totalFrames}\"/>`);\n lines.push(` <playRange offset=\"0\" duration=\"${totalFrames}\"/>`);\n lines.push(' <flags>1</flags>');\n\n // Rig widget export if defined\n if (scene.rig && Object.keys(scene.rig.widgets).length > 0) {\n lines.push(` <scenenode name=\"Rigs\" id=\"${nodeId++}\" factoryID=\"4\" version=\"1\">`);\n for (const [widgetName, widget] of Object.entries(scene.rig.widgets)) {\n lines.push(` <scenenode name=\"${escapeXml(widgetName)}\" id=\"${nodeId++}\" factoryID=\"4\" version=\"1\">`);\n lines.push(' <parameter name=\"Object\" id=\"2\">');\n if (widget.type === 'slider') {\n lines.push(` <parameter name=\"Widget\" id=\"100\" default=\"0\" value=\"${widget.value}\"/>`);\n } else if (widget.type === 'toggle') {\n lines.push(` <parameter name=\"Widget\" id=\"100\" default=\"0\" value=\"${widget.enabled ? 1 : 0}\"/>`);\n } else if (widget.type === 'popup') {\n lines.push(` <parameter name=\"Widget\" id=\"100\" default=\"0\" value=\"${escapeXml(widget.selected)}\"/>`);\n }\n lines.push(' </parameter>');\n lines.push(' </scenenode>');\n }\n lines.push(' </scenenode>');\n }\n\n // Groups and element nodes per shot, inside the camera group when there is one.\n const hasCamera = scene.shots.some((s) => s.camera);\n if (hasCamera) lines.push(...cameraGroupOpen(scene, frameRate, totalFrames, nodeId++));\n for (const [idx, item] of shotTimings.entries()) {\n const shotLabel = item.shot.title\n ? `Shot ${idx + 1}: ${escapeXml(item.shot.title)}`\n : `Shot ${idx + 1}`;\n lines.push(` <group name=\"${shotLabel}\" id=\"${nodeId++}\">`);\n lines.push(` <timing in=\"${item.startFrame}\" out=\"${item.endFrame}\" offset=\"${item.startFrame}\"/>`);\n\n if (item.shot.run) {\n for (const cmd of item.shot.run) {\n if (isElementCommand(cmd) && cmd.element) {\n const el = cmd.element;\n const isText = el.type === 'text';\n const factoryId = isText ? 2 : 3;\n lines.push(` <scenenode name=\"${escapeXml(el.id)}\" id=\"${nodeId++}\" factoryID=\"${factoryId}\" version=\"1\">`);\n lines.push(` <timing in=\"${item.startFrame}\" out=\"${item.endFrame}\" offset=\"${item.startFrame}\"/>`);\n lines.push(' <parameter name=\"Properties\" id=\"1\">');\n lines.push(' <parameter name=\"Transform\" id=\"100\">');\n lines.push(' <parameter name=\"Position\" id=\"101\">');\n lines.push(` <parameter name=\"X\" id=\"1\" value=\"${el.x}\"/>`);\n lines.push(` <parameter name=\"Y\" id=\"2\" value=\"${-el.y}\"/>`);\n lines.push(' </parameter>');\n if (!isText) {\n lines.push(' <parameter name=\"Scale\" id=\"102\">');\n lines.push(` <parameter name=\"X\" id=\"1\" value=\"${el.width}\"/>`);\n lines.push(` <parameter name=\"Y\" id=\"2\" value=\"${el.height}\"/>`);\n lines.push(' </parameter>');\n }\n lines.push(' </parameter>');\n lines.push(' </parameter>');\n\n if (isText && el.text) {\n lines.push(` <text>${escapeXml(el.text)}</text>`);\n }\n lines.push(' </scenenode>');\n }\n }\n }\n\n lines.push(' </group>');\n }\n if (hasCamera) lines.push(' </group>');\n\n lines.push(' </scene>');\n lines.push('</ozml>');\n lines.push('');\n\n return lines.join('\\n');\n}\n","/**\n * Lottie JSON Vector Animation Exporter.\n *\n * Converts Flair scenes, elements, shapes, transforms, and keyframes into\n * official Lottie JSON format (v5.5.2) compatible with Airbnb Lottie-Web,\n * iOS/Android Lottie, Figma, and Telegram.\n */\n\nimport type { Scene } from '../scene/types.js';\nimport { DEFAULT_STAGE, CAMERA_MS, DEFAULT_PERSPECTIVE, boardHeight, centreOf, isAngled, type View } from '../scene/camera.js';\nimport { cameraSamples, sceneStage } from '../scene/cameratrack.js';\nimport { isElementCommand, isUpdateCommand } from '../scene/types.js';\nimport type { KeyframeEase } from '../time/keyframe.js';\n\nexport interface LottieExportOptions {\n /** Target framerate in fps. Default: 30 */\n frameRate?: number;\n}\n\nexport interface LottieKeyframe<T> {\n t: number; // frame number\n s: T; // start value\n e?: T; // end value\n o?: { x: number | number[]; y: number | number[] }; // out tangent ease\n i?: { x: number | number[]; y: number | number[] }; // in tangent ease\n h?: number; // hold keyframe flag\n}\n\nexport interface LottieAnimatable<T, K = T> {\n a: 0 | 1; // 0 = static, 1 = animated\n k: T | LottieKeyframe<K>[];\n}\n\nexport interface LottieTransform {\n p: LottieAnimatable<[number, number, number], [number, number, number]>; // position\n a: LottieAnimatable<[number, number, number], [number, number, number]>; // anchor\n s: LottieAnimatable<[number, number, number], [number, number, number]>; // scale\n r?: LottieAnimatable<number, [number]>; // rotation (2D layers)\n or?: LottieAnimatable<[number, number, number]>; // orientation (3D layers: players expect it with rx/ry/rz)\n rx?: LottieAnimatable<number, [number]>; // X rotation (3D layers)\n ry?: LottieAnimatable<number, [number]>; // Y rotation (3D layers)\n rz?: LottieAnimatable<number, [number]>; // Z rotation (3D layers)\n o: LottieAnimatable<number, [number]>; // opacity\n}\n\n\nexport interface LottieShapeItem {\n ty: string;\n nm?: string;\n [key: string]: unknown;\n}\n\nexport interface LottieLayer {\n ddd: 0 | 1;\n ind: number;\n ty: number; // 0 = precomp, 1 = solid, 2 = image, 3 = null, 4 = shape, 5 = text, 13 = camera\n nm: string;\n /** Index of the parent layer: element layers hang off the camera rig. */\n parent?: number;\n /** Camera layers: perspective (the AE camera's zoom), in px. */\n pe?: LottieAnimatable<number, [number]>;\n sr: number;\n ks: LottieTransform;\n ao: 0 | 1;\n ip: number;\n op: number;\n st: number;\n bm: number;\n shapes?: LottieShapeItem[];\n sw?: number;\n sh?: number;\n sc?: string;\n}\n\nexport interface LottieAnimation {\n v: string;\n fr: number;\n ip: number;\n op: number;\n w: number;\n h: number;\n nm: string;\n ddd: 0 | 1;\n assets: unknown[];\n layers: LottieLayer[];\n}\n\n/**\n * Maps AE speed and influence into normalized Lottie Bezier ease control points (o.x, o.y, i.x, i.y).\n */\nexport function createLottieEase(\n durationFrames: number,\n deltaVal: number,\n outEase?: KeyframeEase,\n inEase?: KeyframeEase\n): { o: { x: number; y: number }; i: { x: number; y: number } } {\n const infOut = Math.max(0.001, Math.min(1.0, (outEase?.influence ?? 33.333) / 100));\n const infIn = Math.max(0.001, Math.min(1.0, (inEase?.influence ?? 33.333) / 100));\n\n const safeDelta = Math.abs(deltaVal) > 1e-6 ? deltaVal : 1;\n const speedOut = outEase?.speed ?? 0;\n const speedIn = inEase?.speed ?? 0;\n\n // Lottie normalized control points\n const ox = infOut;\n const oy = Math.max(0, Math.min(1, (speedOut * infOut * durationFrames) / safeDelta));\n\n const ix = 1.0 - infIn;\n const iy = Math.max(0, Math.min(1, 1.0 - (speedIn * infIn * durationFrames) / safeDelta));\n\n return {\n o: { x: Number(ox.toFixed(4)), y: Number(oy.toFixed(4)) },\n i: { x: Number(ix.toFixed(4)), y: Number(iy.toFixed(4)) },\n };\n}\n\n/** Linear keyframes for a sampled value, or a static value when it never changes. */\nfunction sampled<T extends number | number[]>(samples: { t: number; v: T }[]): LottieAnimatable<T, any> {\n const same = samples.every((x) => JSON.stringify(x.v) === JSON.stringify(samples[0]!.v));\n if (same) return { a: 0, k: samples[0]!.v };\n const keys = samples.map((x, i) => {\n const s = (Array.isArray(x.v) ? x.v : [x.v]) as number[];\n return i < samples.length - 1 ? { t: x.t, s, o: { x: 0, y: 0 }, i: { x: 1, y: 1 } } : { t: x.t, s };\n });\n return { a: 1, k: keys as LottieKeyframe<any>[] };\n}\n\nconst r3 = (n: number) => Math.round(n * 1000) / 1000;\n\n/**\n * The scene's camera as layers: a null \"Camera\" rig the element layers hang\n * off (its anchor is the camera's centre, its scale the zoom and, in 3D, its\n * rotations the angle), and, when the camera is ever angled, an AE camera\n * whose zoom is the perspective distance. Sampled every other frame during\n * moves, so the eased path and perspective come through. Lottie has no\n * motion blur, so that is left out.\n */\nfunction cameraLayers(scene: Scene, fr: number, totalFrames: number): { layers: LottieLayer[]; rig: number; ddd: 0 | 1 } | null {\n if (!scene.shots.some((s) => s.camera)) return null;\n const stage = sceneStage(scene);\n const samples = cameraSamples(scene, fr).map((x) => ({ t: x.frame, view: x.view }));\n const ddd: 0 | 1 = samples.some((x) => isAngled(x.view)) ? 1 : 0;\n const w = stage.width;\n const h = boardHeight(stage);\n const at = <T extends number | number[]>(fn: (v: View) => T) => sampled(samples.map((x) => ({ t: x.t, v: fn(x.view) })));\n const ks: LottieTransform = {\n a: at((v) => {\n const [cx, cy] = centreOf(v, stage);\n return [r3(cx), r3(cy), 0] as [number, number, number];\n }) as LottieTransform['a'],\n p: { a: 0, k: [w / 2, h / 2, 0] },\n s: at((v) => [r3(v.zoom * 100), r3(v.zoom * 100), 100] as [number, number, number]) as LottieTransform['s'],\n o: { a: 0, k: 100 },\n };\n // AE turns the other way round X and Y from CSS (its z points into the screen).\n if (ddd) {\n ks.or = { a: 0, k: [0, 0, 0] };\n ks.rx = at((v) => r3(0 - (v.tilt ?? 0)));\n ks.ry = at((v) => r3(0 - (v.turn ?? 0)));\n ks.rz = at((v) => r3(v.roll ?? 0));\n } else ks.r = at((v) => r3(v.roll ?? 0));\n const layers: LottieLayer[] = [];\n if (ddd) {\n const distance = at((v) => r3((v.perspective ?? DEFAULT_PERSPECTIVE) * w));\n const d0 = (DEFAULT_PERSPECTIVE * w) as number;\n layers.push({\n ddd: 1,\n ind: 1,\n ty: 13,\n nm: 'Camera Lens',\n sr: 1,\n pe: distance,\n ks: {\n p: { a: 0, k: [w / 2, h / 2, -d0] },\n a: { a: 0, k: [w / 2, h / 2, 0] },\n s: { a: 0, k: [100, 100, 100] },\n or: { a: 0, k: [0, 0, 0] },\n rx: { a: 0, k: 0 },\n ry: { a: 0, k: 0 },\n rz: { a: 0, k: 0 },\n o: { a: 0, k: 100 },\n },\n ao: 0,\n ip: 0,\n op: totalFrames,\n st: 0,\n bm: 0,\n });\n }\n const rig = layers.length + 1;\n layers.push({ ddd, ind: rig, ty: 3, nm: 'Camera', sr: 1, ks, ao: 0, ip: 0, op: totalFrames, st: 0, bm: 0 });\n return { layers, rig, ddd };\n}\n\n/**\n * Serializes a Flair Scene into standard Lottie JSON animation. A scene\n * with a camera gets a camera rig the element layers hang off, in 3D (with\n * an AE camera) when the camera is angled.\n */\nexport function exportLottie(scene: Scene, options: LottieExportOptions = {}): LottieAnimation {\n const fr = options.frameRate ?? 30;\n const width = scene.stage?.width ?? DEFAULT_STAGE.width;\n const height = scene.stage?.height ?? DEFAULT_STAGE.height;\n\n // Compute shot timeline frames\n let currentMs = 0;\n const shotTimings = scene.shots.map((shot, idx) => {\n const camMs = idx > 0 && shot.camera ? CAMERA_MS : 0;\n const holdMs = shot.hold ?? 800;\n const durationMs = camMs + holdMs;\n const startMs = currentMs;\n currentMs += durationMs;\n\n const startFrame = Math.round((startMs / 1000) * fr);\n const durationFrames = Math.max(1, Math.round((durationMs / 1000) * fr));\n const endFrame = startFrame + durationFrames;\n\n return {\n shot,\n startFrame,\n durationFrames,\n endFrame,\n };\n });\n\n const totalFrames = Math.max(1, Math.round((currentMs / 1000) * fr));\n\n // Collect elements and track property updates across shots\n interface ElementTrack {\n id: string;\n type: string;\n initialX: number;\n initialY: number;\n width: number;\n height: number;\n text?: string;\n opacity: number;\n inFrame: number;\n outFrame: number;\n posKeyframes: LottieKeyframe<[number, number, number]>[];\n opacityKeyframes: LottieKeyframe<[number]>[];\n }\n\n const tracks = new Map<string, ElementTrack>();\n\n for (const timing of shotTimings) {\n const shot = timing.shot;\n if (!shot.run) continue;\n\n for (const cmd of shot.run) {\n if (isElementCommand(cmd)) {\n const el = cmd.element;\n if (!tracks.has(el.id)) {\n tracks.set(el.id, {\n id: el.id,\n type: el.type,\n initialX: el.x,\n initialY: el.y,\n width: el.width,\n height: el.height,\n text: el.text,\n opacity: el.opacity ?? 1,\n inFrame: timing.startFrame,\n outFrame: totalFrames,\n posKeyframes: [\n {\n t: timing.startFrame,\n s: [el.x + el.width * 0.5, el.y + el.height * 0.5, 0],\n },\n ],\n opacityKeyframes: [\n {\n t: timing.startFrame,\n s: [(el.opacity ?? 1) * 100],\n },\n ],\n });\n }\n } else if (isUpdateCommand(cmd)) {\n const track = tracks.get(cmd.id);\n if (track) {\n const props = cmd.props;\n const prevPos = track.posKeyframes[track.posKeyframes.length - 1];\n const newX = props.x !== undefined ? props.x : prevPos.s[0] - track.width * 0.5;\n const newY = props.y !== undefined ? props.y : prevPos.s[1] - track.height * 0.5;\n\n const ease = createLottieEase(timing.durationFrames, 1);\n prevPos.o = ease.o;\n prevPos.i = ease.i;\n\n track.posKeyframes.push({\n t: timing.endFrame,\n s: [newX + track.width * 0.5, newY + track.height * 0.5, 0],\n });\n\n if (props.opacity !== undefined) {\n const prevOp = track.opacityKeyframes[track.opacityKeyframes.length - 1];\n prevOp.o = ease.o;\n prevOp.i = ease.i;\n track.opacityKeyframes.push({\n t: timing.endFrame,\n s: [props.opacity * 100],\n });\n }\n }\n }\n }\n }\n\n // Build Lottie layers: the camera first, then the elements hanging off its rig.\n const camera = cameraLayers(scene, fr, totalFrames);\n const layers: LottieLayer[] = camera ? [...camera.layers] : [];\n let layerIndex = layers.length + 1;\n\n for (const track of tracks.values()) {\n const isPosAnimated = track.posKeyframes.length > 1;\n const isOpAnimated = track.opacityKeyframes.length > 1;\n\n const p: LottieAnimatable<[number, number, number], [number, number, number]> = isPosAnimated\n ? { a: 1, k: track.posKeyframes }\n : { a: 0, k: [track.initialX + track.width * 0.5, track.initialY + track.height * 0.5, 0] };\n\n const o: LottieAnimatable<number, [number]> = isOpAnimated\n ? { a: 1, k: track.opacityKeyframes }\n : { a: 0, k: track.opacity * 100 };\n\n const ks: LottieTransform = {\n p,\n a: { a: 0, k: [0, 0, 0] },\n s: { a: 0, k: [100, 100, 100] },\n r: { a: 0, k: 0 },\n o,\n };\n\n // Shape items for rectangle / visual element\n const shapes: LottieShapeItem[] = [\n {\n ty: 'gr',\n nm: track.id,\n it: [\n {\n ty: 'rc',\n nm: 'Rectangle Path',\n d: 1,\n s: { a: 0, k: [track.width, track.height] },\n p: { a: 0, k: [0, 0] },\n r: { a: 0, k: 8 },\n },\n {\n ty: 'fl',\n nm: 'Fill',\n c: { a: 0, k: [0.2, 0.4, 0.9, 1] },\n o: { a: 0, k: 100 },\n r: 1,\n },\n {\n ty: 'tr',\n nm: 'Transform',\n p: { a: 0, k: [0, 0] },\n a: { a: 0, k: [0, 0] },\n s: { a: 0, k: [100, 100] },\n r: { a: 0, k: 0 },\n o: { a: 0, k: 100 },\n },\n ],\n },\n ];\n\n layers.push({\n ddd: camera?.ddd ?? 0,\n ind: layerIndex++,\n ...(camera && { parent: camera.rig }),\n ty: 4, // Shape layer\n nm: track.id,\n sr: 1,\n ks,\n ao: 0,\n ip: track.inFrame,\n op: track.outFrame,\n st: track.inFrame,\n bm: 0, // Normal blend mode\n shapes,\n });\n }\n\n return {\n v: '5.5.2',\n fr,\n ip: 0,\n op: totalFrames,\n w: width,\n h: height,\n nm: scene.name || 'Flair Animation',\n ddd: camera?.ddd ?? 0,\n assets: [],\n layers,\n };\n}\n","import type { Scene, Shot } from '../scene/types.js';\nimport { DEFAULT_STAGE, CAMERA_MS, boardHeight, centreOf, projectPoint, type View } from '../scene/camera.js';\nimport { cameraSamples, cameraTrack, sceneStage, type CameraSample } from '../scene/cameratrack.js';\n\nexport interface Fcp7ExportOptions {\n /** Timeline timebase / frame rate (default 30). */\n frameRate?: number;\n /** NTSC flag (default false, or true if 29.97 / 59.94). */\n ntsc?: boolean;\n /** Starting timecode (default \"00:00:00:00\"). */\n timecodeStart?: string;\n /** Sequence name override. */\n sequenceName?: string;\n /** Media path prefix or file base URL (default \"file://localhost/media/\"). */\n mediaPathPrefix?: string;\n /** Include text generator overlay track for titles/captions (default true). */\n includeTextTrack?: boolean;\n /** Include timeline sequence markers (default true). */\n includeMarkers?: boolean;\n}\n\nexport interface EdlExportOptions {\n /** Timeline timebase / frame rate (default 30). */\n frameRate?: number;\n /** Drop frame flag (default false). */\n dropFrame?: boolean;\n /** Starting timecode (default \"01:00:00:00\" as per standard broadcast EDL practice). */\n timecodeStart?: string;\n /** Reel name identifier (default \"AX\", max 8 chars). */\n reel?: string;\n}\n\nexport interface ShotTiming {\n shot: Shot;\n index: number;\n startFrame: number;\n endFrame: number;\n durationFrames: number;\n startMs: number;\n durationMs: number;\n}\n\nfunction escapeXml(unsafe: string): string {\n return unsafe\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Calculates start and duration frame boundaries for each shot in a scene.\n */\nexport function calculateShotTimings(scene: Scene, frameRate: number = 30): ShotTiming[] {\n let currentMs = 0;\n return scene.shots.map((shot, index) => {\n const camMs = index > 0 && shot.camera ? CAMERA_MS : 0;\n const holdMs = shot.hold ?? 800;\n const durationMs = camMs + holdMs;\n const startMs = currentMs;\n currentMs += durationMs;\n\n const startFrame = Math.round((startMs / 1000) * frameRate);\n const durationFrames = Math.max(1, Math.round((durationMs / 1000) * frameRate));\n const endFrame = startFrame + durationFrames;\n\n return {\n shot,\n index,\n startFrame,\n endFrame,\n durationFrames,\n startMs,\n durationMs,\n };\n });\n}\n\n/**\n * Converts frame count to standard SMPTE timecode HH:MM:SS:FF.\n */\nexport function framesToTimecode(\n frame: number,\n timebase: number = 30,\n dropFrame: boolean = false\n): string {\n const roundedFps = Math.round(timebase);\n let totalFrames = Math.max(0, Math.floor(frame));\n\n if (dropFrame && (roundedFps === 30 || roundedFps === 29)) {\n // 29.97 DF: drop frames 0 and 1 at the start of every minute except minutes 0, 10, 20, 30, 40, 50\n const dropFramesPer10Mins = 18;\n const framesPer10Mins = 17982; // 10 * 60 * 30 - 18\n const framesPerMinute = 1798; // 60 * 30 - 2\n\n const d = Math.floor(totalFrames / framesPer10Mins);\n const m = totalFrames % framesPer10Mins;\n\n if (m >= 2) {\n totalFrames += dropFramesPer10Mins * d + 2 * Math.floor((m - 2) / framesPerMinute);\n } else {\n totalFrames += dropFramesPer10Mins * d;\n }\n }\n\n const ff = totalFrames % roundedFps;\n const totalSeconds = Math.floor(totalFrames / roundedFps);\n const ss = totalSeconds % 60;\n const totalMinutes = Math.floor(totalSeconds / 60);\n const mm = totalMinutes % 60;\n const hh = Math.floor(totalMinutes / 60);\n\n const pad = (n: number) => n.toString().padStart(2, '0');\n const separator = dropFrame ? ';' : ':';\n return `${pad(hh)}:${pad(mm)}:${pad(ss)}${separator}${pad(ff)}`;\n}\n\n/**\n * Converts standard SMPTE timecode HH:MM:SS:FF to frame count.\n */\nexport function timecodeToFrames(\n timecode: string,\n timebase: number = 30,\n dropFrame: boolean = false\n): number {\n const parts = timecode.split(/[:;]/).map(Number);\n if (parts.length !== 4 || parts.some(isNaN)) {\n return 0;\n }\n const [hh, mm, ss, ff] = parts;\n const roundedFps = Math.round(timebase);\n\n if (dropFrame && (roundedFps === 30 || roundedFps === 29)) {\n const totalMinutes = hh * 60 + mm;\n const dropCount = (totalMinutes - Math.floor(totalMinutes / 10)) * 2;\n return ((hh * 3600 + mm * 60 + ss) * roundedFps + ff) - dropCount;\n }\n\n return (hh * 3600 + mm * 60 + ss) * roundedFps + ff;\n}\n\nconst r4 = (n: number) => Math.round(n * 10000) / 10000;\n\nconst CORNERS = [\n ['ulcorner', 'Upper Left', 0, 0],\n ['urcorner', 'Upper Right', 1, 0],\n ['lrcorner', 'Lower Right', 1, 1],\n ['llcorner', 'Lower Left', 0, 1],\n] as const;\n\n/**\n * Where the camera puts a full-frame clip's corners, as FCP's Distort\n * offsets from the middle of the frame (fractions of its size, ±0.5 when\n * untouched): a corner pin draws the camera's zoom, pan and 3D angle\n * exactly for a flat picture of the stage.\n */\nfunction cornerPins(view: View, stage: ReturnType<typeof sceneStage>): [number, number][] {\n const w = stage.width;\n const h = boardHeight(stage);\n return CORNERS.map(([, , cx, cy]) => {\n const p = projectPoint(view, stage, [cx * w, cy * h]) ?? [cx * w, cy * h];\n return [r4((p[0] - w / 2) / w), r4((p[1] - h / 2) / h)];\n });\n}\n\n/**\n * The Distort and Motion Blur filters that play a shot's camera on its clip\n * (the clip being a flat render of the whole stage), keyed at frames in the\n * clip; nothing when the camera never leaves the full frame.\n */\nfunction cameraFilters(samples: readonly CameraSample[], stage: ReturnType<typeof sceneStage>, startFrame: number, motionBlur: number, indent: string): string[] {\n const pins = samples.map((x) => cornerPins(x.view, stage));\n const untouched = pins.every((corners) => corners.every(([hz, vt], i) => hz === (CORNERS[i]![2] - 0.5) && vt === (CORNERS[i]![3] - 0.5)));\n const lines: string[] = [];\n if (!untouched) {\n lines.push(`${indent}<filter>`, `${indent} <effect>`, `${indent} <name>Distort</name>`, `${indent} <effectid>deformation</effectid>`, `${indent} <effectcategory>motion</effectcategory>`, `${indent} <effecttype>motion</effecttype>`, `${indent} <mediatype>video</mediatype>`);\n CORNERS.forEach(([id, name], i) => {\n lines.push(`${indent} <parameter>`, `${indent} <parameterid>${id}</parameterid>`, `${indent} <name>${name}</name>`);\n const point = (k: number) => `<value><horiz>${pins[k]![i]![0]}</horiz><vert>${pins[k]![i]![1]}</vert></value>`;\n if (samples.length === 1) lines.push(`${indent} ${point(0)}`);\n else samples.forEach((x, k) => lines.push(`${indent} <keyframe><when>${x.frame - startFrame}</when>${point(k)}</keyframe>`));\n lines.push(`${indent} </parameter>`);\n });\n lines.push(`${indent} </effect>`, `${indent}</filter>`);\n }\n if (motionBlur > 0) {\n lines.push(\n `${indent}<filter>`,\n `${indent} <effect>`,\n `${indent} <name>Motion Blur</name>`,\n `${indent} <effectid>motionblur</effectid>`,\n `${indent} <effectcategory>motion</effectcategory>`,\n `${indent} <effecttype>motion</effecttype>`,\n `${indent} <mediatype>video</mediatype>`,\n `${indent} <parameter>`,\n `${indent} <parameterid>duration</parameterid>`,\n `${indent} <name>% Blur</name>`,\n `${indent} <value>${Math.round(Math.min(1000, motionBlur * 250))}</value>`,\n `${indent} </parameter>`,\n `${indent} <parameter>`,\n `${indent} <parameterid>samples</parameterid>`,\n `${indent} <name>Samples</name>`,\n `${indent} <value>8</value>`,\n `${indent} </parameter>`,\n `${indent} </effect>`,\n `${indent}</filter>`,\n );\n }\n return lines;\n}\n\n/**\n * Serializes a Flair Scene into Apple Final Cut Pro 7 XML Interchange Format (xmeml v4).\n * Compatible with Premiere Pro, DaVinci Resolve, and Final Cut Pro. Each\n * shot's camera (zoom, pan and 3D angle) is a keyframed Distort corner pin\n * on its clip, with Motion Blur when the shot's move blurs.\n */\nexport function exportFcp7Xml(scene: Scene, options: Fcp7ExportOptions = {}): string {\n const frameRate = options.frameRate ?? 30;\n const timebase = Math.round(frameRate);\n const ntsc = options.ntsc ?? (Math.abs(frameRate - 29.97) < 0.05 || Math.abs(frameRate - 59.94) < 0.05);\n const ntscStr = ntsc ? 'TRUE' : 'FALSE';\n const width = scene.stage?.width ?? DEFAULT_STAGE.width;\n const height = scene.stage?.height ?? DEFAULT_STAGE.height;\n const sequenceName = options.sequenceName ?? scene.name ?? 'Flair Sequence';\n const timecodeStart = options.timecodeStart ?? '00:00:00:00';\n const mediaPrefix = options.mediaPathPrefix ?? 'file://localhost/media/';\n const includeTextTrack = options.includeTextTrack ?? true;\n const includeMarkers = options.includeMarkers ?? true;\n\n const timings = calculateShotTimings(scene, frameRate);\n const totalFrames = timings.length > 0 ? timings[timings.length - 1].endFrame : 0;\n const startFrameOffset = timecodeToFrames(timecodeStart, frameRate);\n const stage = sceneStage(scene);\n const samples = scene.shots.some((s) => s.camera) ? cameraSamples(scene, frameRate) : [];\n // A shot's camera keys: its own, after the view it starts from (the last key before it).\n const shotSamples = (index: number, startFrame: number): CameraSample[] => {\n const own = samples.filter((x) => x.shot === index);\n const before = samples.filter((x) => x.shot < index).at(-1);\n if (own.length === 0) return before ? [{ ...before, frame: startFrame, shot: index }] : [];\n return own[0]!.frame > startFrame && before ? [{ ...before, frame: startFrame, shot: index }, ...own] : own;\n };\n\n const lines: string[] = [];\n\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push('<!DOCTYPE xmeml>');\n lines.push('<xmeml version=\"4\">');\n lines.push(` <sequence id=\"sequence-1\">`);\n lines.push(` <name>${escapeXml(sequenceName)}</name>`);\n lines.push(` <duration>${totalFrames}</duration>`);\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(' <timecode>');\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(` <string>${timecodeStart}</string>`);\n lines.push(` <frame>${startFrameOffset}</frame>`);\n lines.push(' <displayformat>NDF</displayformat>');\n lines.push(' </timecode>');\n\n // Sequence Timeline Markers\n if (includeMarkers) {\n timings.forEach(({ shot, index, startFrame, endFrame }) => {\n const name = shot.title || `Shot ${index + 1}`;\n const comment = shot.caption || '';\n lines.push(' <marker>');\n lines.push(` <name>${escapeXml(name)}</name>`);\n lines.push(` <comment>${escapeXml(comment)}</comment>`);\n lines.push(` <in>${startFrame}</in>`);\n lines.push(` <out>${endFrame}</out>`);\n lines.push(' </marker>');\n });\n }\n\n lines.push(' <media>');\n lines.push(' <video>');\n lines.push(' <format>');\n lines.push(' <samplecharacteristics>');\n lines.push(` <width>${width}</width>`);\n lines.push(` <height>${height}</height>`);\n lines.push(' <pixelaspectratio>square</pixelaspectratio>');\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(' </samplecharacteristics>');\n lines.push(' </format>');\n\n // Video Track 1: Base Shot Clips\n lines.push(' <track>');\n lines.push(' <enabled>TRUE</enabled>');\n lines.push(' <locked>FALSE</locked>');\n\n timings.forEach(({ shot, index, startFrame, endFrame, durationFrames }) => {\n const shotName = shot.title || `Shot_${index + 1}`;\n const fileId = `file-${index + 1}`;\n const clipId = `clipitem-${index + 1}`;\n const fileName = `${shotName.replace(/[^a-zA-Z0-9_-]/g, '_')}.mov`;\n const fileUrl = `${mediaPrefix.replace(/\\/+$/, '')}/${fileName}`;\n\n lines.push(` <clipitem id=\"${clipId}\">`);\n lines.push(` <name>${escapeXml(shotName)}</name>`);\n lines.push(` <duration>${durationFrames}</duration>`);\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(` <start>${startFrame}</start>`);\n lines.push(` <end>${endFrame}</end>`);\n lines.push(' <in>0</in>');\n lines.push(` <out>${durationFrames}</out>`);\n\n // File element\n lines.push(` <file id=\"${fileId}\">`);\n lines.push(` <name>${escapeXml(fileName)}</name>`);\n lines.push(` <pathurl>${escapeXml(fileUrl)}</pathurl>`);\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(` <duration>${durationFrames}</duration>`);\n lines.push(' <timecode>');\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(' <string>00:00:00:00</string>');\n lines.push(' <frame>0</frame>');\n lines.push(' <displayformat>NDF</displayformat>');\n lines.push(' </timecode>');\n lines.push(' <media>');\n lines.push(' <video>');\n lines.push(' <samplecharacteristics>');\n lines.push(` <width>${width}</width>`);\n lines.push(` <height>${height}</height>`);\n lines.push(' </samplecharacteristics>');\n lines.push(' </video>');\n lines.push(' </media>');\n lines.push(' </file>');\n\n // Clip marker if caption is present\n if (shot.caption) {\n lines.push(' <marker>');\n lines.push(` <name>${escapeXml(shotName)} Narration</name>`);\n lines.push(` <comment>${escapeXml(shot.caption)}</comment>`);\n lines.push(' <in>0</in>');\n lines.push(` <out>${durationFrames}</out>`);\n lines.push(' </marker>');\n }\n\n // Basic Motion Filter\n lines.push(' <filter>');\n lines.push(' <effect>');\n lines.push(' <name>Basic Motion</name>');\n lines.push(' <effectid>basic</effectid>');\n lines.push(' <effectcategory>motion</effectcategory>');\n lines.push(' <effecttype>motion</effecttype>');\n lines.push(' <mediatype>video</mediatype>');\n lines.push(' <parameter>');\n lines.push(' <parameterid>scale</parameterid>');\n lines.push(' <name>Scale</name>');\n lines.push(' <value>100</value>');\n lines.push(' </parameter>');\n lines.push(' </effect>');\n lines.push(' </filter>');\n const keys = shotSamples(index, startFrame);\n if (keys.length > 0) lines.push(...cameraFilters(keys, stage, startFrame, shot.camera?.motionBlur ?? 0, ' '));\n lines.push(' </clipitem>');\n });\n\n lines.push(' </track>');\n\n // Video Track 2: Title / Narration Overlay Track (Generators)\n const hasTextOverlays = timings.some((t) => t.shot.title || t.shot.caption);\n if (includeTextTrack && hasTextOverlays) {\n lines.push(' <track>');\n lines.push(' <enabled>TRUE</enabled>');\n lines.push(' <locked>FALSE</locked>');\n\n timings.forEach(({ shot, index, startFrame, endFrame, durationFrames }) => {\n const text = shot.title ?? shot.caption;\n if (!text) return;\n\n const generatorId = `generator-title-${index + 1}`;\n lines.push(` <clipitem id=\"${generatorId}\">`);\n lines.push(` <name>${escapeXml(text)}</name>`);\n lines.push(` <duration>${durationFrames}</duration>`);\n lines.push(' <rate>');\n lines.push(` <timebase>${timebase}</timebase>`);\n lines.push(` <ntsc>${ntscStr}</ntsc>`);\n lines.push(' </rate>');\n lines.push(` <start>${startFrame}</start>`);\n lines.push(` <end>${endFrame}</end>`);\n lines.push(' <in>0</in>');\n lines.push(` <out>${durationFrames}</out>`);\n lines.push(' <effect>');\n lines.push(' <name>Text</name>');\n lines.push(' <effectid>Text</effectid>');\n lines.push(' <effectcategory>Text</effectcategory>');\n lines.push(' <effecttype>generator</effecttype>');\n lines.push(' <mediatype>video</mediatype>');\n lines.push(' <parameter>');\n lines.push(' <parameterid>str</parameterid>');\n lines.push(' <name>Text</name>');\n lines.push(` <value>${escapeXml(text)}</value>`);\n lines.push(' </parameter>');\n lines.push(' </effect>');\n lines.push(' </clipitem>');\n });\n\n lines.push(' </track>');\n }\n\n lines.push(' </video>');\n lines.push(' </media>');\n lines.push(' </sequence>');\n lines.push('</xmeml>');\n\n return lines.join('\\n');\n}\n\n/** A camera view as a line an editor can follow: \"zoom 1.6x on (800, 450), tilt 35°, turn -15°, motion blur 1.5\". */\nfunction describeCamera(view: View, motionBlur: number, stage: ReturnType<typeof sceneStage>): string {\n const [cx, cy] = centreOf(view, stage);\n const parts = [`zoom ${r4(view.zoom).toFixed(2)}x on (${Math.round(cx)}, ${Math.round(cy)})`];\n if (view.tilt) parts.push(`tilt ${r4(view.tilt)}°`);\n if (view.turn) parts.push(`turn ${r4(view.turn)}°`);\n if (view.roll) parts.push(`roll ${r4(view.roll)}°`);\n if (view.perspective) parts.push(`perspective ${view.perspective}`);\n if (motionBlur > 0) parts.push(`motion blur ${motionBlur}`);\n return parts.join(', ');\n}\n\n/**\n * Serializes a Flair Scene into standard CMX 3600 Edit Decision List (EDL).\n * The format has no transforms, so each shot's camera is written as a\n * `* CAMERA:` comment.\n * Fully readable by DaVinci Resolve, Premiere Pro, Avid, and Lightworks.\n */\nexport function exportCmx3600Edl(scene: Scene, options: EdlExportOptions = {}): string {\n const frameRate = options.frameRate ?? 30;\n const timebase = Math.round(frameRate);\n const dropFrame = options.dropFrame ?? false;\n const reel = (options.reel ?? 'AX').slice(0, 8).padEnd(8, ' ');\n const title = (scene.name ?? 'UNTITLED').toUpperCase().replace(/[^A-Z0-9_ -]/g, '');\n const startTc = options.timecodeStart ?? '01:00:00:00';\n\n const timings = calculateShotTimings(scene, frameRate);\n let recordFrame = timecodeToFrames(startTc, timebase, dropFrame);\n const track = scene.shots.some((s) => s.camera) ? cameraTrack(scene) : undefined;\n\n const lines: string[] = [];\n lines.push(`TITLE: ${title}`);\n lines.push(dropFrame ? 'FCM: DROP FRAME' : 'FCM: NON-DROP FRAME');\n lines.push('');\n\n timings.forEach(({ shot, index, durationFrames }) => {\n const eventNum = (index + 1).toString().padStart(3, '0');\n const srcInTc = '00:00:00:00';\n const srcOutTc = framesToTimecode(durationFrames, timebase, dropFrame);\n\n const recInTc = framesToTimecode(recordFrame, timebase, dropFrame);\n recordFrame += durationFrames;\n const recOutTc = framesToTimecode(recordFrame, timebase, dropFrame);\n\n // Standard CMX 3600 line format:\n // Event Reel Track Transition SourceIn SourceOut RecordIn RecordOut\n lines.push(`${eventNum} ${reel} V C ${srcInTc} ${srcOutTc} ${recInTc} ${recOutTc}`);\n\n const clipName = shot.title || `Shot ${index + 1}`;\n lines.push(`* FROM CLIP NAME: ${clipName}`);\n if (shot.caption) {\n lines.push(`* COMMENT: ${shot.caption}`);\n }\n // CMX 3600 has no transforms, so the camera is a note for the editor.\n const camera = track?.[index];\n if (camera && shot.camera) lines.push(`* CAMERA: ${describeCamera(camera.to, camera.motionBlur, sceneStage(scene))}`);\n lines.push('');\n });\n\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["r3","escapeXml"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shader/thumbnails.ts","../src/shader/gif.ts","../src/shader/standalone.ts","../src/shader/blend.ts","../src/scene/validate.ts","../src/scene/standalone.ts"],"sourcesContent":["import { imageUrls, parseControls, type ControlDef, type ControlValue } from \"./controls.js\";\nimport {\n compileShader,\n createContext,\n drawFrame,\n resetGlCaches,\n type CompiledShader,\n} from \"./program.js\";\nimport { TextureCache } from \"./textures.js\";\n\n/** Controls a registered thumbnail. */\nexport interface ThumbnailHandle {\n /** Shows another source or other values. A new source is recompiled; new values only redraw. */\n update(source: string, values?: Record<string, ControlValue>): void;\n /** With reduced motion, a hovered thumbnail animates and the others hold a still frame. */\n setHovered(hovered: boolean): void;\n /** Stops drawing into the canvas and frees its program. */\n dispose(): void;\n}\n\nexport interface ThumbnailPoolOptions {\n /** Size of the shared render canvas (default 480 × 270). Thumbnails are drawn at most this large, then scaled. */\n renderWidth?: number;\n renderHeight?: number;\n /** Animation rate (default 30). */\n fps?: number;\n /** `iTime` of the still frame shown with reduced motion or hover-only mode (default 2). */\n stillTime?: number;\n /** Hold still frames unless hovered (default: the `prefers-reduced-motion` media query). */\n reducedMotion?: boolean;\n /** Only animate hovered thumbnails, holding `stillTime` on all others (default false). */\n hoverOnly?: boolean;\n /** Animation policy: `\"always\"` (animate all visible), `\"hover\"` (only animate hovered), or `\"reduced-motion\"` (default: follow `reducedMotion` / `prefers-reduced-motion`). */\n animateMode?: \"always\" | \"hover\" | \"reduced-motion\";\n /** Enable a transparent WebGL drawing buffer and clear each thumbnail canvas before copying (default false). */\n alpha?: boolean;\n}\n\ninterface Entry {\n canvas: HTMLCanvasElement;\n ctx: CanvasRenderingContext2D;\n source: string;\n values?: Record<string, ControlValue>;\n controls: ControlDef[];\n shader: CompiledShader | null;\n failed: boolean;\n dirty: boolean;\n visible: boolean;\n hovered: boolean;\n onStatus?: (ok: boolean) => void;\n}\n\n/**\n * Animated thumbnails from a single WebGL context. Browsers cap live WebGL\n * contexts (about 16), so a gallery cannot give every card its own: the pool\n * renders each visible thumbnail in turn on one hidden canvas and copies the\n * frame into the thumbnail's own 2D canvas, cropped to its aspect ratio like\n * `object-fit: cover`. Thumbnails off screen (by `IntersectionObserver`) cost\n * nothing, and programs compile once per source.\n *\n * Nothing is created until the first `register`.\n */\nexport class ThumbnailPool {\n private readonly renderWidth: number;\n private readonly renderHeight: number;\n private readonly interval: number;\n private readonly fps: number;\n private readonly stillTime: number;\n private readonly reducedMotion: boolean;\n private readonly alpha: boolean;\n private readonly entries = new Set<Entry>();\n private readonly byCanvas = new WeakMap<Element, Entry>();\n private observer: IntersectionObserver | null | undefined;\n private glCanvas: HTMLCanvasElement | null = null;\n private gl: WebGL2RenderingContext | null = null;\n private textures: TextureCache | null = null;\n private raf = 0;\n private last = 0;\n private start: number | null = null;\n\n constructor(options: ThumbnailPoolOptions = {}) {\n this.renderWidth = options.renderWidth ?? 480;\n this.renderHeight = options.renderHeight ?? 270;\n this.fps = options.fps ?? 30;\n this.interval = 1000 / this.fps;\n this.stillTime = options.stillTime ?? 2;\n this.alpha = Boolean(options.alpha);\n const mode = options.animateMode ?? (options.hoverOnly ? \"hover\" : \"reduced-motion\");\n this.reducedMotion =\n mode === \"hover\"\n ? true\n : mode === \"always\"\n ? false\n : (options.reducedMotion ??\n (typeof matchMedia !== \"undefined\" &&\n matchMedia(\"(prefers-reduced-motion: reduce)\").matches));\n }\n\n /**\n * Starts drawing `source` into `canvas`, which should be sized with CSS; its\n * drawing buffer follows its CSS size and the device pixel ratio (up to 2).\n * `onStatus` hears whether the source compiled (and `false` without WebGL2).\n */\n register(\n canvas: HTMLCanvasElement,\n source: string,\n values?: Record<string, ControlValue>,\n onStatus?: (ok: boolean) => void,\n ): ThumbnailHandle {\n const observer = this.intersection();\n const entry: Entry = {\n canvas,\n ctx: canvas.getContext(\"2d\")!,\n source,\n values,\n controls: [],\n shader: null,\n failed: false,\n dirty: true,\n visible: observer === null,\n hovered: false,\n onStatus,\n };\n this.entries.add(entry);\n this.byCanvas.set(canvas, entry);\n observer?.observe(canvas);\n if (!this.context()) onStatus?.(false);\n this.schedule();\n return {\n update: (nextSource, nextValues) => {\n if (nextSource !== entry.source) {\n this.release(entry);\n entry.source = nextSource;\n entry.failed = false;\n }\n entry.values = nextValues;\n entry.dirty = true;\n this.retainTextures();\n this.schedule();\n },\n setHovered: (hovered) => {\n entry.hovered = hovered;\n entry.dirty = true;\n this.schedule();\n },\n dispose: () => {\n observer?.unobserve(canvas);\n this.entries.delete(entry);\n this.byCanvas.delete(canvas);\n this.release(entry);\n this.retainTextures();\n },\n };\n }\n\n /** The number of registered thumbnails. */\n get size(): number {\n return this.entries.size;\n }\n\n private intersection(): IntersectionObserver | null {\n if (this.observer !== undefined) return this.observer;\n this.observer =\n typeof IntersectionObserver === \"undefined\"\n ? null\n : new IntersectionObserver(\n (records) => {\n for (const r of records) {\n const entry = this.byCanvas.get(r.target);\n if (!entry) continue;\n entry.visible = r.isIntersecting;\n if (entry.visible) entry.dirty = true;\n }\n this.schedule();\n },\n { rootMargin: \"100px\" },\n );\n return this.observer;\n }\n\n private context(): WebGL2RenderingContext | null {\n if (this.gl && !this.gl.isContextLost()) return this.gl;\n const canvas = document.createElement(\"canvas\");\n canvas.width = this.renderWidth;\n canvas.height = this.renderHeight;\n canvas.addEventListener(\"webglcontextlost\", (e) => {\n e.preventDefault();\n for (const entry of this.entries) entry.shader = null;\n this.textures?.dispose();\n });\n canvas.addEventListener(\"webglcontextrestored\", () => {\n resetGlCaches();\n if (this.gl) this.textures = this.textureCache(this.gl);\n for (const entry of this.entries) entry.dirty = true;\n this.schedule();\n });\n resetGlCaches();\n this.glCanvas = canvas;\n this.gl = createContext(canvas, false, this.alpha);\n this.textures = this.gl && this.textureCache(this.gl);\n return this.gl;\n }\n\n private textureCache(gl: WebGL2RenderingContext): TextureCache {\n return new TextureCache(gl, (url) => {\n // A thumbnail showing the image redraws with it.\n for (const entry of this.entries) {\n if (imageUrls(entry.controls, entry.values).includes(url)) entry.dirty = true;\n }\n this.schedule();\n });\n }\n\n /** Frees the textures no thumbnail shows any more. */\n private retainTextures() {\n this.textures?.retain(\n [...this.entries].flatMap((entry) => imageUrls(entry.controls, entry.values)),\n );\n }\n\n private release(entry: Entry) {\n if (entry.shader) this.gl?.deleteProgram(entry.shader.program);\n entry.shader = null;\n }\n\n private compile(entry: Entry, gl: WebGL2RenderingContext) {\n entry.controls = parseControls(entry.source);\n const result = compileShader(gl, entry.source, entry.controls);\n entry.shader = result.ok ? result.shader : null;\n entry.failed = !result.ok;\n entry.onStatus?.(result.ok);\n }\n\n private renderEntry(entry: Entry, gl: WebGL2RenderingContext, time: number) {\n if (!entry.shader && !entry.failed) this.compile(entry, gl);\n if (!entry.shader) return;\n sizeCanvas(entry.canvas);\n // Fill the thumbnail the way `object-fit: cover` would, at its aspect ratio.\n const aspect = entry.canvas.width / entry.canvas.height;\n const w = Math.min(this.renderWidth, Math.round(this.renderHeight * aspect));\n const h = Math.min(this.renderHeight, Math.round(w / aspect));\n drawFrame(\n gl,\n entry.shader,\n entry.controls,\n entry.values,\n {\n width: w,\n height: h,\n time,\n delta: this.interval / 1000,\n frame: Math.floor(time * this.fps),\n mouse: [0, 0, 0, 0],\n // A hovered thumbnail is pointed at in its middle, for effects that answer hovers.\n pointer: [w / 2, h / 2, entry.hovered ? 1 : 0, 0],\n },\n this.textures ?? undefined,\n );\n // The GL canvas's drawing buffer is valid until this task ends; copy it now.\n // WebGL draws from the bottom left, so the frame sits at the bottom of the canvas.\n if (this.alpha) {\n entry.ctx.clearRect(0, 0, entry.canvas.width, entry.canvas.height);\n }\n entry.ctx.drawImage(\n this.glCanvas!,\n 0,\n this.renderHeight - h,\n w,\n h,\n 0,\n 0,\n entry.canvas.width,\n entry.canvas.height,\n );\n entry.dirty = false;\n }\n\n private schedule() {\n if (!this.raf && this.entries.size) this.raf = requestAnimationFrame(this.tick);\n }\n\n private tick = (now: number) => {\n this.raf = 0;\n const gl = this.context();\n if (!gl) return;\n this.start ??= now;\n const animate = now - this.last >= this.interval;\n const time = (now - this.start) / 1000;\n let keepGoing = false;\n // Compiling is the slow part, so a row of thumbnails scrolling into view compiles one per frame.\n let compiles = 1;\n for (const entry of this.entries) {\n if (!entry.visible) continue;\n if (!entry.shader && !entry.failed) {\n if (!compiles) {\n keepGoing = true;\n continue;\n }\n compiles--;\n }\n const moving = !this.reducedMotion || entry.hovered;\n if (entry.dirty || (moving && animate)) {\n this.renderEntry(entry, gl, moving ? time : this.stillTime);\n }\n if (moving && entry.shader) keepGoing = true;\n }\n if (animate) this.last = now;\n if (keepGoing) this.schedule();\n };\n}\n\nfunction sizeCanvas(canvas: HTMLCanvasElement) {\n // The layout size, not the bounding box: a rotated or scaled canvas keeps its own proportions.\n const ratio = Math.min(window.devicePixelRatio || 1, 2);\n const w = Math.max(1, Math.round(canvas.clientWidth * ratio));\n const h = Math.max(1, Math.round(canvas.clientHeight * ratio));\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n }\n}\n\nlet shared: ThumbnailPool | null = null;\n\n/** The pool `registerThumbnail` uses, created on first use. */\nexport function sharedThumbnailPool(): ThumbnailPool {\n shared ??= new ThumbnailPool();\n return shared;\n}\n\n/** Registers a thumbnail with the shared pool. See {@link ThumbnailPool.register}. */\nexport function registerThumbnail(\n canvas: HTMLCanvasElement,\n source: string,\n values?: Record<string, ControlValue>,\n onStatus?: (ok: boolean) => void,\n): ThumbnailHandle {\n return sharedThumbnailPool().register(canvas, source, values, onStatus);\n}\n","// gifenc ships without types. The reference (not an import) brings them along when a host\n// type-checks these sources through the `workspace` condition.\n// eslint-disable-next-line @typescript-eslint/triple-slash-reference\n/// <reference path=\"./gifenc.d.ts\" />\nimport type { ControlValue } from \"./controls.js\";\nimport { ShaderFrames } from \"./frames.js\";\n\nexport interface GifOptions {\n source: string;\n values?: Record<string, ControlValue>;\n width: number;\n height: number;\n /** Frames per second. GIF delays are whole hundredths of a second, so 10, 20, 25 and 50 play back exactly. */\n fps: number;\n /** Seconds of animation to record. */\n duration: number;\n /** `iTime` of the first frame (default 0). */\n start?: number;\n /** Called after each frame with the frames done and the total. */\n onProgress?: (done: number, total: number) => void;\n /** Aborting rejects with an `AbortError` `DOMException`. */\n signal?: AbortSignal;\n}\n\n/** Lets the page repaint between frames so progress shows and Cancel works. */\nconst yieldToBrowser = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * Records a shader as an animated GIF that loops forever. Frames are rendered\n * off screen at exact time steps (`start + i / fps`), so the result is smooth\n * whatever the page's frame rate, and nothing on screen is disturbed. Each\n * frame gets its own 256-colour palette. The encoder,\n * [gifenc](https://github.com/mattdesl/gifenc), is loaded on first use.\n *\n * Image controls are loaded before the first frame. Rejects with a\n * `ShaderCompileError` when the source does not compile, and an\n * `ImageLoadError` when an image cannot be loaded.\n */\nexport async function recordGif(options: GifOptions): Promise<Blob> {\n const { source, values, width, height, fps, duration, start = 0, onProgress, signal } = options;\n const { GIFEncoder, quantize, applyPalette } = await import(\"gifenc\");\n\n const frames = new ShaderFrames({ source, values, width, height });\n try {\n await frames.ready();\n const total = Math.max(1, Math.round(duration * fps));\n const delay = 1000 / fps;\n const rgba = new Uint8Array(frames.width * frames.height * 4);\n const gif = GIFEncoder();\n\n for (let i = 0; i < total; i++) {\n if (signal?.aborted) throw new DOMException(\"Cancelled\", \"AbortError\");\n frames.render(start + i / fps, { frame: i, delta: 1 / fps });\n frames.readPixels(rgba);\n const palette = quantize(rgba, 256);\n gif.writeFrame(applyPalette(rgba, palette), frames.width, frames.height, {\n palette,\n delay,\n ...(i === 0 ? { repeat: 0 } : {}),\n });\n onProgress?.(i + 1, total);\n if (i % 2 === 1) await yieldToBrowser();\n }\n\n gif.finish();\n return new Blob([gif.bytes()], { type: \"image/gif\" });\n } finally {\n frames.dispose();\n }\n}\n","import { controlValue, parseControls, type ControlValue } from \"./controls.js\";\nimport { HOVER_SECONDS, MAX_CLICKS, MAX_TRAIL, TRAIL_SECONDS } from \"./pointer.js\";\nimport { VERTEX_SOURCE, wrapSource } from \"./program.js\";\nimport { SVG_EDGE } from \"./textures.js\";\n\nconst escapeHtml = (s: string) =>\n s.replace(/[&<>\"]/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" })[c]!);\n\n/** JSON that is safe inside a `<script>`: a `</script>` or `<!--` in a shader comment cannot end it. */\nconst scriptJson = (value: unknown) => JSON.stringify(value).replace(/</g, \"\\\\u003c\");\n\nexport interface StandaloneOptions {\n /** The comment at the top of the script, saying where the page came from. */\n credit?: string;\n}\n\n/**\n * A self-contained HTML page that runs the shader full-window, with the\n * control values baked in. It has no dependencies and opens from disk. Like\n * the live preview, it feeds `iMouse` from pointer drags and caps the pixel\n * ratio at 2.\n *\n * Images are loaded by URL when the page opens: a `data:` URL travels with\n * the page, an `https:` one needs the network and CORS, and a `blob:` one\n * only lives as long as the page that made it. SVGs are sized to\n * {@link SVG_EDGE} pixels before they are decoded, as `loadImage` does.\n */\nexport function standaloneHtml(\n title: string,\n source: string,\n values?: Record<string, ControlValue>,\n options: StandaloneOptions = {},\n): string {\n const controls = parseControls(source).map((c) => ({\n name: c.name,\n type: c.type,\n value: controlValue(c, values),\n ...(c.texture ? { texture: c.texture } : {}),\n }));\n const credit = (options.credit ?? \"Exported with Flair.\").replace(/\\n/g, \" \");\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>html,body{margin:0;height:100%;background:#000}canvas{display:block;width:100%;height:100%}</style>\n</head>\n<body>\n<canvas id=\"c\"></canvas>\n<script>\n// ${credit.replace(/</g, \"\\\\u003c\")} Shadertoy-style fragment shader on WebGL2.\nconst VERTEX = ${scriptJson(VERTEX_SOURCE)};\nconst FRAGMENT = ${scriptJson(wrapSource(source))};\nconst CONTROLS = ${scriptJson(controls)};\nconst canvas = document.getElementById(\"c\");\nconst gl = canvas.getContext(\"webgl2\");\nif (!gl) document.body.textContent = \"WebGL2 is not available.\";\nfunction compile(type, src) {\n const s = gl.createShader(type);\n gl.shaderSource(s, src);\n gl.compileShader(s);\n if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));\n return s;\n}\nconst program = gl.createProgram();\ngl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));\ngl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAGMENT));\ngl.linkProgram(program);\ngl.useProgram(program);\nconst u = (n) => gl.getUniformLocation(program, n);\nlet unit = 0;\n// An SVG decodes at its own, often icon, size: give it ${SVG_EDGE} px on its long edge (and a\n// viewBox, so the drawing scales) to keep it sharp.\nasync function sizedSvg(url) {\n const doc = new DOMParser().parseFromString(await (await fetch(url)).text(), \"image/svg+xml\");\n const svg = doc.documentElement;\n const len = (n) => { const m = (svg.getAttribute(n) || \"\").trim().match(/^([\\\\d.]+)(px)?$/); return m && +m[1] > 0 ? +m[1] : 0; };\n const box = (svg.getAttribute(\"viewBox\") || \"\").trim().split(/[\\\\s,]+/).map(Number);\n const hasBox = box.length === 4 && box[2] > 0 && box[3] > 0;\n const w = len(\"width\") || (hasBox ? box[2] : 300), h = len(\"height\") || (hasBox ? box[3] : 150);\n const scale = ${SVG_EDGE} / Math.max(w, h);\n if (!hasBox) svg.setAttribute(\"viewBox\", \"0 0 \" + w + \" \" + h);\n svg.setAttribute(\"width\", Math.max(1, Math.round(w * scale)));\n svg.setAttribute(\"height\", Math.max(1, Math.round(h * scale)));\n return URL.createObjectURL(new Blob([new XMLSerializer().serializeToString(svg)], { type: \"image/svg+xml\" }));\n}\nfunction image(loc, url, texture) {\n const i = unit++, t = gl.createTexture();\n const bind = () => { gl.activeTexture(gl.TEXTURE0 + i); gl.bindTexture(gl.TEXTURE_2D, t); };\n const filter = { mipmap: gl.LINEAR_MIPMAP_LINEAR, linear: gl.LINEAR, nearest: gl.NEAREST }[texture.filter];\n const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[texture.wrap];\n bind();\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, texture.filter === \"nearest\" ? gl.NEAREST : gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n gl.uniform1i(loc, i);\n if (!url) return;\n const img = new Image();\n if (!/^(data|blob):/i.test(url)) img.crossOrigin = \"anonymous\";\n img.onload = () => {\n bind();\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);\n gl.generateMipmap(gl.TEXTURE_2D);\n };\n if (/^data:image\\\\/svg\\\\+xml[;,]/i.test(url) || /^(?!data:)[^?#]*\\\\.svg([?#]|$)/i.test(url)) {\n sizedSvg(url).then((src) => { img.src = src; }, () => {});\n } else img.src = url;\n}\nfor (const c of CONTROLS) {\n const loc = u(c.name);\n const v = c.value;\n if (c.type === \"float\") gl.uniform1f(loc, v);\n else if (c.type === \"int\" || c.type === \"bool\") gl.uniform1i(loc, Number(v));\n else if (c.type === \"sampler2D\") image(loc, v, c.texture);\n else gl[\"uniform\" + v.length + \"fv\"](loc, v);\n}\nlet mouse = [0, 0, 0, 0];\nconst px = (e) => { const r = canvas.getBoundingClientRect(); return [(e.clientX - r.left) * canvas.width / r.width, (r.bottom - e.clientY) * canvas.height / r.height]; };\ncanvas.addEventListener(\"pointerdown\", (e) => { const [x, y] = px(e); mouse = [x, y, x, y]; });\ncanvas.addEventListener(\"pointermove\", (e) => { if (e.buttons && mouse[2] > 0) { const [x, y] = px(e); mouse = [x, y, mouse[2], mouse[3]]; } });\naddEventListener(\"pointerup\", () => { if (mouse[2] > 0) mouse = [mouse[0], mouse[1], -mouse[2], -Math.abs(mouse[3])]; });\nlet start = performance.now(), last = start, frame = 0;\n// iPointer (where, how hovered, seconds pressed), iClicks (the latest clicks) and iTrail (where it has just been), newest first.\nconst seconds = () => (performance.now() - start) / 1000;\nlet at = [0, 0], over = false, hoverFrom = 0, hoverAt = -1e9, pressedAt = -1, releasedAt = -1, clicks = [], trail = [];\nconst hovered = (t) => { const k = Math.min(1, Math.max(0, (t - hoverAt) / ${HOVER_SECONDS})); return hoverFrom + ((over ? 1 : 0) - hoverFrom) * k * k * (3 - 2 * k); };\nconst hover = (on) => { const t = seconds(); hoverFrom = hovered(t); hoverAt = t; over = on; };\ncanvas.addEventListener(\"pointerenter\", (e) => { at = px(e); hover(true); toTrail(); });\ncanvas.addEventListener(\"pointerleave\", () => hover(false));\n// A trail point at most every ${TRAIL_SECONDS.toFixed(4)} s; moves in between update the newest.\nconst toTrail = () => { const t = seconds(); if (trail.length && t - trail[0][2] < ${TRAIL_SECONDS}) trail[0] = [at[0], at[1], trail[0][2], 1]; else trail = [[at[0], at[1], t, 1], ...trail].slice(0, ${MAX_TRAIL}); };\ncanvas.addEventListener(\"pointermove\", (e) => { at = px(e); toTrail(); });\ncanvas.addEventListener(\"pointerdown\", (e) => { at = px(e); pressedAt = seconds(); clicks.unshift([at[0], at[1], pressedAt, 1]); clicks = clicks.slice(0, ${MAX_CLICKS}); });\naddEventListener(\"pointerup\", () => { if (pressedAt >= 0) releasedAt = seconds(); pressedAt = -1; });\nfunction draw(now) {\n const dpr = Math.min(devicePixelRatio || 1, 2);\n const w = Math.round(canvas.clientWidth * dpr), h = Math.round(canvas.clientHeight * dpr);\n if (canvas.width !== w || canvas.height !== h) { canvas.width = w; canvas.height = h; }\n gl.viewport(0, 0, w, h);\n gl.uniform3f(u(\"iResolution\"), w, h, 1);\n gl.uniform1f(u(\"iTime\"), (now - start) / 1000);\n gl.uniform1f(u(\"iTimeDelta\"), (now - last) / 1000);\n gl.uniform1i(u(\"iFrame\"), frame++);\n gl.uniform4f(u(\"iMouse\"), mouse[0], mouse[1], mouse[2], mouse[3]);\n const t = (now - start) / 1000;\n gl.uniform4f(u(\"iPointer\"), at[0], at[1], hovered(t), pressedAt >= 0 ? Math.max(0, t - pressedAt) : releasedAt >= 0 ? -Math.max(1e-3, t - releasedAt) : 0);\n const c = new Float32Array(${MAX_CLICKS * 4});\n clicks.forEach((k, i) => c.set(k, i * 4));\n gl.uniform4fv(u(\"iClicks\"), c);\n const p = new Float32Array(${MAX_TRAIL * 4});\n trail.forEach((k, i) => p.set(k, i * 4));\n gl.uniform4fv(u(\"iTrail\"), p);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n last = now;\n requestAnimationFrame(draw);\n}\nrequestAnimationFrame(draw);\n</script>\n</body>\n</html>\n`;\n}\n","/**\n * 38 Layer Blend Modes for Compositing and WebGL Shader Generation.\n * Mathematically matching Adobe After Effects & EffectCraft blending standards.\n */\n\nexport type BlendMode =\n | 'normal'\n | 'dissolve'\n | 'dancingDissolve'\n | 'darken'\n | 'multiply'\n | 'colorBurn'\n | 'classicColorBurn'\n | 'linearBurn'\n | 'darkerColor'\n | 'add'\n | 'lighten'\n | 'screen'\n | 'colorDodge'\n | 'classicColorDodge'\n | 'linearDodge'\n | 'lighterColor'\n | 'overlay'\n | 'softLight'\n | 'hardLight'\n | 'linearLight'\n | 'vividLight'\n | 'pinLight'\n | 'hardMix'\n | 'difference'\n | 'classicDifference'\n | 'exclusion'\n | 'subtract'\n | 'divide'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity'\n | 'stencilAlpha'\n | 'stencilLuma'\n | 'silhouetteAlpha'\n | 'silhouetteLuma'\n | 'alphaAdd'\n | 'luminescentPremul';\n\nexport const ALL_BLEND_MODES: readonly BlendMode[] = [\n 'normal',\n 'dissolve',\n 'dancingDissolve',\n 'darken',\n 'multiply',\n 'colorBurn',\n 'classicColorBurn',\n 'linearBurn',\n 'darkerColor',\n 'add',\n 'lighten',\n 'screen',\n 'colorDodge',\n 'classicColorDodge',\n 'linearDodge',\n 'lighterColor',\n 'overlay',\n 'softLight',\n 'hardLight',\n 'linearLight',\n 'vividLight',\n 'pinLight',\n 'hardMix',\n 'difference',\n 'classicDifference',\n 'exclusion',\n 'subtract',\n 'divide',\n 'hue',\n 'saturation',\n 'color',\n 'luminosity',\n 'stencilAlpha',\n 'stencilLuma',\n 'silhouetteAlpha',\n 'silhouetteLuma',\n 'alphaAdd',\n 'luminescentPremul',\n];\n\nexport type ColorRgba = [number, number, number, number];\n\nfunction lum(r: number, g: number, b: number): number {\n return 0.3 * r + 0.59 * g + 0.11 * b;\n}\n\nfunction sat(r: number, g: number, b: number): number {\n return Math.max(r, g, b) - Math.min(r, g, b);\n}\n\nfunction clipColor(r: number, g: number, b: number): [number, number, number] {\n const l = lum(r, g, b);\n const n = Math.min(r, g, b);\n const x = Math.max(r, g, b);\n let cr = r;\n let cg = g;\n let cb = b;\n if (n < 0.0) {\n const denom = Math.max(1e-9, l - n);\n cr = l + ((cr - l) * l) / denom;\n cg = l + ((cg - l) * l) / denom;\n cb = l + ((cb - l) * l) / denom;\n }\n if (x > 1.0) {\n const denom = Math.max(1e-9, x - l);\n cr = l + ((cr - l) * (1.0 - l)) / denom;\n cg = l + ((cg - l) * (1.0 - l)) / denom;\n cb = l + ((cb - l) * (1.0 - l)) / denom;\n }\n return [cr, cg, cb];\n}\n\nfunction setLum(r: number, g: number, b: number, l: number): [number, number, number] {\n const d = l - lum(r, g, b);\n return clipColor(r + d, g + d, b + d);\n}\n\nfunction setSat(r: number, g: number, b: number, s: number): [number, number, number] {\n const mx = Math.max(r, g, b);\n const mn = Math.min(r, g, b);\n if (mx - mn <= 1e-9) return [0, 0, 0];\n const f = (v: number) => ((v - mn) * s) / (mx - mn);\n return [f(r), f(g), f(b)];\n}\n\nfunction burn(cb: number, cs: number): number {\n if (cb >= 1.0) return 1.0;\n if (cs <= 0.0) return 0.0;\n return 1.0 - Math.min(1.0, (1.0 - cb) / cs);\n}\n\nfunction dodge(cb: number, cs: number): number {\n if (cb <= 0.0) return 0.0;\n if (cs >= 1.0) return 1.0;\n return Math.min(1.0, cb / (1.0 - cs));\n}\n\nfunction hardLight(cb: number, cs: number): number {\n if (cs <= 0.5) {\n return cb * 2.0 * cs;\n }\n const s = 2.0 * cs - 1.0;\n return cb + s - cb * s;\n}\n\nfunction softLight(cb: number, cs: number): number {\n if (cs <= 0.5) {\n return cb - (1.0 - 2.0 * cs) * cb * (1.0 - cb);\n }\n const d = cb <= 0.25 ? ((16.0 * cb - 12.0) * cb + 4.0) * cb : Math.sqrt(Math.max(0.0, cb));\n return cb + (2.0 * cs - 1.0) * (d - cb);\n}\n\nfunction vividLight(cb: number, cs: number): number {\n return cs <= 0.5 ? burn(cb, 2.0 * cs) : dodge(cb, 2.0 * (cs - 0.5));\n}\n\nfunction separableBlend(mode: BlendMode, cb: number, cs: number): number {\n switch (mode) {\n case 'darken':\n return Math.min(cb, cs);\n case 'multiply':\n return cb * cs;\n case 'colorBurn':\n return burn(cb, cs);\n case 'classicColorBurn':\n return cs <= 0.0 ? 0.0 : 1.0 - (1.0 - cb) / cs;\n case 'linearBurn':\n return Math.max(0.0, cb + cs - 1.0);\n case 'add':\n case 'linearDodge':\n return cb + cs;\n case 'lighten':\n return Math.max(cb, cs);\n case 'screen':\n return cb + cs - cb * cs;\n case 'colorDodge':\n return dodge(cb, cs);\n case 'classicColorDodge':\n return cs >= 1.0 ? 1.0 : Math.min(1.0, cb / (1.0 - cs));\n case 'overlay':\n return hardLight(cs, cb);\n case 'softLight':\n return softLight(cb, cs);\n case 'hardLight':\n return hardLight(cb, cs);\n case 'linearLight':\n return Math.min(1.0, Math.max(0.0, cb + 2.0 * cs - 1.0));\n case 'vividLight':\n return vividLight(cb, cs);\n case 'pinLight':\n return cs <= 0.5 ? Math.min(cb, 2.0 * cs) : Math.max(cb, 2.0 * cs - 1.0);\n case 'hardMix':\n return cb + cs >= 1.0 ? 1.0 : 0.0;\n case 'difference':\n case 'classicDifference':\n return Math.abs(cb - cs);\n case 'exclusion':\n return cb + cs - 2.0 * cb * cs;\n case 'subtract':\n return Math.max(0.0, cb - cs);\n case 'divide':\n return cs <= 0.0 ? (cb > 0.0 ? 1.0 : 0.0) : cb / cs;\n default:\n return cs;\n }\n}\n\n/**\n * Evaluates CPU compositing of premultiplied RGBA source onto premultiplied RGBA destination.\n *\n * @param mode Blend mode to apply.\n * @param dst Destination premultiplied RGBA [r, g, b, a] in [0, 1].\n * @param src Source premultiplied RGBA [r, g, b, a] in [0, 1].\n * @param noise Per-pixel noise parameter in [0, 1] for Dissolve modes (default: 0).\n */\nexport function blendRgba(\n mode: BlendMode,\n dst: ColorRgba,\n src: ColorRgba,\n noise = 0,\n): ColorRgba {\n const sa = src[3];\n const da = dst[3];\n\n switch (mode) {\n case 'normal': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, sa + da * k];\n }\n case 'dissolve':\n case 'dancingDissolve': {\n if (sa <= 0.0 || noise >= sa) return [...dst];\n const inv = 1.0 / sa;\n return [src[0] * inv, src[1] * inv, src[2] * inv, 1.0];\n }\n case 'stencilAlpha':\n return [dst[0] * sa, dst[1] * sa, dst[2] * sa, dst[3] * sa];\n case 'silhouetteAlpha': {\n const k = 1.0 - sa;\n return [dst[0] * k, dst[1] * k, dst[2] * k, dst[3] * k];\n }\n case 'stencilLuma':\n case 'silhouetteLuma': {\n const l = lum(src[0], src[1], src[2]);\n const k = mode === 'stencilLuma' ? l : 1.0 - l;\n return [dst[0] * k, dst[1] * k, dst[2] * k, dst[3] * k];\n }\n case 'alphaAdd': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, Math.min(1.0, sa + da)];\n }\n case 'luminescentPremul': {\n const k = 1.0 - sa;\n return [src[0] + dst[0] * k, src[1] + dst[1] * k, src[2] + dst[2] * k, sa + da - sa * da];\n }\n }\n\n if (sa <= 0.0) return [...dst];\n if (da <= 0.0) return [...src];\n\n const cs: [number, number, number] = [src[0] / sa, src[1] / sa, src[2] / sa];\n const cb: [number, number, number] = [dst[0] / da, dst[1] / da, dst[2] / da];\n\n let b: [number, number, number];\n switch (mode) {\n case 'hue':\n b = setLum(...setSat(cs[0], cs[1], cs[2], sat(...cb)), lum(...cb));\n break;\n case 'saturation':\n b = setLum(...setSat(cb[0], cb[1], cb[2], sat(...cs)), lum(...cb));\n break;\n case 'color':\n b = setLum(cs[0], cs[1], cs[2], lum(...cb));\n break;\n case 'luminosity':\n b = setLum(cb[0], cb[1], cb[2], lum(...cs));\n break;\n case 'darkerColor':\n b = lum(...cs) < lum(...cb) ? cs : cb;\n break;\n case 'lighterColor':\n b = lum(...cs) > lum(...cb) ? cs : cb;\n break;\n default:\n b = [\n separableBlend(mode, cb[0], cs[0]),\n separableBlend(mode, cb[1], cs[1]),\n separableBlend(mode, cb[2], cs[2]),\n ];\n break;\n }\n\n const ao = sa + da - sa * da;\n return [\n (1.0 - da) * src[0] + (1.0 - sa) * dst[0] + sa * da * b[0],\n (1.0 - da) * src[1] + (1.0 - sa) * dst[1] + sa * da * b[1],\n (1.0 - da) * src[2] + (1.0 - sa) * dst[2] + sa * da * b[2],\n ao,\n ];\n}\n\n/**\n * Returns a self-contained GLSL shader function for blending two vec4 colors.\n * Useful for runtime GPU compositing across multiple shader layers or passes.\n */\nexport function getGlslBlendFunction(mode: BlendMode): string {\n switch (mode) {\n case 'multiply':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(src.rgb * dst.rgb * src.a + dst.rgb * (1.0 - src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'screen':\n return `vec4 blend(vec4 dst, vec4 src) {\n vec3 res = src.rgb + dst.rgb - src.rgb * dst.rgb;\n return vec4(mix(dst.rgb, res, src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'add':\n case 'linearDodge':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(min(vec3(1.0), dst.rgb + src.rgb * src.a), min(1.0, dst.a + src.a));\n}`;\n case 'overlay':\n return `vec4 blend(vec4 dst, vec4 src) {\n vec3 res = mix(2.0 * dst.rgb * src.rgb, 1.0 - 2.0 * (1.0 - dst.rgb) * (1.0 - src.rgb), step(vec3(0.5), dst.rgb));\n return vec4(mix(dst.rgb, res, src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'difference':\n return `vec4 blend(vec4 dst, vec4 src) {\n return vec4(mix(dst.rgb, abs(dst.rgb - src.rgb), src.a), src.a + dst.a * (1.0 - src.a));\n}`;\n case 'normal':\n default:\n return `vec4 blend(vec4 dst, vec4 src) {\n return src + dst * (1.0 - src.a);\n}`;\n }\n}\n","import { MAX_TILT } from './camera.js';\nimport type { Scene, Shot } from './types.js';\nimport { EFFECT_TYPES } from './effects.js';\n\nexport interface SceneIssue {\n /** 1-based shot index (0 indicates whole scene level). */\n shot: number;\n message: string;\n}\n\nconst LONG_CAPTION = 120;\nconst LONG_TITLE = 60;\nconst MAX_EFFECTS_PER_SHOT = 5;\n\n/** Validates a Scene before running or rendering it. */\nexport function validateScene(scene: Scene): SceneIssue[] {\n const issues: SceneIssue[] = [];\n const say = (shot: number, message: string) => issues.push({ shot, message });\n\n if (!scene.name || typeof scene.name !== 'string') {\n say(0, 'Scene needs a string name.');\n } else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(scene.name)) {\n say(0, `Scene name \"${scene.name}\" should be lowercase words and dashes.`);\n }\n\n if (!Array.isArray(scene.shots) || scene.shots.length === 0) {\n say(0, 'Scene needs at least one shot.');\n return issues;\n }\n\n const mediaKeys = new Set(Object.keys(scene.media ?? {}));\n const effectIds = new Set<string>();\n\n // Check media\n if (scene.media) {\n for (const [id, m] of Object.entries(scene.media)) {\n if (!m || typeof m !== 'object') {\n say(0, `Media \"${id}\" must be an object.`);\n } else if (!m.type) {\n say(0, `Media \"${id}\" needs a type.`);\n } else if (m.type === 'shader' && !m.source) {\n say(0, `Shader media \"${id}\" needs a source string.`);\n }\n }\n }\n\n for (const [idx, shot] of scene.shots.entries()) {\n const shotNum = idx + 1;\n\n // Check camera\n if (shot.camera) {\n if ('frame' in shot.camera) {\n if (!Array.isArray(shot.camera.frame) || shot.camera.frame.length === 0) {\n say(shotNum, 'Dynamic camera framing requires a non-empty frame array of element IDs.');\n }\n } else if ('width' in shot.camera && (shot.camera.width <= 0 || shot.camera.height <= 0)) {\n say(shotNum, 'Camera rectangle width and height must be positive.');\n }\n const cam = shot.camera;\n const angle = (name: 'tilt' | 'turn' | 'roll', max: number) => {\n const v = cam[name];\n if (v !== undefined && !(typeof v === 'number' && Math.abs(v) <= max))\n say(shotNum, `Camera ${name} is degrees between -${max} and ${max}.`);\n };\n angle('tilt', MAX_TILT);\n angle('turn', MAX_TILT);\n angle('roll', 180);\n if (cam.perspective !== undefined && !(typeof cam.perspective === 'number' && cam.perspective >= 0.3))\n say(shotNum, 'Camera perspective is the lens distance as a multiple of the stage width, 0.3 or more (1.2 by default).');\n if (cam.motionBlur !== undefined && !(typeof cam.motionBlur === 'number' && cam.motionBlur >= 0 && cam.motionBlur <= 3))\n say(shotNum, 'Camera motionBlur is 0 (none) to 3 (a whip); 1 is natural.');\n }\n\n // Check hold\n if (shot.hold !== undefined && shot.hold < 0) {\n say(shotNum, 'Shot hold cannot be negative.');\n }\n\n // Check text lengths\n if (shot.caption && shot.caption.length > LONG_CAPTION) {\n say(shotNum, `Caption is long (${shot.caption.length} chars); consider breaking into multiple shots.`);\n }\n if (shot.title && shot.title.length > LONG_TITLE) {\n say(shotNum, `Title is long (${shot.title.length} chars); titles should be concise headings.`);\n }\n\n // Check effects\n const effects = shot.effects ?? [];\n if (effects.length > MAX_EFFECTS_PER_SHOT) {\n say(shotNum, `Shot starts ${effects.length} effects at once; consider keeping effects focused.`);\n }\n\n for (const eff of effects) {\n if (!eff.id || typeof eff.id !== 'string') {\n say(shotNum, 'Every effect needs a unique string id.');\n continue;\n }\n if (effectIds.has(eff.id)) {\n say(shotNum, `Duplicate effect id \"${eff.id}\".`);\n }\n effectIds.add(eff.id);\n\n if (!EFFECT_TYPES.includes(eff.type as any)) {\n say(shotNum, `Unknown effect type \"${(eff as any).type}\".`);\n }\n\n if (eff.type === 'media') {\n if (!eff.media) {\n say(shotNum, `Media effect \"${eff.id}\" needs a media id.`);\n } else if (!mediaKeys.has(eff.media)) {\n say(shotNum, `Media effect \"${eff.id}\" references \"${eff.media}\" which is not defined in scene.media.`);\n }\n }\n\n if (eff.type === 'flow' && !eff.along) {\n say(shotNum, `Flow effect \"${eff.id}\" needs an \"along\" element id.`);\n }\n\n if (['glow', 'ripple', 'spotlight', 'shimmer', 'timer', 'blur', 'burst'].includes(eff.type)) {\n const withIds = eff as { ids?: string[] };\n if (!Array.isArray(withIds.ids) || withIds.ids.length === 0) {\n say(shotNum, `${eff.type} effect \"${eff.id}\" needs a non-empty ids array.`);\n }\n }\n }\n }\n\n return issues;\n}\n","import { CAMERA_MS, type Stage, type View } from './camera.js';\nimport { CAMERA_STEP_MS, cameraAt, cameraTrack, sceneStage } from './cameratrack.js';\nimport type { SceneElement } from './geometry.js';\nimport type { Scene } from './types.js';\n\n/** What the standalone player shows for one shot: the camera's move into it and the elements on stage. */\ninterface ShotPlan {\n /** The camera at each step of its move into the shot (one view when it does not move). */\n move: View[];\n elements: SceneElement[];\n}\n\n/** Each shot's camera move and elements, worked out the way `playScene` plays them. */\nfunction planShots(scene: Scene, stage: Stage): ShotPlan[] {\n return cameraTrack(scene).map((entry) => {\n // The player's steps: the last one lands on the shot's view, with no blur.\n const steps = Math.round(entry.moveMs / CAMERA_STEP_MS);\n const move = Array.from({ length: Math.max(0, steps - 1) }, (_, i) => cameraAt(entry, (i + 1) * CAMERA_STEP_MS, stage));\n move.push(entry.to);\n return { move, elements: entry.elements };\n });\n}\n\n/**\n * Generates a self-contained, standalone HTML player for an animated scene.\n * Includes interactive playback controls, camera moves (panning, zooming,\n * 3D angles and motion blur), title and caption rendering, and embedded\n * WebGL fragment shaders for bespoke motion graphics.\n */\nexport function standaloneSceneHtml(scene: Scene): string {\n const stage = sceneStage(scene);\n const serializedScene = JSON.stringify(scene).replace(/</g, '\\\\u003c');\n const serializedPlan = JSON.stringify({ stage, cameraMs: CAMERA_MS, shots: planShots(scene, stage) }).replace(/</g, '\\\\u003c');\n const title = scene.title || scene.name;\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(title)} - Flair</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0f1117;\n color: #e2e8f0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px;\n user-select: none;\n }\n .player-container {\n position: relative;\n width: 100%;\n max-width: 1200px;\n aspect-ratio: ${stage.width} / ${stage.height};\n background: #000;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(255, 255, 255, 0.1);\n display: flex;\n flex-direction: column;\n }\n .viewport {\n position: relative;\n flex: 1;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n .board {\n position: absolute;\n inset: 0;\n transform-origin: 50% 50%;\n will-change: transform, filter;\n }\n canvas.layer {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n .overlay-title {\n position: absolute;\n top: 24px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(15, 17, 23, 0.85);\n backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.15);\n padding: 8px 24px;\n border-radius: 9999px;\n font-size: 1.15rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: #fff;\n pointer-events: none;\n transition: opacity 0.3s ease;\n opacity: 0;\n }\n .overlay-caption {\n position: absolute;\n bottom: 64px;\n left: 50%;\n transform: translateX(-50%);\n background: rgba(15, 17, 23, 0.88);\n backdrop-filter: blur(8px);\n border: 1px solid rgba(255, 255, 255, 0.15);\n padding: 10px 20px;\n border-radius: 8px;\n font-size: 1.1rem;\n color: #f1f5f9;\n max-width: 80%;\n text-align: center;\n pointer-events: none;\n transition: opacity 0.3s ease;\n opacity: 0;\n }\n .controls-bar {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 52px;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.9), transparent);\n display: flex;\n align-items: center;\n padding: 0 16px;\n gap: 12px;\n z-index: 10;\n }\n .btn {\n background: rgba(255, 255, 255, 0.15);\n border: none;\n color: #fff;\n padding: 6px 14px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 0.9rem;\n font-weight: 500;\n transition: background 0.15s;\n }\n .btn:hover { background: rgba(255, 255, 255, 0.25); }\n .timeline {\n flex: 1;\n height: 6px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 3px;\n cursor: pointer;\n position: relative;\n }\n .timeline-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: #3b82f6;\n border-radius: 3px;\n width: 0%;\n }\n .shot-badge {\n font-size: 0.85rem;\n color: #94a3b8;\n font-variant-numeric: tabular-nums;\n }\n </style>\n</head>\n<body>\n <div class=\"player-container\">\n <div class=\"viewport\" id=\"viewport\">\n <canvas id=\"shaderCanvas\" class=\"layer\"></canvas>\n <div id=\"board\" class=\"board\"><canvas id=\"sceneCanvas\" class=\"layer\"></canvas></div>\n <svg width=\"0\" height=\"0\" style=\"position:absolute\" aria-hidden=\"true\"><filter id=\"motionBlur\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\"><feGaussianBlur id=\"motionBlurAmount\" stdDeviation=\"0 0\"/></filter></svg>\n <div id=\"titleEl\" class=\"overlay-title\"></div>\n <div id=\"captionEl\" class=\"overlay-caption\"></div>\n </div>\n <div class=\"controls-bar\">\n <button id=\"playBtn\" class=\"btn\">Play</button>\n <button id=\"prevBtn\" class=\"btn\">⏮</button>\n <button id=\"nextBtn\" class=\"btn\">⏭</button>\n <div id=\"timeline\" class=\"timeline\">\n <div id=\"progress\" class=\"timeline-progress\"></div>\n </div>\n <div id=\"shotBadge\" class=\"shot-badge\">Shot 1 / 1</div>\n </div>\n </div>\n\n <script>\n const scene = ${serializedScene};\n // Each shot's camera move (zoom, scroll, 3D angle and blur at each step) and elements, worked out in advance.\n const plan = ${serializedPlan};\n const boardEl = document.getElementById('board');\n const sceneCanvas = document.getElementById('sceneCanvas');\n const shaderCanvas = document.getElementById('shaderCanvas');\n const ctx = sceneCanvas.getContext('2d');\n const titleEl = document.getElementById('titleEl');\n const captionEl = document.getElementById('captionEl');\n const playBtn = document.getElementById('playBtn');\n const prevBtn = document.getElementById('prevBtn');\n const nextBtn = document.getElementById('nextBtn');\n const progressEl = document.getElementById('progress');\n const shotBadge = document.getElementById('shotBadge');\n\n let currentShotIdx = 0;\n let isPlaying = false;\n let shotStartTime = performance.now();\n let moveFrom = performance.now();\n let gl = null;\n let shaderProgram = null;\n\n function resize() {\n const rect = sceneCanvas.parentElement.getBoundingClientRect();\n const dpr = window.devicePixelRatio || 1;\n sceneCanvas.width = rect.width * dpr;\n sceneCanvas.height = rect.height * dpr;\n shaderCanvas.width = rect.width * dpr;\n shaderCanvas.height = rect.height * dpr;\n }\n window.addEventListener('resize', resize);\n resize();\n\n // Setup WebGL if shader media is present\n const firstShaderKey = Object.keys(scene.media || {}).find(k => scene.media[k].type === 'shader');\n if (firstShaderKey && scene.media[firstShaderKey].source) {\n try {\n gl = shaderCanvas.getContext('webgl2');\n if (gl) {\n const vs = gl.createShader(gl.VERTEX_SHADER);\n gl.shaderSource(vs, '#version 300 es\\\\nin vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); }');\n gl.compileShader(vs);\n\n const fs = gl.createShader(gl.FRAGMENT_SHADER);\n const fullFrag = '#version 300 es\\\\nprecision highp float;\\\\nout vec4 fragColor;\\\\nuniform vec2 u_resolution;\\\\nuniform float u_time;\\\\n' + scene.media[firstShaderKey].source + '\\\\nvoid main() { mainImage(fragColor, gl_FragCoord.xy); }';\n gl.shaderSource(fs, fullFrag);\n gl.compileShader(fs);\n\n shaderProgram = gl.createProgram();\n gl.attachShader(shaderProgram, vs);\n gl.attachShader(shaderProgram, fs);\n gl.linkProgram(shaderProgram);\n\n const quad = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, quad);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(shaderProgram, 'position');\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n }\n } catch (e) {\n console.warn('Shader setup failed:', e);\n }\n }\n\n function renderShader(timeSeconds) {\n if (!gl || !shaderProgram) return;\n gl.viewport(0, 0, shaderCanvas.width, shaderCanvas.height);\n gl.useProgram(shaderProgram);\n const resLoc = gl.getUniformLocation(shaderProgram, 'u_resolution');\n const timeLoc = gl.getUniformLocation(shaderProgram, 'u_time');\n if (resLoc) gl.uniform2f(resLoc, shaderCanvas.width, shaderCanvas.height);\n if (timeLoc) gl.uniform1f(timeLoc, timeSeconds);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n }\n\n function updateShotUI() {\n const shot = scene.shots[currentShotIdx];\n if (!shot) return;\n\n shotBadge.textContent = 'Shot ' + (currentShotIdx + 1) + ' / ' + scene.shots.length;\n\n if (shot.title) {\n titleEl.textContent = shot.title;\n titleEl.style.opacity = '1';\n } else {\n titleEl.style.opacity = '0';\n }\n\n if (shot.caption) {\n captionEl.textContent = shot.caption;\n captionEl.style.opacity = '1';\n } else {\n captionEl.style.opacity = '0';\n }\n\n const pct = (currentShotIdx / scene.shots.length) * 100;\n progressEl.style.width = pct + '%';\n }\n\n // The camera at time 'now': a step of the current shot's move, or where it settled.\n function cameraNow(now) {\n const move = plan.shots[currentShotIdx].move;\n const u = Math.min(1, (now - moveFrom) / plan.cameraMs);\n return move[Math.min(move.length - 1, Math.floor(u * move.length))];\n }\n\n function applyCamera(view) {\n const w = boardEl.clientWidth || plan.stage.width;\n const angled = view.tilt || view.turn || view.roll;\n boardEl.style.transform = angled\n ? 'perspective(' + (view.perspective || 1.2) * w + 'px) rotateX(' + (view.tilt || 0) + 'deg) rotateY(' + (view.turn || 0) + 'deg) rotateZ(' + (view.roll || 0) + 'deg)'\n : 'none';\n // Motion blur streaks along a pan (taken back through the roll, as the board is blurred before it turns).\n const blur = (view.blur || 0) * w / plan.stage.width;\n if (blur >= 0.05) {\n let sx = blur, sy = blur;\n if (view.blurAngle !== undefined) {\n const a = (view.blurAngle - (view.roll || 0)) * Math.PI / 180;\n sx = Math.max(blur * 0.15, blur * Math.abs(Math.cos(a)));\n sy = Math.max(blur * 0.15, blur * Math.abs(Math.sin(a)));\n }\n document.getElementById('motionBlurAmount').setAttribute('stdDeviation', sx.toFixed(2) + ' ' + sy.toFixed(2));\n boardEl.style.filter = 'url(#motionBlur)';\n } else {\n boardEl.style.filter = 'none';\n }\n }\n\n function draw() {\n const now = performance.now();\n renderShader(now / 1000);\n\n const view = cameraNow(now);\n applyCamera(view);\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, sceneCanvas.width, sceneCanvas.height);\n const k = (sceneCanvas.width / plan.stage.width) * view.zoom;\n ctx.setTransform(k, 0, 0, k, k * view.scrollX, k * view.scrollY);\n\n for (const el of plan.shots[currentShotIdx].elements) {\n ctx.fillStyle = el.backgroundColor || 'rgba(59, 130, 246, 0.4)';\n ctx.strokeStyle = el.strokeColor || '#3b82f6';\n ctx.lineWidth = 3;\n ctx.fillRect(el.x, el.y, el.width, el.height);\n ctx.strokeRect(el.x, el.y, el.width, el.height);\n if (el.text) {\n ctx.fillStyle = '#fff';\n ctx.font = '24px sans-serif';\n ctx.fillText(el.text, el.x + 16, el.y + 40);\n }\n }\n\n // Playing moves on to the next shot once this one's move and hold are over.\n const shot = scene.shots[currentShotIdx];\n if (isPlaying && now - shotStartTime > plan.cameraMs + (shot.hold ?? 800) && currentShotIdx < scene.shots.length - 1) {\n goTo(currentShotIdx + 1, true);\n }\n if (isPlaying || now - moveFrom < plan.cameraMs) {\n requestAnimationFrame(draw);\n }\n }\n\n // Shows a shot: forwards it plays the camera's move into it, otherwise it cuts.\n function goTo(idx, animate) {\n currentShotIdx = idx;\n shotStartTime = performance.now();\n moveFrom = animate ? shotStartTime : shotStartTime - plan.cameraMs;\n updateShotUI();\n requestAnimationFrame(draw);\n }\n\n playBtn.addEventListener('click', () => {\n isPlaying = !isPlaying;\n playBtn.textContent = isPlaying ? 'Pause' : 'Play';\n // The current shot counts as settled, so its hold runs from now.\n if (isPlaying) {\n shotStartTime = performance.now() - plan.cameraMs;\n requestAnimationFrame(draw);\n }\n });\n\n prevBtn.addEventListener('click', () => {\n if (currentShotIdx > 0) goTo(currentShotIdx - 1, false);\n });\n\n nextBtn.addEventListener('click', () => {\n if (currentShotIdx < scene.shots.length - 1) goTo(currentShotIdx + 1, true);\n });\n\n window.addEventListener('resize', () => requestAnimationFrame(draw));\n goTo(0, false);\n </script>\n</body>\n</html>`;\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n"],"mappings":"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","names":["escapeHtml"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/time/noise.ts","../src/scene/types.ts","../src/scene/geometry.ts","../src/scene/camera.ts","../src/scene/check.ts","../src/scene/effects.ts","../src/scene/behavior.ts","../src/scene/report.ts","../src/scene/player.ts","../src/scene/cameratrack.ts"],"sourcesContent":["/**\n * Deterministic pseudo-random number generator, hash mixing, and C2-continuous smooth value noise.\n * Based on 64-bit SplitMix64 mixing and quintic Hermite interpolation.\n *\n * Produces deterministic, platform-independent outputs for wiggle, organic turbulence, and camera shake.\n */\n\n// BigInt constants for SplitMix64\nconst GOLDEN_GAMMA = 0x9e3779b97f4a7c15n;\nconst MULT1 = 0xbf58476d1ce4e5b9n;\nconst MULT2 = 0x94d049bb133111ebn;\nconst MASK64 = 0xffffffffffffffffn;\nconst BASE_MIX = 0x2545f4914f6cdd1dn;\nconst POW2_53 = 9007199254740992; // 2^53\n\n/**\n * SplitMix64 mixing finalizer: maps any 64-bit integer to a high-quality pseudorandom 64-bit integer.\n */\nexport function hash64(x: bigint): bigint {\n let v = (x + GOLDEN_GAMMA) & MASK64;\n v = ((v ^ (v >> 30n)) * MULT1) & MASK64;\n v = ((v ^ (v >> 27n)) * MULT2) & MASK64;\n return (v ^ (v >> 31n)) & MASK64;\n}\n\n/**\n * Combines multiple seed numbers/bigints into a single mixed 64-bit seed.\n */\nexport function mixSeeds(parts: (bigint | number)[]): bigint {\n let h = BASE_MIX;\n for (const p of parts) {\n const val = typeof p === 'bigint' ? p : BigInt(Math.floor(p));\n h = hash64(h ^ hash64(val & MASK64));\n }\n return h;\n}\n\n/**\n * Maps a 64-bit seed to a uniform float in [0, 1).\n */\nexport function unitFloat(seed: bigint | number): number {\n const s = typeof seed === 'bigint' ? seed : BigInt(Math.floor(seed));\n const hashed = hash64(s);\n const shifted = Number(hashed >> 11n);\n return shifted / POW2_53;\n}\n\n/**\n * Quintic Hermite interpolant fade function: 6t^5 - 15t^4 + 10t^3.\n * First and second derivatives are zero at t = 0 and t = 1, ensuring C2 continuity.\n */\nexport function quinticFade(t: number): number {\n return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\n/**\n * Returns a deterministic pseudo-random lattice value in [-1, 1].\n */\nfunction lattice(seed: bigint, i: bigint): number {\n const mixed = seed ^ hash64(i & MASK64);\n return unitFloat(mixed) * 2.0 - 1.0;\n}\n\n/**\n * 1D C2-continuous smooth value noise in [-1, 1].\n * Guaranteed bounded by lattice values, smooth without sudden velocity spikes.\n */\nexport function noise1d(seed: bigint | number, x: number): number {\n if (!Number.isFinite(x)) return 0.0;\n const s = typeof seed === 'bigint' ? seed : BigInt(Math.floor(seed));\n const floorX = Math.floor(x);\n const u = quinticFade(x - floorX);\n const i = BigInt(Math.floor(floorX));\n const a = lattice(s, i);\n const b = lattice(s, i + 1n);\n return a + (b - a) * u;\n}\n\n/**\n * 3D C2-continuous smooth value noise in [-1, 1].\n */\nexport function noise3d(x: number, y: number, z: number, seed: bigint | number = 0): number {\n if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {\n return 0.0;\n }\n const s = typeof seed === 'bigint' ? seed : BigInt(Math.floor(seed));\n const fx = Math.floor(x);\n const fy = Math.floor(y);\n const fz = Math.floor(z);\n\n const u = quinticFade(x - fx);\n const v = quinticFade(y - fy);\n const w = quinticFade(z - fz);\n\n const ix = BigInt(fx);\n const iy = BigInt(fy);\n const iz = BigInt(fz);\n\n const g = (dx: bigint, dy: bigint, dz: bigint): number => {\n const combined = mixSeeds([s, ix + dx, iy + dy, iz + dz]);\n return unitFloat(combined) * 2.0 - 1.0;\n };\n\n const lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\n const x00 = lerp(g(0n, 0n, 0n), g(1n, 0n, 0n), u);\n const x10 = lerp(g(0n, 1n, 0n), g(1n, 1n, 0n), u);\n const x01 = lerp(g(0n, 0n, 1n), g(1n, 0n, 1n), u);\n const x11 = lerp(g(0n, 1n, 1n), g(1n, 1n, 1n), u);\n\n const y0 = lerp(x00, x10, v);\n const y1 = lerp(x01, x11, v);\n\n return lerp(y0, y1, w);\n}\n\n/**\n * Evaluates multi-octave fractal noise wiggle:\n * sum_{k=0}^{octaves-1} amp * (ampMult^k) * noise1d(freq * (2^k) * t)\n *\n * Matches the mathematical formulation of After Effects / EffectCraft wiggle().\n *\n * @param freq Oscillation frequency in Hz (cycles per second).\n * @param amp Maximum deviation amplitude.\n * @param octaves Number of noise detail octaves (default: 3).\n * @param ampMult Amplitude multiplier per octave (default: 0.5).\n * @param t Elapsed time in seconds.\n * @param seed Random seed (default: 42).\n * @param dimension Dimension index (0, 1, 2) to ensure decorrelated multi-axis wiggle.\n */\nexport function fractalWiggle(\n freq: number,\n amp: number,\n octaves = 3,\n ampMult = 0.5,\n t = 0,\n seed = 42,\n dimension = 0,\n): number {\n const oct = Math.max(1, Math.min(16, Math.floor(octaves)));\n let currentAmp = amp;\n let currentFreq = freq;\n let sum = 0.0;\n const baseSeed = BigInt(seed);\n\n for (let k = 0; k < oct; k++) {\n const s = mixSeeds([baseSeed, BigInt(dimension), BigInt(k)]);\n const phaseOffset = unitFloat(s) * 1000.0;\n sum += currentAmp * noise1d(s, currentFreq * t + phaseOffset);\n currentAmp *= ampMult;\n currentFreq *= 2.0;\n }\n\n return sum;\n}\n","import type { Rect, SceneElement } from './geometry.js';\nimport type { CameraAngle, Stage } from './camera.js';\nimport type { Effect } from './effects.js';\nimport type { Behavior } from './behavior.js';\nimport type { SceneRig } from './rig.js';\n\n/** Shader or external visual stream in the scene. */\nexport interface SceneMedia {\n type: 'shader' | string;\n source?: string;\n values?: Record<string, unknown>;\n [field: string]: unknown;\n}\n\nexport interface ElementCommand {\n type: 'element';\n element: SceneElement;\n}\n\nexport interface UpdateCommand {\n type: 'update';\n id: string;\n props: Partial<SceneElement>;\n}\n\nexport interface DeleteCommand {\n type: 'delete';\n id: string;\n}\n\nexport interface ClearCommand {\n type: 'clear';\n}\n\nexport interface CustomCommand {\n type: string;\n [key: string]: unknown;\n}\n\n/** A command to add, move, or style elements in a shot. */\nexport type SceneCommand =\n | ElementCommand\n | UpdateCommand\n | DeleteCommand\n | ClearCommand\n | CustomCommand;\n\nexport function isElementCommand(cmd: SceneCommand): cmd is ElementCommand {\n return cmd.type === 'element' && 'element' in cmd && !!cmd.element;\n}\n\nexport function isUpdateCommand(cmd: SceneCommand): cmd is UpdateCommand {\n return cmd.type === 'update' && 'id' in cmd && typeof cmd.id === 'string';\n}\n\nexport function isDeleteCommand(cmd: SceneCommand): cmd is DeleteCommand {\n return cmd.type === 'delete' && 'id' in cmd && typeof cmd.id === 'string';\n}\n\nexport interface DynamicFraming {\n /** Element IDs to frame. */\n frame: string[];\n /** Proportional padding (default 0.15 for 15%). */\n padding?: number;\n /** Minimum width in scene units. */\n minWidth?: number;\n /** Minimum height in scene units. */\n minHeight?: number;\n}\n\n/**\n * A shot's camera beyond its framing: its 3D angle (see `CameraAngle`) and\n * how much it blurs while moving into the shot. A part left out keeps the\n * value the camera already has, so set `tilt: 0` to come back flat.\n */\nexport interface CameraLens extends CameraAngle {\n /**\n * Motion blur while the camera moves into this shot: 0 (the default) for\n * none, 1 for natural, up to 3 for a whip. The blur follows the move's\n * speed, so it peaks mid-move and is gone when the camera settles.\n */\n motionBlur?: number;\n}\n\n/**\n * Where the camera goes for a shot: a framing box, a dynamic framing, or\n * neither (only a new angle or blur, keeping the framing), with any\n * {@link CameraLens} fields alongside: `{ \"frame\": [\"chart\"], \"tilt\": 30 }`.\n */\nexport type ShotCamera = (Rect | DynamicFraming | Record<never, never>) & CameraLens;\n\n/** One beat of a scene: camera framing, title, narration/caption, commands, and procedural effects. */\nexport interface Shot {\n /** Narration / caption spoken or displayed during this beat. */\n caption?: string;\n /** High-level title or callout displayed on screen. */\n title?: string;\n /** Camera framing box in scene units or dynamic auto-framing target, with an optional 3D angle and motion blur. */\n camera?: ShotCamera;\n /** Pause/dwell duration after this shot's actions run (in milliseconds). Default: 800ms. */\n hold?: number;\n /** Drawing or state change commands played during this shot. */\n run?: SceneCommand[];\n /** Procedural visual effects started in this shot. */\n effects?: Effect[];\n /** IDs of active effects to stop/fade at the start of this shot. */\n stopEffects?: string[];\n /** Continuous procedural parameter and physics behaviors played during this shot. */\n behaviors?: Behavior[];\n}\n\n/** Complete animated scene specification. */\nexport interface Scene {\n name: string;\n title?: string;\n description?: string;\n stage?: Partial<Stage>;\n /** Embedded media (e.g. bespoke WebGL fragment shaders). */\n media?: Record<string, SceneMedia>;\n /** Target duration [minSeconds, maxSeconds]. */\n target?: [number, number];\n /** Interactive macro rigs binding high-level controls to uniforms or element properties. */\n rig?: SceneRig;\n shots: Shot[];\n}\n","export type Point = [number, number];\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** An element placed or animated in a scene. */\nexport interface SceneElement {\n id: string;\n type: string;\n x: number;\n y: number;\n width: number;\n height: number;\n points?: readonly (readonly [number, number])[];\n fontSize?: number;\n text?: string;\n containerId?: string | null;\n isDeleted?: boolean;\n opacity?: number;\n}\n\n/** Bounds in scene units, ignoring rotation. */\nexport function boundsOf(e: SceneElement): Rect {\n if (e.points?.length) {\n const xs = e.points.map((p) => e.x + p[0]);\n const ys = e.points.map((p) => e.y + p[1]);\n const x = Math.min(...xs);\n const y = Math.min(...ys);\n return {\n x,\n y,\n width: Math.max(1, Math.max(...xs) - x),\n height: Math.max(1, Math.max(...ys) - y),\n };\n }\n return { x: e.x, y: e.y, width: Math.max(1, e.width), height: Math.max(1, e.height) };\n}\n\n/** Area of the overlap of two rectangles (0 when they do not meet). */\nexport function overlapArea(a: Rect, b: Rect): number {\n const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);\n const h = Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y);\n return w > 0 && h > 0 ? w * h : 0;\n}\n\nexport const areaOf = (r: Rect): number => r.width * r.height;\n","import { type Rect, type SceneElement, boundsOf } from './geometry.js';\n\nexport interface Stage {\n width: number;\n height: number;\n caption?: number;\n}\n\nexport const DEFAULT_STAGE: Stage = {\n width: 1920,\n height: 1080,\n caption: 0,\n};\n\nexport const CAMERA_MS = 600;\n\n/**\n * How the camera is turned in 3D, in degrees: the board is drawn flat (see\n * {@link View}), then turned round the middle of the stage and seen in\n * perspective. Each is optional and 0 (flat) by default.\n */\nexport interface CameraAngle {\n /** Tips the board back round its horizontal axis (CSS `rotateX`): positive leans the top away, like a desk seen from above. −75 to 75. */\n tilt?: number;\n /** Swings the board round its vertical axis (CSS `rotateY`): positive brings the left side nearer. −75 to 75. */\n turn?: number;\n /** Rolls the board round the line of sight (CSS `rotateZ`), clockwise. −180 to 180. */\n roll?: number;\n /** How far the lens sits from the board, as a multiple of the stage width (default {@link DEFAULT_PERSPECTIVE}); smaller is more dramatic. */\n perspective?: number;\n}\n\n/** The camera's default distance from the board, as a multiple of the stage width. */\nexport const DEFAULT_PERSPECTIVE = 1.2;\n\n/** The largest tilt or turn, in degrees: past this the board is seen edge-on. */\nexport const MAX_TILT = 75;\n\n/**\n * Camera view into the scene: drawn at (point + scroll) × zoom, then turned\n * by its {@link CameraAngle} (see {@link cameraTransform}) and blurred by\n * `blur` while the camera moves.\n */\nexport interface View extends CameraAngle {\n zoom: number;\n scrollX: number;\n scrollY: number;\n /** Motion blur in stage pixels, set while the camera moves (see `ShotCamera.motionBlur`); 0 or absent when still. */\n blur?: number;\n /**\n * The direction the picture streaks in, in degrees on screen (0 is\n * horizontal, 90 vertical), when the move is mostly a pan; absent when it\n * blurs every way (a zoom or a turn).\n */\n blurAngle?: number;\n}\n\nexport const boardHeight = (stage: Stage): number => stage.height - (stage.caption ?? 0);\n\n/** View with `(cx, cy)` in the middle of the board at `zoom`. */\nexport function viewAt(cx: number, cy: number, zoom: number, stage: Stage): View {\n return {\n zoom,\n scrollX: stage.width / (2 * zoom) - cx,\n scrollY: boardHeight(stage) / (2 * zoom) - cy,\n };\n}\n\n/** Scene point in the middle of the board. */\nexport function centreOf(view: View, stage: Stage): [number, number] {\n return [\n stage.width / (2 * view.zoom) - view.scrollX,\n boardHeight(stage) / (2 * view.zoom) - view.scrollY,\n ];\n}\n\n/** The view that fits `rect` on the board, centred and with its aspect kept. */\nexport function frame(rect: Rect, stage: Stage): View {\n if (!(rect.width > 0 && rect.height > 0)) {\n throw new Error('A camera rectangle needs a positive width and height');\n }\n const zoom = Math.min(stage.width / rect.width, boardHeight(stage) / rect.height);\n return viewAt(rect.x + rect.width / 2, rect.y + rect.height / 2, zoom, stage);\n}\n\nexport interface FramingOptions {\n /** Proportional padding added to all sides (e.g., 0.15 for 15%). Default: 0.15 */\n padding?: number;\n /** Minimum width in scene units. */\n minWidth?: number;\n /** Minimum height in scene units. */\n minHeight?: number;\n}\n\n/**\n * Calculates a camera framing rectangle that encloses target elements\n * while preserving the stage aspect ratio and applying optional margin padding.\n */\nexport function calculateFramingRect(\n elements: readonly SceneElement[],\n stage: Stage,\n options: FramingOptions = {},\n): Rect {\n const activeElements = elements.filter((e) => !e.isDeleted);\n const targetH = boardHeight(stage);\n const stageAspect = stage.width / Math.max(1, targetH);\n\n if (activeElements.length === 0) {\n return {\n x: 0,\n y: 0,\n width: stage.width,\n height: targetH,\n };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const el of activeElements) {\n const b = boundsOf(el);\n if (b.x < minX) minX = b.x;\n if (b.y < minY) minY = b.y;\n if (b.x + b.width > maxX) maxX = b.x + b.width;\n if (b.y + b.height > maxY) maxY = b.y + b.height;\n }\n\n let width = maxX - minX;\n let height = maxY - minY;\n\n // Apply padding (default 15%)\n const padding = options.padding ?? 0.15;\n if (padding > 0) {\n const padX = width * padding;\n const padY = height * padding;\n minX -= padX;\n minY -= padY;\n width += padX * 2;\n height += padY * 2;\n }\n\n // Apply min dimensions\n if (options.minWidth && width < options.minWidth) {\n const diff = options.minWidth - width;\n minX -= diff / 2;\n width = options.minWidth;\n }\n if (options.minHeight && height < options.minHeight) {\n const diff = options.minHeight - height;\n minY -= diff / 2;\n height = options.minHeight;\n }\n\n // Preserve stage aspect ratio centered around midpoint\n const currentAspect = width / Math.max(1, height);\n if (currentAspect < stageAspect) {\n // Taller than stage: expand width\n const targetWidth = height * stageAspect;\n const diff = targetWidth - width;\n minX -= diff / 2;\n width = targetWidth;\n } else if (currentAspect > stageAspect) {\n // Wider than stage: expand height\n const targetHeight = width / stageAspect;\n const diff = targetHeight - height;\n minY -= diff / 2;\n height = targetHeight;\n }\n\n return {\n x: minX,\n y: minY,\n width,\n height,\n };\n}\n\nconst ANGLES = ['tilt', 'turn', 'roll'] as const;\n\n/** Whether a view is turned in 3D at all. */\nexport function isAngled(view: CameraAngle): boolean {\n return ANGLES.some((k) => (view[k] ?? 0) !== 0);\n}\n\n/** The view's angle with each part clamped to its range (see {@link CameraAngle}); flat parts are left out. */\nexport function cameraAngle(view: CameraAngle): CameraAngle {\n const out: CameraAngle = {};\n const clamp = (v: number, max: number) => Math.max(-max, Math.min(max, v));\n if (view.tilt) out.tilt = clamp(view.tilt, MAX_TILT);\n if (view.turn) out.turn = clamp(view.turn, MAX_TILT);\n if (view.roll) out.roll = clamp(view.roll, 180);\n if (view.perspective !== undefined && view.perspective !== DEFAULT_PERSPECTIVE) out.perspective = Math.max(0.3, view.perspective);\n return out;\n}\n\n/**\n * The view part-way (`t` from 0 to 1) from one view to another.\n * Centre moves linearly and zoom scales exponentially for smooth motion;\n * angles and perspective move linearly.\n */\nexport function between(from: View, to: View, t: number, stage: Stage): View {\n const [x0, y0] = centreOf(from, stage);\n const [x1, y1] = centreOf(to, stage);\n const zoom = from.zoom * Math.pow(to.zoom / from.zoom, t);\n const view: View = viewAt(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, zoom, stage);\n if (!isAngled(from) && !isAngled(to) && from.perspective === to.perspective) return view;\n const lerp = (a = 0, b = 0) => a + (b - a) * t;\n return {\n ...view,\n ...cameraAngle({\n tilt: lerp(from.tilt, to.tilt),\n turn: lerp(from.turn, to.turn),\n roll: lerp(from.roll, to.roll),\n perspective: lerp(from.perspective ?? DEFAULT_PERSPECTIVE, to.perspective ?? DEFAULT_PERSPECTIVE),\n }),\n };\n}\n\n/**\n * The CSS `transform` that turns a board layer (a box the stage's size,\n * with `transform-origin` in its middle) to the view's angle, or \"none\" when\n * the view is flat. Zoom and scroll are not in it: draw them inside the\n * layer, as for a flat view. `width` is the layer's width on screen in CSS\n * pixels (default: the stage width).\n */\nexport function cameraTransform(view: View, stage: Stage, width = stage.width): string {\n if (!isAngled(view)) return 'none';\n const a = cameraAngle(view);\n const distance = (a.perspective ?? DEFAULT_PERSPECTIVE) * width;\n return `perspective(${round(distance)}px) rotateX(${round(a.tilt ?? 0)}deg) rotateY(${round(a.turn ?? 0)}deg) rotateZ(${round(a.roll ?? 0)}deg)`;\n}\n\n/**\n * The view's motion blur as standard deviations along the board layer's own\n * x and y axes, in CSS pixels at a layer `width` wide: for an SVG\n * `<feGaussianBlur stdDeviation=\"x y\">`, so a pan streaks along its\n * direction (CSS `blur()` can only blur every way). The layer is turned\n * after it is blurred, so the direction is taken back through its roll.\n * [0, 0] when still.\n */\nexport function cameraBlur(view: View, stage: Stage, width = stage.width): [number, number] {\n const blur = (view.blur ?? 0) * (width / stage.width);\n if (!(blur >= 0.05)) return [0, 0];\n if (view.blurAngle === undefined) return [round(blur), round(blur)];\n const a = ((view.blurAngle - (cameraAngle(view).roll ?? 0)) * Math.PI) / 180;\n // A little blur across the streak keeps it from looking like a scanline.\n const across = blur * 0.15;\n return [round(Math.max(across, blur * Math.abs(Math.cos(a)))), round(Math.max(across, blur * Math.abs(Math.sin(a))))];\n}\n\n/** The CSS `filter` for the view's motion blur at a layer `width` CSS pixels wide (the same every way; see {@link cameraBlur} for a streak), or \"none\". */\nexport function cameraFilter(view: View, stage: Stage, width = stage.width): string {\n const blur = (view.blur ?? 0) * (width / stage.width);\n return blur >= 0.05 ? `blur(${round(blur)}px)` : 'none';\n}\n\nconst round = (n: number) => Math.round(n * 1000) / 1000;\n\n/**\n * Where a scene point lands on screen, in stage pixels from the top left of\n * the board: drawn by the view's zoom and scroll, then turned and seen in\n * perspective as {@link cameraTransform} does. Null when the point is\n * behind the camera.\n */\nexport function projectPoint(view: View, stage: Stage, [x, y]: readonly [number, number]): [number, number] | null {\n const cx = stage.width / 2;\n const cy = boardHeight(stage) / 2;\n let px = (x + view.scrollX) * view.zoom - cx;\n let py = (y + view.scrollY) * view.zoom - cy;\n if (!isAngled(view)) return [px + cx, py + cy];\n const a = cameraAngle(view);\n const rad = Math.PI / 180;\n // CSS applies rotateX(tilt) rotateY(turn) rotateZ(roll) right to left: roll first.\n const [sr, cr] = [Math.sin((a.roll ?? 0) * rad), Math.cos((a.roll ?? 0) * rad)];\n [px, py] = [px * cr - py * sr, px * sr + py * cr];\n const [st, ct] = [Math.sin((a.turn ?? 0) * rad), Math.cos((a.turn ?? 0) * rad)];\n let pz = -px * st;\n px = px * ct;\n const [sx, cx2] = [Math.sin((a.tilt ?? 0) * rad), Math.cos((a.tilt ?? 0) * rad)];\n [py, pz] = [py * cx2 - pz * sx, py * sx + pz * cx2];\n const distance = (a.perspective ?? DEFAULT_PERSPECTIVE) * stage.width;\n if (pz >= distance) return null;\n const k = distance / (distance - pz);\n return [cx + px * k, cy + py * k];\n}\n\n/** How far the picture moves on screen between two views: as a pan (x, y), and as zooming and turning. */\nfunction screenMotion(from: View, to: View, stage: Stage): { dx: number; dy: number; other: number } {\n const [x0, y0] = centreOf(from, stage);\n const [x1, y1] = centreOf(to, stage);\n const zoom = Math.sqrt(from.zoom * to.zoom);\n const scale = Math.abs(Math.log(to.zoom / from.zoom)) * stage.width * 0.5;\n const turn = ANGLES.reduce((sum, k) => sum + Math.abs((to[k] ?? 0) - (from[k] ?? 0)), 0) * (stage.width / 180);\n return { dx: (x1 - x0) * zoom, dy: (y1 - y0) * zoom, other: scale + turn };\n}\n\n/**\n * Motion blur for one step of a camera move, in stage pixels: how far the\n * picture moves on screen between two views (centre, zoom and angle), times\n * `strength` (`ShotCamera.motionBlur`), capped at 40.\n */\nexport function motionBlur(from: View, to: View, stage: Stage, strength: number): number {\n if (!(strength > 0)) return 0;\n const { dx, dy, other } = screenMotion(from, to, stage);\n return round(Math.min(40, (Math.hypot(dx, dy) + other) * 0.35 * strength));\n}\n\n/**\n * The direction a step of a camera move streaks in, in degrees on screen\n * (0 to 180, 0 horizontal), when most of it is a pan; undefined when it is\n * mostly zooming or turning, which blurs every way.\n */\nexport function motionBlurAngle(from: View, to: View, stage: Stage): number | undefined {\n const { dx, dy, other } = screenMotion(from, to, stage);\n const pan = Math.hypot(dx, dy);\n if (!(pan > 0) || pan < 0.6 * (pan + other)) return undefined;\n const a = (Math.atan2(dy, dx) * 180) / Math.PI;\n return round(((a % 180) + 180) % 180);\n}\n\n/** The blur fields of a step of a camera move with this strength: `{ blur, blurAngle }`, or nothing when still. */\nexport function moveBlur(from: View, to: View, stage: Stage, strength: number): Pick<View, 'blur' | 'blurAngle'> {\n const blur = motionBlur(from, to, stage, strength);\n if (!(blur > 0)) return {};\n const blurAngle = motionBlurAngle(from, to, stage);\n return blurAngle === undefined ? { blur } : { blur, blurAngle };\n}\n\n/** The part of the scene the camera currently shows. */\nexport function visibleRect(view: View, stage: Stage): Rect {\n return {\n x: -view.scrollX,\n y: -view.scrollY,\n width: stage.width / view.zoom,\n height: boardHeight(stage) / view.zoom,\n };\n}\n\n/** Smooth cubic ease in / ease out. */\nexport const easeInOutCubic = (t: number): number =>\n t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;\n","import { boardHeight, projectPoint, type View, type Stage } from './camera.js';\nimport { boundsOf, overlapArea, areaOf, type Rect, type SceneElement } from './geometry.js';\nimport { isElementCommand, type Shot } from './types.js';\n\nexport interface ShotCheck {\n /** Elements this shot drew or changed that the camera frame leaves out (\"outside\") or cuts (\"cut\"). */\n framing: { id: string; how: 'outside' | 'cut' }[];\n /** Elements this shot drew that overlap each other significantly. */\n overlaps: [string, string][];\n /** Smallest text size on screen in CSS px. */\n smallestText?: { id: string; text: string; px: number };\n /** Any text smaller than min readable threshold (e.g. 18px). */\n smallTexts?: { id: string; text: string; px: number }[];\n /** Elements clipped by the screen edges. */\n edges: string[];\n /** Elements covered by the title bar. */\n underTitle: string[];\n}\n\nexport const NO_CHECK: ShotCheck = { framing: [], overlaps: [], edges: [], underTitle: [] };\n\nexport const SMALL_TEXT_PX = 24;\n\nexport function titleBox(title: string, stage: Stage): Rect {\n const width = Math.min(stage.width, title.length * 20 + 64);\n return { x: (stage.width - width) / 2, y: 24, width, height: 60 };\n}\n\nexport function touchedIds(shot: Shot): string[] {\n const ids = new Set<string>();\n for (const cmd of shot.run ?? []) {\n if (isElementCommand(cmd)) {\n ids.add(cmd.element.id);\n } else if ('id' in cmd && typeof (cmd as any).id === 'string') {\n ids.add((cmd as any).id);\n }\n }\n return [...ids];\n}\n\nconst IN_FRAME = 0.95;\n\n/**\n * Where a scene box lands on screen (stage pixels from the board's top\n * left), turned and in perspective as the view draws it: the bounds of its\n * four corners. Null when part of it is behind the camera.\n */\nexport function screenBox(box: Rect, view: View, stage: Stage): Rect | null {\n const corners = [\n [box.x, box.y],\n [box.x + box.width, box.y],\n [box.x, box.y + box.height],\n [box.x + box.width, box.y + box.height],\n ].map(([x, y]) => projectPoint(view, stage, [x!, y!]));\n if (corners.some((c) => !c)) return null;\n const xs = corners.map((c) => c![0]);\n const ys = corners.map((c) => c![1]);\n const x = Math.min(...xs);\n const y = Math.min(...ys);\n return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };\n}\n\n/**\n * Evaluates the elements on stage against the current camera view and shot\n * settings. Framing, edges, text size and the title are judged on screen,\n * where the view (its angle included) puts each element.\n */\nexport function checkShot(\n shot: Shot,\n elements: readonly SceneElement[],\n view: View,\n stage: Stage,\n title?: string,\n): ShotCheck {\n const screen: Rect = { x: 0, y: 0, width: stage.width, height: boardHeight(stage) };\n const touched = new Set(touchedIds(shot));\n const framing: { id: string; how: 'outside' | 'cut' }[] = [];\n const edges: string[] = [];\n const overlaps: [string, string][] = [];\n const activeElements = elements.filter((e) => !e.isDeleted && (e.opacity === undefined || e.opacity > 0));\n\n for (const el of activeElements) {\n const box = screenBox(boundsOf(el), view, stage);\n const overlap = box ? overlapArea(box, screen) : 0;\n const boxArea = box ? areaOf(box) : 0;\n\n if (touched.has(el.id)) {\n if (overlap === 0) {\n framing.push({ id: el.id, how: 'outside' });\n } else if (boxArea > 0 && overlap / boxArea < IN_FRAME) {\n framing.push({ id: el.id, how: 'cut' });\n }\n } else if (overlap > 0 && boxArea > 0 && overlap / boxArea < IN_FRAME) {\n edges.push(el.id);\n }\n }\n\n // Check overlaps among touched elements\n const touchedList = activeElements.filter((e) => touched.has(e.id));\n for (let i = 0; i < touchedList.length; i++) {\n for (let j = i + 1; j < touchedList.length; j++) {\n const a = touchedList[i]!;\n const b = touchedList[j]!;\n const aBox = boundsOf(a);\n const bBox = boundsOf(b);\n const ol = overlapArea(aBox, bBox);\n const minArea = Math.min(areaOf(aBox), areaOf(bBox));\n if (minArea > 0 && ol / minArea > 0.3) {\n overlaps.push([a.id, b.id]);\n }\n }\n }\n\n // Check text readability under current zoom\n const smallTexts: { id: string; text: string; px: number }[] = [];\n let smallestText: { id: string; text: string; px: number } | undefined;\n\n for (const el of activeElements) {\n if (el.text && el.fontSize) {\n // The text's height on screen where it sits: zoom, and perspective when the camera is angled.\n const b = boundsOf(el);\n const top = projectPoint(view, stage, [b.x + b.width / 2, b.y + b.height / 2]);\n const bottom = projectPoint(view, stage, [b.x + b.width / 2, b.y + b.height / 2 + el.fontSize]);\n if (!top || !bottom) continue;\n const px = Math.round(Math.hypot(bottom[0] - top[0], bottom[1] - top[1]));\n const item = { id: el.id, text: el.text, px };\n if (!smallestText || px < smallestText.px) {\n smallestText = item;\n }\n if (px < SMALL_TEXT_PX) {\n smallTexts.push(item);\n }\n }\n }\n\n // Check title obstruction\n const underTitle: string[] = [];\n if (title) {\n const tBox = titleBox(title, stage);\n for (const el of activeElements) {\n const box = screenBox(boundsOf(el), view, stage);\n if (box && overlapArea(box, tBox) > 0) {\n underTitle.push(el.id);\n }\n }\n }\n\n return {\n framing,\n overlaps,\n smallestText,\n smallTexts: smallTexts.length ? smallTexts : undefined,\n edges,\n underTitle,\n };\n}\n","import type { Rect } from './geometry.js';\n\nexport interface EffectBase {\n /** Unique identifier for the effect so it can be stopped or updated. */\n id: string;\n /** End the effect when this many subsequent shots begin. */\n shots?: number;\n /** Duration in milliseconds after which the effect stops automatically. */\n duration?: number;\n /** When to start: 'start' of shot or after commands are 'drawn'. Default: 'drawn'. */\n at?: 'start' | 'drawn';\n}\n\nexport interface GradientEffect extends EffectBase {\n type: 'gradient';\n colors: string[];\n rect?: Rect;\n ids?: string[];\n angle?: number;\n period?: number;\n opacity?: number;\n}\n\nexport interface FlowEffect extends EffectBase {\n type: 'flow';\n along: string;\n color?: string;\n dots?: number;\n period?: number;\n size?: number;\n direction?: 'forward' | 'back' | 'both';\n label?: string;\n}\n\nexport interface GlowEffect extends EffectBase {\n type: 'glow';\n ids: string[];\n color?: string;\n size?: number;\n pulse?: boolean;\n}\n\nexport interface SpotlightEffect extends EffectBase {\n type: 'spotlight';\n ids: string[];\n dim?: number;\n padding?: number;\n}\n\nexport interface ShimmerEffect extends EffectBase {\n type: 'shimmer';\n ids: string[];\n color?: string;\n period?: number;\n}\n\nexport interface RippleEffect extends EffectBase {\n type: 'ripple';\n ids: string[];\n color?: string;\n period?: number;\n size?: number;\n times?: number;\n}\n\nexport interface TimerEffect extends EffectBase {\n type: 'timer';\n ids: string[];\n time?: number;\n fill?: boolean;\n repeat?: boolean;\n color?: string;\n size?: number;\n}\n\nexport interface CounterEffect extends EffectBase {\n type: 'counter';\n to: number;\n from?: number;\n time?: number;\n text?: string;\n decimals?: number;\n near?: string;\n x?: number;\n y?: number;\n color?: string;\n size?: number;\n}\n\nexport interface HighlightEffect extends EffectBase {\n type: 'highlight';\n ids: string[];\n style?: 'marker' | 'underline';\n color?: string;\n}\n\nexport interface BlurEffect extends EffectBase {\n type: 'blur';\n ids: string[];\n amount?: number;\n padding?: number;\n}\n\nexport interface BurstEffect extends EffectBase {\n type: 'burst';\n ids: string[];\n color?: string;\n sparks?: number;\n size?: number;\n}\n\n/** Media layer effect driving a WebGL shader (or other media stream) under or over the scene. */\nexport interface MediaEffect extends EffectBase {\n type: 'media';\n /** ID in Scene.media. */\n media: string;\n rect?: Rect;\n ids?: string[];\n padding?: number;\n layer?: 'under' | 'over';\n opacity?: number;\n}\n\nexport type Effect =\n | GradientEffect\n | FlowEffect\n | GlowEffect\n | SpotlightEffect\n | ShimmerEffect\n | RippleEffect\n | TimerEffect\n | CounterEffect\n | HighlightEffect\n | BlurEffect\n | BurstEffect\n | MediaEffect;\n\nexport const EFFECT_TYPES = [\n 'gradient',\n 'flow',\n 'glow',\n 'spotlight',\n 'shimmer',\n 'ripple',\n 'timer',\n 'counter',\n 'highlight',\n 'blur',\n 'burst',\n 'media',\n] as const;\n\nexport const EFFECT_FADE_MS = 300;\n\nexport interface RunningEffect {\n effect: Effect;\n /** Timestamp when started (ms). */\n from: number;\n /** Timestamp when stopping/fading begins (ms). */\n until?: number;\n}\n\n/** Returns true if the effect has completely expired and finished fading out. */\nexport function effectGone(running: RunningEffect, now: number): boolean {\n if (running.until !== undefined) {\n return now >= running.until + EFFECT_FADE_MS;\n }\n if (running.effect.duration !== undefined) {\n return now >= running.from + running.effect.duration + EFFECT_FADE_MS;\n }\n return false;\n}\n\n/** Returns current opacity multiplier (0 to 1) accounting for fade in / fade out. */\nexport function effectOpacity(running: RunningEffect, now: number): number {\n if (now < running.from) return 0;\n // Fade in over first 150ms\n const inFade = Math.min(1, (now - running.from) / 150);\n\n if (running.until !== undefined) {\n const out = Math.max(0, 1 - (now - running.until) / EFFECT_FADE_MS);\n return inFade * out;\n }\n if (running.effect.duration !== undefined) {\n const remaining = running.from + running.effect.duration - now;\n if (remaining < 0) {\n const out = Math.max(0, 1 + remaining / EFFECT_FADE_MS);\n return inFade * out;\n }\n }\n return inFade;\n}\n","import type { EasingCurve } from '../time/retiming.js';\n\nexport interface BehaviorBase {\n id?: string;\n /** Target element ID or media ID. */\n target: string;\n /** Property on element (e.g. 'x', 'y', 'scale', 'rotation', 'opacity') or uniform on media. */\n property: string;\n}\n\nexport interface OscillateBehavior extends BehaviorBase {\n type: 'oscillate';\n wave?: 'sine' | 'square' | 'triangle' | 'sawtooth';\n amplitude: number;\n /** Frequency in Hz (cycles per second). */\n frequency: number;\n /** Phase offset in radians (default 0). */\n phase?: number;\n /** Base center value. If undefined, treated as relative offset added to current base value. */\n center?: number;\n}\n\nexport interface RampBehavior extends BehaviorBase {\n type: 'ramp';\n from?: number;\n to: number;\n ease?: EasingCurve;\n}\n\nexport interface RateBehavior extends BehaviorBase {\n type: 'rate';\n /** Rate of change per second (e.g. degrees/sec or units/sec). */\n rate: number;\n}\n\nexport interface WriggleBehavior extends BehaviorBase {\n type: 'wriggle';\n /** Maximum amplitude of deviation. */\n amount: number;\n /** Speed / frequency of variation in Hz. */\n frequency: number;\n /** Number of noise octaves for organic complexity (default: 3). */\n octaves?: number;\n /** Deterministic random seed. */\n seed?: number;\n}\n\nexport interface OvershootBehavior extends BehaviorBase {\n type: 'overshoot';\n targetValue: number;\n startValue?: number;\n /** Oscillation frequency / cycles (default: 3). */\n cycles?: number;\n /** Damping factor (higher settles faster, default: 4.0). */\n damping?: number;\n}\n\nexport interface ThrowBehavior extends BehaviorBase {\n type: 'throw';\n /** Initial speed in units per second. */\n initialVelocity: number;\n /** Friction / deceleration factor (default: 2.0). */\n friction?: number;\n}\n\nexport type Behavior =\n | OscillateBehavior\n | RampBehavior\n | RateBehavior\n | WriggleBehavior\n | OvershootBehavior\n | ThrowBehavior;\n\nimport { fractalWiggle } from '../time/noise.js';\n\n/**\n * Computes smooth organic 1D multi-octave C2 noise wander matching AE wiggle.\n */\nfunction evaluateWriggle(\n t: number,\n freq: number,\n amount: number,\n octaves = 3,\n seed = 42,\n): number {\n return fractalWiggle(freq, amount, octaves, 0.5, t, seed, 0);\n}\n\n/**\n * Evaluates the new numeric value for a behavior at a given elapsed time.\n * @param behavior The behavior specification.\n * @param elapsedSec Elapsed time within the shot/behavior duration (in seconds).\n * @param durationSec Total duration of the shot/behavior (in seconds).\n * @param baseValue Initial or fallback value before behavior was applied.\n */\nexport function evaluateBehavior(\n behavior: Behavior,\n elapsedSec: number,\n durationSec: number,\n baseValue = 0,\n): number {\n const t = Math.max(0, elapsedSec);\n\n switch (behavior.type) {\n case 'oscillate': {\n const freq = behavior.frequency;\n const phase = behavior.phase ?? 0;\n const angle = 2 * Math.PI * freq * t + phase;\n let waveVal = 0;\n\n switch (behavior.wave) {\n case 'square':\n waveVal = Math.sin(angle) >= 0 ? 1 : -1;\n break;\n case 'triangle':\n waveVal = (2 / Math.PI) * Math.asin(Math.sin(angle));\n break;\n case 'sawtooth': {\n const norm = (angle / (2 * Math.PI)) % 1;\n waveVal = 2 * (norm < 0 ? norm + 1 : norm) - 1;\n break;\n }\n case 'sine':\n default:\n waveVal = Math.sin(angle);\n break;\n }\n\n const offset = waveVal * behavior.amplitude;\n return behavior.center !== undefined ? behavior.center + offset : baseValue + offset;\n }\n\n case 'ramp': {\n const from = behavior.from ?? baseValue;\n const to = behavior.to;\n const p = durationSec > 0 ? Math.min(1, Math.max(0, t / durationSec)) : 1;\n let eased = p;\n\n switch (behavior.ease) {\n case 'easeIn':\n eased = p * p;\n break;\n case 'easeOut':\n eased = p * (2 - p);\n break;\n case 'easeInOut':\n eased = p < 0.5 ? 2 * p * p : -1 + (4 - 2 * p) * p;\n break;\n case 'linear':\n default:\n eased = p;\n break;\n }\n\n return from + (to - from) * eased;\n }\n\n case 'rate': {\n return baseValue + behavior.rate * t;\n }\n\n case 'wriggle': {\n const wander = evaluateWriggle(\n t,\n behavior.frequency,\n behavior.amount,\n behavior.octaves ?? 3,\n behavior.seed ?? 42,\n );\n return baseValue + wander;\n }\n\n case 'overshoot': {\n const from = behavior.startValue ?? baseValue;\n const target = behavior.targetValue;\n const cycles = behavior.cycles ?? 3;\n const damping = behavior.damping ?? 4.0;\n\n if (durationSec <= 0) return target;\n const p = Math.min(1, t / durationSec);\n if (p >= 1) return target;\n\n // Damped harmonic oscillation: 1 - e^(-damping * p) * cos(cycles * pi * p)\n const envelope = Math.exp(-damping * p);\n const oscillation = Math.cos(cycles * Math.PI * p);\n const progress = 1 - envelope * oscillation;\n\n return from + (target - from) * progress;\n }\n\n case 'throw': {\n const v0 = behavior.initialVelocity;\n const k = Math.max(0.001, behavior.friction ?? 2.0);\n // Continuous displacement under linear drag: x(t) = (v0 / k) * (1 - e^(-k * t))\n const displacement = (v0 / k) * (1 - Math.exp(-k * t));\n return baseValue + displacement;\n }\n }\n}\n","import type { ShotCheck } from './check.js';\nimport type { Scene } from './types.js';\n\nexport interface ShotLog extends ShotCheck {\n index: number;\n start: number;\n drawn: number;\n end: number;\n caption?: string;\n title?: string;\n failed: string[];\n effects?: string[];\n}\n\nexport interface ReviewOptions {\n target?: [number, number];\n minTextPx?: number;\n}\n\nexport interface Review {\n totalMs: number;\n warnings: string[];\n notes: string[];\n markdown: string;\n}\n\n/** Formats a structured diagnostic report of scene playback. */\nexport function reviewScene(scene: Scene, logs: readonly ShotLog[], options: ReviewOptions = {}): Review {\n const warnings: string[] = [];\n const notes: string[] = [];\n const minTextPx = options.minTextPx ?? 18;\n const totalMs = logs.length > 0 ? logs[logs.length - 1]!.end : 0;\n const totalSec = (totalMs / 1000).toFixed(1);\n\n if (scene.target) {\n const [minS, maxS] = scene.target;\n const durS = totalMs / 1000;\n if (durS < minS) {\n warnings.push(`Scene is ${totalSec}s, shorter than target minimum (${minS}s).`);\n } else if (durS > maxS) {\n warnings.push(`Scene is ${totalSec}s, longer than target maximum (${maxS}s).`);\n }\n }\n\n for (const log of logs) {\n const shotPrefix = `Shot ${log.index}:`;\n\n if (log.failed.length > 0) {\n warnings.push(`${shotPrefix} ${log.failed.length} command(s) failed: ${log.failed.join(', ')}`);\n }\n\n for (const f of log.framing) {\n warnings.push(`${shotPrefix} element \"${f.id}\" is ${f.how} of the camera frame.`);\n }\n\n for (const [a, b] of log.overlaps) {\n notes.push(`${shotPrefix} elements \"${a}\" and \"${b}\" overlap significantly.`);\n }\n\n if (log.smallTexts) {\n for (const t of log.smallTexts) {\n if (t.px < minTextPx) {\n warnings.push(`${shotPrefix} text \"${t.text}\" is only ${t.px}px on screen (less than ${minTextPx}px minimum).`);\n }\n }\n }\n\n for (const id of log.underTitle) {\n warnings.push(`${shotPrefix} element \"${id}\" is partially covered by the title bar.`);\n }\n }\n\n // Generate markdown\n const lines: string[] = [\n `# Scene Preview Review: ${scene.title || scene.name}`,\n `**Total Duration:** ${totalSec}s (${logs.length} shots)`,\n '',\n ];\n\n if (warnings.length > 0) {\n lines.push('### ⚠️ Warnings to Address:');\n for (const w of warnings) lines.push(`- ${w}`);\n lines.push('');\n } else {\n lines.push('✅ No critical layout or framing warnings.');\n lines.push('');\n }\n\n if (notes.length > 0) {\n lines.push('### ℹ️ Layout Notes:');\n for (const n of notes) lines.push(`- ${n}`);\n lines.push('');\n }\n\n lines.push('### Shot Breakdown');\n lines.push('| Shot | Start | End | Duration | Title / Caption |');\n lines.push('|:---:|:---:|:---:|:---:|:---|');\n for (const log of logs) {\n const dur = ((log.end - log.start) / 1000).toFixed(1);\n const text = log.title || log.caption || '—';\n lines.push(`| ${log.index} | ${(log.start / 1000).toFixed(1)}s | ${(log.end / 1000).toFixed(1)}s | ${dur}s | ${text.slice(0, 40)} |`);\n }\n\n return {\n totalMs,\n warnings,\n notes,\n markdown: lines.join('\\n'),\n };\n}\n","import type { Clock } from '../time/clock.js';\nimport { ManualClock } from '../time/clock.js';\nimport {\n between,\n boardHeight,\n cameraAngle,\n centreOf,\n CAMERA_MS,\n DEFAULT_STAGE,\n easeInOutCubic,\n frame,\n calculateFramingRect,\n isAngled,\n moveBlur,\n projectPoint,\n viewAt,\n type Stage,\n type View,\n} from './camera.js';\nimport { checkShot } from './check.js';\nimport { effectGone, type RunningEffect } from './effects.js';\nimport { evaluateBehavior } from './behavior.js';\nimport type { Rect, SceneElement } from './geometry.js';\nimport { reviewScene, type Review, type ShotLog } from './report.js';\nimport {\n isDeleteCommand,\n isElementCommand,\n isUpdateCommand,\n type Scene,\n type SceneCommand,\n type Shot,\n} from './types.js';\n\nexport interface SceneBoard {\n getView(): View;\n setView(view: View): void;\n elements(): readonly SceneElement[];\n runCommand(cmd: SceneCommand): void | Promise<void>;\n setTitle?(text: string, opacity: number): void;\n setCaption?(text: string): void;\n setEffects?(effects: readonly RunningEffect[]): void;\n setMediaTime?(mediaId: string, timeSeconds: number): void;\n}\n\n/** Pure in-memory board implementation suitable for headless simulation, testing, and SSR. */\nexport class MemorySceneBoard implements SceneBoard {\n private view: View;\n private elMap = new Map<string, SceneElement>();\n private currentTitle = '';\n private currentCaption = '';\n private runningEffects: readonly RunningEffect[] = [];\n\n constructor(stage: Stage = DEFAULT_STAGE) {\n this.view = viewAt(stage.width / 2, stage.height / 2, 1, stage);\n }\n\n getView(): View {\n return this.view;\n }\n\n setView(view: View): void {\n this.view = view;\n }\n\n elements(): readonly SceneElement[] {\n return Array.from(this.elMap.values());\n }\n\n runCommand(cmd: SceneCommand): void {\n if (isElementCommand(cmd)) {\n this.elMap.set(cmd.element.id, { ...cmd.element });\n } else if (isUpdateCommand(cmd)) {\n const existing = this.elMap.get(cmd.id);\n if (existing) {\n this.elMap.set(cmd.id, { ...existing, ...cmd.props });\n }\n } else if (isDeleteCommand(cmd)) {\n this.elMap.delete(cmd.id);\n } else if (cmd.type === 'clear') {\n this.elMap.clear();\n }\n }\n\n setTitle(text: string): void {\n this.currentTitle = text;\n }\n\n setCaption(text: string): void {\n this.currentCaption = text;\n }\n\n setEffects(effects: readonly RunningEffect[]): void {\n this.runningEffects = effects;\n }\n}\n\n/**\n * The view a shot's camera moves to: its framing (a box, or the elements it\n * frames, from the board or the shot's own commands) at its angle, zoomed\n * out when angled so the framed box still fits. Framing\n * or angle parts the camera leaves out keep `current`'s. Undefined when the\n * shot has no camera.\n */\nexport function shotView(\n shot: Shot,\n elements: readonly SceneElement[],\n stage: Stage,\n current: View,\n): View | undefined {\n const camera = shot.camera;\n if (!camera) return undefined;\n let view: View = { zoom: current.zoom, scrollX: current.scrollX, scrollY: current.scrollY };\n let framed: Rect | undefined;\n if ('frame' in camera) {\n const frameIds = new Set(camera.frame);\n const targetElements = elements.filter((el) => frameIds.has(el.id));\n if (targetElements.length === 0) {\n for (const cmd of shot.run ?? []) {\n if (isElementCommand(cmd) && frameIds.has(cmd.element.id)) targetElements.push(cmd.element);\n }\n }\n framed = calculateFramingRect(targetElements, stage, {\n padding: camera.padding,\n minWidth: camera.minWidth,\n minHeight: camera.minHeight,\n });\n } else if ('width' in camera) {\n framed = camera as Rect;\n }\n if (framed) view = frame(framed, stage);\n const angle = cameraAngle({\n tilt: camera.tilt ?? current.tilt,\n turn: camera.turn ?? current.turn,\n roll: camera.roll ?? current.roll,\n perspective: camera.perspective ?? current.perspective,\n });\n const angled: View = { ...view, ...angle };\n return framed && isAngled(angled) ? fitAngled(angled, framed, stage) : angled;\n}\n\n/**\n * An angled view zoomed out (round the same centre) until the framed box,\n * turned and in perspective, fits the board again: perspective makes the\n * near side bigger, so a flat framing would cut it.\n */\nfunction fitAngled(view: View, box: Rect, stage: Stage): View {\n const [cx, cy] = centreOf(view, stage);\n const halfW = stage.width / 2;\n const halfH = boardHeight(stage) / 2;\n let fitted = view;\n for (let i = 0; i < 4; i++) {\n let spread = 0;\n for (const corner of [[box.x, box.y], [box.x + box.width, box.y], [box.x, box.y + box.height], [box.x + box.width, box.y + box.height]] as const) {\n const p = projectPoint(fitted, stage, corner);\n spread = p ? Math.max(spread, Math.abs(p[0] - halfW) / halfW, Math.abs(p[1] - halfH) / halfH) : Infinity;\n if (!p) break;\n }\n if (spread <= 1.001) break;\n const zoom = fitted.zoom / (Number.isFinite(spread) ? spread : 2);\n fitted = { ...fitted, ...viewAt(cx, cy, zoom, stage) };\n }\n return fitted;\n}\n\nexport interface PlayOptions {\n check?: boolean;\n afterShot?: (log: ShotLog) => void | Promise<void>;\n}\n\nexport interface PlayResult {\n ok: boolean;\n duration: number;\n shots: ShotLog[];\n}\n\n/**\n * Plays a scene's shots sequentially on `board` driven by `clock`.\n * When using a `ManualClock`, the execution is completely deterministic and stepped.\n */\nexport async function playScene(\n scene: Scene,\n board: SceneBoard,\n clock: Clock,\n options: PlayOptions = {},\n): Promise<PlayResult> {\n const stage: Stage = {\n width: scene.stage?.width ?? DEFAULT_STAGE.width,\n height: scene.stage?.height ?? DEFAULT_STAGE.height,\n caption: scene.stage?.caption ?? DEFAULT_STAGE.caption,\n };\n\n const shots: ShotLog[] = [];\n const begin = clock.now();\n let runningEffects: RunningEffect[] = [];\n const endsAt = new Map<RunningEffect, number>();\n\n const updateEffects = (next: RunningEffect[]) => {\n runningEffects = next.filter((r) => !effectGone(r, clock.now()));\n board.setEffects?.(runningEffects);\n };\n\n for (const [idx, shot] of scene.shots.entries()) {\n const shotStart = clock.now() - begin;\n const failedCmds: string[] = [];\n\n // Stop requested effects\n const stopIds = new Set(shot.stopEffects ?? []);\n if (stopIds.size > 0 || runningEffects.some((r) => endsAt.get(r) === idx)) {\n const now = clock.now();\n updateEffects(\n runningEffects.map((r) =>\n r.until === undefined && (stopIds.has(r.effect.id) || endsAt.get(r) === idx)\n ? { ...r, until: now }\n : r,\n ),\n );\n }\n\n // Camera move\n const targetView = shotView(shot, board.elements(), stage, board.getView());\n if (targetView) {\n if (idx === 0) {\n board.setView(targetView);\n } else {\n const fromView = board.getView();\n const strength = shot.camera?.motionBlur ?? 0;\n const steps = 15;\n const stepMs = CAMERA_MS / steps;\n let last = fromView;\n for (let s = 1; s <= steps; s++) {\n const t = easeInOutCubic(s / steps);\n const view = between(fromView, targetView, t, stage);\n board.setView(s < steps ? { ...view, ...moveBlur(last, view, stage, strength) } : view);\n last = view;\n await clock.sleep(stepMs);\n }\n }\n }\n\n // Set title and caption\n if (shot.title !== undefined) {\n board.setTitle?.(shot.title, 1);\n }\n if (shot.caption !== undefined) {\n board.setCaption?.(shot.caption);\n }\n\n // Start effects scheduled at 'start'\n const startEffects = (shot.effects ?? []).filter((e) => e.at === 'start');\n if (startEffects.length > 0) {\n const now = clock.now();\n const added = startEffects.map((effect): RunningEffect => ({ effect, from: now }));\n for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);\n updateEffects([...runningEffects, ...added]);\n }\n\n // Run commands\n for (const cmd of shot.run ?? []) {\n try {\n await board.runCommand(cmd);\n } catch (err: any) {\n failedCmds.push(err.message || String(err));\n }\n }\n\n // Start effects scheduled after 'drawn' (default)\n const drawnEffects = (shot.effects ?? []).filter((e) => e.at !== 'start');\n if (drawnEffects.length > 0) {\n const now = clock.now();\n const added = drawnEffects.map((effect): RunningEffect => ({ effect, from: now }));\n for (const r of added) if (r.effect.shots) endsAt.set(r, idx + r.effect.shots);\n updateEffects([...runningEffects, ...added]);\n }\n\n const drawnTime = clock.now() - begin;\n\n // Check shot layout & framing\n const check = options.check\n ? checkShot(shot, board.elements(), board.getView(), stage, shot.title)\n : { framing: [], overlaps: [], edges: [], underTitle: [] };\n\n // Hold dwell time and active behaviors\n const holdMs = shot.hold ?? 800;\n const behaviors = shot.behaviors ?? [];\n if (behaviors.length > 0) {\n const initialProps = new Map<string, number>();\n for (const b of behaviors) {\n const key = `${b.target}:${b.property}`;\n const el = board.elements().find((e) => e.id === b.target);\n if (el && typeof (el as any)[b.property] === 'number') {\n initialProps.set(key, (el as any)[b.property]);\n } else {\n initialProps.set(key, 0);\n }\n }\n\n const stepMs = 50;\n const steps = Math.max(1, Math.round(holdMs / stepMs));\n const actualStepMs = holdMs / steps;\n const durationSec = holdMs / 1000;\n\n for (let s = 1; s <= steps; s++) {\n await clock.sleep(actualStepMs);\n const elapsedSec = (s * actualStepMs) / 1000;\n for (const b of behaviors) {\n const baseVal = initialProps.get(`${b.target}:${b.property}`) ?? 0;\n const nextVal = evaluateBehavior(b, elapsedSec, durationSec, baseVal);\n board.runCommand({\n type: 'update',\n id: b.target,\n props: { [b.property]: nextVal },\n });\n }\n }\n } else {\n await clock.sleep(holdMs);\n }\n\n const shotEnd = clock.now() - begin;\n\n const log: ShotLog = {\n ...check,\n index: idx + 1,\n start: shotStart,\n drawn: drawnTime,\n end: shotEnd,\n caption: shot.caption,\n title: shot.title,\n failed: failedCmds,\n effects: runningEffects.map((r) => r.effect.id),\n };\n\n shots.push(log);\n if (options.afterShot) {\n await options.afterShot(log);\n }\n }\n\n const duration = clock.now() - begin;\n const ok = shots.every((s) => s.failed.length === 0 && s.framing.every((f) => f.how !== 'outside'));\n\n return { ok, duration, shots };\n}\n\n/**\n * Simulates a scene instantaneously using a ManualClock and MemorySceneBoard,\n * returning full timing details, layout checks, and diagnostic report.\n */\nexport async function simulateScene(\n scene: Scene,\n options: { minTextPx?: number } = {},\n): Promise<{ result: PlayResult; review: Review }> {\n const clock = new ManualClock();\n const board = new MemorySceneBoard();\n\n let done = false;\n let playError: any = null;\n const playPromise = playScene(scene, board, clock, { check: true })\n .then((res) => {\n done = true;\n return res;\n })\n .catch((err) => {\n done = true;\n playError = err;\n throw err;\n });\n\n // Advance manual clock until simulation finishes\n for (let i = 0; i < 5000 && !done; i++) {\n await clock.advance(50);\n }\n\n if (playError) throw playError;\n const result = await playPromise;\n const review = reviewScene(scene, result.shots, {\n target: scene.target,\n minTextPx: options.minTextPx,\n });\n\n return { result, review };\n}\n","import { between, CAMERA_MS, DEFAULT_STAGE, easeInOutCubic, moveBlur, viewAt, type Stage, type View } from './camera.js';\nimport type { SceneElement } from './geometry.js';\nimport { MemorySceneBoard, shotView } from './player.js';\nimport type { Scene, Shot } from './types.js';\n\n/** The camera through one shot, timed as `playScene` plays it: a move from `from` to `to`, then a hold. */\nexport interface CameraShot {\n shot: Shot;\n index: number;\n /** When the shot starts on the scene's timeline, in ms. */\n startMs: number;\n /** How long the camera moves at the start of the shot (0 when it cuts or stays). */\n moveMs: number;\n /** How long the shot holds after the move. */\n holdMs: number;\n from: View;\n to: View;\n /** The shot's `camera.motionBlur` (0 for none). */\n motionBlur: number;\n /** The elements on stage once the shot's commands have run. */\n elements: SceneElement[];\n}\n\n/** The scene's stage with defaults filled in. */\nexport function sceneStage(scene: Scene): Stage {\n return {\n width: scene.stage?.width ?? DEFAULT_STAGE.width,\n height: scene.stage?.height ?? DEFAULT_STAGE.height,\n caption: scene.stage?.caption ?? DEFAULT_STAGE.caption,\n };\n}\n\n/**\n * The camera through every shot of a scene, worked out the way `playScene`\n * moves it, for exporters and players that lay a scene out on a timeline:\n * each shot's move (framing, angle and blur strength) and hold.\n */\nexport function cameraTrack(scene: Scene): CameraShot[] {\n const stage = sceneStage(scene);\n const board = new MemorySceneBoard(stage);\n let view: View = viewAt(stage.width / 2, (stage.height - (stage.caption ?? 0)) / 2, 1, stage);\n let startMs = 0;\n return scene.shots.map((shot, index) => {\n const target = shotView(shot, board.elements(), stage, view);\n const moveMs = index > 0 && shot.camera ? CAMERA_MS : 0;\n const from = index === 0 && target ? target : view;\n const to = target ?? view;\n const holdMs = shot.hold ?? 800;\n for (const cmd of shot.run ?? []) board.runCommand(cmd);\n const entry: CameraShot = {\n shot,\n index,\n startMs,\n moveMs,\n holdMs,\n from,\n to,\n motionBlur: shot.camera?.motionBlur ?? 0,\n elements: board.elements().map((e) => ({ ...e })),\n };\n view = to;\n startMs += moveMs + holdMs;\n return entry;\n });\n}\n\n/** The time the player waits between steps of a camera move, in ms (its blur is measured over one). */\nexport const CAMERA_STEP_MS = CAMERA_MS / 15;\n\n/**\n * The camera `ms` into a shot: part-way through its move (eased, with its\n * motion blur) or settled.\n */\nexport function cameraAt(entry: CameraShot, ms: number, stage: Stage): View {\n if (!(entry.moveMs > 0) || ms >= entry.moveMs) return entry.to;\n const u = Math.max(0, ms) / entry.moveMs;\n const view = between(entry.from, entry.to, easeInOutCubic(u), stage);\n if (!(entry.motionBlur > 0)) return view;\n const before = between(entry.from, entry.to, easeInOutCubic(Math.max(0, u - CAMERA_STEP_MS / entry.moveMs)), stage);\n return { ...view, ...moveBlur(before, view, stage, entry.motionBlur) };\n}\n\n/** The camera at one frame of a scene's timeline. */\nexport interface CameraSample {\n frame: number;\n view: View;\n /** The shot (0-based) the frame is in. */\n shot: number;\n}\n\n/**\n * The camera sampled for a timeline at `frameRate`: every `every` frames\n * during each move (so an editor's straight-line interpolation follows the\n * eased path and perspective), and once where each move settles. Views\n * include the move's motion blur.\n */\nexport function cameraSamples(scene: Scene, frameRate: number, every = 2): CameraSample[] {\n const stage = sceneStage(scene);\n const samples: CameraSample[] = [];\n // A later sample on the same frame (a shot with no hold) replaces the earlier one.\n const add = (sample: CameraSample) => {\n if (samples.at(-1)?.frame === sample.frame) samples.pop();\n samples.push(sample);\n };\n for (const entry of cameraTrack(scene)) {\n const start = Math.round((entry.startMs / 1000) * frameRate);\n const moveFrames = Math.round((entry.moveMs / 1000) * frameRate);\n for (let f = 0; f < moveFrames; f += every) {\n add({ frame: start + f, view: cameraAt(entry, (f / frameRate) * 1000, stage), shot: entry.index });\n }\n add({ frame: start + moveFrames, view: entry.to, shot: entry.index });\n }\n return samples;\n}\n"],"mappings":"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","names":["x","y"]}