@vosjs/elements 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/dist/bundle.js +1 -1
- package/dist/index.js +240 -21
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
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
|
+
[](https://www.npmjs.com/package/@vosjs/elements)
|
|
6
|
+
[](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 file.onError = (e) => reject(new Error(String(e)));\n file.onReady = (info) => {\n const track = info.videoTracks?.[0];\n if (!track) return reject(new Error(\"[vos] no video track in mp4\"));\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 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 };\n data.fileStart = 0;\n file.appendBuffer(data);\n file.flush();\n queueMicrotask(() => {\n if (!this.samples.length) return reject(new Error(\"[vos] no samples extracted\"));\n this.syncIndices = buildSyncIndices(this.samples);\n resolve();\n });\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 {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0,\n frameSource = \"html5\"\n } = element;\n const useWebCodecs = frameSource === \"webcodecs\" || frameSource === \"auto\" && isFrameAccurateSupported();\n if (useWebCodecs) {\n const source = await FrameAccurateVideoSource.create(src);\n const { width: width2, height: height2 } = resolveSize(size, source.codedWidth, source.codedHeight);\n const texture2 = new THREE.CanvasTexture(source.canvas);\n applyTextureSettings(texture2, THREE);\n const material2 = new THREE.MeshBasicMaterial({\n map: texture2,\n transparent: true,\n depthWrite: false\n });\n const mesh2 = new THREE.Mesh(new THREE.PlaneGeometry(width2, height2), material2);\n mesh2.userData.videoSource = source;\n mesh2.userData.texture = texture2;\n texture2.needsUpdate = true;\n return { mesh: mesh2, width: width2, height: height2, video: null, videoSource: source, texture: texture2 };\n }\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 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, 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,7 +561,207 @@ 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
|
+
file.onError = (e) => reject(new Error(String(e)));
|
|
654
|
+
file.onReady = (info) => {
|
|
655
|
+
const track = info.videoTracks?.[0];
|
|
656
|
+
if (!track) return reject(new Error("[vos] no video track in mp4"));
|
|
657
|
+
this.codedWidth = track.video.width;
|
|
658
|
+
this.codedHeight = track.video.height;
|
|
659
|
+
this.durationSec = info.duration / info.timescale;
|
|
660
|
+
this.fps = track.nb_samples / this.durationSec || 30;
|
|
661
|
+
this.decoder.configure({
|
|
662
|
+
codec: track.codec,
|
|
663
|
+
description: this.getDescription(MP4Box, file, track.id),
|
|
664
|
+
codedWidth: this.codedWidth,
|
|
665
|
+
codedHeight: this.codedHeight
|
|
666
|
+
});
|
|
667
|
+
file.setExtractionOptions(track.id, null, { nbSamples: Infinity });
|
|
668
|
+
file.start();
|
|
669
|
+
};
|
|
670
|
+
file.onSamples = (_id, _user, arr) => {
|
|
671
|
+
for (const s of arr) {
|
|
672
|
+
this.samples.push({
|
|
673
|
+
cts: s.cts / s.timescale,
|
|
674
|
+
isSync: !!s.is_sync,
|
|
675
|
+
chunk: new EncodedVideoChunk({
|
|
676
|
+
type: s.is_sync ? "key" : "delta",
|
|
677
|
+
timestamp: Math.round(s.cts / s.timescale * 1e6),
|
|
678
|
+
duration: Math.round(s.duration / s.timescale * 1e6),
|
|
679
|
+
data: s.data
|
|
680
|
+
})
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
data.fileStart = 0;
|
|
685
|
+
file.appendBuffer(data);
|
|
686
|
+
file.flush();
|
|
687
|
+
queueMicrotask(() => {
|
|
688
|
+
if (!this.samples.length) return reject(new Error("[vos] no samples extracted"));
|
|
689
|
+
this.syncIndices = buildSyncIndices(this.samples);
|
|
690
|
+
resolve();
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
getDescription(MP4Box, file, trackId) {
|
|
695
|
+
const trak = file.getTrackById(trackId);
|
|
696
|
+
for (const entry of trak.mdia.minf.stbl.stsd.entries) {
|
|
697
|
+
const box = entry.avcC ?? entry.hvcC ?? entry.vpcC ?? entry.av1C;
|
|
698
|
+
if (box) {
|
|
699
|
+
const stream = new MP4Box.DataStream(void 0, 0, MP4Box.DataStream.BIG_ENDIAN);
|
|
700
|
+
box.write(stream);
|
|
701
|
+
return new Uint8Array(stream.buffer, 8);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
throw new Error("[vos] no codec description (avcC/hvcC/vpcC/av1C) found");
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Decode the exact frame at presentation time `t` and draw it to `canvas`.
|
|
708
|
+
* Decodes the whole GOP (keyframe → next keyframe) in decode order, then
|
|
709
|
+
* selects the output by PTS. Returns when the canvas is updated.
|
|
710
|
+
*/
|
|
711
|
+
async seekTo(tSec) {
|
|
712
|
+
if (!this.samples.length) return;
|
|
713
|
+
const targetDI = targetDecodeIndex(this.samples, tSec);
|
|
714
|
+
const [ki, ni] = gopBounds(this.syncIndices, this.samples.length, targetDI);
|
|
715
|
+
const targetTs = Math.round(this.samples[targetDI].cts * 1e6);
|
|
716
|
+
this.lastError = null;
|
|
717
|
+
const collected = [];
|
|
718
|
+
this.onFrame = (f) => collected.push(f);
|
|
719
|
+
for (let i = ki; i < ni; i++) this.decoder.decode(this.samples[i].chunk);
|
|
720
|
+
await this.decoder.flush();
|
|
721
|
+
this.onFrame = null;
|
|
722
|
+
if (this.lastError) throw this.lastError;
|
|
723
|
+
let best;
|
|
724
|
+
let bestD = Infinity;
|
|
725
|
+
for (const f of collected) {
|
|
726
|
+
const d = Math.abs(f.timestamp - targetTs);
|
|
727
|
+
if (d < bestD) {
|
|
728
|
+
bestD = d;
|
|
729
|
+
best = f;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (best) this.ctx.drawImage(best, 0, 0);
|
|
733
|
+
for (const f of collected) f.close();
|
|
734
|
+
}
|
|
735
|
+
dispose() {
|
|
736
|
+
try {
|
|
737
|
+
this.decoder.close();
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
556
743
|
// src/renderers/video.ts
|
|
744
|
+
function resolveSize(size, intrinsicW, intrinsicH) {
|
|
745
|
+
let width = size.width ?? intrinsicW;
|
|
746
|
+
let height = size.height ?? intrinsicH;
|
|
747
|
+
if (size.width === "auto" && size.height !== "auto" && size.height) {
|
|
748
|
+
const targetHeight = typeof size.height === "number" ? size.height : intrinsicH;
|
|
749
|
+
width = intrinsicW / intrinsicH * targetHeight;
|
|
750
|
+
height = targetHeight;
|
|
751
|
+
} else if (size.height === "auto" && size.width !== "auto" && size.width) {
|
|
752
|
+
const targetWidth = typeof size.width === "number" ? size.width : intrinsicW;
|
|
753
|
+
height = intrinsicH / intrinsicW * targetWidth;
|
|
754
|
+
width = targetWidth;
|
|
755
|
+
}
|
|
756
|
+
return { width, height };
|
|
757
|
+
}
|
|
758
|
+
function applyTextureSettings(texture, THREE) {
|
|
759
|
+
texture.minFilter = THREE.LinearFilter;
|
|
760
|
+
texture.magFilter = THREE.LinearFilter;
|
|
761
|
+
texture.format = THREE.RGBAFormat;
|
|
762
|
+
texture.generateMipmaps = false;
|
|
763
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
764
|
+
}
|
|
557
765
|
async function renderVideoElement(element, _resolution, THREE) {
|
|
558
766
|
const {
|
|
559
767
|
src,
|
|
@@ -561,8 +769,26 @@ async function renderVideoElement(element, _resolution, THREE) {
|
|
|
561
769
|
loop = true,
|
|
562
770
|
muted = true,
|
|
563
771
|
playbackRate = 1,
|
|
564
|
-
startTime = 0
|
|
772
|
+
startTime = 0,
|
|
773
|
+
frameSource = "html5"
|
|
565
774
|
} = element;
|
|
775
|
+
const useWebCodecs = frameSource === "webcodecs" || frameSource === "auto" && isFrameAccurateSupported();
|
|
776
|
+
if (useWebCodecs) {
|
|
777
|
+
const source = await FrameAccurateVideoSource.create(src);
|
|
778
|
+
const { width: width2, height: height2 } = resolveSize(size, source.codedWidth, source.codedHeight);
|
|
779
|
+
const texture2 = new THREE.CanvasTexture(source.canvas);
|
|
780
|
+
applyTextureSettings(texture2, THREE);
|
|
781
|
+
const material2 = new THREE.MeshBasicMaterial({
|
|
782
|
+
map: texture2,
|
|
783
|
+
transparent: true,
|
|
784
|
+
depthWrite: false
|
|
785
|
+
});
|
|
786
|
+
const mesh2 = new THREE.Mesh(new THREE.PlaneGeometry(width2, height2), material2);
|
|
787
|
+
mesh2.userData.videoSource = source;
|
|
788
|
+
mesh2.userData.texture = texture2;
|
|
789
|
+
texture2.needsUpdate = true;
|
|
790
|
+
return { mesh: mesh2, width: width2, height: height2, video: null, videoSource: source, texture: texture2 };
|
|
791
|
+
}
|
|
566
792
|
let video;
|
|
567
793
|
const cached = AssetCache.getVideo(src);
|
|
568
794
|
if (cached) {
|
|
@@ -587,23 +813,9 @@ async function renderVideoElement(element, _resolution, THREE) {
|
|
|
587
813
|
}
|
|
588
814
|
video.currentTime = startTime;
|
|
589
815
|
video.pause();
|
|
590
|
-
|
|
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
|
-
}
|
|
816
|
+
const { width, height } = resolveSize(size, video.videoWidth, video.videoHeight);
|
|
601
817
|
const texture = new THREE.VideoTexture(video);
|
|
602
|
-
texture
|
|
603
|
-
texture.magFilter = THREE.LinearFilter;
|
|
604
|
-
texture.format = THREE.RGBAFormat;
|
|
605
|
-
texture.generateMipmaps = false;
|
|
606
|
-
texture.colorSpace = THREE.SRGBColorSpace;
|
|
818
|
+
applyTextureSettings(texture, THREE);
|
|
607
819
|
const material = new THREE.MeshBasicMaterial({
|
|
608
820
|
map: texture,
|
|
609
821
|
transparent: true,
|
|
@@ -613,7 +825,7 @@ async function renderVideoElement(element, _resolution, THREE) {
|
|
|
613
825
|
const mesh = new THREE.Mesh(geometry, material);
|
|
614
826
|
mesh.userData.video = video;
|
|
615
827
|
mesh.userData.texture = texture;
|
|
616
|
-
return { mesh, width, height, video };
|
|
828
|
+
return { mesh, width, height, video, videoSource: null, texture };
|
|
617
829
|
}
|
|
618
830
|
|
|
619
831
|
// src/renderElements.ts
|
|
@@ -666,6 +878,8 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
|
|
|
666
878
|
let segments = null;
|
|
667
879
|
const segmentMeshes = [];
|
|
668
880
|
let videoElement = null;
|
|
881
|
+
let videoSource = null;
|
|
882
|
+
let videoTexture = null;
|
|
669
883
|
if (config.type === "text" && config.split) {
|
|
670
884
|
const splitResult = renderSplitTextElement(config, resolution, THREE);
|
|
671
885
|
elementWidth = splitResult.totalWidth;
|
|
@@ -731,7 +945,9 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
|
|
|
731
945
|
elementHeight = result.height;
|
|
732
946
|
mesh.userData.video = result.video;
|
|
733
947
|
videoElement = result.video;
|
|
734
|
-
|
|
948
|
+
videoSource = result.videoSource;
|
|
949
|
+
videoTexture = result.texture;
|
|
950
|
+
videoElement?.pause();
|
|
735
951
|
} else {
|
|
736
952
|
mesh = new THREE.Mesh(
|
|
737
953
|
new THREE.PlaneGeometry(100, 100),
|
|
@@ -784,7 +1000,9 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
|
|
|
784
1000
|
mesh.position.x,
|
|
785
1001
|
-mesh.position.y,
|
|
786
1002
|
config.opacity ?? 1,
|
|
787
|
-
videoElement
|
|
1003
|
+
videoElement,
|
|
1004
|
+
videoSource,
|
|
1005
|
+
videoTexture
|
|
788
1006
|
);
|
|
789
1007
|
const elementInstance = {
|
|
790
1008
|
config,
|
|
@@ -798,6 +1016,7 @@ async function renderElements(elementsConfig, overlayScenes, resolution, THREE)
|
|
|
798
1016
|
}
|
|
799
1017
|
},
|
|
800
1018
|
destroy: () => {
|
|
1019
|
+
videoSource?.dispose?.();
|
|
801
1020
|
const targetScene = getScene(config);
|
|
802
1021
|
if (segmentMeshes.length > 0) {
|
|
803
1022
|
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 file.onError = (e: unknown) => reject(new Error(String(e)))\n file.onReady = (info: any) => {\n const track = info.videoTracks?.[0]\n if (!track) return reject(new Error('[vos] no video track in mp4'))\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 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 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 }\n ;(data as any).fileStart = 0\n file.appendBuffer(data)\n file.flush()\n // Keep DECODE order (do NOT sort by cts) — B-frames need decode order.\n queueMicrotask(() => {\n if (!this.samples.length) return reject(new Error('[vos] no samples extracted'))\n this.syncIndices = buildSyncIndices(this.samples)\n resolve()\n })\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 {\n src,\n size = {},\n loop = true,\n muted = true,\n playbackRate = 1,\n startTime = 0,\n frameSource = 'html5',\n } = element\n\n const useWebCodecs =\n frameSource === 'webcodecs' ||\n (frameSource === 'auto' && isFrameAccurateSupported())\n\n // ---- Frame-accurate WebCodecs path ----------------------------------------\n if (useWebCodecs) {\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\n // initial frame already drawn at t=0 by create()\n texture.needsUpdate = true\n\n return { mesh, width, height, video: null, videoSource: source, texture }\n }\n\n // ---- Legacy HTMLVideoElement path -----------------------------------------\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\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, 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,WAAK,UAAU,CAAC,MAAe,OAAO,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAC1D,WAAK,UAAU,CAAC,SAAc;AAC5B,cAAM,QAAQ,KAAK,cAAc,CAAC;AAClC,YAAI,CAAC,MAAO,QAAO,OAAO,IAAI,MAAM,6BAA6B,CAAC;AAClE,aAAK,aAAa,MAAM,MAAM;AAC9B,aAAK,cAAc,MAAM,MAAM;AAC/B,aAAK,cAAc,KAAK,WAAW,KAAK;AACxC,aAAK,MAAM,MAAM,aAAa,KAAK,eAAe;AAClD,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;AACnB,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;AAAA,MACF;AACC,MAAC,KAAa,YAAY;AAC3B,WAAK,aAAa,IAAI;AACtB,WAAK,MAAM;AAEX,qBAAe,MAAM;AACnB,YAAI,CAAC,KAAK,QAAQ,OAAQ,QAAO,OAAO,IAAI,MAAM,4BAA4B,CAAC;AAC/E,aAAK,cAAc,iBAAiB,KAAK,OAAO;AAChD,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,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;;;ACtKA,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;AAAA,IACJ;AAAA,IACA,OAAO,CAAC;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,IAAI;AAEJ,QAAM,eACJ,gBAAgB,eACf,gBAAgB,UAAU,yBAAyB;AAGtD,MAAI,cAAc;AAChB,UAAM,SAAS,MAAM,yBAAyB,OAAO,GAAG;AACxD,UAAM,EAAE,OAAAC,QAAO,QAAAC,QAAO,IAAI,YAAY,MAAM,OAAO,YAAY,OAAO,WAAW;AAEjF,UAAMC,WAAU,IAAI,MAAM,cAAc,OAAO,MAAM;AACrD,yBAAqBA,UAAS,KAAK;AAEnC,UAAMC,YAAW,IAAI,MAAM,kBAAkB;AAAA,MAC3C,KAAKD;AAAA,MACL,aAAa;AAAA,MACb,YAAY;AAAA,IACd,CAAC;AACD,UAAME,QAAO,IAAI,MAAM,KAAK,IAAI,MAAM,cAAcJ,QAAOC,OAAM,GAAGE,SAAQ;AAC5E,IAAAC,MAAK,SAAS,cAAc;AAC5B,IAAAA,MAAK,SAAS,UAAUF;AAGxB,IAAAA,SAAQ,cAAc;AAEtB,WAAO,EAAE,MAAAE,OAAM,OAAAJ,QAAO,QAAAC,SAAQ,OAAO,MAAM,aAAa,QAAQ,SAAAC,SAAQ;AAAA,EAC1E;AAGA,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;AAED,QAAM,WAAW,IAAI,MAAM,cAAc,OAAO,MAAM;AACtD,QAAM,OAAO,IAAI,MAAM,KAAK,UAAU,QAAQ;AAE9C,OAAK,SAAS,QAAQ;AACtB,OAAK,SAAS,UAAU;AAExB,SAAO,EAAE,MAAM,OAAO,QAAQ,OAAO,aAAa,MAAM,QAAQ;AAClE;;;ACpHA,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","width","height","texture","material","mesh"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vosjs/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
}
|