@vosjs/elements 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @vosjs/elements
2
+
3
+ > The Vos element system — text / image / SVG / video renderers for Three.js overlays, shipped as both a typed ESM factory and an injectable IIFE bundle.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@vosjs/elements.svg)](https://www.npmjs.com/package/@vosjs/elements)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/vosjs/vos/blob/main/LICENSE)
7
+
8
+ Part of [Vos](https://github.com/vosjs/vos). Renders 2D overlay elements (text, images, SVG, video) as Three.js meshes positioned over a scene. `three` is an optional peer dependency and is provided at runtime (never bundled).
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pnpm add @vosjs/elements three
14
+ ```
15
+
16
+ ## Two entry points
17
+
18
+ ### `@vosjs/elements` — typed ESM factory
19
+
20
+ For app/build-time use (e.g. client-side export pipelines):
21
+
22
+ ```ts
23
+ import * as THREE from 'three'
24
+ import { createVosElements } from '@vosjs/elements'
25
+
26
+ const elements = createVosElements(THREE)
27
+ const map = await elements.renderElements(elementsConfig, overlayScenes, resolution)
28
+ // ...
29
+ elements.disposeElements(map)
30
+ ```
31
+
32
+ ### `@vosjs/elements/bundle` — injectable IIFE string
33
+
34
+ For injecting into a sandboxed render context (the iframe/Worker that runs compiled Vos templates). The string defines a global `__vosElementsFactory`:
35
+
36
+ ```ts
37
+ import { generateRenderTemplate } from '@vosjs/core/runtime'
38
+ import { elementsBundleCode } from '@vosjs/elements/bundle'
39
+
40
+ const html = generateRenderTemplate(compiledCode, {
41
+ mode: 'playback',
42
+ elementsBundleCode,
43
+ })
44
+ ```
45
+
46
+ ## License
47
+
48
+ [MIT](https://github.com/vosjs/vos/blob/main/LICENSE) © Hongbin Li
package/dist/bundle.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED by bundle.mjs — injectable IIFE defining globalThis.__vosElementsFactory
2
- export const elementsBundleCode = "\"use strict\";\nvar __vosElementsFactory = (() => {\n var __defProp = Object.defineProperty;\n var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __hasOwnProp = Object.prototype.hasOwnProperty;\n var __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n };\n var __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n };\n var __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n // src/index.ts\n var index_exports = {};\n __export(index_exports, {\n createVosElements: () => createVosElements,\n renderElements: () => renderElements\n });\n\n // src/assetCache.ts\n var AssetCache = {\n images: /* @__PURE__ */ new Map(),\n videos: /* @__PURE__ */ new Map(),\n svgContents: /* @__PURE__ */ new Map(),\n getImage(url) {\n return this.images.get(url) || null;\n },\n getVideo(url) {\n return this.videos.get(url) || null;\n },\n getSVG(url) {\n return this.svgContents.get(url) || null;\n },\n dispose() {\n this.videos.forEach((cached) => {\n cached.element.pause();\n cached.element.src = \"\";\n cached.element.load();\n if (cached.blobUrl) {\n URL.revokeObjectURL(cached.blobUrl);\n }\n });\n this.images.clear();\n this.videos.clear();\n this.svgContents.clear();\n }\n };\n function extractAssetUrls(elementsConfig) {\n const assets = {\n images: [],\n videos: [],\n svgs: []\n };\n for (const el of elementsConfig) {\n if (el.type === \"image\" && el.src) {\n assets.images.push(el.src);\n } else if (el.type === \"video\" && el.src) {\n assets.videos.push(el.src);\n } else if (el.type === \"svg\" && el.src) {\n assets.svgs.push(el.src);\n }\n }\n assets.images = [...new Set(assets.images)];\n assets.videos = [...new Set(assets.videos)];\n assets.svgs = [...new Set(assets.svgs)];\n return assets;\n }\n async function preloadImage(url) {\n if (AssetCache.images.has(url)) return;\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise((resolve) => {\n img.onload = () => resolve();\n img.onerror = () => {\n console.warn(\"Failed to preload image:\", url);\n resolve();\n };\n img.src = url;\n });\n AssetCache.images.set(url, {\n element: img,\n width: img.naturalWidth,\n height: img.naturalHeight\n });\n }\n async function preloadVideo(url) {\n if (AssetCache.videos.has(url)) return;\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\"Failed to fetch video: \" + response.status);\n }\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.crossOrigin = \"anonymous\";\n video.muted = true;\n video.playsInline = true;\n video.preload = \"auto\";\n await new Promise((resolve, reject) => {\n video.oncanplaythrough = () => resolve();\n video.onerror = reject;\n video.src = blobUrl;\n video.load();\n });\n video.currentTime = 0;\n await new Promise((resolve) => {\n video.onseeked = () => resolve();\n setTimeout(() => resolve(), 100);\n });\n video.pause();\n console.log(\"Video preloaded:\", url, \"duration:\", video.duration);\n AssetCache.videos.set(url, {\n element: video,\n blobUrl,\n duration: video.duration,\n width: video.videoWidth,\n height: video.videoHeight\n });\n } catch (e) {\n console.warn(\"Failed to preload video:\", url, e);\n }\n }\n async function preloadSVG(url) {\n if (AssetCache.svgContents.has(url)) return;\n try {\n if (url.startsWith(\"<svg\") || url.startsWith(\"<?xml\")) {\n AssetCache.svgContents.set(url, url);\n return;\n }\n if (url.startsWith(\"data:image/svg\")) {\n const base64 = url.split(\",\")[1];\n const svgContent2 = atob(base64);\n AssetCache.svgContents.set(url, svgContent2);\n return;\n }\n const response = await fetch(url);\n const svgContent = await response.text();\n AssetCache.svgContents.set(url, svgContent);\n } catch (e) {\n console.warn(\"Failed to preload SVG:\", url, e);\n AssetCache.svgContents.set(url, \"\");\n }\n }\n async function preloadAssets(elementsConfig) {\n const assets = extractAssetUrls(elementsConfig);\n const total = assets.images.length + assets.videos.length + assets.svgs.length;\n if (total === 0) return;\n let loaded = 0;\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: \"PRELOAD_PROGRESS\", loaded: 0, total },\n \"*\"\n );\n }\n const preloadWithProgress = async (fn, url) => {\n await fn(url);\n loaded++;\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: \"PRELOAD_PROGRESS\", loaded, total },\n \"*\"\n );\n }\n };\n const promises = [\n ...assets.images.map((url) => preloadWithProgress(preloadImage, url)),\n ...assets.videos.map((url) => preloadWithProgress(preloadVideo, url)),\n ...assets.svgs.map((url) => preloadWithProgress(preloadSVG, url))\n ];\n await Promise.all(promises);\n }\n\n // src/createElementProps.ts\n function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1, videoElement = null) {\n const baseScaleX = mesh.scale.x;\n const baseScaleY = mesh.scale.y;\n const state = {\n x: initialX,\n y: initialY,\n z: 0,\n opacity: initialOpacity,\n scale: 1,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n rotationX: 0,\n rotationY: 0,\n zIndex: mesh.userData.zIndex ?? 0,\n // Video-specific properties (only meaningful if videoElement is provided)\n currentTime: videoElement ? videoElement.currentTime : 0,\n playing: false,\n startOffset: 0\n };\n const updateMeshPosition = () => {\n mesh.position.x = state.x;\n mesh.position.y = -state.y;\n mesh.position.z = state.z;\n };\n const updateMeshTransform = () => {\n mesh.scale.set(\n baseScaleX * state.scale * state.scaleX,\n baseScaleY * state.scale * state.scaleY,\n 1\n );\n mesh.rotation.set(\n state.rotationX * Math.PI / 180,\n state.rotationY * Math.PI / 180,\n state.rotation * Math.PI / 180\n );\n };\n const updateMeshOpacity = () => {\n const mat = mesh.material;\n mat.opacity = state.opacity;\n mat.needsUpdate = true;\n };\n const updateVideoPlayback = () => {\n if (!videoElement) return;\n const vos = window.__vos__;\n const shouldPlay = state.playing && !vos?.isPaused;\n if (shouldPlay) {\n if (videoElement.paused) {\n const timeDiff = Math.abs(videoElement.currentTime - state.currentTime);\n if (timeDiff > 0.5) {\n videoElement.currentTime = state.currentTime;\n }\n videoElement.play().catch(() => {\n });\n }\n } else {\n videoElement.pause();\n if (Math.abs(videoElement.currentTime - state.currentTime) > 0.05) {\n videoElement.currentTime = state.currentTime;\n }\n }\n };\n if (videoElement) {\n const vos = window.__vos__;\n if (vos?.videoCallbacks) {\n vos.videoCallbacks.add(updateVideoPlayback);\n }\n }\n const updateVideoCurrentTime = () => {\n if (!videoElement) return;\n const vos = window.__vos__;\n if (!state.playing || vos?.isPaused) {\n videoElement.currentTime = state.currentTime;\n }\n };\n return new Proxy(state, {\n set(target, prop, value) {\n target[prop] = value;\n switch (prop) {\n case \"x\":\n case \"y\":\n case \"z\":\n updateMeshPosition();\n break;\n case \"scale\":\n case \"scaleX\":\n case \"scaleY\":\n case \"rotation\":\n case \"rotationX\":\n case \"rotationY\":\n updateMeshTransform();\n break;\n case \"opacity\":\n updateMeshOpacity();\n break;\n case \"zIndex\":\n mesh.renderOrder = value;\n mesh.userData.zIndex = value;\n break;\n case \"currentTime\":\n updateVideoCurrentTime();\n break;\n case \"playing\":\n case \"startOffset\":\n updateVideoPlayback();\n break;\n }\n return true;\n },\n get(target, prop) {\n if (prop === \"duration\" && videoElement) {\n return videoElement.duration;\n }\n return target[prop];\n }\n });\n }\n\n // src/renderers/image.ts\n async function renderImageElement(element, _resolution, THREE) {\n const { src, size = {} } = element;\n let img;\n const cached = AssetCache.getImage(src);\n if (cached) {\n img = cached.element;\n } else {\n img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = src;\n });\n }\n let width = size.width ?? img.naturalWidth;\n let height = size.height ?? img.naturalHeight;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n const targetHeight = typeof size.height === \"number\" ? size.height : img.naturalHeight;\n width = img.naturalWidth / img.naturalHeight * targetHeight;\n height = targetHeight;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n const targetWidth = typeof size.width === \"number\" ? size.width : img.naturalWidth;\n height = img.naturalHeight / img.naturalWidth * targetWidth;\n width = targetWidth;\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n const fit = size.fit ?? \"fill\";\n if (fit === \"contain\" || fit === \"cover\") {\n const scale = fit === \"contain\" ? Math.min(width / img.naturalWidth, height / img.naturalHeight) : Math.max(width / img.naturalWidth, height / img.naturalHeight);\n const drawWidth = img.naturalWidth * scale;\n const drawHeight = img.naturalHeight * scale;\n const offsetX = (width - drawWidth) / 2;\n const offsetY = (height - drawHeight) / 2;\n ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);\n } else {\n ctx.drawImage(img, 0, 0, width, height);\n }\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(width, height);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, width, height };\n }\n\n // src/renderers/svg.ts\n async function renderSVGElement(element, _resolution, THREE) {\n const { src, size = {}, colors = {} } = element;\n let svgContent = AssetCache.getSVG(src);\n if (!svgContent) {\n if (src.startsWith(\"http\") || src.startsWith(\"/\")) {\n const response = await fetch(src);\n svgContent = await response.text();\n } else {\n svgContent = src;\n }\n }\n Object.entries(colors).forEach(([selector, color]) => {\n const regex = new RegExp(`(${selector}[^>]*)(fill|stroke)=\"[^\"]*\"`, \"g\");\n svgContent = svgContent.replace(regex, `$1$2=\"${color}\"`);\n });\n const parser = new DOMParser();\n const svgDoc = parser.parseFromString(svgContent, \"image/svg+xml\");\n const svgElement = svgDoc.documentElement;\n const viewBox = svgElement.getAttribute(\"viewBox\");\n let svgWidth = parseFloat(svgElement.getAttribute(\"width\") || \"\") || 100;\n let svgHeight = parseFloat(svgElement.getAttribute(\"height\") || \"\") || 100;\n if (viewBox) {\n const [, , vbW, vbH] = viewBox.split(\" \").map(Number);\n svgWidth = vbW || svgWidth;\n svgHeight = vbH || svgHeight;\n }\n let width = size.width ?? svgWidth;\n let height = size.height ?? svgHeight;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n width = svgWidth / svgHeight * size.height;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n height = svgHeight / svgWidth * size.width;\n }\n svgElement.setAttribute(\"width\", String(width));\n svgElement.setAttribute(\"height\", String(height));\n const updatedSvg = new XMLSerializer().serializeToString(svgElement);\n const blob = new Blob([updatedSvg], { type: \"image/svg+xml\" });\n const url = URL.createObjectURL(blob);\n const img = new Image();\n await new Promise((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = url;\n });\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n ctx.drawImage(img, 0, 0, width, height);\n URL.revokeObjectURL(url);\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(width, height);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, width, height };\n }\n\n // src/renderers/text.ts\n function renderTextElement(element, _resolution, THREE) {\n const { content, font = {} } = element;\n const fontSize = font.size ?? 24;\n const fontFamily = font.family ?? \"Inter, system-ui, sans-serif\";\n const fontWeight = font.weight ?? \"normal\";\n const fontStyle = font.style ?? \"normal\";\n const color = font.color ?? \"#ffffff\";\n const align = font.align ?? \"left\";\n const letterSpacing = font.letterSpacing ?? 0;\n const lineHeight = font.lineHeight ?? 1.2;\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.font = fontString;\n const lines = content.split(\"\\n\");\n let maxWidth = 0;\n lines.forEach((line) => {\n let w = 0;\n for (let i = 0; i < line.length; i++) {\n w += ctx.measureText(line[i]).width;\n if (i < line.length - 1) w += letterSpacing;\n }\n if (w > maxWidth) maxWidth = w;\n });\n const totalHeight = lines.length * fontSize * lineHeight;\n const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 10;\n const canvasWidth = Math.ceil(maxWidth + padding * 2);\n const canvasHeight = Math.ceil(totalHeight + padding * 2);\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n ctx.font = fontString;\n ctx.textBaseline = \"top\";\n ctx.textAlign = align;\n ctx.clearRect(0, 0, canvasWidth, canvasHeight);\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color;\n ctx.shadowBlur = element.shadow.blur;\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0;\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0;\n }\n let textX = padding;\n if (align === \"center\") textX = canvasWidth / 2;\n else if (align === \"right\") textX = canvasWidth - padding;\n lines.forEach((line, i) => {\n const y = padding + i * fontSize * lineHeight;\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color;\n ctx.lineWidth = element.stroke.width;\n ctx.lineJoin = \"round\";\n ctx.strokeText(line, textX, y);\n }\n ctx.fillStyle = color;\n ctx.fillText(line, textX, y);\n });\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, canvas, width: canvasWidth, height: canvasHeight };\n }\n function renderTextSegment(text, font, element, THREE) {\n const fontSize = font.size ?? 24;\n const fontFamily = font.family ?? \"Inter, system-ui, sans-serif\";\n const fontWeight = font.weight ?? \"normal\";\n const fontStyle = font.style ?? \"normal\";\n const color = font.color ?? \"#ffffff\";\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.font = fontString;\n const metrics = ctx.measureText(text);\n const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 4;\n const canvasWidth = Math.ceil(metrics.width + padding * 2);\n const canvasHeight = Math.ceil(fontSize * 1.4 + padding * 2);\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n ctx.font = fontString;\n ctx.textBaseline = \"top\";\n ctx.textAlign = \"left\";\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color;\n ctx.shadowBlur = element.shadow.blur;\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0;\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0;\n }\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color;\n ctx.lineWidth = element.stroke.width;\n ctx.lineJoin = \"round\";\n ctx.strokeText(text, padding, padding);\n }\n ctx.fillStyle = color;\n ctx.fillText(text, padding, padding);\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);\n const mesh = new THREE.Mesh(geometry, material);\n return {\n mesh,\n width: canvasWidth,\n height: canvasHeight,\n textWidth: metrics.width\n };\n }\n function renderSplitTextElement(element, _resolution, THREE) {\n const { content, font = {}, split } = element;\n const splitType = split?.type ?? \"chars\";\n const letterSpacing = font.letterSpacing ?? 0;\n let segments;\n if (splitType === \"chars\") {\n segments = content.split(\"\");\n } else if (splitType === \"words\") {\n segments = content.split(/\\s+/);\n } else {\n segments = content.split(\"\\n\");\n }\n const meshes = [];\n let totalWidth = 0;\n segments.forEach((text, i) => {\n if (text.length === 0) return;\n const result = renderTextSegment(text, font, element, THREE);\n meshes.push({\n mesh: result.mesh,\n width: result.width,\n height: result.height,\n textWidth: result.textWidth,\n text\n });\n totalWidth += result.textWidth;\n if (i < segments.length - 1) {\n totalWidth += splitType === \"words\" ? (font.size ?? 24) * 0.4 : letterSpacing;\n }\n });\n const totalHeight = meshes[0]?.height ?? 0;\n let currentX = -totalWidth / 2;\n meshes.forEach((item, i) => {\n item.offsetX = currentX + item.textWidth / 2;\n item.offsetY = 0;\n currentX += item.textWidth;\n if (i < meshes.length - 1) {\n currentX += splitType === \"words\" ? (font.size ?? 24) * 0.4 : letterSpacing;\n }\n });\n return { meshes, totalWidth, totalHeight };\n }\n\n // src/renderers/video.ts\n async function renderVideoElement(element, _resolution, THREE) {\n const {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0\n } = element;\n let video;\n const cached = AssetCache.getVideo(src);\n if (cached) {\n video = cached.element;\n video.loop = loop;\n video.muted = muted;\n video.playbackRate = playbackRate;\n } else {\n video = document.createElement(\"video\");\n video.crossOrigin = \"anonymous\";\n video.src = src;\n video.loop = loop;\n video.muted = muted;\n video.playbackRate = playbackRate;\n video.playsInline = true;\n video.preload = \"auto\";\n await new Promise((resolve, reject) => {\n video.oncanplaythrough = () => resolve();\n video.onerror = reject;\n video.load();\n });\n }\n video.currentTime = startTime;\n video.pause();\n let width = size.width ?? video.videoWidth;\n let height = size.height ?? video.videoHeight;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n const targetHeight = typeof size.height === \"number\" ? size.height : video.videoHeight;\n width = video.videoWidth / video.videoHeight * targetHeight;\n height = targetHeight;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n const targetWidth = typeof size.width === \"number\" ? size.width : video.videoWidth;\n height = video.videoHeight / video.videoWidth * targetWidth;\n width = targetWidth;\n }\n const texture = new THREE.VideoTexture(video);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.format = THREE.RGBAFormat;\n texture.generateMipmaps = false;\n texture.colorSpace = THREE.SRGBColorSpace;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(width, height);\n const mesh = new THREE.Mesh(geometry, material);\n mesh.userData.video = video;\n mesh.userData.texture = texture;\n return { mesh, width, height, video };\n }\n\n // src/renderElements.ts\n function calculatePosition(position, resolution, elementWidth, elementHeight) {\n const { width, height } = resolution;\n const halfW = elementWidth / 2;\n const halfH = elementHeight / 2;\n if (typeof position === \"string\") {\n switch (position) {\n case \"center\":\n return { x: width / 2 - halfW, y: height / 2 - halfH };\n case \"top-left\":\n return { x: 0, y: 0 };\n case \"top-center\":\n return { x: width / 2 - halfW, y: 0 };\n case \"top-right\":\n return { x: width - elementWidth, y: 0 };\n case \"center-left\":\n return { x: 0, y: height / 2 - halfH };\n case \"center-right\":\n return { x: width - elementWidth, y: height / 2 - halfH };\n case \"bottom-left\":\n return { x: 0, y: height - elementHeight };\n case \"bottom-center\":\n return { x: width / 2 - halfW, y: height - elementHeight };\n case \"bottom-right\":\n return { x: width - elementWidth, y: height - elementHeight };\n default:\n return { x: 0, y: 0 };\n }\n }\n const x = typeof position.x === \"string\" ? parseFloat(position.x) / 100 * width : position.x;\n const y = typeof position.y === \"string\" ? parseFloat(position.y) / 100 * height : position.y;\n return { x, y };\n }\n var DESIGN_HEIGHT = 1080;\n async function renderElements(elementsConfig, overlayScenes, resolution, THREE) {\n const getScene = (config) => overlayScenes[config.zIndex ?? 100];\n await preloadAssets(elementsConfig);\n const elementMap = /* @__PURE__ */ new Map();\n const resolutionScale = resolution.height / DESIGN_HEIGHT;\n for (let i = 0; i < elementsConfig.length; i++) {\n const config = elementsConfig[i];\n const id = config.id ?? `element_${i}`;\n try {\n let mesh;\n let canvas = null;\n let elementWidth = 0;\n let elementHeight = 0;\n let segments = null;\n const segmentMeshes = [];\n let videoElement = null;\n if (config.type === \"text\" && config.split) {\n const splitResult = renderSplitTextElement(config, resolution, THREE);\n elementWidth = splitResult.totalWidth;\n elementHeight = splitResult.totalHeight;\n const scaledWidth = elementWidth * resolutionScale;\n const scaledHeight = elementHeight * resolutionScale;\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight\n );\n const basePosX = x - resolution.width / 2 + scaledWidth / 2;\n const basePosY = -(y - resolution.height / 2 + scaledHeight / 2);\n let transformX = 0;\n let transformY = 0;\n if (config.transform) {\n transformX = (config.transform.translateX ?? 0) * resolutionScale;\n transformY = -((config.transform.translateY ?? 0) * resolutionScale);\n }\n const zIndex = config.zIndex ?? 100;\n segments = splitResult.meshes.map((item, si) => {\n const segMesh = item.mesh;\n segMesh.scale.set(resolutionScale, resolutionScale, 1);\n segMesh.position.x = basePosX + item.offsetX * resolutionScale + transformX;\n segMesh.position.y = basePosY + item.offsetY * resolutionScale + transformY;\n segMesh.position.z = 0;\n segMesh.renderOrder = zIndex + i * 0.01 + si * 1e-3;\n if (config.opacity !== void 0) {\n segMesh.material.opacity = config.opacity;\n }\n getScene(config).add(segMesh);\n segmentMeshes.push(segMesh);\n return createElementProps(\n THREE,\n segMesh,\n segMesh.position.x,\n -segMesh.position.y,\n config.opacity ?? 1\n );\n });\n mesh = splitResult.meshes[0]?.mesh ?? new THREE.Mesh();\n } else if (config.type === \"text\") {\n const result = renderTextElement(config, resolution, THREE);\n mesh = result.mesh;\n canvas = result.canvas;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"image\") {\n const result = await renderImageElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"svg\") {\n const result = await renderSVGElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"video\") {\n const result = await renderVideoElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n mesh.userData.video = result.video;\n videoElement = result.video;\n videoElement.pause();\n } else {\n mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })\n );\n console.warn(`Element type \"${config.type}\" not implemented`);\n }\n if (!config.split) {\n const scaledWidth = elementWidth * resolutionScale;\n const scaledHeight = elementHeight * resolutionScale;\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight\n );\n const posX = x - resolution.width / 2 + scaledWidth / 2;\n const posY = -(y - resolution.height / 2 + scaledHeight / 2);\n mesh.scale.set(resolutionScale, resolutionScale, 1);\n mesh.position.x = posX;\n mesh.position.y = posY;\n mesh.position.z = 0;\n const zIndex = config.zIndex ?? 100;\n mesh.renderOrder = zIndex + i * 0.01;\n if (config.opacity !== void 0) {\n ;\n mesh.material.opacity = config.opacity;\n mesh.material.transparent = true;\n }\n if (config.transform) {\n const t = config.transform;\n if (t.translateX) mesh.position.x += t.translateX * resolutionScale;\n if (t.translateY) mesh.position.y -= t.translateY * resolutionScale;\n if (t.translateZ) mesh.position.z += t.translateZ;\n if (t.scale)\n mesh.scale.set(\n t.scale * resolutionScale,\n t.scale * resolutionScale,\n 1\n );\n if (t.rotateZ || t.rotation) {\n mesh.rotation.z = (t.rotateZ ?? t.rotation ?? 0) * Math.PI / 180;\n }\n }\n getScene(config).add(mesh);\n }\n const props = createElementProps(\n THREE,\n mesh,\n mesh.position.x,\n -mesh.position.y,\n config.opacity ?? 1,\n videoElement\n );\n const elementInstance = {\n config,\n mesh,\n node: null,\n props,\n segments,\n setContent: (_content) => {\n if (config.type === \"text\" && canvas) {\n console.warn(\"setContent not fully implemented in inline renderer\");\n }\n },\n destroy: () => {\n const targetScene = getScene(config);\n if (segmentMeshes.length > 0) {\n segmentMeshes.forEach((m) => {\n targetScene.remove(m);\n m.geometry.dispose();\n const mat = m.material;\n if (mat.map) mat.map.dispose();\n mat.dispose();\n });\n } else {\n targetScene.remove(mesh);\n mesh.geometry.dispose();\n const mat = mesh.material;\n if (mat.map) mat.map.dispose();\n mat.dispose();\n }\n }\n };\n elementMap.set(id, elementInstance);\n } catch (error) {\n console.warn(\n `[vos] Failed to render element \"${id}\" (${config.type}):`,\n error\n );\n const fallbackMesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })\n );\n getScene(config).add(fallbackMesh);\n const props = createElementProps(THREE, fallbackMesh, 0, 0, 0);\n elementMap.set(id, {\n config,\n mesh: fallbackMesh,\n node: null,\n props,\n segments: null,\n setContent: () => {\n },\n destroy: () => {\n getScene(config).remove(fallbackMesh);\n fallbackMesh.geometry.dispose();\n fallbackMesh.material.dispose();\n }\n });\n }\n }\n return elementMap;\n }\n\n // src/index.ts\n function createVosElements(THREE) {\n return {\n renderElements: (elementsConfig, overlayScenes, resolution) => renderElements(elementsConfig, overlayScenes, resolution, THREE),\n disposeElements: (elementMap) => {\n elementMap.forEach((instance) => instance.destroy?.());\n elementMap.clear();\n }\n };\n }\n return __toCommonJS(index_exports);\n})();\n"
2
+ export const elementsBundleCode = "\"use strict\";\nvar __vosElementsFactory = (() => {\n var __defProp = Object.defineProperty;\n var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __hasOwnProp = Object.prototype.hasOwnProperty;\n var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\n var __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n };\n var __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n };\n var __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== \"symbol\" ? key + \"\" : key, value);\n\n // src/index.ts\n var index_exports = {};\n __export(index_exports, {\n createVosElements: () => createVosElements,\n renderElements: () => renderElements\n });\n\n // src/assetCache.ts\n var AssetCache = {\n images: /* @__PURE__ */ new Map(),\n videos: /* @__PURE__ */ new Map(),\n svgContents: /* @__PURE__ */ new Map(),\n getImage(url) {\n return this.images.get(url) || null;\n },\n getVideo(url) {\n return this.videos.get(url) || null;\n },\n getSVG(url) {\n return this.svgContents.get(url) || null;\n },\n dispose() {\n this.videos.forEach((cached) => {\n cached.element.pause();\n cached.element.src = \"\";\n cached.element.load();\n if (cached.blobUrl) {\n URL.revokeObjectURL(cached.blobUrl);\n }\n });\n this.images.clear();\n this.videos.clear();\n this.svgContents.clear();\n }\n };\n function extractAssetUrls(elementsConfig) {\n const assets = {\n images: [],\n videos: [],\n svgs: []\n };\n for (const el of elementsConfig) {\n if (el.type === \"image\" && el.src) {\n assets.images.push(el.src);\n } else if (el.type === \"video\" && el.src) {\n assets.videos.push(el.src);\n } else if (el.type === \"svg\" && el.src) {\n assets.svgs.push(el.src);\n }\n }\n assets.images = [...new Set(assets.images)];\n assets.videos = [...new Set(assets.videos)];\n assets.svgs = [...new Set(assets.svgs)];\n return assets;\n }\n async function preloadImage(url) {\n if (AssetCache.images.has(url)) return;\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise((resolve) => {\n img.onload = () => resolve();\n img.onerror = () => {\n console.warn(\"Failed to preload image:\", url);\n resolve();\n };\n img.src = url;\n });\n AssetCache.images.set(url, {\n element: img,\n width: img.naturalWidth,\n height: img.naturalHeight\n });\n }\n async function preloadVideo(url) {\n if (AssetCache.videos.has(url)) return;\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\"Failed to fetch video: \" + response.status);\n }\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n const video = document.createElement(\"video\");\n video.crossOrigin = \"anonymous\";\n video.muted = true;\n video.playsInline = true;\n video.preload = \"auto\";\n await new Promise((resolve, reject) => {\n video.oncanplaythrough = () => resolve();\n video.onerror = reject;\n video.src = blobUrl;\n video.load();\n });\n video.currentTime = 0;\n await new Promise((resolve) => {\n video.onseeked = () => resolve();\n setTimeout(() => resolve(), 100);\n });\n video.pause();\n console.log(\"Video preloaded:\", url, \"duration:\", video.duration);\n AssetCache.videos.set(url, {\n element: video,\n blobUrl,\n duration: video.duration,\n width: video.videoWidth,\n height: video.videoHeight\n });\n } catch (e) {\n console.warn(\"Failed to preload video:\", url, e);\n }\n }\n async function preloadSVG(url) {\n if (AssetCache.svgContents.has(url)) return;\n try {\n if (url.startsWith(\"<svg\") || url.startsWith(\"<?xml\")) {\n AssetCache.svgContents.set(url, url);\n return;\n }\n if (url.startsWith(\"data:image/svg\")) {\n const base64 = url.split(\",\")[1];\n const svgContent2 = atob(base64);\n AssetCache.svgContents.set(url, svgContent2);\n return;\n }\n const response = await fetch(url);\n const svgContent = await response.text();\n AssetCache.svgContents.set(url, svgContent);\n } catch (e) {\n console.warn(\"Failed to preload SVG:\", url, e);\n AssetCache.svgContents.set(url, \"\");\n }\n }\n async function preloadAssets(elementsConfig) {\n const assets = extractAssetUrls(elementsConfig);\n const total = assets.images.length + assets.videos.length + assets.svgs.length;\n if (total === 0) return;\n let loaded = 0;\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: \"PRELOAD_PROGRESS\", loaded: 0, total },\n \"*\"\n );\n }\n const preloadWithProgress = async (fn, url) => {\n await fn(url);\n loaded++;\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: \"PRELOAD_PROGRESS\", loaded, total },\n \"*\"\n );\n }\n };\n const promises = [\n ...assets.images.map((url) => preloadWithProgress(preloadImage, url)),\n ...assets.videos.map((url) => preloadWithProgress(preloadVideo, url)),\n ...assets.svgs.map((url) => preloadWithProgress(preloadSVG, url))\n ];\n await Promise.all(promises);\n }\n\n // src/createElementProps.ts\n function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1, videoElement = null, videoSource = null, videoTexture = null) {\n const baseScaleX = mesh.scale.x;\n const baseScaleY = mesh.scale.y;\n const state = {\n x: initialX,\n y: initialY,\n z: 0,\n opacity: initialOpacity,\n scale: 1,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n rotationX: 0,\n rotationY: 0,\n zIndex: mesh.userData.zIndex ?? 0,\n // Video-specific properties (only meaningful if videoElement is provided)\n currentTime: videoElement ? videoElement.currentTime : 0,\n playing: false,\n startOffset: 0\n };\n const updateMeshPosition = () => {\n mesh.position.x = state.x;\n mesh.position.y = -state.y;\n mesh.position.z = state.z;\n };\n const updateMeshTransform = () => {\n mesh.scale.set(\n baseScaleX * state.scale * state.scaleX,\n baseScaleY * state.scale * state.scaleY,\n 1\n );\n mesh.rotation.set(\n state.rotationX * Math.PI / 180,\n state.rotationY * Math.PI / 180,\n state.rotation * Math.PI / 180\n );\n };\n const updateMeshOpacity = () => {\n const mat = mesh.material;\n mat.opacity = state.opacity;\n mat.needsUpdate = true;\n };\n const updateVideoPlayback = () => {\n if (!videoElement) return;\n const vos = window.__vos__;\n const shouldPlay = state.playing && !vos?.isPaused;\n if (shouldPlay) {\n if (videoElement.paused) {\n const timeDiff = Math.abs(videoElement.currentTime - state.currentTime);\n if (timeDiff > 0.5) {\n videoElement.currentTime = state.currentTime;\n }\n videoElement.play().catch(() => {\n });\n }\n } else {\n videoElement.pause();\n if (Math.abs(videoElement.currentTime - state.currentTime) > 0.05) {\n videoElement.currentTime = state.currentTime;\n }\n }\n };\n if (videoElement) {\n const vos = window.__vos__;\n if (vos?.videoCallbacks) {\n vos.videoCallbacks.add(updateVideoPlayback);\n }\n }\n const updateVideoCurrentTime = () => {\n if (videoSource) {\n const vos2 = window.__vos__;\n const p = videoSource.seekTo(state.currentTime).then(() => {\n if (videoTexture) videoTexture.needsUpdate = true;\n }).catch((e) => console.error(\"[vos] frame decode failed\", e));\n vos2?.registerDecode?.(p);\n return;\n }\n if (!videoElement) return;\n const vos = window.__vos__;\n if (!state.playing || vos?.isPaused) {\n videoElement.currentTime = state.currentTime;\n }\n };\n return new Proxy(state, {\n set(target, prop, value) {\n target[prop] = value;\n switch (prop) {\n case \"x\":\n case \"y\":\n case \"z\":\n updateMeshPosition();\n break;\n case \"scale\":\n case \"scaleX\":\n case \"scaleY\":\n case \"rotation\":\n case \"rotationX\":\n case \"rotationY\":\n updateMeshTransform();\n break;\n case \"opacity\":\n updateMeshOpacity();\n break;\n case \"zIndex\":\n mesh.renderOrder = value;\n mesh.userData.zIndex = value;\n break;\n case \"currentTime\":\n updateVideoCurrentTime();\n break;\n case \"playing\":\n case \"startOffset\":\n updateVideoPlayback();\n break;\n }\n return true;\n },\n get(target, prop) {\n if (prop === \"duration\" && videoElement) {\n return videoElement.duration;\n }\n return target[prop];\n }\n });\n }\n\n // src/renderers/image.ts\n async function renderImageElement(element, _resolution, THREE) {\n const { src, size = {} } = element;\n let img;\n const cached = AssetCache.getImage(src);\n if (cached) {\n img = cached.element;\n } else {\n img = new Image();\n img.crossOrigin = \"anonymous\";\n await new Promise((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = src;\n });\n }\n let width = size.width ?? img.naturalWidth;\n let height = size.height ?? img.naturalHeight;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n const targetHeight = typeof size.height === \"number\" ? size.height : img.naturalHeight;\n width = img.naturalWidth / img.naturalHeight * targetHeight;\n height = targetHeight;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n const targetWidth = typeof size.width === \"number\" ? size.width : img.naturalWidth;\n height = img.naturalHeight / img.naturalWidth * targetWidth;\n width = targetWidth;\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n const fit = size.fit ?? \"fill\";\n if (fit === \"contain\" || fit === \"cover\") {\n const scale = fit === \"contain\" ? Math.min(width / img.naturalWidth, height / img.naturalHeight) : Math.max(width / img.naturalWidth, height / img.naturalHeight);\n const drawWidth = img.naturalWidth * scale;\n const drawHeight = img.naturalHeight * scale;\n const offsetX = (width - drawWidth) / 2;\n const offsetY = (height - drawHeight) / 2;\n ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);\n } else {\n ctx.drawImage(img, 0, 0, width, height);\n }\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(width, height);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, width, height };\n }\n\n // src/renderers/svg.ts\n async function renderSVGElement(element, _resolution, THREE) {\n const { src, size = {}, colors = {} } = element;\n let svgContent = AssetCache.getSVG(src);\n if (!svgContent) {\n if (src.startsWith(\"http\") || src.startsWith(\"/\")) {\n const response = await fetch(src);\n svgContent = await response.text();\n } else {\n svgContent = src;\n }\n }\n Object.entries(colors).forEach(([selector, color]) => {\n const regex = new RegExp(`(${selector}[^>]*)(fill|stroke)=\"[^\"]*\"`, \"g\");\n svgContent = svgContent.replace(regex, `$1$2=\"${color}\"`);\n });\n const parser = new DOMParser();\n const svgDoc = parser.parseFromString(svgContent, \"image/svg+xml\");\n const svgElement = svgDoc.documentElement;\n const viewBox = svgElement.getAttribute(\"viewBox\");\n let svgWidth = parseFloat(svgElement.getAttribute(\"width\") || \"\") || 100;\n let svgHeight = parseFloat(svgElement.getAttribute(\"height\") || \"\") || 100;\n if (viewBox) {\n const [, , vbW, vbH] = viewBox.split(\" \").map(Number);\n svgWidth = vbW || svgWidth;\n svgHeight = vbH || svgHeight;\n }\n let width = size.width ?? svgWidth;\n let height = size.height ?? svgHeight;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n width = svgWidth / svgHeight * size.height;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n height = svgHeight / svgWidth * size.width;\n }\n svgElement.setAttribute(\"width\", String(width));\n svgElement.setAttribute(\"height\", String(height));\n const updatedSvg = new XMLSerializer().serializeToString(svgElement);\n const blob = new Blob([updatedSvg], { type: \"image/svg+xml\" });\n const url = URL.createObjectURL(blob);\n const img = new Image();\n await new Promise((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = reject;\n img.src = url;\n });\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext(\"2d\");\n ctx.drawImage(img, 0, 0, width, height);\n URL.revokeObjectURL(url);\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(width, height);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, width, height };\n }\n\n // src/renderers/text.ts\n function renderTextElement(element, _resolution, THREE) {\n const { content, font = {} } = element;\n const fontSize = font.size ?? 24;\n const fontFamily = font.family ?? \"Inter, system-ui, sans-serif\";\n const fontWeight = font.weight ?? \"normal\";\n const fontStyle = font.style ?? \"normal\";\n const color = font.color ?? \"#ffffff\";\n const align = font.align ?? \"left\";\n const letterSpacing = font.letterSpacing ?? 0;\n const lineHeight = font.lineHeight ?? 1.2;\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.font = fontString;\n const lines = content.split(\"\\n\");\n let maxWidth = 0;\n lines.forEach((line) => {\n let w = 0;\n for (let i = 0; i < line.length; i++) {\n w += ctx.measureText(line[i]).width;\n if (i < line.length - 1) w += letterSpacing;\n }\n if (w > maxWidth) maxWidth = w;\n });\n const totalHeight = lines.length * fontSize * lineHeight;\n const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 10;\n const canvasWidth = Math.ceil(maxWidth + padding * 2);\n const canvasHeight = Math.ceil(totalHeight + padding * 2);\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n ctx.font = fontString;\n ctx.textBaseline = \"top\";\n ctx.textAlign = align;\n ctx.clearRect(0, 0, canvasWidth, canvasHeight);\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color;\n ctx.shadowBlur = element.shadow.blur;\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0;\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0;\n }\n let textX = padding;\n if (align === \"center\") textX = canvasWidth / 2;\n else if (align === \"right\") textX = canvasWidth - padding;\n lines.forEach((line, i) => {\n const y = padding + i * fontSize * lineHeight;\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color;\n ctx.lineWidth = element.stroke.width;\n ctx.lineJoin = \"round\";\n ctx.strokeText(line, textX, y);\n }\n ctx.fillStyle = color;\n ctx.fillText(line, textX, y);\n });\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);\n const mesh = new THREE.Mesh(geometry, material);\n return { mesh, canvas, width: canvasWidth, height: canvasHeight };\n }\n function renderTextSegment(text, font, element, THREE) {\n const fontSize = font.size ?? 24;\n const fontFamily = font.family ?? \"Inter, system-ui, sans-serif\";\n const fontWeight = font.weight ?? \"normal\";\n const fontStyle = font.style ?? \"normal\";\n const color = font.color ?? \"#ffffff\";\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.font = fontString;\n const metrics = ctx.measureText(text);\n const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 4;\n const canvasWidth = Math.ceil(metrics.width + padding * 2);\n const canvasHeight = Math.ceil(fontSize * 1.4 + padding * 2);\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n ctx.font = fontString;\n ctx.textBaseline = \"top\";\n ctx.textAlign = \"left\";\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color;\n ctx.shadowBlur = element.shadow.blur;\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0;\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0;\n }\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color;\n ctx.lineWidth = element.stroke.width;\n ctx.lineJoin = \"round\";\n ctx.strokeText(text, padding, padding);\n }\n ctx.fillStyle = color;\n ctx.fillText(text, padding, padding);\n const texture = new THREE.CanvasTexture(canvas);\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.needsUpdate = true;\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);\n const mesh = new THREE.Mesh(geometry, material);\n return {\n mesh,\n width: canvasWidth,\n height: canvasHeight,\n textWidth: metrics.width\n };\n }\n function renderSplitTextElement(element, _resolution, THREE) {\n const { content, font = {}, split } = element;\n const splitType = split?.type ?? \"chars\";\n const letterSpacing = font.letterSpacing ?? 0;\n let segments;\n if (splitType === \"chars\") {\n segments = content.split(\"\");\n } else if (splitType === \"words\") {\n segments = content.split(/\\s+/);\n } else {\n segments = content.split(\"\\n\");\n }\n const meshes = [];\n let totalWidth = 0;\n segments.forEach((text, i) => {\n if (text.length === 0) return;\n const result = renderTextSegment(text, font, element, THREE);\n meshes.push({\n mesh: result.mesh,\n width: result.width,\n height: result.height,\n textWidth: result.textWidth,\n text\n });\n totalWidth += result.textWidth;\n if (i < segments.length - 1) {\n totalWidth += splitType === \"words\" ? (font.size ?? 24) * 0.4 : letterSpacing;\n }\n });\n const totalHeight = meshes[0]?.height ?? 0;\n let currentX = -totalWidth / 2;\n meshes.forEach((item, i) => {\n item.offsetX = currentX + item.textWidth / 2;\n item.offsetY = 0;\n currentX += item.textWidth;\n if (i < meshes.length - 1) {\n currentX += splitType === \"words\" ? (font.size ?? 24) * 0.4 : letterSpacing;\n }\n });\n return { meshes, totalWidth, totalHeight };\n }\n\n // src/video/sampleIndex.ts\n function buildSyncIndices(samples) {\n const out = [];\n for (let i = 0; i < samples.length; i++) if (samples[i].isSync) out.push(i);\n return out;\n }\n function targetDecodeIndex(samples, tSec) {\n let di = 0;\n let best = -Infinity;\n for (let i = 0; i < samples.length; i++) {\n const c = samples[i].cts;\n if (c <= tSec && c > best) {\n best = c;\n di = i;\n }\n }\n return di;\n }\n function gopBounds(syncIndices, totalSamples, targetDI) {\n let ki = 0;\n for (const si of syncIndices) {\n if (si <= targetDI) ki = si;\n else break;\n }\n let ni = totalSamples;\n for (const si of syncIndices) {\n if (si > targetDI) {\n ni = si;\n break;\n }\n }\n return [ki, ni];\n }\n\n // src/video/FrameAccurateVideoSource.ts\n var MP4BOX_URL = \"https://esm.sh/mp4box@0.5.2\";\n var mp4boxPromise = null;\n function loadMp4Box() {\n if (!mp4boxPromise) {\n mp4boxPromise = import(\n /* @vite-ignore */\n MP4BOX_URL\n ).then((m) => m.default ?? m);\n }\n return mp4boxPromise;\n }\n function isFrameAccurateSupported() {\n return typeof VideoDecoder !== \"undefined\" && typeof EncodedVideoChunk !== \"undefined\";\n }\n var FrameAccurateVideoSource = class _FrameAccurateVideoSource {\n constructor() {\n __publicField(this, \"canvas\");\n __publicField(this, \"ctx\");\n __publicField(this, \"decoder\");\n __publicField(this, \"samples\", []);\n // DECODE order (as delivered by mp4box)\n __publicField(this, \"syncIndices\", []);\n __publicField(this, \"onFrame\", null);\n __publicField(this, \"lastError\", null);\n __publicField(this, \"durationSec\", 0);\n __publicField(this, \"fps\", 30);\n __publicField(this, \"codedWidth\", 0);\n __publicField(this, \"codedHeight\", 0);\n this.canvas = document.createElement(\"canvas\");\n this.ctx = this.canvas.getContext(\"2d\");\n }\n static async create(src) {\n const self = new _FrameAccurateVideoSource();\n self.decoder = new VideoDecoder({\n output: (f) => self.onFrame?.(f),\n error: (e) => {\n self.lastError = e;\n console.error(\"[vos] VideoDecoder error\", e);\n }\n });\n const buf = await fetch(src).then((r) => {\n if (!r.ok) throw new Error(`[vos] failed to fetch video: ${src} (${r.status})`);\n return r.arrayBuffer();\n });\n await self.demux(buf);\n self.canvas.width = self.codedWidth;\n self.canvas.height = self.codedHeight;\n await self.seekTo(0);\n return self;\n }\n async demux(data) {\n const MP4Box = await loadMp4Box();\n return new Promise((resolve, reject) => {\n const file = MP4Box.createFile();\n let expected = Infinity;\n let settled = false;\n const finish = () => {\n if (settled) return;\n settled = true;\n if (!this.samples.length) {\n reject(new Error(\"[vos] no samples extracted\"));\n return;\n }\n this.syncIndices = buildSyncIndices(this.samples);\n resolve();\n };\n file.onError = (e) => {\n if (settled) return;\n settled = true;\n reject(new Error(String(e)));\n };\n file.onReady = (info) => {\n const track = info.videoTracks?.[0];\n if (!track) {\n settled = true;\n reject(new Error(\"[vos] no video track in mp4\"));\n return;\n }\n this.codedWidth = track.video.width;\n this.codedHeight = track.video.height;\n this.durationSec = info.duration / info.timescale;\n this.fps = track.nb_samples / this.durationSec || 30;\n expected = track.nb_samples || Infinity;\n this.decoder.configure({\n codec: track.codec,\n description: this.getDescription(MP4Box, file, track.id),\n codedWidth: this.codedWidth,\n codedHeight: this.codedHeight\n });\n file.setExtractionOptions(track.id, null, { nbSamples: Infinity });\n file.start();\n };\n file.onSamples = (_id, _user, arr) => {\n for (const s of arr) {\n this.samples.push({\n cts: s.cts / s.timescale,\n isSync: !!s.is_sync,\n chunk: new EncodedVideoChunk({\n type: s.is_sync ? \"key\" : \"delta\",\n timestamp: Math.round(s.cts / s.timescale * 1e6),\n duration: Math.round(s.duration / s.timescale * 1e6),\n data: s.data\n })\n });\n }\n if (this.samples.length >= expected) finish();\n };\n data.fileStart = 0;\n file.appendBuffer(data);\n file.flush();\n setTimeout(finish, 2e3);\n });\n }\n getDescription(MP4Box, file, trackId) {\n const trak = file.getTrackById(trackId);\n for (const entry of trak.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.vpcC ?? entry.av1C;\n if (box) {\n const stream = new MP4Box.DataStream(void 0, 0, MP4Box.DataStream.BIG_ENDIAN);\n box.write(stream);\n return new Uint8Array(stream.buffer, 8);\n }\n }\n throw new Error(\"[vos] no codec description (avcC/hvcC/vpcC/av1C) found\");\n }\n /**\n * Decode the exact frame at presentation time `t` and draw it to `canvas`.\n * Decodes the whole GOP (keyframe → next keyframe) in decode order, then\n * selects the output by PTS. Returns when the canvas is updated.\n */\n async seekTo(tSec) {\n if (!this.samples.length) return;\n const targetDI = targetDecodeIndex(this.samples, tSec);\n const [ki, ni] = gopBounds(this.syncIndices, this.samples.length, targetDI);\n const targetTs = Math.round(this.samples[targetDI].cts * 1e6);\n this.lastError = null;\n const collected = [];\n this.onFrame = (f) => collected.push(f);\n for (let i = ki; i < ni; i++) this.decoder.decode(this.samples[i].chunk);\n await this.decoder.flush();\n this.onFrame = null;\n if (this.lastError) throw this.lastError;\n let best;\n let bestD = Infinity;\n for (const f of collected) {\n const d = Math.abs(f.timestamp - targetTs);\n if (d < bestD) {\n bestD = d;\n best = f;\n }\n }\n if (best) this.ctx.drawImage(best, 0, 0);\n for (const f of collected) f.close();\n }\n dispose() {\n try {\n this.decoder.close();\n } catch {\n }\n }\n };\n\n // src/renderers/video.ts\n function resolveSize(size, intrinsicW, intrinsicH) {\n let width = size.width ?? intrinsicW;\n let height = size.height ?? intrinsicH;\n if (size.width === \"auto\" && size.height !== \"auto\" && size.height) {\n const targetHeight = typeof size.height === \"number\" ? size.height : intrinsicH;\n width = intrinsicW / intrinsicH * targetHeight;\n height = targetHeight;\n } else if (size.height === \"auto\" && size.width !== \"auto\" && size.width) {\n const targetWidth = typeof size.width === \"number\" ? size.width : intrinsicW;\n height = intrinsicH / intrinsicW * targetWidth;\n width = targetWidth;\n }\n return { width, height };\n }\n function applyTextureSettings(texture, THREE) {\n texture.minFilter = THREE.LinearFilter;\n texture.magFilter = THREE.LinearFilter;\n texture.format = THREE.RGBAFormat;\n texture.generateMipmaps = false;\n texture.colorSpace = THREE.SRGBColorSpace;\n }\n async function renderVideoElement(element, _resolution, THREE) {\n const frameSource = element.frameSource ?? \"html5\";\n const useWebCodecs = frameSource === \"webcodecs\" || frameSource === \"auto\" && isFrameAccurateSupported();\n if (useWebCodecs) {\n try {\n return await renderWebCodecsVideo(element, THREE);\n } catch (err) {\n console.warn(\n \"[vos] frame-accurate video unavailable; falling back to html5 \\u2014\",\n err instanceof Error ? err.message : err\n );\n }\n }\n return renderHtml5Video(element, THREE);\n }\n async function renderWebCodecsVideo(element, THREE) {\n const { src, size = {} } = element;\n const source = await FrameAccurateVideoSource.create(src);\n const { width, height } = resolveSize(size, source.codedWidth, source.codedHeight);\n const texture = new THREE.CanvasTexture(source.canvas);\n applyTextureSettings(texture, THREE);\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material);\n mesh.userData.videoSource = source;\n mesh.userData.texture = texture;\n texture.needsUpdate = true;\n return { mesh, width, height, video: null, videoSource: source, texture };\n }\n async function renderHtml5Video(element, THREE) {\n const {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0\n } = element;\n let video;\n const cached = AssetCache.getVideo(src);\n if (cached) {\n video = cached.element;\n video.loop = loop;\n video.muted = muted;\n video.playbackRate = playbackRate;\n } else {\n video = document.createElement(\"video\");\n video.crossOrigin = \"anonymous\";\n video.src = src;\n video.loop = loop;\n video.muted = muted;\n video.playbackRate = playbackRate;\n video.playsInline = true;\n video.preload = \"auto\";\n await new Promise((resolve, reject) => {\n video.oncanplaythrough = () => resolve();\n video.onerror = reject;\n video.load();\n });\n }\n video.currentTime = startTime;\n video.pause();\n const { width, height } = resolveSize(size, video.videoWidth, video.videoHeight);\n const texture = new THREE.VideoTexture(video);\n applyTextureSettings(texture, THREE);\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false\n });\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material);\n mesh.userData.video = video;\n mesh.userData.texture = texture;\n return { mesh, width, height, video, videoSource: null, texture };\n }\n\n // src/renderElements.ts\n function calculatePosition(position, resolution, elementWidth, elementHeight) {\n const { width, height } = resolution;\n const halfW = elementWidth / 2;\n const halfH = elementHeight / 2;\n if (typeof position === \"string\") {\n switch (position) {\n case \"center\":\n return { x: width / 2 - halfW, y: height / 2 - halfH };\n case \"top-left\":\n return { x: 0, y: 0 };\n case \"top-center\":\n return { x: width / 2 - halfW, y: 0 };\n case \"top-right\":\n return { x: width - elementWidth, y: 0 };\n case \"center-left\":\n return { x: 0, y: height / 2 - halfH };\n case \"center-right\":\n return { x: width - elementWidth, y: height / 2 - halfH };\n case \"bottom-left\":\n return { x: 0, y: height - elementHeight };\n case \"bottom-center\":\n return { x: width / 2 - halfW, y: height - elementHeight };\n case \"bottom-right\":\n return { x: width - elementWidth, y: height - elementHeight };\n default:\n return { x: 0, y: 0 };\n }\n }\n const x = typeof position.x === \"string\" ? parseFloat(position.x) / 100 * width : position.x;\n const y = typeof position.y === \"string\" ? parseFloat(position.y) / 100 * height : position.y;\n return { x, y };\n }\n var DESIGN_HEIGHT = 1080;\n async function renderElements(elementsConfig, overlayScenes, resolution, THREE) {\n const getScene = (config) => overlayScenes[config.zIndex ?? 100];\n await preloadAssets(elementsConfig);\n const elementMap = /* @__PURE__ */ new Map();\n const resolutionScale = resolution.height / DESIGN_HEIGHT;\n for (let i = 0; i < elementsConfig.length; i++) {\n const config = elementsConfig[i];\n const id = config.id ?? `element_${i}`;\n try {\n let mesh;\n let canvas = null;\n let elementWidth = 0;\n let elementHeight = 0;\n let segments = null;\n const segmentMeshes = [];\n let videoElement = null;\n let videoSource = null;\n let videoTexture = null;\n if (config.type === \"text\" && config.split) {\n const splitResult = renderSplitTextElement(config, resolution, THREE);\n elementWidth = splitResult.totalWidth;\n elementHeight = splitResult.totalHeight;\n const scaledWidth = elementWidth * resolutionScale;\n const scaledHeight = elementHeight * resolutionScale;\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight\n );\n const basePosX = x - resolution.width / 2 + scaledWidth / 2;\n const basePosY = -(y - resolution.height / 2 + scaledHeight / 2);\n let transformX = 0;\n let transformY = 0;\n if (config.transform) {\n transformX = (config.transform.translateX ?? 0) * resolutionScale;\n transformY = -((config.transform.translateY ?? 0) * resolutionScale);\n }\n const zIndex = config.zIndex ?? 100;\n segments = splitResult.meshes.map((item, si) => {\n const segMesh = item.mesh;\n segMesh.scale.set(resolutionScale, resolutionScale, 1);\n segMesh.position.x = basePosX + item.offsetX * resolutionScale + transformX;\n segMesh.position.y = basePosY + item.offsetY * resolutionScale + transformY;\n segMesh.position.z = 0;\n segMesh.renderOrder = zIndex + i * 0.01 + si * 1e-3;\n if (config.opacity !== void 0) {\n segMesh.material.opacity = config.opacity;\n }\n getScene(config).add(segMesh);\n segmentMeshes.push(segMesh);\n return createElementProps(\n THREE,\n segMesh,\n segMesh.position.x,\n -segMesh.position.y,\n config.opacity ?? 1\n );\n });\n mesh = splitResult.meshes[0]?.mesh ?? new THREE.Mesh();\n } else if (config.type === \"text\") {\n const result = renderTextElement(config, resolution, THREE);\n mesh = result.mesh;\n canvas = result.canvas;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"image\") {\n const result = await renderImageElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"svg\") {\n const result = await renderSVGElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n } else if (config.type === \"video\") {\n const result = await renderVideoElement(config, resolution, THREE);\n mesh = result.mesh;\n elementWidth = result.width;\n elementHeight = result.height;\n mesh.userData.video = result.video;\n videoElement = result.video;\n videoSource = result.videoSource;\n videoTexture = result.texture;\n videoElement?.pause();\n } else {\n mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })\n );\n console.warn(`Element type \"${config.type}\" not implemented`);\n }\n if (!config.split) {\n const scaledWidth = elementWidth * resolutionScale;\n const scaledHeight = elementHeight * resolutionScale;\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight\n );\n const posX = x - resolution.width / 2 + scaledWidth / 2;\n const posY = -(y - resolution.height / 2 + scaledHeight / 2);\n mesh.scale.set(resolutionScale, resolutionScale, 1);\n mesh.position.x = posX;\n mesh.position.y = posY;\n mesh.position.z = 0;\n const zIndex = config.zIndex ?? 100;\n mesh.renderOrder = zIndex + i * 0.01;\n if (config.opacity !== void 0) {\n ;\n mesh.material.opacity = config.opacity;\n mesh.material.transparent = true;\n }\n if (config.transform) {\n const t = config.transform;\n if (t.translateX) mesh.position.x += t.translateX * resolutionScale;\n if (t.translateY) mesh.position.y -= t.translateY * resolutionScale;\n if (t.translateZ) mesh.position.z += t.translateZ;\n if (t.scale)\n mesh.scale.set(\n t.scale * resolutionScale,\n t.scale * resolutionScale,\n 1\n );\n if (t.rotateZ || t.rotation) {\n mesh.rotation.z = (t.rotateZ ?? t.rotation ?? 0) * Math.PI / 180;\n }\n }\n getScene(config).add(mesh);\n }\n const props = createElementProps(\n THREE,\n mesh,\n mesh.position.x,\n -mesh.position.y,\n config.opacity ?? 1,\n videoElement,\n videoSource,\n videoTexture\n );\n const elementInstance = {\n config,\n mesh,\n node: null,\n props,\n segments,\n setContent: (_content) => {\n if (config.type === \"text\" && canvas) {\n console.warn(\"setContent not fully implemented in inline renderer\");\n }\n },\n destroy: () => {\n videoSource?.dispose?.();\n const targetScene = getScene(config);\n if (segmentMeshes.length > 0) {\n segmentMeshes.forEach((m) => {\n targetScene.remove(m);\n m.geometry.dispose();\n const mat = m.material;\n if (mat.map) mat.map.dispose();\n mat.dispose();\n });\n } else {\n targetScene.remove(mesh);\n mesh.geometry.dispose();\n const mat = mesh.material;\n if (mat.map) mat.map.dispose();\n mat.dispose();\n }\n }\n };\n elementMap.set(id, elementInstance);\n } catch (error) {\n console.warn(\n `[vos] Failed to render element \"${id}\" (${config.type}):`,\n error\n );\n const fallbackMesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })\n );\n getScene(config).add(fallbackMesh);\n const props = createElementProps(THREE, fallbackMesh, 0, 0, 0);\n elementMap.set(id, {\n config,\n mesh: fallbackMesh,\n node: null,\n props,\n segments: null,\n setContent: () => {\n },\n destroy: () => {\n getScene(config).remove(fallbackMesh);\n fallbackMesh.geometry.dispose();\n fallbackMesh.material.dispose();\n }\n });\n }\n }\n return elementMap;\n }\n\n // src/index.ts\n function createVosElements(THREE) {\n return {\n renderElements: (elementsConfig, overlayScenes, resolution) => renderElements(elementsConfig, overlayScenes, resolution, THREE),\n disposeElements: (elementMap) => {\n elementMap.forEach((instance) => instance.destroy?.());\n elementMap.clear();\n }\n };\n }\n return __toCommonJS(index_exports);\n})();\n"
package/dist/index.js CHANGED
@@ -153,7 +153,7 @@ async function preloadAssets(elementsConfig) {
153
153
  }
154
154
 
155
155
  // src/createElementProps.ts
156
- function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1, videoElement = null) {
156
+ function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1, videoElement = null, videoSource = null, videoTexture = null) {
157
157
  const baseScaleX = mesh.scale.x;
158
158
  const baseScaleY = mesh.scale.y;
159
159
  const state = {
@@ -222,6 +222,14 @@ function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1
222
222
  }
223
223
  }
224
224
  const updateVideoCurrentTime = () => {
225
+ if (videoSource) {
226
+ const vos2 = window.__vos__;
227
+ const p = videoSource.seekTo(state.currentTime).then(() => {
228
+ if (videoTexture) videoTexture.needsUpdate = true;
229
+ }).catch((e) => console.error("[vos] frame decode failed", e));
230
+ vos2?.registerDecode?.(p);
231
+ return;
232
+ }
225
233
  if (!videoElement) return;
226
234
  const vos = window.__vos__;
227
235
  if (!state.playing || vos?.isPaused) {
@@ -553,8 +561,258 @@ function renderSplitTextElement(element, _resolution, THREE) {
553
561
  return { meshes, totalWidth, totalHeight };
554
562
  }
555
563
 
564
+ // src/video/sampleIndex.ts
565
+ function buildSyncIndices(samples) {
566
+ const out = [];
567
+ for (let i = 0; i < samples.length; i++) if (samples[i].isSync) out.push(i);
568
+ return out;
569
+ }
570
+ function targetDecodeIndex(samples, tSec) {
571
+ let di = 0;
572
+ let best = -Infinity;
573
+ for (let i = 0; i < samples.length; i++) {
574
+ const c = samples[i].cts;
575
+ if (c <= tSec && c > best) {
576
+ best = c;
577
+ di = i;
578
+ }
579
+ }
580
+ return di;
581
+ }
582
+ function gopBounds(syncIndices, totalSamples, targetDI) {
583
+ let ki = 0;
584
+ for (const si of syncIndices) {
585
+ if (si <= targetDI) ki = si;
586
+ else break;
587
+ }
588
+ let ni = totalSamples;
589
+ for (const si of syncIndices) {
590
+ if (si > targetDI) {
591
+ ni = si;
592
+ break;
593
+ }
594
+ }
595
+ return [ki, ni];
596
+ }
597
+
598
+ // src/video/FrameAccurateVideoSource.ts
599
+ var MP4BOX_URL = "https://esm.sh/mp4box@0.5.2";
600
+ var mp4boxPromise = null;
601
+ function loadMp4Box() {
602
+ if (!mp4boxPromise) {
603
+ mp4boxPromise = import(
604
+ /* @vite-ignore */
605
+ MP4BOX_URL
606
+ ).then((m) => m.default ?? m);
607
+ }
608
+ return mp4boxPromise;
609
+ }
610
+ function isFrameAccurateSupported() {
611
+ return typeof VideoDecoder !== "undefined" && typeof EncodedVideoChunk !== "undefined";
612
+ }
613
+ var FrameAccurateVideoSource = class _FrameAccurateVideoSource {
614
+ canvas;
615
+ ctx;
616
+ decoder;
617
+ samples = [];
618
+ // DECODE order (as delivered by mp4box)
619
+ syncIndices = [];
620
+ onFrame = null;
621
+ lastError = null;
622
+ durationSec = 0;
623
+ fps = 30;
624
+ codedWidth = 0;
625
+ codedHeight = 0;
626
+ constructor() {
627
+ this.canvas = document.createElement("canvas");
628
+ this.ctx = this.canvas.getContext("2d");
629
+ }
630
+ static async create(src) {
631
+ const self = new _FrameAccurateVideoSource();
632
+ self.decoder = new VideoDecoder({
633
+ output: (f) => self.onFrame?.(f),
634
+ error: (e) => {
635
+ self.lastError = e;
636
+ console.error("[vos] VideoDecoder error", e);
637
+ }
638
+ });
639
+ const buf = await fetch(src).then((r) => {
640
+ if (!r.ok) throw new Error(`[vos] failed to fetch video: ${src} (${r.status})`);
641
+ return r.arrayBuffer();
642
+ });
643
+ await self.demux(buf);
644
+ self.canvas.width = self.codedWidth;
645
+ self.canvas.height = self.codedHeight;
646
+ await self.seekTo(0);
647
+ return self;
648
+ }
649
+ async demux(data) {
650
+ const MP4Box = await loadMp4Box();
651
+ return new Promise((resolve, reject) => {
652
+ const file = MP4Box.createFile();
653
+ let expected = Infinity;
654
+ let settled = false;
655
+ const finish = () => {
656
+ if (settled) return;
657
+ settled = true;
658
+ if (!this.samples.length) {
659
+ reject(new Error("[vos] no samples extracted"));
660
+ return;
661
+ }
662
+ this.syncIndices = buildSyncIndices(this.samples);
663
+ resolve();
664
+ };
665
+ file.onError = (e) => {
666
+ if (settled) return;
667
+ settled = true;
668
+ reject(new Error(String(e)));
669
+ };
670
+ file.onReady = (info) => {
671
+ const track = info.videoTracks?.[0];
672
+ if (!track) {
673
+ settled = true;
674
+ reject(new Error("[vos] no video track in mp4"));
675
+ return;
676
+ }
677
+ this.codedWidth = track.video.width;
678
+ this.codedHeight = track.video.height;
679
+ this.durationSec = info.duration / info.timescale;
680
+ this.fps = track.nb_samples / this.durationSec || 30;
681
+ expected = track.nb_samples || Infinity;
682
+ this.decoder.configure({
683
+ codec: track.codec,
684
+ description: this.getDescription(MP4Box, file, track.id),
685
+ codedWidth: this.codedWidth,
686
+ codedHeight: this.codedHeight
687
+ });
688
+ file.setExtractionOptions(track.id, null, { nbSamples: Infinity });
689
+ file.start();
690
+ };
691
+ file.onSamples = (_id, _user, arr) => {
692
+ for (const s of arr) {
693
+ this.samples.push({
694
+ cts: s.cts / s.timescale,
695
+ isSync: !!s.is_sync,
696
+ chunk: new EncodedVideoChunk({
697
+ type: s.is_sync ? "key" : "delta",
698
+ timestamp: Math.round(s.cts / s.timescale * 1e6),
699
+ duration: Math.round(s.duration / s.timescale * 1e6),
700
+ data: s.data
701
+ })
702
+ });
703
+ }
704
+ if (this.samples.length >= expected) finish();
705
+ };
706
+ data.fileStart = 0;
707
+ file.appendBuffer(data);
708
+ file.flush();
709
+ setTimeout(finish, 2e3);
710
+ });
711
+ }
712
+ getDescription(MP4Box, file, trackId) {
713
+ const trak = file.getTrackById(trackId);
714
+ for (const entry of trak.mdia.minf.stbl.stsd.entries) {
715
+ const box = entry.avcC ?? entry.hvcC ?? entry.vpcC ?? entry.av1C;
716
+ if (box) {
717
+ const stream = new MP4Box.DataStream(void 0, 0, MP4Box.DataStream.BIG_ENDIAN);
718
+ box.write(stream);
719
+ return new Uint8Array(stream.buffer, 8);
720
+ }
721
+ }
722
+ throw new Error("[vos] no codec description (avcC/hvcC/vpcC/av1C) found");
723
+ }
724
+ /**
725
+ * Decode the exact frame at presentation time `t` and draw it to `canvas`.
726
+ * Decodes the whole GOP (keyframe → next keyframe) in decode order, then
727
+ * selects the output by PTS. Returns when the canvas is updated.
728
+ */
729
+ async seekTo(tSec) {
730
+ if (!this.samples.length) return;
731
+ const targetDI = targetDecodeIndex(this.samples, tSec);
732
+ const [ki, ni] = gopBounds(this.syncIndices, this.samples.length, targetDI);
733
+ const targetTs = Math.round(this.samples[targetDI].cts * 1e6);
734
+ this.lastError = null;
735
+ const collected = [];
736
+ this.onFrame = (f) => collected.push(f);
737
+ for (let i = ki; i < ni; i++) this.decoder.decode(this.samples[i].chunk);
738
+ await this.decoder.flush();
739
+ this.onFrame = null;
740
+ if (this.lastError) throw this.lastError;
741
+ let best;
742
+ let bestD = Infinity;
743
+ for (const f of collected) {
744
+ const d = Math.abs(f.timestamp - targetTs);
745
+ if (d < bestD) {
746
+ bestD = d;
747
+ best = f;
748
+ }
749
+ }
750
+ if (best) this.ctx.drawImage(best, 0, 0);
751
+ for (const f of collected) f.close();
752
+ }
753
+ dispose() {
754
+ try {
755
+ this.decoder.close();
756
+ } catch {
757
+ }
758
+ }
759
+ };
760
+
556
761
  // src/renderers/video.ts
762
+ function resolveSize(size, intrinsicW, intrinsicH) {
763
+ let width = size.width ?? intrinsicW;
764
+ let height = size.height ?? intrinsicH;
765
+ if (size.width === "auto" && size.height !== "auto" && size.height) {
766
+ const targetHeight = typeof size.height === "number" ? size.height : intrinsicH;
767
+ width = intrinsicW / intrinsicH * targetHeight;
768
+ height = targetHeight;
769
+ } else if (size.height === "auto" && size.width !== "auto" && size.width) {
770
+ const targetWidth = typeof size.width === "number" ? size.width : intrinsicW;
771
+ height = intrinsicH / intrinsicW * targetWidth;
772
+ width = targetWidth;
773
+ }
774
+ return { width, height };
775
+ }
776
+ function applyTextureSettings(texture, THREE) {
777
+ texture.minFilter = THREE.LinearFilter;
778
+ texture.magFilter = THREE.LinearFilter;
779
+ texture.format = THREE.RGBAFormat;
780
+ texture.generateMipmaps = false;
781
+ texture.colorSpace = THREE.SRGBColorSpace;
782
+ }
557
783
  async function renderVideoElement(element, _resolution, THREE) {
784
+ const frameSource = element.frameSource ?? "html5";
785
+ const useWebCodecs = frameSource === "webcodecs" || frameSource === "auto" && isFrameAccurateSupported();
786
+ if (useWebCodecs) {
787
+ try {
788
+ return await renderWebCodecsVideo(element, THREE);
789
+ } catch (err) {
790
+ console.warn(
791
+ "[vos] frame-accurate video unavailable; falling back to html5 \u2014",
792
+ err instanceof Error ? err.message : err
793
+ );
794
+ }
795
+ }
796
+ return renderHtml5Video(element, THREE);
797
+ }
798
+ async function renderWebCodecsVideo(element, THREE) {
799
+ const { src, size = {} } = element;
800
+ const source = await FrameAccurateVideoSource.create(src);
801
+ const { width, height } = resolveSize(size, source.codedWidth, source.codedHeight);
802
+ const texture = new THREE.CanvasTexture(source.canvas);
803
+ applyTextureSettings(texture, THREE);
804
+ const material = new THREE.MeshBasicMaterial({
805
+ map: texture,
806
+ transparent: true,
807
+ depthWrite: false
808
+ });
809
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material);
810
+ mesh.userData.videoSource = source;
811
+ mesh.userData.texture = texture;
812
+ texture.needsUpdate = true;
813
+ return { mesh, width, height, video: null, videoSource: source, texture };
814
+ }
815
+ async function renderHtml5Video(element, THREE) {
558
816
  const {
559
817
  src,
560
818
  size = {},
@@ -587,33 +845,18 @@ async function renderVideoElement(element, _resolution, THREE) {
587
845
  }
588
846
  video.currentTime = startTime;
589
847
  video.pause();
590
- let width = size.width ?? video.videoWidth;
591
- let height = size.height ?? video.videoHeight;
592
- if (size.width === "auto" && size.height !== "auto" && size.height) {
593
- const targetHeight = typeof size.height === "number" ? size.height : video.videoHeight;
594
- width = video.videoWidth / video.videoHeight * targetHeight;
595
- height = targetHeight;
596
- } else if (size.height === "auto" && size.width !== "auto" && size.width) {
597
- const targetWidth = typeof size.width === "number" ? size.width : video.videoWidth;
598
- height = video.videoHeight / video.videoWidth * targetWidth;
599
- width = targetWidth;
600
- }
848
+ const { width, height } = resolveSize(size, video.videoWidth, video.videoHeight);
601
849
  const texture = new THREE.VideoTexture(video);
602
- texture.minFilter = THREE.LinearFilter;
603
- texture.magFilter = THREE.LinearFilter;
604
- texture.format = THREE.RGBAFormat;
605
- texture.generateMipmaps = false;
606
- texture.colorSpace = THREE.SRGBColorSpace;
850
+ applyTextureSettings(texture, THREE);
607
851
  const material = new THREE.MeshBasicMaterial({
608
852
  map: texture,
609
853
  transparent: true,
610
854
  depthWrite: false
611
855
  });
612
- const geometry = new THREE.PlaneGeometry(width, height);
613
- const mesh = new THREE.Mesh(geometry, material);
856
+ const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material);
614
857
  mesh.userData.video = video;
615
858
  mesh.userData.texture = texture;
616
- return { mesh, width, height, video };
859
+ return { mesh, width, height, video, videoSource: null, texture };
617
860
  }
618
861
 
619
862
  // src/renderElements.ts
@@ -666,6 +909,8 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
666
909
  let segments = null;
667
910
  const segmentMeshes = [];
668
911
  let videoElement = null;
912
+ let videoSource = null;
913
+ let videoTexture = null;
669
914
  if (config.type === "text" && config.split) {
670
915
  const splitResult = renderSplitTextElement(config, resolution, THREE);
671
916
  elementWidth = splitResult.totalWidth;
@@ -731,7 +976,9 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
731
976
  elementHeight = result.height;
732
977
  mesh.userData.video = result.video;
733
978
  videoElement = result.video;
734
- videoElement.pause();
979
+ videoSource = result.videoSource;
980
+ videoTexture = result.texture;
981
+ videoElement?.pause();
735
982
  } else {
736
983
  mesh = new THREE.Mesh(
737
984
  new THREE.PlaneGeometry(100, 100),
@@ -784,7 +1031,9 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
784
1031
  mesh.position.x,
785
1032
  -mesh.position.y,
786
1033
  config.opacity ?? 1,
787
- videoElement
1034
+ videoElement,
1035
+ videoSource,
1036
+ videoTexture
788
1037
  );
789
1038
  const elementInstance = {
790
1039
  config,
@@ -798,6 +1047,7 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
798
1047
  }
799
1048
  },
800
1049
  destroy: () => {
1050
+ videoSource?.dispose?.();
801
1051
  const targetScene = getScene(config);
802
1052
  if (segmentMeshes.length > 0) {
803
1053
  segmentMeshes.forEach((m) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assetCache.ts","../src/createElementProps.ts","../src/renderers/image.ts","../src/renderers/svg.ts","../src/renderers/text.ts","../src/renderers/video.ts","../src/renderElements.ts","../src/index.ts"],"sourcesContent":["/**\n * Global asset cache - stores preloaded assets by URL.\n * Handles images, videos, and SVGs.\n */\nexport const AssetCache = {\n images: new Map<\n string,\n { element: HTMLImageElement; width: number; height: number }\n >(),\n videos: new Map<\n string,\n {\n element: HTMLVideoElement\n blobUrl?: string\n duration: number\n width: number\n height: number\n }\n >(),\n svgContents: new Map<string, string>(),\n\n getImage(url: string) {\n return this.images.get(url) || null\n },\n getVideo(url: string) {\n return this.videos.get(url) || null\n },\n getSVG(url: string) {\n return this.svgContents.get(url) || null\n },\n\n dispose() {\n this.videos.forEach((cached) => {\n cached.element.pause()\n cached.element.src = ''\n cached.element.load()\n if (cached.blobUrl) {\n URL.revokeObjectURL(cached.blobUrl)\n }\n })\n this.images.clear()\n this.videos.clear()\n this.svgContents.clear()\n },\n}\n\n/**\n * Extract all asset URLs from elements config\n */\nfunction extractAssetUrls(elementsConfig: any[]) {\n const assets: {\n images: string[]\n videos: string[]\n svgs: string[]\n } = {\n images: [],\n videos: [],\n svgs: [],\n }\n\n for (const el of elementsConfig) {\n if (el.type === 'image' && el.src) {\n assets.images.push(el.src)\n } else if (el.type === 'video' && el.src) {\n assets.videos.push(el.src)\n } else if (el.type === 'svg' && el.src) {\n assets.svgs.push(el.src)\n }\n }\n\n assets.images = [...new Set(assets.images)]\n assets.videos = [...new Set(assets.videos)]\n assets.svgs = [...new Set(assets.svgs)]\n\n return assets\n}\n\nasync function preloadImage(url: string) {\n if (AssetCache.images.has(url)) return\n\n const img = new Image()\n img.crossOrigin = 'anonymous'\n\n await new Promise<void>((resolve) => {\n img.onload = () => resolve()\n img.onerror = () => {\n console.warn('Failed to preload image:', url)\n resolve()\n }\n img.src = url\n })\n\n AssetCache.images.set(url, {\n element: img,\n width: img.naturalWidth,\n height: img.naturalHeight,\n })\n}\n\nasync function preloadVideo(url: string) {\n if (AssetCache.videos.has(url)) return\n\n try {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error('Failed to fetch video: ' + response.status)\n }\n const blob = await response.blob()\n const blobUrl = URL.createObjectURL(blob)\n\n const video = document.createElement('video')\n video.crossOrigin = 'anonymous'\n video.muted = true\n video.playsInline = true\n video.preload = 'auto'\n\n await new Promise<void>((resolve, reject) => {\n video.oncanplaythrough = () => resolve()\n video.onerror = reject\n video.src = blobUrl\n video.load()\n })\n\n video.currentTime = 0\n await new Promise<void>((resolve) => {\n video.onseeked = () => resolve()\n setTimeout(() => resolve(), 100)\n })\n\n video.pause()\n\n console.log('Video preloaded:', url, 'duration:', video.duration)\n\n AssetCache.videos.set(url, {\n element: video,\n blobUrl,\n duration: video.duration,\n width: video.videoWidth,\n height: video.videoHeight,\n })\n } catch (e) {\n console.warn('Failed to preload video:', url, e)\n }\n}\n\nasync function preloadSVG(url: string) {\n if (AssetCache.svgContents.has(url)) return\n\n try {\n if (url.startsWith('<svg') || url.startsWith('<?xml')) {\n AssetCache.svgContents.set(url, url)\n return\n }\n\n if (url.startsWith('data:image/svg')) {\n const base64 = url.split(',')[1]\n const svgContent = atob(base64)\n AssetCache.svgContents.set(url, svgContent)\n return\n }\n\n const response = await fetch(url)\n const svgContent = await response.text()\n AssetCache.svgContents.set(url, svgContent)\n } catch (e) {\n console.warn('Failed to preload SVG:', url, e)\n AssetCache.svgContents.set(url, '')\n }\n}\n\n/**\n * Preload all assets from elements config.\n * Reports progress via postMessage to parent.\n */\nexport async function preloadAssets(elementsConfig: any[]) {\n const assets = extractAssetUrls(elementsConfig)\n const total = assets.images.length + assets.videos.length + assets.svgs.length\n\n if (total === 0) return\n\n let loaded = 0\n\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'PRELOAD_PROGRESS', loaded: 0, total },\n '*',\n )\n }\n\n const preloadWithProgress = async (\n fn: (url: string) => Promise<void>,\n url: string,\n ) => {\n await fn(url)\n loaded++\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'PRELOAD_PROGRESS', loaded, total },\n '*',\n )\n }\n }\n\n const promises = [\n ...assets.images.map((url) => preloadWithProgress(preloadImage, url)),\n ...assets.videos.map((url) => preloadWithProgress(preloadVideo, url)),\n ...assets.svgs.map((url) => preloadWithProgress(preloadSVG, url)),\n ]\n\n await Promise.all(promises)\n}\n","import type * as THREE_NS from 'three'\n\n/**\n * Create GSAP-animatable props proxy for an element.\n * For video elements, pass the videoElement to enable currentTime animation.\n */\nexport function createElementProps(\n _THREE: typeof THREE_NS,\n mesh: THREE_NS.Mesh,\n initialX: number,\n initialY: number,\n initialOpacity = 1,\n videoElement: HTMLVideoElement | null = null,\n) {\n // Capture base scale (set by renderer for resolution scaling)\n const baseScaleX = mesh.scale.x\n const baseScaleY = mesh.scale.y\n\n const state: Record<string, any> = {\n x: initialX,\n y: initialY,\n z: 0,\n opacity: initialOpacity,\n scale: 1,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n rotationX: 0,\n rotationY: 0,\n zIndex: mesh.userData.zIndex ?? 0,\n // Video-specific properties (only meaningful if videoElement is provided)\n currentTime: videoElement ? videoElement.currentTime : 0,\n playing: false,\n startOffset: 0,\n }\n\n const updateMeshPosition = () => {\n mesh.position.x = state.x\n mesh.position.y = -state.y\n mesh.position.z = state.z\n }\n\n const updateMeshTransform = () => {\n mesh.scale.set(\n baseScaleX * state.scale * state.scaleX,\n baseScaleY * state.scale * state.scaleY,\n 1,\n )\n mesh.rotation.set(\n (state.rotationX * Math.PI) / 180,\n (state.rotationY * Math.PI) / 180,\n (state.rotation * Math.PI) / 180,\n )\n }\n\n const updateMeshOpacity = () => {\n const mat = mesh.material as THREE_NS.MeshBasicMaterial\n mat.opacity = state.opacity\n mat.needsUpdate = true\n }\n\n const updateVideoPlayback = () => {\n if (!videoElement) return\n\n // Video plays only if:\n // 1. It is marked as 'playing' (active in timeline)\n // 2. The global timeline is NOT paused\n const vos = (window as any).__vos__\n const shouldPlay = state.playing && !vos?.isPaused\n\n if (shouldPlay) {\n if (videoElement.paused) {\n const timeDiff = Math.abs(videoElement.currentTime - state.currentTime)\n if (timeDiff > 0.5) {\n videoElement.currentTime = state.currentTime\n }\n videoElement.play().catch(() => {})\n }\n } else {\n videoElement.pause()\n if (Math.abs(videoElement.currentTime - state.currentTime) > 0.05) {\n videoElement.currentTime = state.currentTime\n }\n }\n }\n\n // Register callback for global pause/resume\n if (videoElement) {\n const vos = (window as any).__vos__\n if (vos?.videoCallbacks) {\n vos.videoCallbacks.add(updateVideoPlayback)\n }\n }\n\n const updateVideoCurrentTime = () => {\n if (!videoElement) return\n const vos = (window as any).__vos__\n if (!state.playing || vos?.isPaused) {\n videoElement.currentTime = state.currentTime\n }\n }\n\n return new Proxy(state, {\n set(target, prop, value) {\n target[prop as string] = value\n switch (prop) {\n case 'x':\n case 'y':\n case 'z':\n updateMeshPosition()\n break\n case 'scale':\n case 'scaleX':\n case 'scaleY':\n case 'rotation':\n case 'rotationX':\n case 'rotationY':\n updateMeshTransform()\n break\n case 'opacity':\n updateMeshOpacity()\n break\n case 'zIndex':\n mesh.renderOrder = value\n mesh.userData.zIndex = value\n break\n case 'currentTime':\n updateVideoCurrentTime()\n break\n case 'playing':\n case 'startOffset':\n updateVideoPlayback()\n break\n }\n return true\n },\n get(target, prop) {\n if (prop === 'duration' && videoElement) {\n return videoElement.duration\n }\n return target[prop as string]\n },\n })\n}\n","import { AssetCache } from '../assetCache'\nimport type * as THREE_NS from 'three'\n\n/**\n * Load and render an image element\n */\nexport async function renderImageElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { src, size = {} } = element\n\n let img: HTMLImageElement\n const cached = AssetCache.getImage(src)\n if (cached) {\n img = cached.element\n } else {\n img = new Image()\n img.crossOrigin = 'anonymous'\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = reject\n img.src = src\n })\n }\n\n let width = size.width ?? img.naturalWidth\n let height = size.height ?? img.naturalHeight\n\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n const targetHeight =\n typeof size.height === 'number' ? size.height : img.naturalHeight\n width = (img.naturalWidth / img.naturalHeight) * targetHeight\n height = targetHeight\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n const targetWidth =\n typeof size.width === 'number' ? size.width : img.naturalWidth\n height = (img.naturalHeight / img.naturalWidth) * targetWidth\n width = targetWidth\n }\n\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')!\n\n const fit = size.fit ?? 'fill'\n if (fit === 'contain' || fit === 'cover') {\n const scale =\n fit === 'contain'\n ? Math.min(width / img.naturalWidth, height / img.naturalHeight)\n : Math.max(width / img.naturalWidth, height / img.naturalHeight)\n const drawWidth = img.naturalWidth * scale\n const drawHeight = img.naturalHeight * scale\n const offsetX = (width - drawWidth) / 2\n const offsetY = (height - drawHeight) / 2\n ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight)\n } else {\n ctx.drawImage(img, 0, 0, width, height)\n }\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(width, height)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, width, height }\n}\n","import { AssetCache } from '../assetCache'\nimport type * as THREE_NS from 'three'\n\n/**\n * Load and render an SVG element\n */\nexport async function renderSVGElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { src, size = {}, colors = {} } = element\n\n let svgContent = AssetCache.getSVG(src)\n if (!svgContent) {\n if (src.startsWith('http') || src.startsWith('/')) {\n const response = await fetch(src)\n svgContent = await response.text()\n } else {\n svgContent = src\n }\n }\n\n Object.entries(colors).forEach(([selector, color]) => {\n const regex = new RegExp(`(${selector}[^>]*)(fill|stroke)=\"[^\"]*\"`, 'g')\n svgContent = svgContent!.replace(regex, `$1$2=\"${color}\"`)\n })\n\n const parser = new DOMParser()\n const svgDoc = parser.parseFromString(svgContent!, 'image/svg+xml')\n const svgElement = svgDoc.documentElement\n\n const viewBox = svgElement.getAttribute('viewBox')\n let svgWidth = parseFloat(svgElement.getAttribute('width') || '') || 100\n let svgHeight = parseFloat(svgElement.getAttribute('height') || '') || 100\n\n if (viewBox) {\n const [, , vbW, vbH] = viewBox.split(' ').map(Number)\n svgWidth = vbW || svgWidth\n svgHeight = vbH || svgHeight\n }\n\n let width = size.width ?? svgWidth\n let height = size.height ?? svgHeight\n\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n width = (svgWidth / svgHeight) * size.height\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n height = (svgHeight / svgWidth) * size.width\n }\n\n svgElement.setAttribute('width', String(width))\n svgElement.setAttribute('height', String(height))\n const updatedSvg = new XMLSerializer().serializeToString(svgElement)\n\n const blob = new Blob([updatedSvg], { type: 'image/svg+xml' })\n const url = URL.createObjectURL(blob)\n\n const img = new Image()\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = reject\n img.src = url\n })\n\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')!\n ctx.drawImage(img, 0, 0, width, height)\n\n URL.revokeObjectURL(url)\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(width, height)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, width, height }\n}\n","import type * as THREE_NS from 'three'\n\n/**\n * Render text to canvas and create textured plane\n */\nexport function renderTextElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { content, font = {} } = element\n const fontSize = font.size ?? 24\n const fontFamily = font.family ?? 'Inter, system-ui, sans-serif'\n const fontWeight = font.weight ?? 'normal'\n const fontStyle = font.style ?? 'normal'\n const color = font.color ?? '#ffffff'\n const align = font.align ?? 'left'\n const letterSpacing = font.letterSpacing ?? 0\n const lineHeight = font.lineHeight ?? 1.2\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')!\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`\n ctx.font = fontString\n\n const lines = content.split('\\n')\n let maxWidth = 0\n lines.forEach((line: string) => {\n let w = 0\n for (let i = 0; i < line.length; i++) {\n w += ctx.measureText(line[i]).width\n if (i < line.length - 1) w += letterSpacing\n }\n if (w > maxWidth) maxWidth = w\n })\n\n const totalHeight = lines.length * fontSize * lineHeight\n const padding =\n Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 10\n const canvasWidth = Math.ceil(maxWidth + padding * 2)\n const canvasHeight = Math.ceil(totalHeight + padding * 2)\n\n canvas.width = canvasWidth\n canvas.height = canvasHeight\n ctx.font = fontString\n ctx.textBaseline = 'top'\n ctx.textAlign = align as CanvasTextAlign\n ctx.clearRect(0, 0, canvasWidth, canvasHeight)\n\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color\n ctx.shadowBlur = element.shadow.blur\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0\n }\n\n let textX = padding\n if (align === 'center') textX = canvasWidth / 2\n else if (align === 'right') textX = canvasWidth - padding\n\n lines.forEach((line: string, i: number) => {\n const y = padding + i * fontSize * lineHeight\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color\n ctx.lineWidth = element.stroke.width\n ctx.lineJoin = 'round'\n ctx.strokeText(line, textX, y)\n }\n ctx.fillStyle = color\n ctx.fillText(line, textX, y)\n })\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, canvas, width: canvasWidth, height: canvasHeight }\n}\n\n/**\n * Render a single text segment (char/word) to canvas and create mesh\n */\nexport function renderTextSegment(\n text: string,\n font: any,\n element: any,\n THREE: typeof THREE_NS,\n) {\n const fontSize = font.size ?? 24\n const fontFamily = font.family ?? 'Inter, system-ui, sans-serif'\n const fontWeight = font.weight ?? 'normal'\n const fontStyle = font.style ?? 'normal'\n const color = font.color ?? '#ffffff'\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')!\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`\n ctx.font = fontString\n\n const metrics = ctx.measureText(text)\n const padding =\n Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 4\n const canvasWidth = Math.ceil(metrics.width + padding * 2)\n const canvasHeight = Math.ceil(fontSize * 1.4 + padding * 2)\n\n canvas.width = canvasWidth\n canvas.height = canvasHeight\n ctx.font = fontString\n ctx.textBaseline = 'top'\n ctx.textAlign = 'left'\n\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color\n ctx.shadowBlur = element.shadow.blur\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0\n }\n\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color\n ctx.lineWidth = element.stroke.width\n ctx.lineJoin = 'round'\n ctx.strokeText(text, padding, padding)\n }\n ctx.fillStyle = color\n ctx.fillText(text, padding, padding)\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight)\n const mesh = new THREE.Mesh(geometry, material)\n\n return {\n mesh,\n width: canvasWidth,\n height: canvasHeight,\n textWidth: metrics.width,\n }\n}\n\n/**\n * Render split text - creates multiple meshes (one per char/word/line)\n */\nexport function renderSplitTextElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { content, font = {}, split } = element\n const splitType = split?.type ?? 'chars'\n const letterSpacing = font.letterSpacing ?? 0\n\n let segments: string[]\n if (splitType === 'chars') {\n segments = content.split('')\n } else if (splitType === 'words') {\n segments = content.split(/\\s+/)\n } else {\n segments = content.split('\\n')\n }\n\n const meshes: any[] = []\n let totalWidth = 0\n\n segments.forEach((text: string, i: number) => {\n if (text.length === 0) return\n\n const result = renderTextSegment(text, font, element, THREE)\n meshes.push({\n mesh: result.mesh,\n width: result.width,\n height: result.height,\n textWidth: result.textWidth,\n text,\n })\n totalWidth += result.textWidth\n if (i < segments.length - 1) {\n totalWidth +=\n splitType === 'words' ? (font.size ?? 24) * 0.4 : letterSpacing\n }\n })\n\n const totalHeight = meshes[0]?.height ?? 0\n let currentX = -totalWidth / 2\n\n meshes.forEach((item: any, i: number) => {\n item.offsetX = currentX + item.textWidth / 2\n item.offsetY = 0\n currentX += item.textWidth\n if (i < meshes.length - 1) {\n currentX +=\n splitType === 'words' ? (font.size ?? 24) * 0.4 : letterSpacing\n }\n })\n\n return { meshes, totalWidth, totalHeight }\n}\n","import { AssetCache } from '../assetCache'\nimport type * as THREE_NS from 'three'\n\n/**\n * Load and render a video element\n */\nexport async function renderVideoElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0,\n } = element\n\n let video: HTMLVideoElement\n const cached = AssetCache.getVideo(src)\n if (cached) {\n video = cached.element\n video.loop = loop\n video.muted = muted\n video.playbackRate = playbackRate\n } else {\n video = document.createElement('video')\n video.crossOrigin = 'anonymous'\n video.src = src\n video.loop = loop\n video.muted = muted\n video.playbackRate = playbackRate\n video.playsInline = true\n video.preload = 'auto'\n\n await new Promise<void>((resolve, reject) => {\n video.oncanplaythrough = () => resolve()\n video.onerror = reject\n video.load()\n })\n }\n\n video.currentTime = startTime\n video.pause()\n\n let width = size.width ?? video.videoWidth\n let height = size.height ?? video.videoHeight\n\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n const targetHeight =\n typeof size.height === 'number' ? size.height : video.videoHeight\n width = (video.videoWidth / video.videoHeight) * targetHeight\n height = targetHeight\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n const targetWidth =\n typeof size.width === 'number' ? size.width : video.videoWidth\n height = (video.videoHeight / video.videoWidth) * targetWidth\n width = targetWidth\n }\n\n const texture = new THREE.VideoTexture(video)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.format = THREE.RGBAFormat\n texture.generateMipmaps = false\n texture.colorSpace = THREE.SRGBColorSpace\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(width, height)\n const mesh = new THREE.Mesh(geometry, material)\n\n mesh.userData.video = video\n mesh.userData.texture = texture\n\n return { mesh, width, height, video }\n}\n","import { preloadAssets } from './assetCache'\nimport { createElementProps } from './createElementProps'\nimport { renderImageElement } from './renderers/image'\nimport { renderSVGElement } from './renderers/svg'\nimport { renderSplitTextElement, renderTextElement } from './renderers/text'\nimport { renderVideoElement } from './renderers/video'\nimport type * as THREE_NS from 'three'\n\n/**\n * Calculate position from config\n */\nfunction calculatePosition(\n position: any,\n resolution: any,\n elementWidth: number,\n elementHeight: number,\n) {\n const { width, height } = resolution\n const halfW = elementWidth / 2\n const halfH = elementHeight / 2\n\n if (typeof position === 'string') {\n switch (position) {\n case 'center':\n return { x: width / 2 - halfW, y: height / 2 - halfH }\n case 'top-left':\n return { x: 0, y: 0 }\n case 'top-center':\n return { x: width / 2 - halfW, y: 0 }\n case 'top-right':\n return { x: width - elementWidth, y: 0 }\n case 'center-left':\n return { x: 0, y: height / 2 - halfH }\n case 'center-right':\n return { x: width - elementWidth, y: height / 2 - halfH }\n case 'bottom-left':\n return { x: 0, y: height - elementHeight }\n case 'bottom-center':\n return { x: width / 2 - halfW, y: height - elementHeight }\n case 'bottom-right':\n return { x: width - elementWidth, y: height - elementHeight }\n default:\n return { x: 0, y: 0 }\n }\n }\n\n const x =\n typeof position.x === 'string'\n ? (parseFloat(position.x) / 100) * width\n : position.x\n const y =\n typeof position.y === 'string'\n ? (parseFloat(position.y) / 100) * height\n : position.y\n return { x, y }\n}\n\n// Design resolution baseline\nconst DESIGN_HEIGHT = 1080\n\n/**\n * Render all elements to a dedicated overlay scene with pixel-space camera.\n */\nexport async function renderElements(\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n THREE: typeof THREE_NS,\n) {\n const getScene = (config: any) => overlayScenes[config.zIndex ?? 100]\n await preloadAssets(elementsConfig)\n\n const elementMap = new Map()\n const resolutionScale = resolution.height / DESIGN_HEIGHT\n\n for (let i = 0; i < elementsConfig.length; i++) {\n const config = elementsConfig[i]\n const id = config.id ?? `element_${i}`\n\n try {\n let mesh: THREE_NS.Mesh\n let canvas: HTMLCanvasElement | null = null\n let elementWidth = 0\n let elementHeight = 0\n let segments: any = null\n const segmentMeshes: THREE_NS.Mesh[] = []\n let videoElement: HTMLVideoElement | null = null\n\n if (config.type === 'text' && config.split) {\n const splitResult = renderSplitTextElement(config, resolution, THREE)\n elementWidth = splitResult.totalWidth\n elementHeight = splitResult.totalHeight\n\n const scaledWidth = elementWidth * resolutionScale\n const scaledHeight = elementHeight * resolutionScale\n\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight,\n )\n const basePosX = x - resolution.width / 2 + scaledWidth / 2\n const basePosY = -(y - resolution.height / 2 + scaledHeight / 2)\n\n let transformX = 0\n let transformY = 0\n if (config.transform) {\n transformX = (config.transform.translateX ?? 0) * resolutionScale\n transformY = -((config.transform.translateY ?? 0) * resolutionScale)\n }\n\n const zIndex = config.zIndex ?? 100\n\n segments = splitResult.meshes.map((item: any, si: number) => {\n const segMesh = item.mesh\n segMesh.scale.set(resolutionScale, resolutionScale, 1)\n segMesh.position.x =\n basePosX + item.offsetX * resolutionScale + transformX\n segMesh.position.y =\n basePosY + item.offsetY * resolutionScale + transformY\n segMesh.position.z = 0\n segMesh.renderOrder = zIndex + i * 0.01 + si * 0.001\n\n if (config.opacity !== undefined) {\n segMesh.material.opacity = config.opacity\n }\n\n getScene(config).add(segMesh)\n segmentMeshes.push(segMesh)\n\n return createElementProps(\n THREE,\n segMesh,\n segMesh.position.x,\n -segMesh.position.y,\n config.opacity ?? 1,\n )\n })\n\n mesh = splitResult.meshes[0]?.mesh ?? new THREE.Mesh()\n } else if (config.type === 'text') {\n const result = renderTextElement(config, resolution, THREE)\n mesh = result.mesh\n canvas = result.canvas\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'image') {\n const result = await renderImageElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'svg') {\n const result = await renderSVGElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'video') {\n const result = await renderVideoElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n mesh.userData.video = result.video\n videoElement = result.video\n videoElement.pause()\n } else {\n mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 }),\n )\n console.warn(`Element type \"${config.type}\" not implemented`)\n }\n\n if (!config.split) {\n const scaledWidth = elementWidth * resolutionScale\n const scaledHeight = elementHeight * resolutionScale\n\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight,\n )\n\n const posX = x - resolution.width / 2 + scaledWidth / 2\n const posY = -(y - resolution.height / 2 + scaledHeight / 2)\n\n mesh.scale.set(resolutionScale, resolutionScale, 1)\n mesh.position.x = posX\n mesh.position.y = posY\n mesh.position.z = 0\n\n const zIndex = config.zIndex ?? 100\n mesh.renderOrder = zIndex + i * 0.01\n\n if (config.opacity !== undefined) {\n ;(mesh.material as THREE_NS.MeshBasicMaterial).opacity =\n config.opacity\n ;(mesh.material as THREE_NS.MeshBasicMaterial).transparent = true\n }\n\n if (config.transform) {\n const t = config.transform\n if (t.translateX) mesh.position.x += t.translateX * resolutionScale\n if (t.translateY) mesh.position.y -= t.translateY * resolutionScale\n if (t.translateZ) mesh.position.z += t.translateZ\n if (t.scale)\n mesh.scale.set(\n t.scale * resolutionScale,\n t.scale * resolutionScale,\n 1,\n )\n if (t.rotateZ || t.rotation) {\n mesh.rotation.z = ((t.rotateZ ?? t.rotation ?? 0) * Math.PI) / 180\n }\n }\n\n getScene(config).add(mesh)\n }\n\n const props = createElementProps(\n THREE,\n mesh,\n mesh.position.x,\n -mesh.position.y,\n config.opacity ?? 1,\n videoElement,\n )\n\n const elementInstance = {\n config,\n mesh,\n node: null,\n props,\n segments,\n setContent: (_content: string) => {\n if (config.type === 'text' && canvas) {\n console.warn('setContent not fully implemented in inline renderer')\n }\n },\n destroy: () => {\n const targetScene = getScene(config)\n if (segmentMeshes.length > 0) {\n segmentMeshes.forEach((m) => {\n targetScene.remove(m)\n m.geometry.dispose()\n const mat = m.material as THREE_NS.MeshBasicMaterial\n if (mat.map) mat.map.dispose()\n mat.dispose()\n })\n } else {\n targetScene.remove(mesh)\n mesh.geometry.dispose()\n const mat = mesh.material as THREE_NS.MeshBasicMaterial\n if (mat.map) mat.map.dispose()\n mat.dispose()\n }\n },\n }\n\n elementMap.set(id, elementInstance)\n } catch (error) {\n console.warn(\n `[vos] Failed to render element \"${id}\" (${config.type}):`,\n error,\n )\n // Insert transparent placeholder so layout/animation refs still work\n const fallbackMesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 }),\n )\n getScene(config).add(fallbackMesh)\n const props = createElementProps(THREE, fallbackMesh, 0, 0, 0)\n elementMap.set(id, {\n config,\n mesh: fallbackMesh,\n node: null,\n props,\n segments: null,\n setContent: () => {},\n destroy: () => {\n getScene(config).remove(fallbackMesh)\n fallbackMesh.geometry.dispose()\n fallbackMesh.material.dispose()\n },\n })\n }\n }\n\n return elementMap\n}\n","import { renderElements } from './renderElements'\nimport type * as THREE_NS from 'three'\n\nexport interface VosElements {\n renderElements: (\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n THREE: typeof THREE_NS,\n ) => Promise<Map<string, any>>\n disposeElements: (elementMap: Map<string, any>) => void\n}\n\n/**\n * Factory: create the Vos element system bound to a THREE instance.\n */\nexport function createVosElements(THREE: typeof THREE_NS): VosElements {\n return {\n renderElements: (\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n ) => renderElements(elementsConfig, overlayScenes, resolution, THREE),\n disposeElements: (elementMap: Map<string, any>) => {\n elementMap.forEach((instance) => instance.destroy?.())\n elementMap.clear()\n },\n }\n}\n\nexport { renderElements } from './renderElements'\n"],"mappings":"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","names":["svgContent"]}
1
+ {"version":3,"sources":["../src/assetCache.ts","../src/createElementProps.ts","../src/renderers/image.ts","../src/renderers/svg.ts","../src/renderers/text.ts","../src/video/sampleIndex.ts","../src/video/FrameAccurateVideoSource.ts","../src/renderers/video.ts","../src/renderElements.ts","../src/index.ts"],"sourcesContent":["/**\n * Global asset cache - stores preloaded assets by URL.\n * Handles images, videos, and SVGs.\n */\nexport const AssetCache = {\n images: new Map<\n string,\n { element: HTMLImageElement; width: number; height: number }\n >(),\n videos: new Map<\n string,\n {\n element: HTMLVideoElement\n blobUrl?: string\n duration: number\n width: number\n height: number\n }\n >(),\n svgContents: new Map<string, string>(),\n\n getImage(url: string) {\n return this.images.get(url) || null\n },\n getVideo(url: string) {\n return this.videos.get(url) || null\n },\n getSVG(url: string) {\n return this.svgContents.get(url) || null\n },\n\n dispose() {\n this.videos.forEach((cached) => {\n cached.element.pause()\n cached.element.src = ''\n cached.element.load()\n if (cached.blobUrl) {\n URL.revokeObjectURL(cached.blobUrl)\n }\n })\n this.images.clear()\n this.videos.clear()\n this.svgContents.clear()\n },\n}\n\n/**\n * Extract all asset URLs from elements config\n */\nfunction extractAssetUrls(elementsConfig: any[]) {\n const assets: {\n images: string[]\n videos: string[]\n svgs: string[]\n } = {\n images: [],\n videos: [],\n svgs: [],\n }\n\n for (const el of elementsConfig) {\n if (el.type === 'image' && el.src) {\n assets.images.push(el.src)\n } else if (el.type === 'video' && el.src) {\n assets.videos.push(el.src)\n } else if (el.type === 'svg' && el.src) {\n assets.svgs.push(el.src)\n }\n }\n\n assets.images = [...new Set(assets.images)]\n assets.videos = [...new Set(assets.videos)]\n assets.svgs = [...new Set(assets.svgs)]\n\n return assets\n}\n\nasync function preloadImage(url: string) {\n if (AssetCache.images.has(url)) return\n\n const img = new Image()\n img.crossOrigin = 'anonymous'\n\n await new Promise<void>((resolve) => {\n img.onload = () => resolve()\n img.onerror = () => {\n console.warn('Failed to preload image:', url)\n resolve()\n }\n img.src = url\n })\n\n AssetCache.images.set(url, {\n element: img,\n width: img.naturalWidth,\n height: img.naturalHeight,\n })\n}\n\nasync function preloadVideo(url: string) {\n if (AssetCache.videos.has(url)) return\n\n try {\n const response = await fetch(url)\n if (!response.ok) {\n throw new Error('Failed to fetch video: ' + response.status)\n }\n const blob = await response.blob()\n const blobUrl = URL.createObjectURL(blob)\n\n const video = document.createElement('video')\n video.crossOrigin = 'anonymous'\n video.muted = true\n video.playsInline = true\n video.preload = 'auto'\n\n await new Promise<void>((resolve, reject) => {\n video.oncanplaythrough = () => resolve()\n video.onerror = reject\n video.src = blobUrl\n video.load()\n })\n\n video.currentTime = 0\n await new Promise<void>((resolve) => {\n video.onseeked = () => resolve()\n setTimeout(() => resolve(), 100)\n })\n\n video.pause()\n\n console.log('Video preloaded:', url, 'duration:', video.duration)\n\n AssetCache.videos.set(url, {\n element: video,\n blobUrl,\n duration: video.duration,\n width: video.videoWidth,\n height: video.videoHeight,\n })\n } catch (e) {\n console.warn('Failed to preload video:', url, e)\n }\n}\n\nasync function preloadSVG(url: string) {\n if (AssetCache.svgContents.has(url)) return\n\n try {\n if (url.startsWith('<svg') || url.startsWith('<?xml')) {\n AssetCache.svgContents.set(url, url)\n return\n }\n\n if (url.startsWith('data:image/svg')) {\n const base64 = url.split(',')[1]\n const svgContent = atob(base64)\n AssetCache.svgContents.set(url, svgContent)\n return\n }\n\n const response = await fetch(url)\n const svgContent = await response.text()\n AssetCache.svgContents.set(url, svgContent)\n } catch (e) {\n console.warn('Failed to preload SVG:', url, e)\n AssetCache.svgContents.set(url, '')\n }\n}\n\n/**\n * Preload all assets from elements config.\n * Reports progress via postMessage to parent.\n */\nexport async function preloadAssets(elementsConfig: any[]) {\n const assets = extractAssetUrls(elementsConfig)\n const total = assets.images.length + assets.videos.length + assets.svgs.length\n\n if (total === 0) return\n\n let loaded = 0\n\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'PRELOAD_PROGRESS', loaded: 0, total },\n '*',\n )\n }\n\n const preloadWithProgress = async (\n fn: (url: string) => Promise<void>,\n url: string,\n ) => {\n await fn(url)\n loaded++\n if (window.parent !== window) {\n window.parent.postMessage(\n { type: 'PRELOAD_PROGRESS', loaded, total },\n '*',\n )\n }\n }\n\n const promises = [\n ...assets.images.map((url) => preloadWithProgress(preloadImage, url)),\n ...assets.videos.map((url) => preloadWithProgress(preloadVideo, url)),\n ...assets.svgs.map((url) => preloadWithProgress(preloadSVG, url)),\n ]\n\n await Promise.all(promises)\n}\n","import type * as THREE_NS from 'three'\n\n/**\n * Create GSAP-animatable props proxy for an element.\n * For video elements, pass the videoElement to enable currentTime animation.\n */\ninterface FrameAccurateSource {\n seekTo: (tSec: number) => Promise<void>\n}\n\nexport function createElementProps(\n _THREE: typeof THREE_NS,\n mesh: THREE_NS.Mesh,\n initialX: number,\n initialY: number,\n initialOpacity = 1,\n videoElement: HTMLVideoElement | null = null,\n videoSource: FrameAccurateSource | null = null,\n videoTexture: THREE_NS.Texture | null = null,\n) {\n // Capture base scale (set by renderer for resolution scaling)\n const baseScaleX = mesh.scale.x\n const baseScaleY = mesh.scale.y\n\n const state: Record<string, any> = {\n x: initialX,\n y: initialY,\n z: 0,\n opacity: initialOpacity,\n scale: 1,\n scaleX: 1,\n scaleY: 1,\n rotation: 0,\n rotationX: 0,\n rotationY: 0,\n zIndex: mesh.userData.zIndex ?? 0,\n // Video-specific properties (only meaningful if videoElement is provided)\n currentTime: videoElement ? videoElement.currentTime : 0,\n playing: false,\n startOffset: 0,\n }\n\n const updateMeshPosition = () => {\n mesh.position.x = state.x\n mesh.position.y = -state.y\n mesh.position.z = state.z\n }\n\n const updateMeshTransform = () => {\n mesh.scale.set(\n baseScaleX * state.scale * state.scaleX,\n baseScaleY * state.scale * state.scaleY,\n 1,\n )\n mesh.rotation.set(\n (state.rotationX * Math.PI) / 180,\n (state.rotationY * Math.PI) / 180,\n (state.rotation * Math.PI) / 180,\n )\n }\n\n const updateMeshOpacity = () => {\n const mat = mesh.material as THREE_NS.MeshBasicMaterial\n mat.opacity = state.opacity\n mat.needsUpdate = true\n }\n\n const updateVideoPlayback = () => {\n if (!videoElement) return\n\n // Video plays only if:\n // 1. It is marked as 'playing' (active in timeline)\n // 2. The global timeline is NOT paused\n const vos = (window as any).__vos__\n const shouldPlay = state.playing && !vos?.isPaused\n\n if (shouldPlay) {\n if (videoElement.paused) {\n const timeDiff = Math.abs(videoElement.currentTime - state.currentTime)\n if (timeDiff > 0.5) {\n videoElement.currentTime = state.currentTime\n }\n videoElement.play().catch(() => {})\n }\n } else {\n videoElement.pause()\n if (Math.abs(videoElement.currentTime - state.currentTime) > 0.05) {\n videoElement.currentTime = state.currentTime\n }\n }\n }\n\n // Register callback for global pause/resume\n if (videoElement) {\n const vos = (window as any).__vos__\n if (vos?.videoCallbacks) {\n vos.videoCallbacks.add(updateVideoPlayback)\n }\n }\n\n const updateVideoCurrentTime = () => {\n // Frame-accurate path: decode the exact frame and register the decode so\n // waitForVideosReady() awaits it (deterministic export/scrub).\n if (videoSource) {\n const vos = (window as any).__vos__\n const p = videoSource\n .seekTo(state.currentTime)\n .then(() => {\n if (videoTexture) videoTexture.needsUpdate = true\n })\n .catch((e: unknown) => console.error('[vos] frame decode failed', e))\n vos?.registerDecode?.(p)\n return\n }\n // Legacy HTMLVideoElement path.\n if (!videoElement) return\n const vos = (window as any).__vos__\n if (!state.playing || vos?.isPaused) {\n videoElement.currentTime = state.currentTime\n }\n }\n\n return new Proxy(state, {\n set(target, prop, value) {\n target[prop as string] = value\n switch (prop) {\n case 'x':\n case 'y':\n case 'z':\n updateMeshPosition()\n break\n case 'scale':\n case 'scaleX':\n case 'scaleY':\n case 'rotation':\n case 'rotationX':\n case 'rotationY':\n updateMeshTransform()\n break\n case 'opacity':\n updateMeshOpacity()\n break\n case 'zIndex':\n mesh.renderOrder = value\n mesh.userData.zIndex = value\n break\n case 'currentTime':\n updateVideoCurrentTime()\n break\n case 'playing':\n case 'startOffset':\n updateVideoPlayback()\n break\n }\n return true\n },\n get(target, prop) {\n if (prop === 'duration' && videoElement) {\n return videoElement.duration\n }\n return target[prop as string]\n },\n })\n}\n","import { AssetCache } from '../assetCache'\nimport type * as THREE_NS from 'three'\n\n/**\n * Load and render an image element\n */\nexport async function renderImageElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { src, size = {} } = element\n\n let img: HTMLImageElement\n const cached = AssetCache.getImage(src)\n if (cached) {\n img = cached.element\n } else {\n img = new Image()\n img.crossOrigin = 'anonymous'\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = reject\n img.src = src\n })\n }\n\n let width = size.width ?? img.naturalWidth\n let height = size.height ?? img.naturalHeight\n\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n const targetHeight =\n typeof size.height === 'number' ? size.height : img.naturalHeight\n width = (img.naturalWidth / img.naturalHeight) * targetHeight\n height = targetHeight\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n const targetWidth =\n typeof size.width === 'number' ? size.width : img.naturalWidth\n height = (img.naturalHeight / img.naturalWidth) * targetWidth\n width = targetWidth\n }\n\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')!\n\n const fit = size.fit ?? 'fill'\n if (fit === 'contain' || fit === 'cover') {\n const scale =\n fit === 'contain'\n ? Math.min(width / img.naturalWidth, height / img.naturalHeight)\n : Math.max(width / img.naturalWidth, height / img.naturalHeight)\n const drawWidth = img.naturalWidth * scale\n const drawHeight = img.naturalHeight * scale\n const offsetX = (width - drawWidth) / 2\n const offsetY = (height - drawHeight) / 2\n ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight)\n } else {\n ctx.drawImage(img, 0, 0, width, height)\n }\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(width, height)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, width, height }\n}\n","import { AssetCache } from '../assetCache'\nimport type * as THREE_NS from 'three'\n\n/**\n * Load and render an SVG element\n */\nexport async function renderSVGElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { src, size = {}, colors = {} } = element\n\n let svgContent = AssetCache.getSVG(src)\n if (!svgContent) {\n if (src.startsWith('http') || src.startsWith('/')) {\n const response = await fetch(src)\n svgContent = await response.text()\n } else {\n svgContent = src\n }\n }\n\n Object.entries(colors).forEach(([selector, color]) => {\n const regex = new RegExp(`(${selector}[^>]*)(fill|stroke)=\"[^\"]*\"`, 'g')\n svgContent = svgContent!.replace(regex, `$1$2=\"${color}\"`)\n })\n\n const parser = new DOMParser()\n const svgDoc = parser.parseFromString(svgContent!, 'image/svg+xml')\n const svgElement = svgDoc.documentElement\n\n const viewBox = svgElement.getAttribute('viewBox')\n let svgWidth = parseFloat(svgElement.getAttribute('width') || '') || 100\n let svgHeight = parseFloat(svgElement.getAttribute('height') || '') || 100\n\n if (viewBox) {\n const [, , vbW, vbH] = viewBox.split(' ').map(Number)\n svgWidth = vbW || svgWidth\n svgHeight = vbH || svgHeight\n }\n\n let width = size.width ?? svgWidth\n let height = size.height ?? svgHeight\n\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n width = (svgWidth / svgHeight) * size.height\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n height = (svgHeight / svgWidth) * size.width\n }\n\n svgElement.setAttribute('width', String(width))\n svgElement.setAttribute('height', String(height))\n const updatedSvg = new XMLSerializer().serializeToString(svgElement)\n\n const blob = new Blob([updatedSvg], { type: 'image/svg+xml' })\n const url = URL.createObjectURL(blob)\n\n const img = new Image()\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve()\n img.onerror = reject\n img.src = url\n })\n\n const canvas = document.createElement('canvas')\n canvas.width = width\n canvas.height = height\n const ctx = canvas.getContext('2d')!\n ctx.drawImage(img, 0, 0, width, height)\n\n URL.revokeObjectURL(url)\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(width, height)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, width, height }\n}\n","import type * as THREE_NS from 'three'\n\n/**\n * Render text to canvas and create textured plane\n */\nexport function renderTextElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { content, font = {} } = element\n const fontSize = font.size ?? 24\n const fontFamily = font.family ?? 'Inter, system-ui, sans-serif'\n const fontWeight = font.weight ?? 'normal'\n const fontStyle = font.style ?? 'normal'\n const color = font.color ?? '#ffffff'\n const align = font.align ?? 'left'\n const letterSpacing = font.letterSpacing ?? 0\n const lineHeight = font.lineHeight ?? 1.2\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')!\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`\n ctx.font = fontString\n\n const lines = content.split('\\n')\n let maxWidth = 0\n lines.forEach((line: string) => {\n let w = 0\n for (let i = 0; i < line.length; i++) {\n w += ctx.measureText(line[i]).width\n if (i < line.length - 1) w += letterSpacing\n }\n if (w > maxWidth) maxWidth = w\n })\n\n const totalHeight = lines.length * fontSize * lineHeight\n const padding =\n Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 10\n const canvasWidth = Math.ceil(maxWidth + padding * 2)\n const canvasHeight = Math.ceil(totalHeight + padding * 2)\n\n canvas.width = canvasWidth\n canvas.height = canvasHeight\n ctx.font = fontString\n ctx.textBaseline = 'top'\n ctx.textAlign = align as CanvasTextAlign\n ctx.clearRect(0, 0, canvasWidth, canvasHeight)\n\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color\n ctx.shadowBlur = element.shadow.blur\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0\n }\n\n let textX = padding\n if (align === 'center') textX = canvasWidth / 2\n else if (align === 'right') textX = canvasWidth - padding\n\n lines.forEach((line: string, i: number) => {\n const y = padding + i * fontSize * lineHeight\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color\n ctx.lineWidth = element.stroke.width\n ctx.lineJoin = 'round'\n ctx.strokeText(line, textX, y)\n }\n ctx.fillStyle = color\n ctx.fillText(line, textX, y)\n })\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight)\n const mesh = new THREE.Mesh(geometry, material)\n\n return { mesh, canvas, width: canvasWidth, height: canvasHeight }\n}\n\n/**\n * Render a single text segment (char/word) to canvas and create mesh\n */\nexport function renderTextSegment(\n text: string,\n font: any,\n element: any,\n THREE: typeof THREE_NS,\n) {\n const fontSize = font.size ?? 24\n const fontFamily = font.family ?? 'Inter, system-ui, sans-serif'\n const fontWeight = font.weight ?? 'normal'\n const fontStyle = font.style ?? 'normal'\n const color = font.color ?? '#ffffff'\n\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')!\n const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`\n ctx.font = fontString\n\n const metrics = ctx.measureText(text)\n const padding =\n Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 4\n const canvasWidth = Math.ceil(metrics.width + padding * 2)\n const canvasHeight = Math.ceil(fontSize * 1.4 + padding * 2)\n\n canvas.width = canvasWidth\n canvas.height = canvasHeight\n ctx.font = fontString\n ctx.textBaseline = 'top'\n ctx.textAlign = 'left'\n\n if (element.shadow) {\n ctx.shadowColor = element.shadow.color\n ctx.shadowBlur = element.shadow.blur\n ctx.shadowOffsetX = element.shadow.offsetX ?? 0\n ctx.shadowOffsetY = element.shadow.offsetY ?? 0\n }\n\n if (element.stroke) {\n ctx.strokeStyle = element.stroke.color\n ctx.lineWidth = element.stroke.width\n ctx.lineJoin = 'round'\n ctx.strokeText(text, padding, padding)\n }\n ctx.fillStyle = color\n ctx.fillText(text, padding, padding)\n\n const texture = new THREE.CanvasTexture(canvas)\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.needsUpdate = true\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n\n const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight)\n const mesh = new THREE.Mesh(geometry, material)\n\n return {\n mesh,\n width: canvasWidth,\n height: canvasHeight,\n textWidth: metrics.width,\n }\n}\n\n/**\n * Render split text - creates multiple meshes (one per char/word/line)\n */\nexport function renderSplitTextElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const { content, font = {}, split } = element\n const splitType = split?.type ?? 'chars'\n const letterSpacing = font.letterSpacing ?? 0\n\n let segments: string[]\n if (splitType === 'chars') {\n segments = content.split('')\n } else if (splitType === 'words') {\n segments = content.split(/\\s+/)\n } else {\n segments = content.split('\\n')\n }\n\n const meshes: any[] = []\n let totalWidth = 0\n\n segments.forEach((text: string, i: number) => {\n if (text.length === 0) return\n\n const result = renderTextSegment(text, font, element, THREE)\n meshes.push({\n mesh: result.mesh,\n width: result.width,\n height: result.height,\n textWidth: result.textWidth,\n text,\n })\n totalWidth += result.textWidth\n if (i < segments.length - 1) {\n totalWidth +=\n splitType === 'words' ? (font.size ?? 24) * 0.4 : letterSpacing\n }\n })\n\n const totalHeight = meshes[0]?.height ?? 0\n let currentX = -totalWidth / 2\n\n meshes.forEach((item: any, i: number) => {\n item.offsetX = currentX + item.textWidth / 2\n item.offsetY = 0\n currentX += item.textWidth\n if (i < meshes.length - 1) {\n currentX +=\n splitType === 'words' ? (font.size ?? 24) * 0.4 : letterSpacing\n }\n })\n\n return { meshes, totalWidth, totalHeight }\n}\n","/**\n * Pure sample-indexing helpers for frame-accurate video seeking.\n *\n * Separated from the WebCodecs glue so the tricky part — selecting the right\n * decode range for a presentation time, given B-frames — is unit-testable\n * without a browser. (Feeding samples in CTS/presentation order instead of\n * decode order is what produced `EncodingError` in the S2 spike; these helpers\n * encode the correct decode-order GOP selection.)\n */\n\nexport interface SampleMeta {\n /** Composition (presentation) timestamp in seconds. */\n cts: number\n /** Whether this is a sync sample (keyframe / IDR). */\n isSync: boolean\n}\n\n/** Decode-order indices of all keyframes, ascending. */\nexport function buildSyncIndices(samples: SampleMeta[]): number[] {\n const out: number[] = []\n for (let i = 0; i < samples.length; i++) if (samples[i].isSync) out.push(i)\n return out\n}\n\n/**\n * Decode-order index of the sample whose CTS is the largest ≤ t.\n * Samples are in DECODE order (not sorted by CTS), so this is a linear scan.\n */\nexport function targetDecodeIndex(samples: SampleMeta[], tSec: number): number {\n let di = 0\n let best = -Infinity\n for (let i = 0; i < samples.length; i++) {\n const c = samples[i].cts\n if (c <= tSec && c > best) {\n best = c\n di = i\n }\n }\n return di\n}\n\n/**\n * GOP bounds [ki, ni) in decode order that contain `targetDI`:\n * ki = keyframe at/before targetDI, ni = next keyframe after it (or end).\n * Decode samples[ki..ni-1] in order, then select the output by PTS.\n */\nexport function gopBounds(\n syncIndices: number[],\n totalSamples: number,\n targetDI: number,\n): [number, number] {\n let ki = 0\n for (const si of syncIndices) {\n if (si <= targetDI) ki = si\n else break\n }\n let ni = totalSamples\n for (const si of syncIndices) {\n if (si > targetDI) {\n ni = si\n break\n }\n }\n return [ki, ni]\n}\n","/**\n * Frame-accurate video source: WebCodecs `VideoDecoder` + mp4box demux.\n *\n * `seekTo(t)` decodes the exact frame at presentation time `t` (deterministically,\n * including B-frames) and draws it to `canvas` — which the caller wraps in a\n * THREE.CanvasTexture. This replaces `HTMLVideoElement.currentTime` sync, which is\n * NOT frame-accurate (audio-clock-backed, keyframe-snaps, async).\n *\n * Validated by the S2 spike (deterministic + ~12ms/seek @1080p). mp4box is loaded\n * from esm.sh at runtime (consistent with vos's CDN addon strategy; keeps the\n * injectable elements bundle lean). Requires a secure context with WebCodecs.\n */\nimport {\n buildSyncIndices,\n gopBounds,\n targetDecodeIndex,\n type SampleMeta,\n} from './sampleIndex'\n\nconst MP4BOX_URL = 'https://esm.sh/mp4box@0.5.2'\n\nlet mp4boxPromise: Promise<any> | null = null\nfunction loadMp4Box(): Promise<any> {\n if (!mp4boxPromise) {\n mp4boxPromise = import(/* @vite-ignore */ MP4BOX_URL).then((m) => m.default ?? m)\n }\n return mp4boxPromise\n}\n\ninterface DecodeSample extends SampleMeta {\n chunk: EncodedVideoChunk\n}\n\nexport function isFrameAccurateSupported(): boolean {\n return typeof VideoDecoder !== 'undefined' && typeof EncodedVideoChunk !== 'undefined'\n}\n\nexport class FrameAccurateVideoSource {\n readonly canvas: HTMLCanvasElement\n private ctx: CanvasRenderingContext2D\n private decoder!: VideoDecoder\n private samples: DecodeSample[] = [] // DECODE order (as delivered by mp4box)\n private syncIndices: number[] = []\n private onFrame: ((f: VideoFrame) => void) | null = null\n private lastError: unknown = null\n durationSec = 0\n fps = 30\n codedWidth = 0\n codedHeight = 0\n\n private constructor() {\n this.canvas = document.createElement('canvas')\n this.ctx = this.canvas.getContext('2d')!\n }\n\n static async create(src: string): Promise<FrameAccurateVideoSource> {\n const self = new FrameAccurateVideoSource()\n self.decoder = new VideoDecoder({\n output: (f) => self.onFrame?.(f),\n error: (e) => {\n self.lastError = e\n console.error('[vos] VideoDecoder error', e)\n },\n })\n const buf = await fetch(src).then((r) => {\n if (!r.ok) throw new Error(`[vos] failed to fetch video: ${src} (${r.status})`)\n return r.arrayBuffer()\n })\n await self.demux(buf)\n self.canvas.width = self.codedWidth\n self.canvas.height = self.codedHeight\n await self.seekTo(0)\n return self\n }\n\n private async demux(data: ArrayBuffer): Promise<void> {\n const MP4Box = await loadMp4Box()\n return new Promise<void>((resolve, reject) => {\n const file = MP4Box.createFile()\n let expected = Infinity\n let settled = false\n const finish = () => {\n if (settled) return\n settled = true\n if (!this.samples.length) {\n reject(new Error('[vos] no samples extracted'))\n return\n }\n this.syncIndices = buildSyncIndices(this.samples)\n resolve()\n }\n file.onError = (e: unknown) => {\n if (settled) return\n settled = true\n reject(new Error(String(e)))\n }\n file.onReady = (info: any) => {\n const track = info.videoTracks?.[0]\n if (!track) {\n settled = true\n reject(new Error('[vos] no video track in mp4'))\n return\n }\n this.codedWidth = track.video.width\n this.codedHeight = track.video.height\n this.durationSec = info.duration / info.timescale\n this.fps = track.nb_samples / this.durationSec || 30\n expected = track.nb_samples || Infinity\n this.decoder.configure({\n codec: track.codec,\n description: this.getDescription(MP4Box, file, track.id),\n codedWidth: this.codedWidth,\n codedHeight: this.codedHeight,\n })\n file.setExtractionOptions(track.id, null, { nbSamples: Infinity })\n file.start()\n }\n file.onSamples = (_id: number, _user: unknown, arr: any[]) => {\n for (const s of arr) {\n // DECODE order preserved (do NOT sort by cts) — B-frames need decode order.\n this.samples.push({\n cts: s.cts / s.timescale,\n isSync: !!s.is_sync,\n chunk: new EncodedVideoChunk({\n type: s.is_sync ? 'key' : 'delta',\n timestamp: Math.round((s.cts / s.timescale) * 1e6),\n duration: Math.round((s.duration / s.timescale) * 1e6),\n data: s.data,\n }),\n })\n }\n if (this.samples.length >= expected) finish()\n }\n ;(data as any).fileStart = 0\n file.appendBuffer(data)\n file.flush()\n // mp4box may deliver samples synchronously (during flush) or across later\n // tasks. Settle shortly after flush with whatever arrived — resolve if any\n // samples were extracted, else reject. Avoids both the race (premature\n // \"no samples\") and hangs.\n setTimeout(finish, 2000)\n })\n }\n\n private getDescription(MP4Box: any, file: any, trackId: number): Uint8Array {\n const trak = file.getTrackById(trackId)\n for (const entry of trak.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.vpcC ?? entry.av1C\n if (box) {\n const stream = new MP4Box.DataStream(undefined, 0, MP4Box.DataStream.BIG_ENDIAN)\n box.write(stream)\n return new Uint8Array(stream.buffer, 8) // strip 8-byte box header\n }\n }\n throw new Error('[vos] no codec description (avcC/hvcC/vpcC/av1C) found')\n }\n\n /**\n * Decode the exact frame at presentation time `t` and draw it to `canvas`.\n * Decodes the whole GOP (keyframe → next keyframe) in decode order, then\n * selects the output by PTS. Returns when the canvas is updated.\n */\n async seekTo(tSec: number): Promise<void> {\n if (!this.samples.length) return\n const targetDI = targetDecodeIndex(this.samples, tSec)\n const [ki, ni] = gopBounds(this.syncIndices, this.samples.length, targetDI)\n const targetTs = Math.round(this.samples[targetDI].cts * 1e6)\n\n this.lastError = null\n const collected: VideoFrame[] = []\n this.onFrame = (f) => collected.push(f)\n for (let i = ki; i < ni; i++) this.decoder.decode(this.samples[i].chunk)\n await this.decoder.flush()\n this.onFrame = null\n if (this.lastError) throw this.lastError\n\n let best: VideoFrame | undefined\n let bestD = Infinity\n for (const f of collected) {\n const d = Math.abs(f.timestamp - targetTs)\n if (d < bestD) {\n bestD = d\n best = f\n }\n }\n if (best) this.ctx.drawImage(best, 0, 0)\n for (const f of collected) f.close()\n }\n\n dispose(): void {\n try {\n this.decoder.close()\n } catch {\n // already closed\n }\n }\n}\n","import { AssetCache } from '../assetCache'\nimport {\n FrameAccurateVideoSource,\n isFrameAccurateSupported,\n} from '../video/FrameAccurateVideoSource'\nimport type * as THREE_NS from 'three'\n\n/** Resolve element dimensions from an intrinsic w/h + optional size config (with 'auto'). */\nfunction resolveSize(size: any, intrinsicW: number, intrinsicH: number) {\n let width = size.width ?? intrinsicW\n let height = size.height ?? intrinsicH\n if (size.width === 'auto' && size.height !== 'auto' && size.height) {\n const targetHeight = typeof size.height === 'number' ? size.height : intrinsicH\n width = (intrinsicW / intrinsicH) * targetHeight\n height = targetHeight\n } else if (size.height === 'auto' && size.width !== 'auto' && size.width) {\n const targetWidth = typeof size.width === 'number' ? size.width : intrinsicW\n height = (intrinsicH / intrinsicW) * targetWidth\n width = targetWidth\n }\n return { width, height }\n}\n\nfunction applyTextureSettings(texture: THREE_NS.Texture, THREE: typeof THREE_NS) {\n texture.minFilter = THREE.LinearFilter\n texture.magFilter = THREE.LinearFilter\n texture.format = THREE.RGBAFormat\n texture.generateMipmaps = false\n texture.colorSpace = THREE.SRGBColorSpace\n}\n\n/**\n * Load and render a video element.\n *\n * `frameSource`:\n * - 'html5' (default): HTMLVideoElement + VideoTexture (legacy; currentTime sync, NOT frame-accurate)\n * - 'webcodecs': frame-accurate WebCodecs decode (deterministic export/scrub)\n * - 'auto': webcodecs when supported, else html5\n */\nexport async function renderVideoElement(\n element: any,\n _resolution: any,\n THREE: typeof THREE_NS,\n) {\n const frameSource: string = element.frameSource ?? 'html5'\n const useWebCodecs =\n frameSource === 'webcodecs' ||\n (frameSource === 'auto' && isFrameAccurateSupported())\n\n // ---- Frame-accurate WebCodecs path ----------------------------------------\n // On any failure (non-MP4 container, unsupported codec, decode error) we fall\n // through to the robust HTMLVideoElement path instead of failing the element —\n // black video is never an acceptable outcome.\n if (useWebCodecs) {\n try {\n return await renderWebCodecsVideo(element, THREE)\n } catch (err) {\n console.warn(\n '[vos] frame-accurate video unavailable; falling back to html5 —',\n err instanceof Error ? err.message : err,\n )\n }\n }\n\n return renderHtml5Video(element, THREE)\n}\n\nasync function renderWebCodecsVideo(element: any, THREE: typeof THREE_NS) {\n const { src, size = {} } = element\n const source = await FrameAccurateVideoSource.create(src)\n const { width, height } = resolveSize(size, source.codedWidth, source.codedHeight)\n\n const texture = new THREE.CanvasTexture(source.canvas)\n applyTextureSettings(texture, THREE)\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material)\n mesh.userData.videoSource = source\n mesh.userData.texture = texture\n texture.needsUpdate = true // initial frame drawn at t=0 by create()\n\n return { mesh, width, height, video: null, videoSource: source, texture }\n}\n\n// ---- Legacy HTMLVideoElement path -------------------------------------------\nasync function renderHtml5Video(element: any, THREE: typeof THREE_NS) {\n const {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0,\n } = element\n\n let video: HTMLVideoElement\n const cached = AssetCache.getVideo(src)\n if (cached) {\n video = cached.element\n video.loop = loop\n video.muted = muted\n video.playbackRate = playbackRate\n } else {\n video = document.createElement('video')\n video.crossOrigin = 'anonymous'\n video.src = src\n video.loop = loop\n video.muted = muted\n video.playbackRate = playbackRate\n video.playsInline = true\n video.preload = 'auto'\n\n await new Promise<void>((resolve, reject) => {\n video.oncanplaythrough = () => resolve()\n video.onerror = reject\n video.load()\n })\n }\n\n video.currentTime = startTime\n video.pause()\n\n const { width, height } = resolveSize(size, video.videoWidth, video.videoHeight)\n\n const texture = new THREE.VideoTexture(video)\n applyTextureSettings(texture, THREE)\n\n const material = new THREE.MeshBasicMaterial({\n map: texture,\n transparent: true,\n depthWrite: false,\n })\n const mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), material)\n mesh.userData.video = video\n mesh.userData.texture = texture\n\n return { mesh, width, height, video, videoSource: null, texture }\n}\n","import { preloadAssets } from './assetCache'\nimport { createElementProps } from './createElementProps'\nimport { renderImageElement } from './renderers/image'\nimport { renderSVGElement } from './renderers/svg'\nimport { renderSplitTextElement, renderTextElement } from './renderers/text'\nimport { renderVideoElement } from './renderers/video'\nimport type * as THREE_NS from 'three'\n\n/**\n * Calculate position from config\n */\nfunction calculatePosition(\n position: any,\n resolution: any,\n elementWidth: number,\n elementHeight: number,\n) {\n const { width, height } = resolution\n const halfW = elementWidth / 2\n const halfH = elementHeight / 2\n\n if (typeof position === 'string') {\n switch (position) {\n case 'center':\n return { x: width / 2 - halfW, y: height / 2 - halfH }\n case 'top-left':\n return { x: 0, y: 0 }\n case 'top-center':\n return { x: width / 2 - halfW, y: 0 }\n case 'top-right':\n return { x: width - elementWidth, y: 0 }\n case 'center-left':\n return { x: 0, y: height / 2 - halfH }\n case 'center-right':\n return { x: width - elementWidth, y: height / 2 - halfH }\n case 'bottom-left':\n return { x: 0, y: height - elementHeight }\n case 'bottom-center':\n return { x: width / 2 - halfW, y: height - elementHeight }\n case 'bottom-right':\n return { x: width - elementWidth, y: height - elementHeight }\n default:\n return { x: 0, y: 0 }\n }\n }\n\n const x =\n typeof position.x === 'string'\n ? (parseFloat(position.x) / 100) * width\n : position.x\n const y =\n typeof position.y === 'string'\n ? (parseFloat(position.y) / 100) * height\n : position.y\n return { x, y }\n}\n\n// Design resolution baseline\nconst DESIGN_HEIGHT = 1080\n\n/**\n * Render all elements to a dedicated overlay scene with pixel-space camera.\n */\nexport async function renderElements(\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n THREE: typeof THREE_NS,\n) {\n const getScene = (config: any) => overlayScenes[config.zIndex ?? 100]\n await preloadAssets(elementsConfig)\n\n const elementMap = new Map()\n const resolutionScale = resolution.height / DESIGN_HEIGHT\n\n for (let i = 0; i < elementsConfig.length; i++) {\n const config = elementsConfig[i]\n const id = config.id ?? `element_${i}`\n\n try {\n let mesh: THREE_NS.Mesh\n let canvas: HTMLCanvasElement | null = null\n let elementWidth = 0\n let elementHeight = 0\n let segments: any = null\n const segmentMeshes: THREE_NS.Mesh[] = []\n let videoElement: HTMLVideoElement | null = null\n let videoSource: any = null\n let videoTexture: THREE_NS.Texture | null = null\n\n if (config.type === 'text' && config.split) {\n const splitResult = renderSplitTextElement(config, resolution, THREE)\n elementWidth = splitResult.totalWidth\n elementHeight = splitResult.totalHeight\n\n const scaledWidth = elementWidth * resolutionScale\n const scaledHeight = elementHeight * resolutionScale\n\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight,\n )\n const basePosX = x - resolution.width / 2 + scaledWidth / 2\n const basePosY = -(y - resolution.height / 2 + scaledHeight / 2)\n\n let transformX = 0\n let transformY = 0\n if (config.transform) {\n transformX = (config.transform.translateX ?? 0) * resolutionScale\n transformY = -((config.transform.translateY ?? 0) * resolutionScale)\n }\n\n const zIndex = config.zIndex ?? 100\n\n segments = splitResult.meshes.map((item: any, si: number) => {\n const segMesh = item.mesh\n segMesh.scale.set(resolutionScale, resolutionScale, 1)\n segMesh.position.x =\n basePosX + item.offsetX * resolutionScale + transformX\n segMesh.position.y =\n basePosY + item.offsetY * resolutionScale + transformY\n segMesh.position.z = 0\n segMesh.renderOrder = zIndex + i * 0.01 + si * 0.001\n\n if (config.opacity !== undefined) {\n segMesh.material.opacity = config.opacity\n }\n\n getScene(config).add(segMesh)\n segmentMeshes.push(segMesh)\n\n return createElementProps(\n THREE,\n segMesh,\n segMesh.position.x,\n -segMesh.position.y,\n config.opacity ?? 1,\n )\n })\n\n mesh = splitResult.meshes[0]?.mesh ?? new THREE.Mesh()\n } else if (config.type === 'text') {\n const result = renderTextElement(config, resolution, THREE)\n mesh = result.mesh\n canvas = result.canvas\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'image') {\n const result = await renderImageElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'svg') {\n const result = await renderSVGElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n } else if (config.type === 'video') {\n const result = await renderVideoElement(config, resolution, THREE)\n mesh = result.mesh\n elementWidth = result.width\n elementHeight = result.height\n mesh.userData.video = result.video\n videoElement = result.video\n videoSource = result.videoSource\n videoTexture = result.texture\n videoElement?.pause() // null on the webcodecs path\n } else {\n mesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 }),\n )\n console.warn(`Element type \"${config.type}\" not implemented`)\n }\n\n if (!config.split) {\n const scaledWidth = elementWidth * resolutionScale\n const scaledHeight = elementHeight * resolutionScale\n\n const { x, y } = calculatePosition(\n config.position,\n resolution,\n scaledWidth,\n scaledHeight,\n )\n\n const posX = x - resolution.width / 2 + scaledWidth / 2\n const posY = -(y - resolution.height / 2 + scaledHeight / 2)\n\n mesh.scale.set(resolutionScale, resolutionScale, 1)\n mesh.position.x = posX\n mesh.position.y = posY\n mesh.position.z = 0\n\n const zIndex = config.zIndex ?? 100\n mesh.renderOrder = zIndex + i * 0.01\n\n if (config.opacity !== undefined) {\n ;(mesh.material as THREE_NS.MeshBasicMaterial).opacity =\n config.opacity\n ;(mesh.material as THREE_NS.MeshBasicMaterial).transparent = true\n }\n\n if (config.transform) {\n const t = config.transform\n if (t.translateX) mesh.position.x += t.translateX * resolutionScale\n if (t.translateY) mesh.position.y -= t.translateY * resolutionScale\n if (t.translateZ) mesh.position.z += t.translateZ\n if (t.scale)\n mesh.scale.set(\n t.scale * resolutionScale,\n t.scale * resolutionScale,\n 1,\n )\n if (t.rotateZ || t.rotation) {\n mesh.rotation.z = ((t.rotateZ ?? t.rotation ?? 0) * Math.PI) / 180\n }\n }\n\n getScene(config).add(mesh)\n }\n\n const props = createElementProps(\n THREE,\n mesh,\n mesh.position.x,\n -mesh.position.y,\n config.opacity ?? 1,\n videoElement,\n videoSource,\n videoTexture,\n )\n\n const elementInstance = {\n config,\n mesh,\n node: null,\n props,\n segments,\n setContent: (_content: string) => {\n if (config.type === 'text' && canvas) {\n console.warn('setContent not fully implemented in inline renderer')\n }\n },\n destroy: () => {\n videoSource?.dispose?.()\n const targetScene = getScene(config)\n if (segmentMeshes.length > 0) {\n segmentMeshes.forEach((m) => {\n targetScene.remove(m)\n m.geometry.dispose()\n const mat = m.material as THREE_NS.MeshBasicMaterial\n if (mat.map) mat.map.dispose()\n mat.dispose()\n })\n } else {\n targetScene.remove(mesh)\n mesh.geometry.dispose()\n const mat = mesh.material as THREE_NS.MeshBasicMaterial\n if (mat.map) mat.map.dispose()\n mat.dispose()\n }\n },\n }\n\n elementMap.set(id, elementInstance)\n } catch (error) {\n console.warn(\n `[vos] Failed to render element \"${id}\" (${config.type}):`,\n error,\n )\n // Insert transparent placeholder so layout/animation refs still work\n const fallbackMesh = new THREE.Mesh(\n new THREE.PlaneGeometry(100, 100),\n new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 }),\n )\n getScene(config).add(fallbackMesh)\n const props = createElementProps(THREE, fallbackMesh, 0, 0, 0)\n elementMap.set(id, {\n config,\n mesh: fallbackMesh,\n node: null,\n props,\n segments: null,\n setContent: () => {},\n destroy: () => {\n getScene(config).remove(fallbackMesh)\n fallbackMesh.geometry.dispose()\n fallbackMesh.material.dispose()\n },\n })\n }\n }\n\n return elementMap\n}\n","import { renderElements } from './renderElements'\nimport type * as THREE_NS from 'three'\n\nexport interface VosElements {\n renderElements: (\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n THREE: typeof THREE_NS,\n ) => Promise<Map<string, any>>\n disposeElements: (elementMap: Map<string, any>) => void\n}\n\n/**\n * Factory: create the Vos element system bound to a THREE instance.\n */\nexport function createVosElements(THREE: typeof THREE_NS): VosElements {\n return {\n renderElements: (\n elementsConfig: any[],\n overlayScenes: Record<number, THREE_NS.Scene>,\n resolution: any,\n ) => renderElements(elementsConfig, overlayScenes, resolution, THREE),\n disposeElements: (elementMap: Map<string, any>) => {\n elementMap.forEach((instance) => instance.destroy?.())\n elementMap.clear()\n },\n }\n}\n\nexport { renderElements } from './renderElements'\n"],"mappings":";AAIO,IAAM,aAAa;AAAA,EACxB,QAAQ,oBAAI,IAGV;AAAA,EACF,QAAQ,oBAAI,IASV;AAAA,EACF,aAAa,oBAAI,IAAoB;AAAA,EAErC,SAAS,KAAa;AACpB,WAAO,KAAK,OAAO,IAAI,GAAG,KAAK;AAAA,EACjC;AAAA,EACA,SAAS,KAAa;AACpB,WAAO,KAAK,OAAO,IAAI,GAAG,KAAK;AAAA,EACjC;AAAA,EACA,OAAO,KAAa;AAClB,WAAO,KAAK,YAAY,IAAI,GAAG,KAAK;AAAA,EACtC;AAAA,EAEA,UAAU;AACR,SAAK,OAAO,QAAQ,CAAC,WAAW;AAC9B,aAAO,QAAQ,MAAM;AACrB,aAAO,QAAQ,MAAM;AACrB,aAAO,QAAQ,KAAK;AACpB,UAAI,OAAO,SAAS;AAClB,YAAI,gBAAgB,OAAO,OAAO;AAAA,MACpC;AAAA,IACF,CAAC;AACD,SAAK,OAAO,MAAM;AAClB,SAAK,OAAO,MAAM;AAClB,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;AAKA,SAAS,iBAAiB,gBAAuB;AAC/C,QAAM,SAIF;AAAA,IACF,QAAQ,CAAC;AAAA,IACT,QAAQ,CAAC;AAAA,IACT,MAAM,CAAC;AAAA,EACT;AAEA,aAAW,MAAM,gBAAgB;AAC/B,QAAI,GAAG,SAAS,WAAW,GAAG,KAAK;AACjC,aAAO,OAAO,KAAK,GAAG,GAAG;AAAA,IAC3B,WAAW,GAAG,SAAS,WAAW,GAAG,KAAK;AACxC,aAAO,OAAO,KAAK,GAAG,GAAG;AAAA,IAC3B,WAAW,GAAG,SAAS,SAAS,GAAG,KAAK;AACtC,aAAO,KAAK,KAAK,GAAG,GAAG;AAAA,IACzB;AAAA,EACF;AAEA,SAAO,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,MAAM,CAAC;AAC1C,SAAO,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,MAAM,CAAC;AAC1C,SAAO,OAAO,CAAC,GAAG,IAAI,IAAI,OAAO,IAAI,CAAC;AAEtC,SAAO;AACT;AAEA,eAAe,aAAa,KAAa;AACvC,MAAI,WAAW,OAAO,IAAI,GAAG,EAAG;AAEhC,QAAM,MAAM,IAAI,MAAM;AACtB,MAAI,cAAc;AAElB,QAAM,IAAI,QAAc,CAAC,YAAY;AACnC,QAAI,SAAS,MAAM,QAAQ;AAC3B,QAAI,UAAU,MAAM;AAClB,cAAQ,KAAK,4BAA4B,GAAG;AAC5C,cAAQ;AAAA,IACV;AACA,QAAI,MAAM;AAAA,EACZ,CAAC;AAED,aAAW,OAAO,IAAI,KAAK;AAAA,IACzB,SAAS;AAAA,IACT,OAAO,IAAI;AAAA,IACX,QAAQ,IAAI;AAAA,EACd,CAAC;AACH;AAEA,eAAe,aAAa,KAAa;AACvC,MAAI,WAAW,OAAO,IAAI,GAAG,EAAG;AAEhC,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG;AAChC,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,4BAA4B,SAAS,MAAM;AAAA,IAC7D;AACA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,UAAM,UAAU,IAAI,gBAAgB,IAAI;AAExC,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,cAAc;AACpB,UAAM,QAAQ;AACd,UAAM,cAAc;AACpB,UAAM,UAAU;AAEhB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,mBAAmB,MAAM,QAAQ;AACvC,YAAM,UAAU;AAChB,YAAM,MAAM;AACZ,YAAM,KAAK;AAAA,IACb,CAAC;AAED,UAAM,cAAc;AACpB,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,WAAW,MAAM,QAAQ;AAC/B,iBAAW,MAAM,QAAQ,GAAG,GAAG;AAAA,IACjC,CAAC;AAED,UAAM,MAAM;AAEZ,YAAQ,IAAI,oBAAoB,KAAK,aAAa,MAAM,QAAQ;AAEhE,eAAW,OAAO,IAAI,KAAK;AAAA,MACzB,SAAS;AAAA,MACT;AAAA,MACA,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,QAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH,SAAS,GAAG;AACV,YAAQ,KAAK,4BAA4B,KAAK,CAAC;AAAA,EACjD;AACF;AAEA,eAAe,WAAW,KAAa;AACrC,MAAI,WAAW,YAAY,IAAI,GAAG,EAAG;AAErC,MAAI;AACF,QAAI,IAAI,WAAW,MAAM,KAAK,IAAI,WAAW,OAAO,GAAG;AACrD,iBAAW,YAAY,IAAI,KAAK,GAAG;AACnC;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,gBAAgB,GAAG;AACpC,YAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC;AAC/B,YAAMA,cAAa,KAAK,MAAM;AAC9B,iBAAW,YAAY,IAAI,KAAKA,WAAU;AAC1C;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,GAAG;AAChC,UAAM,aAAa,MAAM,SAAS,KAAK;AACvC,eAAW,YAAY,IAAI,KAAK,UAAU;AAAA,EAC5C,SAAS,GAAG;AACV,YAAQ,KAAK,0BAA0B,KAAK,CAAC;AAC7C,eAAW,YAAY,IAAI,KAAK,EAAE;AAAA,EACpC;AACF;AAMA,eAAsB,cAAc,gBAAuB;AACzD,QAAM,SAAS,iBAAiB,cAAc;AAC9C,QAAM,QAAQ,OAAO,OAAO,SAAS,OAAO,OAAO,SAAS,OAAO,KAAK;AAExE,MAAI,UAAU,EAAG;AAEjB,MAAI,SAAS;AAEb,MAAI,OAAO,WAAW,QAAQ;AAC5B,WAAO,OAAO;AAAA,MACZ,EAAE,MAAM,oBAAoB,QAAQ,GAAG,MAAM;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,QAAM,sBAAsB,OAC1B,IACA,QACG;AACH,UAAM,GAAG,GAAG;AACZ;AACA,QAAI,OAAO,WAAW,QAAQ;AAC5B,aAAO,OAAO;AAAA,QACZ,EAAE,MAAM,oBAAoB,QAAQ,MAAM;AAAA,QAC1C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW;AAAA,IACf,GAAG,OAAO,OAAO,IAAI,CAAC,QAAQ,oBAAoB,cAAc,GAAG,CAAC;AAAA,IACpE,GAAG,OAAO,OAAO,IAAI,CAAC,QAAQ,oBAAoB,cAAc,GAAG,CAAC;AAAA,IACpE,GAAG,OAAO,KAAK,IAAI,CAAC,QAAQ,oBAAoB,YAAY,GAAG,CAAC;AAAA,EAClE;AAEA,QAAM,QAAQ,IAAI,QAAQ;AAC5B;;;ACxMO,SAAS,mBACd,QACA,MACA,UACA,UACA,iBAAiB,GACjB,eAAwC,MACxC,cAA0C,MAC1C,eAAwC,MACxC;AAEA,QAAM,aAAa,KAAK,MAAM;AAC9B,QAAM,aAAa,KAAK,MAAM;AAE9B,QAAM,QAA6B;AAAA,IACjC,GAAG;AAAA,IACH,GAAG;AAAA,IACH,GAAG;AAAA,IACH,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,WAAW;AAAA,IACX,WAAW;AAAA,IACX,QAAQ,KAAK,SAAS,UAAU;AAAA;AAAA,IAEhC,aAAa,eAAe,aAAa,cAAc;AAAA,IACvD,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAEA,QAAM,qBAAqB,MAAM;AAC/B,SAAK,SAAS,IAAI,MAAM;AACxB,SAAK,SAAS,IAAI,CAAC,MAAM;AACzB,SAAK,SAAS,IAAI,MAAM;AAAA,EAC1B;AAEA,QAAM,sBAAsB,MAAM;AAChC,SAAK,MAAM;AAAA,MACT,aAAa,MAAM,QAAQ,MAAM;AAAA,MACjC,aAAa,MAAM,QAAQ,MAAM;AAAA,MACjC;AAAA,IACF;AACA,SAAK,SAAS;AAAA,MACX,MAAM,YAAY,KAAK,KAAM;AAAA,MAC7B,MAAM,YAAY,KAAK,KAAM;AAAA,MAC7B,MAAM,WAAW,KAAK,KAAM;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,UAAM,MAAM,KAAK;AACjB,QAAI,UAAU,MAAM;AACpB,QAAI,cAAc;AAAA,EACpB;AAEA,QAAM,sBAAsB,MAAM;AAChC,QAAI,CAAC,aAAc;AAKnB,UAAM,MAAO,OAAe;AAC5B,UAAM,aAAa,MAAM,WAAW,CAAC,KAAK;AAE1C,QAAI,YAAY;AACd,UAAI,aAAa,QAAQ;AACvB,cAAM,WAAW,KAAK,IAAI,aAAa,cAAc,MAAM,WAAW;AACtE,YAAI,WAAW,KAAK;AAClB,uBAAa,cAAc,MAAM;AAAA,QACnC;AACA,qBAAa,KAAK,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACpC;AAAA,IACF,OAAO;AACL,mBAAa,MAAM;AACnB,UAAI,KAAK,IAAI,aAAa,cAAc,MAAM,WAAW,IAAI,MAAM;AACjE,qBAAa,cAAc,MAAM;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAGA,MAAI,cAAc;AAChB,UAAM,MAAO,OAAe;AAC5B,QAAI,KAAK,gBAAgB;AACvB,UAAI,eAAe,IAAI,mBAAmB;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,yBAAyB,MAAM;AAGnC,QAAI,aAAa;AACf,YAAMC,OAAO,OAAe;AAC5B,YAAM,IAAI,YACP,OAAO,MAAM,WAAW,EACxB,KAAK,MAAM;AACV,YAAI,aAAc,cAAa,cAAc;AAAA,MAC/C,CAAC,EACA,MAAM,CAAC,MAAe,QAAQ,MAAM,6BAA6B,CAAC,CAAC;AACtE,MAAAA,MAAK,iBAAiB,CAAC;AACvB;AAAA,IACF;AAEA,QAAI,CAAC,aAAc;AACnB,UAAM,MAAO,OAAe;AAC5B,QAAI,CAAC,MAAM,WAAW,KAAK,UAAU;AACnC,mBAAa,cAAc,MAAM;AAAA,IACnC;AAAA,EACF;AAEA,SAAO,IAAI,MAAM,OAAO;AAAA,IACtB,IAAI,QAAQ,MAAM,OAAO;AACvB,aAAO,IAAc,IAAI;AACzB,cAAQ,MAAM;AAAA,QACZ,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AACH,6BAAmB;AACnB;AAAA,QACF,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AAAA,QACL,KAAK;AACH,8BAAoB;AACpB;AAAA,QACF,KAAK;AACH,4BAAkB;AAClB;AAAA,QACF,KAAK;AACH,eAAK,cAAc;AACnB,eAAK,SAAS,SAAS;AACvB;AAAA,QACF,KAAK;AACH,iCAAuB;AACvB;AAAA,QACF,KAAK;AAAA,QACL,KAAK;AACH,8BAAoB;AACpB;AAAA,MACJ;AACA,aAAO;AAAA,IACT;AAAA,IACA,IAAI,QAAQ,MAAM;AAChB,UAAI,SAAS,cAAc,cAAc;AACvC,eAAO,aAAa;AAAA,MACtB;AACA,aAAO,OAAO,IAAc;AAAA,IAC9B;AAAA,EACF,CAAC;AACH;;;AC7JA,eAAsB,mBACpB,SACA,aACA,OACA;AACA,QAAM,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI;AAE3B,MAAI;AACJ,QAAM,SAAS,WAAW,SAAS,GAAG;AACtC,MAAI,QAAQ;AACV,UAAM,OAAO;AAAA,EACf,OAAO;AACL,UAAM,IAAI,MAAM;AAChB,QAAI,cAAc;AAClB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAI,SAAS,MAAM,QAAQ;AAC3B,UAAI,UAAU;AACd,UAAI,MAAM;AAAA,IACZ,CAAC;AAAA,EACH;AAEA,MAAI,QAAQ,KAAK,SAAS,IAAI;AAC9B,MAAI,SAAS,KAAK,UAAU,IAAI;AAEhC,MAAI,KAAK,UAAU,UAAU,KAAK,WAAW,UAAU,KAAK,QAAQ;AAClE,UAAM,eACJ,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS,IAAI;AACtD,YAAS,IAAI,eAAe,IAAI,gBAAiB;AACjD,aAAS;AAAA,EACX,WAAW,KAAK,WAAW,UAAU,KAAK,UAAU,UAAU,KAAK,OAAO;AACxE,UAAM,cACJ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,IAAI;AACpD,aAAU,IAAI,gBAAgB,IAAI,eAAgB;AAClD,YAAQ;AAAA,EACV;AAEA,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,MAAM,OAAO,WAAW,IAAI;AAElC,QAAM,MAAM,KAAK,OAAO;AACxB,MAAI,QAAQ,aAAa,QAAQ,SAAS;AACxC,UAAM,QACJ,QAAQ,YACJ,KAAK,IAAI,QAAQ,IAAI,cAAc,SAAS,IAAI,aAAa,IAC7D,KAAK,IAAI,QAAQ,IAAI,cAAc,SAAS,IAAI,aAAa;AACnE,UAAM,YAAY,IAAI,eAAe;AACrC,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,WAAW,QAAQ,aAAa;AACtC,UAAM,WAAW,SAAS,cAAc;AACxC,QAAI,UAAU,KAAK,SAAS,SAAS,WAAW,UAAU;AAAA,EAC5D,OAAO;AACL,QAAI,UAAU,KAAK,GAAG,GAAG,OAAO,MAAM;AAAA,EACxC;AAEA,QAAM,UAAU,IAAI,MAAM,cAAc,MAAM;AAC9C,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,MAAM;AAC1B,UAAQ,cAAc;AAEtB,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AAED,QAAM,WAAW,IAAI,MAAM,cAAc,OAAO,MAAM;AACtD,QAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAE9C,SAAO,EAAE,MAAM,OAAO,OAAO;AAC/B;;;ACvEA,eAAsB,iBACpB,SACA,aACA,OACA;AACA,QAAM,EAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC,EAAE,IAAI;AAExC,MAAI,aAAa,WAAW,OAAO,GAAG;AACtC,MAAI,CAAC,YAAY;AACf,QAAI,IAAI,WAAW,MAAM,KAAK,IAAI,WAAW,GAAG,GAAG;AACjD,YAAM,WAAW,MAAM,MAAM,GAAG;AAChC,mBAAa,MAAM,SAAS,KAAK;AAAA,IACnC,OAAO;AACL,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU,KAAK,MAAM;AACpD,UAAM,QAAQ,IAAI,OAAO,IAAI,QAAQ,+BAA+B,GAAG;AACvE,iBAAa,WAAY,QAAQ,OAAO,SAAS,KAAK,GAAG;AAAA,EAC3D,CAAC;AAED,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,SAAS,OAAO,gBAAgB,YAAa,eAAe;AAClE,QAAM,aAAa,OAAO;AAE1B,QAAM,UAAU,WAAW,aAAa,SAAS;AACjD,MAAI,WAAW,WAAW,WAAW,aAAa,OAAO,KAAK,EAAE,KAAK;AACrE,MAAI,YAAY,WAAW,WAAW,aAAa,QAAQ,KAAK,EAAE,KAAK;AAEvE,MAAI,SAAS;AACX,UAAM,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AACpD,eAAW,OAAO;AAClB,gBAAY,OAAO;AAAA,EACrB;AAEA,MAAI,QAAQ,KAAK,SAAS;AAC1B,MAAI,SAAS,KAAK,UAAU;AAE5B,MAAI,KAAK,UAAU,UAAU,KAAK,WAAW,UAAU,KAAK,QAAQ;AAClE,YAAS,WAAW,YAAa,KAAK;AAAA,EACxC,WAAW,KAAK,WAAW,UAAU,KAAK,UAAU,UAAU,KAAK,OAAO;AACxE,aAAU,YAAY,WAAY,KAAK;AAAA,EACzC;AAEA,aAAW,aAAa,SAAS,OAAO,KAAK,CAAC;AAC9C,aAAW,aAAa,UAAU,OAAO,MAAM,CAAC;AAChD,QAAM,aAAa,IAAI,cAAc,EAAE,kBAAkB,UAAU;AAEnE,QAAM,OAAO,IAAI,KAAK,CAAC,UAAU,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC7D,QAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,QAAM,MAAM,IAAI,MAAM;AACtB,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,QAAI,SAAS,MAAM,QAAQ;AAC3B,QAAI,UAAU;AACd,QAAI,MAAM;AAAA,EACZ,CAAC;AAED,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,UAAU,KAAK,GAAG,GAAG,OAAO,MAAM;AAEtC,MAAI,gBAAgB,GAAG;AAEvB,QAAM,UAAU,IAAI,MAAM,cAAc,MAAM;AAC9C,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,MAAM;AAC1B,UAAQ,cAAc;AAEtB,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AAED,QAAM,WAAW,IAAI,MAAM,cAAc,OAAO,MAAM;AACtD,QAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAE9C,SAAO,EAAE,MAAM,OAAO,OAAO;AAC/B;;;ACnFO,SAAS,kBACd,SACA,aACA,OACA;AACA,QAAM,EAAE,SAAS,OAAO,CAAC,EAAE,IAAI;AAC/B,QAAM,WAAW,KAAK,QAAQ;AAC9B,QAAM,aAAa,KAAK,UAAU;AAClC,QAAM,aAAa,KAAK,UAAU;AAClC,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,aAAa,KAAK,cAAc;AAEtC,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,QAAM,aAAa,GAAG,SAAS,IAAI,UAAU,IAAI,QAAQ,MAAM,UAAU;AACzE,MAAI,OAAO;AAEX,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,MAAI,WAAW;AACf,QAAM,QAAQ,CAAC,SAAiB;AAC9B,QAAI,IAAI;AACR,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,WAAK,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE;AAC9B,UAAI,IAAI,KAAK,SAAS,EAAG,MAAK;AAAA,IAChC;AACA,QAAI,IAAI,SAAU,YAAW;AAAA,EAC/B,CAAC;AAED,QAAM,cAAc,MAAM,SAAS,WAAW;AAC9C,QAAM,UACJ,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,QAAQ,QAAQ,CAAC,IAAI,IAAI;AACxE,QAAM,cAAc,KAAK,KAAK,WAAW,UAAU,CAAC;AACpD,QAAM,eAAe,KAAK,KAAK,cAAc,UAAU,CAAC;AAExD,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,MAAI,OAAO;AACX,MAAI,eAAe;AACnB,MAAI,YAAY;AAChB,MAAI,UAAU,GAAG,GAAG,aAAa,YAAY;AAE7C,MAAI,QAAQ,QAAQ;AAClB,QAAI,cAAc,QAAQ,OAAO;AACjC,QAAI,aAAa,QAAQ,OAAO;AAChC,QAAI,gBAAgB,QAAQ,OAAO,WAAW;AAC9C,QAAI,gBAAgB,QAAQ,OAAO,WAAW;AAAA,EAChD;AAEA,MAAI,QAAQ;AACZ,MAAI,UAAU,SAAU,SAAQ,cAAc;AAAA,WACrC,UAAU,QAAS,SAAQ,cAAc;AAElD,QAAM,QAAQ,CAAC,MAAc,MAAc;AACzC,UAAM,IAAI,UAAU,IAAI,WAAW;AACnC,QAAI,QAAQ,QAAQ;AAClB,UAAI,cAAc,QAAQ,OAAO;AACjC,UAAI,YAAY,QAAQ,OAAO;AAC/B,UAAI,WAAW;AACf,UAAI,WAAW,MAAM,OAAO,CAAC;AAAA,IAC/B;AACA,QAAI,YAAY;AAChB,QAAI,SAAS,MAAM,OAAO,CAAC;AAAA,EAC7B,CAAC;AAED,QAAM,UAAU,IAAI,MAAM,cAAc,MAAM;AAC9C,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,MAAM;AAC1B,UAAQ,cAAc;AAEtB,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AAED,QAAM,WAAW,IAAI,MAAM,cAAc,aAAa,YAAY;AAClE,QAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAE9C,SAAO,EAAE,MAAM,QAAQ,OAAO,aAAa,QAAQ,aAAa;AAClE;AAKO,SAAS,kBACd,MACA,MACA,SACA,OACA;AACA,QAAM,WAAW,KAAK,QAAQ;AAC9B,QAAM,aAAa,KAAK,UAAU;AAClC,QAAM,aAAa,KAAK,UAAU;AAClC,QAAM,YAAY,KAAK,SAAS;AAChC,QAAM,QAAQ,KAAK,SAAS;AAE5B,QAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,QAAM,aAAa,GAAG,SAAS,IAAI,UAAU,IAAI,QAAQ,MAAM,UAAU;AACzE,MAAI,OAAO;AAEX,QAAM,UAAU,IAAI,YAAY,IAAI;AACpC,QAAM,UACJ,KAAK,IAAI,QAAQ,QAAQ,SAAS,GAAG,QAAQ,QAAQ,QAAQ,CAAC,IAAI,IAAI;AACxE,QAAM,cAAc,KAAK,KAAK,QAAQ,QAAQ,UAAU,CAAC;AACzD,QAAM,eAAe,KAAK,KAAK,WAAW,MAAM,UAAU,CAAC;AAE3D,SAAO,QAAQ;AACf,SAAO,SAAS;AAChB,MAAI,OAAO;AACX,MAAI,eAAe;AACnB,MAAI,YAAY;AAEhB,MAAI,QAAQ,QAAQ;AAClB,QAAI,cAAc,QAAQ,OAAO;AACjC,QAAI,aAAa,QAAQ,OAAO;AAChC,QAAI,gBAAgB,QAAQ,OAAO,WAAW;AAC9C,QAAI,gBAAgB,QAAQ,OAAO,WAAW;AAAA,EAChD;AAEA,MAAI,QAAQ,QAAQ;AAClB,QAAI,cAAc,QAAQ,OAAO;AACjC,QAAI,YAAY,QAAQ,OAAO;AAC/B,QAAI,WAAW;AACf,QAAI,WAAW,MAAM,SAAS,OAAO;AAAA,EACvC;AACA,MAAI,YAAY;AAChB,MAAI,SAAS,MAAM,SAAS,OAAO;AAEnC,QAAM,UAAU,IAAI,MAAM,cAAc,MAAM;AAC9C,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,MAAM;AAC1B,UAAQ,cAAc;AAEtB,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AAED,QAAM,WAAW,IAAI,MAAM,cAAc,aAAa,YAAY;AAClE,QAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAE9C,SAAO;AAAA,IACL;AAAA,IACA,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,QAAQ;AAAA,EACrB;AACF;AAKO,SAAS,uBACd,SACA,aACA,OACA;AACA,QAAM,EAAE,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI;AACtC,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,gBAAgB,KAAK,iBAAiB;AAE5C,MAAI;AACJ,MAAI,cAAc,SAAS;AACzB,eAAW,QAAQ,MAAM,EAAE;AAAA,EAC7B,WAAW,cAAc,SAAS;AAChC,eAAW,QAAQ,MAAM,KAAK;AAAA,EAChC,OAAO;AACL,eAAW,QAAQ,MAAM,IAAI;AAAA,EAC/B;AAEA,QAAM,SAAgB,CAAC;AACvB,MAAI,aAAa;AAEjB,WAAS,QAAQ,CAAC,MAAc,MAAc;AAC5C,QAAI,KAAK,WAAW,EAAG;AAEvB,UAAM,SAAS,kBAAkB,MAAM,MAAM,SAAS,KAAK;AAC3D,WAAO,KAAK;AAAA,MACV,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,QAAQ,OAAO;AAAA,MACf,WAAW,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AACD,kBAAc,OAAO;AACrB,QAAI,IAAI,SAAS,SAAS,GAAG;AAC3B,oBACE,cAAc,WAAW,KAAK,QAAQ,MAAM,MAAM;AAAA,IACtD;AAAA,EACF,CAAC;AAED,QAAM,cAAc,OAAO,CAAC,GAAG,UAAU;AACzC,MAAI,WAAW,CAAC,aAAa;AAE7B,SAAO,QAAQ,CAAC,MAAW,MAAc;AACvC,SAAK,UAAU,WAAW,KAAK,YAAY;AAC3C,SAAK,UAAU;AACf,gBAAY,KAAK;AACjB,QAAI,IAAI,OAAO,SAAS,GAAG;AACzB,kBACE,cAAc,WAAW,KAAK,QAAQ,MAAM,MAAM;AAAA,IACtD;AAAA,EACF,CAAC;AAED,SAAO,EAAE,QAAQ,YAAY,YAAY;AAC3C;;;ACrMO,SAAS,iBAAiB,SAAiC;AAChE,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,IAAK,KAAI,QAAQ,CAAC,EAAE,OAAQ,KAAI,KAAK,CAAC;AAC1E,SAAO;AACT;AAMO,SAAS,kBAAkB,SAAuB,MAAsB;AAC7E,MAAI,KAAK;AACT,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,IAAI,QAAQ,CAAC,EAAE;AACrB,QAAI,KAAK,QAAQ,IAAI,MAAM;AACzB,aAAO;AACP,WAAK;AAAA,IACP;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,UACd,aACA,cACA,UACkB;AAClB,MAAI,KAAK;AACT,aAAW,MAAM,aAAa;AAC5B,QAAI,MAAM,SAAU,MAAK;AAAA,QACpB;AAAA,EACP;AACA,MAAI,KAAK;AACT,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,UAAU;AACjB,WAAK;AACL;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC,IAAI,EAAE;AAChB;;;AC7CA,IAAM,aAAa;AAEnB,IAAI,gBAAqC;AACzC,SAAS,aAA2B;AAClC,MAAI,CAAC,eAAe;AAClB,oBAAgB;AAAA;AAAA,MAA0B;AAAA,MAAY,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC;AAAA,EAClF;AACA,SAAO;AACT;AAMO,SAAS,2BAAoC;AAClD,SAAO,OAAO,iBAAiB,eAAe,OAAO,sBAAsB;AAC7E;AAEO,IAAM,2BAAN,MAAM,0BAAyB;AAAA,EAC3B;AAAA,EACD;AAAA,EACA;AAAA,EACA,UAA0B,CAAC;AAAA;AAAA,EAC3B,cAAwB,CAAC;AAAA,EACzB,UAA4C;AAAA,EAC5C,YAAqB;AAAA,EAC7B,cAAc;AAAA,EACd,MAAM;AAAA,EACN,aAAa;AAAA,EACb,cAAc;AAAA,EAEN,cAAc;AACpB,SAAK,SAAS,SAAS,cAAc,QAAQ;AAC7C,SAAK,MAAM,KAAK,OAAO,WAAW,IAAI;AAAA,EACxC;AAAA,EAEA,aAAa,OAAO,KAAgD;AAClE,UAAM,OAAO,IAAI,0BAAyB;AAC1C,SAAK,UAAU,IAAI,aAAa;AAAA,MAC9B,QAAQ,CAAC,MAAM,KAAK,UAAU,CAAC;AAAA,MAC/B,OAAO,CAAC,MAAM;AACZ,aAAK,YAAY;AACjB,gBAAQ,MAAM,4BAA4B,CAAC;AAAA,MAC7C;AAAA,IACF,CAAC;AACD,UAAM,MAAM,MAAM,MAAM,GAAG,EAAE,KAAK,CAAC,MAAM;AACvC,UAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,gCAAgC,GAAG,KAAK,EAAE,MAAM,GAAG;AAC9E,aAAO,EAAE,YAAY;AAAA,IACvB,CAAC;AACD,UAAM,KAAK,MAAM,GAAG;AACpB,SAAK,OAAO,QAAQ,KAAK;AACzB,SAAK,OAAO,SAAS,KAAK;AAC1B,UAAM,KAAK,OAAO,CAAC;AACnB,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,MAAM,MAAkC;AACpD,UAAM,SAAS,MAAM,WAAW;AAChC,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,YAAM,OAAO,OAAO,WAAW;AAC/B,UAAI,WAAW;AACf,UAAI,UAAU;AACd,YAAM,SAAS,MAAM;AACnB,YAAI,QAAS;AACb,kBAAU;AACV,YAAI,CAAC,KAAK,QAAQ,QAAQ;AACxB,iBAAO,IAAI,MAAM,4BAA4B,CAAC;AAC9C;AAAA,QACF;AACA,aAAK,cAAc,iBAAiB,KAAK,OAAO;AAChD,gBAAQ;AAAA,MACV;AACA,WAAK,UAAU,CAAC,MAAe;AAC7B,YAAI,QAAS;AACb,kBAAU;AACV,eAAO,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MAC7B;AACA,WAAK,UAAU,CAAC,SAAc;AAC5B,cAAM,QAAQ,KAAK,cAAc,CAAC;AAClC,YAAI,CAAC,OAAO;AACV,oBAAU;AACV,iBAAO,IAAI,MAAM,6BAA6B,CAAC;AAC/C;AAAA,QACF;AACA,aAAK,aAAa,MAAM,MAAM;AAC9B,aAAK,cAAc,MAAM,MAAM;AAC/B,aAAK,cAAc,KAAK,WAAW,KAAK;AACxC,aAAK,MAAM,MAAM,aAAa,KAAK,eAAe;AAClD,mBAAW,MAAM,cAAc;AAC/B,aAAK,QAAQ,UAAU;AAAA,UACrB,OAAO,MAAM;AAAA,UACb,aAAa,KAAK,eAAe,QAAQ,MAAM,MAAM,EAAE;AAAA,UACvD,YAAY,KAAK;AAAA,UACjB,aAAa,KAAK;AAAA,QACpB,CAAC;AACD,aAAK,qBAAqB,MAAM,IAAI,MAAM,EAAE,WAAW,SAAS,CAAC;AACjE,aAAK,MAAM;AAAA,MACb;AACA,WAAK,YAAY,CAAC,KAAa,OAAgB,QAAe;AAC5D,mBAAW,KAAK,KAAK;AAEnB,eAAK,QAAQ,KAAK;AAAA,YAChB,KAAK,EAAE,MAAM,EAAE;AAAA,YACf,QAAQ,CAAC,CAAC,EAAE;AAAA,YACZ,OAAO,IAAI,kBAAkB;AAAA,cAC3B,MAAM,EAAE,UAAU,QAAQ;AAAA,cAC1B,WAAW,KAAK,MAAO,EAAE,MAAM,EAAE,YAAa,GAAG;AAAA,cACjD,UAAU,KAAK,MAAO,EAAE,WAAW,EAAE,YAAa,GAAG;AAAA,cACrD,MAAM,EAAE;AAAA,YACV,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AACA,YAAI,KAAK,QAAQ,UAAU,SAAU,QAAO;AAAA,MAC9C;AACC,MAAC,KAAa,YAAY;AAC3B,WAAK,aAAa,IAAI;AACtB,WAAK,MAAM;AAKX,iBAAW,QAAQ,GAAI;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEQ,eAAe,QAAa,MAAW,SAA6B;AAC1E,UAAM,OAAO,KAAK,aAAa,OAAO;AACtC,eAAW,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,SAAS;AACpD,YAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAC5D,UAAI,KAAK;AACP,cAAM,SAAS,IAAI,OAAO,WAAW,QAAW,GAAG,OAAO,WAAW,UAAU;AAC/E,YAAI,MAAM,MAAM;AAChB,eAAO,IAAI,WAAW,OAAO,QAAQ,CAAC;AAAA,MACxC;AAAA,IACF;AACA,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO,MAA6B;AACxC,QAAI,CAAC,KAAK,QAAQ,OAAQ;AAC1B,UAAM,WAAW,kBAAkB,KAAK,SAAS,IAAI;AACrD,UAAM,CAAC,IAAI,EAAE,IAAI,UAAU,KAAK,aAAa,KAAK,QAAQ,QAAQ,QAAQ;AAC1E,UAAM,WAAW,KAAK,MAAM,KAAK,QAAQ,QAAQ,EAAE,MAAM,GAAG;AAE5D,SAAK,YAAY;AACjB,UAAM,YAA0B,CAAC;AACjC,SAAK,UAAU,CAAC,MAAM,UAAU,KAAK,CAAC;AACtC,aAAS,IAAI,IAAI,IAAI,IAAI,IAAK,MAAK,QAAQ,OAAO,KAAK,QAAQ,CAAC,EAAE,KAAK;AACvE,UAAM,KAAK,QAAQ,MAAM;AACzB,SAAK,UAAU;AACf,QAAI,KAAK,UAAW,OAAM,KAAK;AAE/B,QAAI;AACJ,QAAI,QAAQ;AACZ,eAAW,KAAK,WAAW;AACzB,YAAM,IAAI,KAAK,IAAI,EAAE,YAAY,QAAQ;AACzC,UAAI,IAAI,OAAO;AACb,gBAAQ;AACR,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,KAAM,MAAK,IAAI,UAAU,MAAM,GAAG,CAAC;AACvC,eAAW,KAAK,UAAW,GAAE,MAAM;AAAA,EACrC;AAAA,EAEA,UAAgB;AACd,QAAI;AACF,WAAK,QAAQ,MAAM;AAAA,IACrB,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AC5LA,SAAS,YAAY,MAAW,YAAoB,YAAoB;AACtE,MAAI,QAAQ,KAAK,SAAS;AAC1B,MAAI,SAAS,KAAK,UAAU;AAC5B,MAAI,KAAK,UAAU,UAAU,KAAK,WAAW,UAAU,KAAK,QAAQ;AAClE,UAAM,eAAe,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AACrE,YAAS,aAAa,aAAc;AACpC,aAAS;AAAA,EACX,WAAW,KAAK,WAAW,UAAU,KAAK,UAAU,UAAU,KAAK,OAAO;AACxE,UAAM,cAAc,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAClE,aAAU,aAAa,aAAc;AACrC,YAAQ;AAAA,EACV;AACA,SAAO,EAAE,OAAO,OAAO;AACzB;AAEA,SAAS,qBAAqB,SAA2B,OAAwB;AAC/E,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,MAAM;AAC1B,UAAQ,SAAS,MAAM;AACvB,UAAQ,kBAAkB;AAC1B,UAAQ,aAAa,MAAM;AAC7B;AAUA,eAAsB,mBACpB,SACA,aACA,OACA;AACA,QAAM,cAAsB,QAAQ,eAAe;AACnD,QAAM,eACJ,gBAAgB,eACf,gBAAgB,UAAU,yBAAyB;AAMtD,MAAI,cAAc;AAChB,QAAI;AACF,aAAO,MAAM,qBAAqB,SAAS,KAAK;AAAA,IAClD,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN;AAAA,QACA,eAAe,QAAQ,IAAI,UAAU;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAEA,SAAO,iBAAiB,SAAS,KAAK;AACxC;AAEA,eAAe,qBAAqB,SAAc,OAAwB;AACxE,QAAM,EAAE,KAAK,OAAO,CAAC,EAAE,IAAI;AAC3B,QAAM,SAAS,MAAM,yBAAyB,OAAO,GAAG;AACxD,QAAM,EAAE,OAAO,OAAO,IAAI,YAAY,MAAM,OAAO,YAAY,OAAO,WAAW;AAEjF,QAAM,UAAU,IAAI,MAAM,cAAc,OAAO,MAAM;AACrD,uBAAqB,SAAS,KAAK;AAEnC,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AACD,QAAM,OAAO,IAAI,MAAM,KAAK,IAAI,MAAM,cAAc,OAAO,MAAM,GAAG,QAAQ;AAC5E,OAAK,SAAS,cAAc;AAC5B,OAAK,SAAS,UAAU;AACxB,UAAQ,cAAc;AAEtB,SAAO,EAAE,MAAM,OAAO,QAAQ,OAAO,MAAM,aAAa,QAAQ,QAAQ;AAC1E;AAGA,eAAe,iBAAiB,SAAc,OAAwB;AACpE,QAAM;AAAA,IACJ;AAAA,IACA,OAAO,CAAC;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,YAAY;AAAA,EACd,IAAI;AAEJ,MAAI;AACJ,QAAM,SAAS,WAAW,SAAS,GAAG;AACtC,MAAI,QAAQ;AACV,YAAQ,OAAO;AACf,UAAM,OAAO;AACb,UAAM,QAAQ;AACd,UAAM,eAAe;AAAA,EACvB,OAAO;AACL,YAAQ,SAAS,cAAc,OAAO;AACtC,UAAM,cAAc;AACpB,UAAM,MAAM;AACZ,UAAM,OAAO;AACb,UAAM,QAAQ;AACd,UAAM,eAAe;AACrB,UAAM,cAAc;AACpB,UAAM,UAAU;AAEhB,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,mBAAmB,MAAM,QAAQ;AACvC,YAAM,UAAU;AAChB,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAEA,QAAM,cAAc;AACpB,QAAM,MAAM;AAEZ,QAAM,EAAE,OAAO,OAAO,IAAI,YAAY,MAAM,MAAM,YAAY,MAAM,WAAW;AAE/E,QAAM,UAAU,IAAI,MAAM,aAAa,KAAK;AAC5C,uBAAqB,SAAS,KAAK;AAEnC,QAAM,WAAW,IAAI,MAAM,kBAAkB;AAAA,IAC3C,KAAK;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd,CAAC;AACD,QAAM,OAAO,IAAI,MAAM,KAAK,IAAI,MAAM,cAAc,OAAO,MAAM,GAAG,QAAQ;AAC5E,OAAK,SAAS,QAAQ;AACtB,OAAK,SAAS,UAAU;AAExB,SAAO,EAAE,MAAM,OAAO,QAAQ,OAAO,aAAa,MAAM,QAAQ;AAClE;;;AClIA,SAAS,kBACP,UACA,YACA,cACA,eACA;AACA,QAAM,EAAE,OAAO,OAAO,IAAI;AAC1B,QAAM,QAAQ,eAAe;AAC7B,QAAM,QAAQ,gBAAgB;AAE9B,MAAI,OAAO,aAAa,UAAU;AAChC,YAAQ,UAAU;AAAA,MAChB,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,GAAG,SAAS,IAAI,MAAM;AAAA,MACvD,KAAK;AACH,eAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,MACtB,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,GAAG,EAAE;AAAA,MACtC,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,cAAc,GAAG,EAAE;AAAA,MACzC,KAAK;AACH,eAAO,EAAE,GAAG,GAAG,GAAG,SAAS,IAAI,MAAM;AAAA,MACvC,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,cAAc,GAAG,SAAS,IAAI,MAAM;AAAA,MAC1D,KAAK;AACH,eAAO,EAAE,GAAG,GAAG,GAAG,SAAS,cAAc;AAAA,MAC3C,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,IAAI,OAAO,GAAG,SAAS,cAAc;AAAA,MAC3D,KAAK;AACH,eAAO,EAAE,GAAG,QAAQ,cAAc,GAAG,SAAS,cAAc;AAAA,MAC9D;AACE,eAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,IACJ,OAAO,SAAS,MAAM,WACjB,WAAW,SAAS,CAAC,IAAI,MAAO,QACjC,SAAS;AACf,QAAM,IACJ,OAAO,SAAS,MAAM,WACjB,WAAW,SAAS,CAAC,IAAI,MAAO,SACjC,SAAS;AACf,SAAO,EAAE,GAAG,EAAE;AAChB;AAGA,IAAM,gBAAgB;AAKtB,eAAsB,eACpB,gBACA,eACA,YACA,OACA;AACA,QAAM,WAAW,CAAC,WAAgB,cAAc,OAAO,UAAU,GAAG;AACpE,QAAM,cAAc,cAAc;AAElC,QAAM,aAAa,oBAAI,IAAI;AAC3B,QAAM,kBAAkB,WAAW,SAAS;AAE5C,WAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK;AAC9C,UAAM,SAAS,eAAe,CAAC;AAC/B,UAAM,KAAK,OAAO,MAAM,WAAW,CAAC;AAEpC,QAAI;AACF,UAAI;AACJ,UAAI,SAAmC;AACvC,UAAI,eAAe;AACnB,UAAI,gBAAgB;AACpB,UAAI,WAAgB;AACpB,YAAM,gBAAiC,CAAC;AACxC,UAAI,eAAwC;AAC5C,UAAI,cAAmB;AACvB,UAAI,eAAwC;AAE5C,UAAI,OAAO,SAAS,UAAU,OAAO,OAAO;AAC1C,cAAM,cAAc,uBAAuB,QAAQ,YAAY,KAAK;AACpE,uBAAe,YAAY;AAC3B,wBAAgB,YAAY;AAE5B,cAAM,cAAc,eAAe;AACnC,cAAM,eAAe,gBAAgB;AAErC,cAAM,EAAE,GAAG,EAAE,IAAI;AAAA,UACf,OAAO;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,QACF;AACA,cAAM,WAAW,IAAI,WAAW,QAAQ,IAAI,cAAc;AAC1D,cAAM,WAAW,EAAE,IAAI,WAAW,SAAS,IAAI,eAAe;AAE9D,YAAI,aAAa;AACjB,YAAI,aAAa;AACjB,YAAI,OAAO,WAAW;AACpB,wBAAc,OAAO,UAAU,cAAc,KAAK;AAClD,uBAAa,GAAG,OAAO,UAAU,cAAc,KAAK;AAAA,QACtD;AAEA,cAAM,SAAS,OAAO,UAAU;AAEhC,mBAAW,YAAY,OAAO,IAAI,CAAC,MAAW,OAAe;AAC3D,gBAAM,UAAU,KAAK;AACrB,kBAAQ,MAAM,IAAI,iBAAiB,iBAAiB,CAAC;AACrD,kBAAQ,SAAS,IACf,WAAW,KAAK,UAAU,kBAAkB;AAC9C,kBAAQ,SAAS,IACf,WAAW,KAAK,UAAU,kBAAkB;AAC9C,kBAAQ,SAAS,IAAI;AACrB,kBAAQ,cAAc,SAAS,IAAI,OAAO,KAAK;AAE/C,cAAI,OAAO,YAAY,QAAW;AAChC,oBAAQ,SAAS,UAAU,OAAO;AAAA,UACpC;AAEA,mBAAS,MAAM,EAAE,IAAI,OAAO;AAC5B,wBAAc,KAAK,OAAO;AAE1B,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA,QAAQ,SAAS;AAAA,YACjB,CAAC,QAAQ,SAAS;AAAA,YAClB,OAAO,WAAW;AAAA,UACpB;AAAA,QACF,CAAC;AAED,eAAO,YAAY,OAAO,CAAC,GAAG,QAAQ,IAAI,MAAM,KAAK;AAAA,MACvD,WAAW,OAAO,SAAS,QAAQ;AACjC,cAAM,SAAS,kBAAkB,QAAQ,YAAY,KAAK;AAC1D,eAAO,OAAO;AACd,iBAAS,OAAO;AAChB,uBAAe,OAAO;AACtB,wBAAgB,OAAO;AAAA,MACzB,WAAW,OAAO,SAAS,SAAS;AAClC,cAAM,SAAS,MAAM,mBAAmB,QAAQ,YAAY,KAAK;AACjE,eAAO,OAAO;AACd,uBAAe,OAAO;AACtB,wBAAgB,OAAO;AAAA,MACzB,WAAW,OAAO,SAAS,OAAO;AAChC,cAAM,SAAS,MAAM,iBAAiB,QAAQ,YAAY,KAAK;AAC/D,eAAO,OAAO;AACd,uBAAe,OAAO;AACtB,wBAAgB,OAAO;AAAA,MACzB,WAAW,OAAO,SAAS,SAAS;AAClC,cAAM,SAAS,MAAM,mBAAmB,QAAQ,YAAY,KAAK;AACjE,eAAO,OAAO;AACd,uBAAe,OAAO;AACtB,wBAAgB,OAAO;AACvB,aAAK,SAAS,QAAQ,OAAO;AAC7B,uBAAe,OAAO;AACtB,sBAAc,OAAO;AACrB,uBAAe,OAAO;AACtB,sBAAc,MAAM;AAAA,MACtB,OAAO;AACL,eAAO,IAAI,MAAM;AAAA,UACf,IAAI,MAAM,cAAc,KAAK,GAAG;AAAA,UAChC,IAAI,MAAM,kBAAkB,EAAE,aAAa,MAAM,SAAS,EAAE,CAAC;AAAA,QAC/D;AACA,gBAAQ,KAAK,iBAAiB,OAAO,IAAI,mBAAmB;AAAA,MAC9D;AAEA,UAAI,CAAC,OAAO,OAAO;AACjB,cAAM,cAAc,eAAe;AACnC,cAAM,eAAe,gBAAgB;AAErC,cAAM,EAAE,GAAG,EAAE,IAAI;AAAA,UACf,OAAO;AAAA,UACP;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAEA,cAAM,OAAO,IAAI,WAAW,QAAQ,IAAI,cAAc;AACtD,cAAM,OAAO,EAAE,IAAI,WAAW,SAAS,IAAI,eAAe;AAE1D,aAAK,MAAM,IAAI,iBAAiB,iBAAiB,CAAC;AAClD,aAAK,SAAS,IAAI;AAClB,aAAK,SAAS,IAAI;AAClB,aAAK,SAAS,IAAI;AAElB,cAAM,SAAS,OAAO,UAAU;AAChC,aAAK,cAAc,SAAS,IAAI;AAEhC,YAAI,OAAO,YAAY,QAAW;AAChC;AAAC,UAAC,KAAK,SAAwC,UAC7C,OAAO;AACR,UAAC,KAAK,SAAwC,cAAc;AAAA,QAC/D;AAEA,YAAI,OAAO,WAAW;AACpB,gBAAM,IAAI,OAAO;AACjB,cAAI,EAAE,WAAY,MAAK,SAAS,KAAK,EAAE,aAAa;AACpD,cAAI,EAAE,WAAY,MAAK,SAAS,KAAK,EAAE,aAAa;AACpD,cAAI,EAAE,WAAY,MAAK,SAAS,KAAK,EAAE;AACvC,cAAI,EAAE;AACJ,iBAAK,MAAM;AAAA,cACT,EAAE,QAAQ;AAAA,cACV,EAAE,QAAQ;AAAA,cACV;AAAA,YACF;AACF,cAAI,EAAE,WAAW,EAAE,UAAU;AAC3B,iBAAK,SAAS,KAAM,EAAE,WAAW,EAAE,YAAY,KAAK,KAAK,KAAM;AAAA,UACjE;AAAA,QACF;AAEA,iBAAS,MAAM,EAAE,IAAI,IAAI;AAAA,MAC3B;AAEA,YAAM,QAAQ;AAAA,QACZ;AAAA,QACA;AAAA,QACA,KAAK,SAAS;AAAA,QACd,CAAC,KAAK,SAAS;AAAA,QACf,OAAO,WAAW;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,YAAM,kBAAkB;AAAA,QACtB;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,YAAY,CAAC,aAAqB;AAChC,cAAI,OAAO,SAAS,UAAU,QAAQ;AACpC,oBAAQ,KAAK,qDAAqD;AAAA,UACpE;AAAA,QACF;AAAA,QACA,SAAS,MAAM;AACb,uBAAa,UAAU;AACvB,gBAAM,cAAc,SAAS,MAAM;AACnC,cAAI,cAAc,SAAS,GAAG;AAC5B,0BAAc,QAAQ,CAAC,MAAM;AAC3B,0BAAY,OAAO,CAAC;AACpB,gBAAE,SAAS,QAAQ;AACnB,oBAAM,MAAM,EAAE;AACd,kBAAI,IAAI,IAAK,KAAI,IAAI,QAAQ;AAC7B,kBAAI,QAAQ;AAAA,YACd,CAAC;AAAA,UACH,OAAO;AACL,wBAAY,OAAO,IAAI;AACvB,iBAAK,SAAS,QAAQ;AACtB,kBAAM,MAAM,KAAK;AACjB,gBAAI,IAAI,IAAK,KAAI,IAAI,QAAQ;AAC7B,gBAAI,QAAQ;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAEA,iBAAW,IAAI,IAAI,eAAe;AAAA,IACpC,SAAS,OAAO;AACd,cAAQ;AAAA,QACN,mCAAmC,EAAE,MAAM,OAAO,IAAI;AAAA,QACtD;AAAA,MACF;AAEA,YAAM,eAAe,IAAI,MAAM;AAAA,QAC7B,IAAI,MAAM,cAAc,KAAK,GAAG;AAAA,QAChC,IAAI,MAAM,kBAAkB,EAAE,aAAa,MAAM,SAAS,EAAE,CAAC;AAAA,MAC/D;AACA,eAAS,MAAM,EAAE,IAAI,YAAY;AACjC,YAAM,QAAQ,mBAAmB,OAAO,cAAc,GAAG,GAAG,CAAC;AAC7D,iBAAW,IAAI,IAAI;AAAA,QACjB;AAAA,QACA,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,UAAU;AAAA,QACV,YAAY,MAAM;AAAA,QAAC;AAAA,QACnB,SAAS,MAAM;AACb,mBAAS,MAAM,EAAE,OAAO,YAAY;AACpC,uBAAa,SAAS,QAAQ;AAC9B,uBAAa,SAAS,QAAQ;AAAA,QAChC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;;;ACzRO,SAAS,kBAAkB,OAAqC;AACrE,SAAO;AAAA,IACL,gBAAgB,CACd,gBACA,eACA,eACG,eAAe,gBAAgB,eAAe,YAAY,KAAK;AAAA,IACpE,iBAAiB,CAAC,eAAiC;AACjD,iBAAW,QAAQ,CAAC,aAAa,SAAS,UAAU,CAAC;AACrD,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF;AACF;","names":["svgContent","vos"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vosjs/elements",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Vos element system — text / image / SVG / video renderers for Three.js overlays, shipped as both a typed ESM factory and an injectable IIFE bundle.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,7 +47,8 @@
47
47
  "esbuild": "^0.27.0",
48
48
  "three": "^0.182.0",
49
49
  "tsup": "^8.5.0",
50
- "typescript": "^5"
50
+ "typescript": "^5",
51
+ "vitest": "^3.0.0"
51
52
  },
52
53
  "publishConfig": {
53
54
  "access": "public"
@@ -56,6 +57,7 @@
56
57
  "build": "tsup && node bundle.mjs",
57
58
  "typecheck": "tsc --noEmit",
58
59
  "lint": "eslint .",
60
+ "test": "vitest run",
59
61
  "clean": "rm -rf dist node_modules"
60
62
  }
61
63
  }