@vosjs/elements 0.1.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.
@@ -0,0 +1 @@
1
+ export declare const elementsBundleCode: string
package/dist/bundle.js ADDED
@@ -0,0 +1,2 @@
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"
@@ -0,0 +1,17 @@
1
+ import * as THREE_NS from 'three';
2
+
3
+ /**
4
+ * Render all elements to a dedicated overlay scene with pixel-space camera.
5
+ */
6
+ declare function renderElements(elementsConfig: any[], overlayScenes: Record<number, THREE_NS.Scene>, resolution: any, THREE: typeof THREE_NS): Promise<Map<any, any>>;
7
+
8
+ interface VosElements {
9
+ renderElements: (elementsConfig: any[], overlayScenes: Record<number, THREE_NS.Scene>, resolution: any, THREE: typeof THREE_NS) => Promise<Map<string, any>>;
10
+ disposeElements: (elementMap: Map<string, any>) => void;
11
+ }
12
+ /**
13
+ * Factory: create the Vos element system bound to a THREE instance.
14
+ */
15
+ declare function createVosElements(THREE: typeof THREE_NS): VosElements;
16
+
17
+ export { type VosElements, createVosElements, renderElements };
package/dist/index.js ADDED
@@ -0,0 +1,864 @@
1
+ // src/assetCache.ts
2
+ var AssetCache = {
3
+ images: /* @__PURE__ */ new Map(),
4
+ videos: /* @__PURE__ */ new Map(),
5
+ svgContents: /* @__PURE__ */ new Map(),
6
+ getImage(url) {
7
+ return this.images.get(url) || null;
8
+ },
9
+ getVideo(url) {
10
+ return this.videos.get(url) || null;
11
+ },
12
+ getSVG(url) {
13
+ return this.svgContents.get(url) || null;
14
+ },
15
+ dispose() {
16
+ this.videos.forEach((cached) => {
17
+ cached.element.pause();
18
+ cached.element.src = "";
19
+ cached.element.load();
20
+ if (cached.blobUrl) {
21
+ URL.revokeObjectURL(cached.blobUrl);
22
+ }
23
+ });
24
+ this.images.clear();
25
+ this.videos.clear();
26
+ this.svgContents.clear();
27
+ }
28
+ };
29
+ function extractAssetUrls(elementsConfig) {
30
+ const assets = {
31
+ images: [],
32
+ videos: [],
33
+ svgs: []
34
+ };
35
+ for (const el of elementsConfig) {
36
+ if (el.type === "image" && el.src) {
37
+ assets.images.push(el.src);
38
+ } else if (el.type === "video" && el.src) {
39
+ assets.videos.push(el.src);
40
+ } else if (el.type === "svg" && el.src) {
41
+ assets.svgs.push(el.src);
42
+ }
43
+ }
44
+ assets.images = [...new Set(assets.images)];
45
+ assets.videos = [...new Set(assets.videos)];
46
+ assets.svgs = [...new Set(assets.svgs)];
47
+ return assets;
48
+ }
49
+ async function preloadImage(url) {
50
+ if (AssetCache.images.has(url)) return;
51
+ const img = new Image();
52
+ img.crossOrigin = "anonymous";
53
+ await new Promise((resolve) => {
54
+ img.onload = () => resolve();
55
+ img.onerror = () => {
56
+ console.warn("Failed to preload image:", url);
57
+ resolve();
58
+ };
59
+ img.src = url;
60
+ });
61
+ AssetCache.images.set(url, {
62
+ element: img,
63
+ width: img.naturalWidth,
64
+ height: img.naturalHeight
65
+ });
66
+ }
67
+ async function preloadVideo(url) {
68
+ if (AssetCache.videos.has(url)) return;
69
+ try {
70
+ const response = await fetch(url);
71
+ if (!response.ok) {
72
+ throw new Error("Failed to fetch video: " + response.status);
73
+ }
74
+ const blob = await response.blob();
75
+ const blobUrl = URL.createObjectURL(blob);
76
+ const video = document.createElement("video");
77
+ video.crossOrigin = "anonymous";
78
+ video.muted = true;
79
+ video.playsInline = true;
80
+ video.preload = "auto";
81
+ await new Promise((resolve, reject) => {
82
+ video.oncanplaythrough = () => resolve();
83
+ video.onerror = reject;
84
+ video.src = blobUrl;
85
+ video.load();
86
+ });
87
+ video.currentTime = 0;
88
+ await new Promise((resolve) => {
89
+ video.onseeked = () => resolve();
90
+ setTimeout(() => resolve(), 100);
91
+ });
92
+ video.pause();
93
+ console.log("Video preloaded:", url, "duration:", video.duration);
94
+ AssetCache.videos.set(url, {
95
+ element: video,
96
+ blobUrl,
97
+ duration: video.duration,
98
+ width: video.videoWidth,
99
+ height: video.videoHeight
100
+ });
101
+ } catch (e) {
102
+ console.warn("Failed to preload video:", url, e);
103
+ }
104
+ }
105
+ async function preloadSVG(url) {
106
+ if (AssetCache.svgContents.has(url)) return;
107
+ try {
108
+ if (url.startsWith("<svg") || url.startsWith("<?xml")) {
109
+ AssetCache.svgContents.set(url, url);
110
+ return;
111
+ }
112
+ if (url.startsWith("data:image/svg")) {
113
+ const base64 = url.split(",")[1];
114
+ const svgContent2 = atob(base64);
115
+ AssetCache.svgContents.set(url, svgContent2);
116
+ return;
117
+ }
118
+ const response = await fetch(url);
119
+ const svgContent = await response.text();
120
+ AssetCache.svgContents.set(url, svgContent);
121
+ } catch (e) {
122
+ console.warn("Failed to preload SVG:", url, e);
123
+ AssetCache.svgContents.set(url, "");
124
+ }
125
+ }
126
+ async function preloadAssets(elementsConfig) {
127
+ const assets = extractAssetUrls(elementsConfig);
128
+ const total = assets.images.length + assets.videos.length + assets.svgs.length;
129
+ if (total === 0) return;
130
+ let loaded = 0;
131
+ if (window.parent !== window) {
132
+ window.parent.postMessage(
133
+ { type: "PRELOAD_PROGRESS", loaded: 0, total },
134
+ "*"
135
+ );
136
+ }
137
+ const preloadWithProgress = async (fn, url) => {
138
+ await fn(url);
139
+ loaded++;
140
+ if (window.parent !== window) {
141
+ window.parent.postMessage(
142
+ { type: "PRELOAD_PROGRESS", loaded, total },
143
+ "*"
144
+ );
145
+ }
146
+ };
147
+ const promises = [
148
+ ...assets.images.map((url) => preloadWithProgress(preloadImage, url)),
149
+ ...assets.videos.map((url) => preloadWithProgress(preloadVideo, url)),
150
+ ...assets.svgs.map((url) => preloadWithProgress(preloadSVG, url))
151
+ ];
152
+ await Promise.all(promises);
153
+ }
154
+
155
+ // src/createElementProps.ts
156
+ function createElementProps(_THREE, mesh, initialX, initialY, initialOpacity = 1, videoElement = null) {
157
+ const baseScaleX = mesh.scale.x;
158
+ const baseScaleY = mesh.scale.y;
159
+ const state = {
160
+ x: initialX,
161
+ y: initialY,
162
+ z: 0,
163
+ opacity: initialOpacity,
164
+ scale: 1,
165
+ scaleX: 1,
166
+ scaleY: 1,
167
+ rotation: 0,
168
+ rotationX: 0,
169
+ rotationY: 0,
170
+ zIndex: mesh.userData.zIndex ?? 0,
171
+ // Video-specific properties (only meaningful if videoElement is provided)
172
+ currentTime: videoElement ? videoElement.currentTime : 0,
173
+ playing: false,
174
+ startOffset: 0
175
+ };
176
+ const updateMeshPosition = () => {
177
+ mesh.position.x = state.x;
178
+ mesh.position.y = -state.y;
179
+ mesh.position.z = state.z;
180
+ };
181
+ const updateMeshTransform = () => {
182
+ mesh.scale.set(
183
+ baseScaleX * state.scale * state.scaleX,
184
+ baseScaleY * state.scale * state.scaleY,
185
+ 1
186
+ );
187
+ mesh.rotation.set(
188
+ state.rotationX * Math.PI / 180,
189
+ state.rotationY * Math.PI / 180,
190
+ state.rotation * Math.PI / 180
191
+ );
192
+ };
193
+ const updateMeshOpacity = () => {
194
+ const mat = mesh.material;
195
+ mat.opacity = state.opacity;
196
+ mat.needsUpdate = true;
197
+ };
198
+ const updateVideoPlayback = () => {
199
+ if (!videoElement) return;
200
+ const vos = window.__vos__;
201
+ const shouldPlay = state.playing && !vos?.isPaused;
202
+ if (shouldPlay) {
203
+ if (videoElement.paused) {
204
+ const timeDiff = Math.abs(videoElement.currentTime - state.currentTime);
205
+ if (timeDiff > 0.5) {
206
+ videoElement.currentTime = state.currentTime;
207
+ }
208
+ videoElement.play().catch(() => {
209
+ });
210
+ }
211
+ } else {
212
+ videoElement.pause();
213
+ if (Math.abs(videoElement.currentTime - state.currentTime) > 0.05) {
214
+ videoElement.currentTime = state.currentTime;
215
+ }
216
+ }
217
+ };
218
+ if (videoElement) {
219
+ const vos = window.__vos__;
220
+ if (vos?.videoCallbacks) {
221
+ vos.videoCallbacks.add(updateVideoPlayback);
222
+ }
223
+ }
224
+ const updateVideoCurrentTime = () => {
225
+ if (!videoElement) return;
226
+ const vos = window.__vos__;
227
+ if (!state.playing || vos?.isPaused) {
228
+ videoElement.currentTime = state.currentTime;
229
+ }
230
+ };
231
+ return new Proxy(state, {
232
+ set(target, prop, value) {
233
+ target[prop] = value;
234
+ switch (prop) {
235
+ case "x":
236
+ case "y":
237
+ case "z":
238
+ updateMeshPosition();
239
+ break;
240
+ case "scale":
241
+ case "scaleX":
242
+ case "scaleY":
243
+ case "rotation":
244
+ case "rotationX":
245
+ case "rotationY":
246
+ updateMeshTransform();
247
+ break;
248
+ case "opacity":
249
+ updateMeshOpacity();
250
+ break;
251
+ case "zIndex":
252
+ mesh.renderOrder = value;
253
+ mesh.userData.zIndex = value;
254
+ break;
255
+ case "currentTime":
256
+ updateVideoCurrentTime();
257
+ break;
258
+ case "playing":
259
+ case "startOffset":
260
+ updateVideoPlayback();
261
+ break;
262
+ }
263
+ return true;
264
+ },
265
+ get(target, prop) {
266
+ if (prop === "duration" && videoElement) {
267
+ return videoElement.duration;
268
+ }
269
+ return target[prop];
270
+ }
271
+ });
272
+ }
273
+
274
+ // src/renderers/image.ts
275
+ async function renderImageElement(element, _resolution, THREE) {
276
+ const { src, size = {} } = element;
277
+ let img;
278
+ const cached = AssetCache.getImage(src);
279
+ if (cached) {
280
+ img = cached.element;
281
+ } else {
282
+ img = new Image();
283
+ img.crossOrigin = "anonymous";
284
+ await new Promise((resolve, reject) => {
285
+ img.onload = () => resolve();
286
+ img.onerror = reject;
287
+ img.src = src;
288
+ });
289
+ }
290
+ let width = size.width ?? img.naturalWidth;
291
+ let height = size.height ?? img.naturalHeight;
292
+ if (size.width === "auto" && size.height !== "auto" && size.height) {
293
+ const targetHeight = typeof size.height === "number" ? size.height : img.naturalHeight;
294
+ width = img.naturalWidth / img.naturalHeight * targetHeight;
295
+ height = targetHeight;
296
+ } else if (size.height === "auto" && size.width !== "auto" && size.width) {
297
+ const targetWidth = typeof size.width === "number" ? size.width : img.naturalWidth;
298
+ height = img.naturalHeight / img.naturalWidth * targetWidth;
299
+ width = targetWidth;
300
+ }
301
+ const canvas = document.createElement("canvas");
302
+ canvas.width = width;
303
+ canvas.height = height;
304
+ const ctx = canvas.getContext("2d");
305
+ const fit = size.fit ?? "fill";
306
+ if (fit === "contain" || fit === "cover") {
307
+ const scale = fit === "contain" ? Math.min(width / img.naturalWidth, height / img.naturalHeight) : Math.max(width / img.naturalWidth, height / img.naturalHeight);
308
+ const drawWidth = img.naturalWidth * scale;
309
+ const drawHeight = img.naturalHeight * scale;
310
+ const offsetX = (width - drawWidth) / 2;
311
+ const offsetY = (height - drawHeight) / 2;
312
+ ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
313
+ } else {
314
+ ctx.drawImage(img, 0, 0, width, height);
315
+ }
316
+ const texture = new THREE.CanvasTexture(canvas);
317
+ texture.minFilter = THREE.LinearFilter;
318
+ texture.magFilter = THREE.LinearFilter;
319
+ texture.needsUpdate = true;
320
+ const material = new THREE.MeshBasicMaterial({
321
+ map: texture,
322
+ transparent: true,
323
+ depthWrite: false
324
+ });
325
+ const geometry = new THREE.PlaneGeometry(width, height);
326
+ const mesh = new THREE.Mesh(geometry, material);
327
+ return { mesh, width, height };
328
+ }
329
+
330
+ // src/renderers/svg.ts
331
+ async function renderSVGElement(element, _resolution, THREE) {
332
+ const { src, size = {}, colors = {} } = element;
333
+ let svgContent = AssetCache.getSVG(src);
334
+ if (!svgContent) {
335
+ if (src.startsWith("http") || src.startsWith("/")) {
336
+ const response = await fetch(src);
337
+ svgContent = await response.text();
338
+ } else {
339
+ svgContent = src;
340
+ }
341
+ }
342
+ Object.entries(colors).forEach(([selector, color]) => {
343
+ const regex = new RegExp(`(${selector}[^>]*)(fill|stroke)="[^"]*"`, "g");
344
+ svgContent = svgContent.replace(regex, `$1$2="${color}"`);
345
+ });
346
+ const parser = new DOMParser();
347
+ const svgDoc = parser.parseFromString(svgContent, "image/svg+xml");
348
+ const svgElement = svgDoc.documentElement;
349
+ const viewBox = svgElement.getAttribute("viewBox");
350
+ let svgWidth = parseFloat(svgElement.getAttribute("width") || "") || 100;
351
+ let svgHeight = parseFloat(svgElement.getAttribute("height") || "") || 100;
352
+ if (viewBox) {
353
+ const [, , vbW, vbH] = viewBox.split(" ").map(Number);
354
+ svgWidth = vbW || svgWidth;
355
+ svgHeight = vbH || svgHeight;
356
+ }
357
+ let width = size.width ?? svgWidth;
358
+ let height = size.height ?? svgHeight;
359
+ if (size.width === "auto" && size.height !== "auto" && size.height) {
360
+ width = svgWidth / svgHeight * size.height;
361
+ } else if (size.height === "auto" && size.width !== "auto" && size.width) {
362
+ height = svgHeight / svgWidth * size.width;
363
+ }
364
+ svgElement.setAttribute("width", String(width));
365
+ svgElement.setAttribute("height", String(height));
366
+ const updatedSvg = new XMLSerializer().serializeToString(svgElement);
367
+ const blob = new Blob([updatedSvg], { type: "image/svg+xml" });
368
+ const url = URL.createObjectURL(blob);
369
+ const img = new Image();
370
+ await new Promise((resolve, reject) => {
371
+ img.onload = () => resolve();
372
+ img.onerror = reject;
373
+ img.src = url;
374
+ });
375
+ const canvas = document.createElement("canvas");
376
+ canvas.width = width;
377
+ canvas.height = height;
378
+ const ctx = canvas.getContext("2d");
379
+ ctx.drawImage(img, 0, 0, width, height);
380
+ URL.revokeObjectURL(url);
381
+ const texture = new THREE.CanvasTexture(canvas);
382
+ texture.minFilter = THREE.LinearFilter;
383
+ texture.magFilter = THREE.LinearFilter;
384
+ texture.needsUpdate = true;
385
+ const material = new THREE.MeshBasicMaterial({
386
+ map: texture,
387
+ transparent: true,
388
+ depthWrite: false
389
+ });
390
+ const geometry = new THREE.PlaneGeometry(width, height);
391
+ const mesh = new THREE.Mesh(geometry, material);
392
+ return { mesh, width, height };
393
+ }
394
+
395
+ // src/renderers/text.ts
396
+ function renderTextElement(element, _resolution, THREE) {
397
+ const { content, font = {} } = element;
398
+ const fontSize = font.size ?? 24;
399
+ const fontFamily = font.family ?? "Inter, system-ui, sans-serif";
400
+ const fontWeight = font.weight ?? "normal";
401
+ const fontStyle = font.style ?? "normal";
402
+ const color = font.color ?? "#ffffff";
403
+ const align = font.align ?? "left";
404
+ const letterSpacing = font.letterSpacing ?? 0;
405
+ const lineHeight = font.lineHeight ?? 1.2;
406
+ const canvas = document.createElement("canvas");
407
+ const ctx = canvas.getContext("2d");
408
+ const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
409
+ ctx.font = fontString;
410
+ const lines = content.split("\n");
411
+ let maxWidth = 0;
412
+ lines.forEach((line) => {
413
+ let w = 0;
414
+ for (let i = 0; i < line.length; i++) {
415
+ w += ctx.measureText(line[i]).width;
416
+ if (i < line.length - 1) w += letterSpacing;
417
+ }
418
+ if (w > maxWidth) maxWidth = w;
419
+ });
420
+ const totalHeight = lines.length * fontSize * lineHeight;
421
+ const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 10;
422
+ const canvasWidth = Math.ceil(maxWidth + padding * 2);
423
+ const canvasHeight = Math.ceil(totalHeight + padding * 2);
424
+ canvas.width = canvasWidth;
425
+ canvas.height = canvasHeight;
426
+ ctx.font = fontString;
427
+ ctx.textBaseline = "top";
428
+ ctx.textAlign = align;
429
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
430
+ if (element.shadow) {
431
+ ctx.shadowColor = element.shadow.color;
432
+ ctx.shadowBlur = element.shadow.blur;
433
+ ctx.shadowOffsetX = element.shadow.offsetX ?? 0;
434
+ ctx.shadowOffsetY = element.shadow.offsetY ?? 0;
435
+ }
436
+ let textX = padding;
437
+ if (align === "center") textX = canvasWidth / 2;
438
+ else if (align === "right") textX = canvasWidth - padding;
439
+ lines.forEach((line, i) => {
440
+ const y = padding + i * fontSize * lineHeight;
441
+ if (element.stroke) {
442
+ ctx.strokeStyle = element.stroke.color;
443
+ ctx.lineWidth = element.stroke.width;
444
+ ctx.lineJoin = "round";
445
+ ctx.strokeText(line, textX, y);
446
+ }
447
+ ctx.fillStyle = color;
448
+ ctx.fillText(line, textX, y);
449
+ });
450
+ const texture = new THREE.CanvasTexture(canvas);
451
+ texture.minFilter = THREE.LinearFilter;
452
+ texture.magFilter = THREE.LinearFilter;
453
+ texture.needsUpdate = true;
454
+ const material = new THREE.MeshBasicMaterial({
455
+ map: texture,
456
+ transparent: true,
457
+ depthWrite: false
458
+ });
459
+ const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);
460
+ const mesh = new THREE.Mesh(geometry, material);
461
+ return { mesh, canvas, width: canvasWidth, height: canvasHeight };
462
+ }
463
+ function renderTextSegment(text, font, element, THREE) {
464
+ const fontSize = font.size ?? 24;
465
+ const fontFamily = font.family ?? "Inter, system-ui, sans-serif";
466
+ const fontWeight = font.weight ?? "normal";
467
+ const fontStyle = font.style ?? "normal";
468
+ const color = font.color ?? "#ffffff";
469
+ const canvas = document.createElement("canvas");
470
+ const ctx = canvas.getContext("2d");
471
+ const fontString = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
472
+ ctx.font = fontString;
473
+ const metrics = ctx.measureText(text);
474
+ const padding = Math.max(element.stroke?.width ?? 0, element.shadow?.blur ?? 0) * 2 + 4;
475
+ const canvasWidth = Math.ceil(metrics.width + padding * 2);
476
+ const canvasHeight = Math.ceil(fontSize * 1.4 + padding * 2);
477
+ canvas.width = canvasWidth;
478
+ canvas.height = canvasHeight;
479
+ ctx.font = fontString;
480
+ ctx.textBaseline = "top";
481
+ ctx.textAlign = "left";
482
+ if (element.shadow) {
483
+ ctx.shadowColor = element.shadow.color;
484
+ ctx.shadowBlur = element.shadow.blur;
485
+ ctx.shadowOffsetX = element.shadow.offsetX ?? 0;
486
+ ctx.shadowOffsetY = element.shadow.offsetY ?? 0;
487
+ }
488
+ if (element.stroke) {
489
+ ctx.strokeStyle = element.stroke.color;
490
+ ctx.lineWidth = element.stroke.width;
491
+ ctx.lineJoin = "round";
492
+ ctx.strokeText(text, padding, padding);
493
+ }
494
+ ctx.fillStyle = color;
495
+ ctx.fillText(text, padding, padding);
496
+ const texture = new THREE.CanvasTexture(canvas);
497
+ texture.minFilter = THREE.LinearFilter;
498
+ texture.magFilter = THREE.LinearFilter;
499
+ texture.needsUpdate = true;
500
+ const material = new THREE.MeshBasicMaterial({
501
+ map: texture,
502
+ transparent: true,
503
+ depthWrite: false
504
+ });
505
+ const geometry = new THREE.PlaneGeometry(canvasWidth, canvasHeight);
506
+ const mesh = new THREE.Mesh(geometry, material);
507
+ return {
508
+ mesh,
509
+ width: canvasWidth,
510
+ height: canvasHeight,
511
+ textWidth: metrics.width
512
+ };
513
+ }
514
+ function renderSplitTextElement(element, _resolution, THREE) {
515
+ const { content, font = {}, split } = element;
516
+ const splitType = split?.type ?? "chars";
517
+ const letterSpacing = font.letterSpacing ?? 0;
518
+ let segments;
519
+ if (splitType === "chars") {
520
+ segments = content.split("");
521
+ } else if (splitType === "words") {
522
+ segments = content.split(/\s+/);
523
+ } else {
524
+ segments = content.split("\n");
525
+ }
526
+ const meshes = [];
527
+ let totalWidth = 0;
528
+ segments.forEach((text, i) => {
529
+ if (text.length === 0) return;
530
+ const result = renderTextSegment(text, font, element, THREE);
531
+ meshes.push({
532
+ mesh: result.mesh,
533
+ width: result.width,
534
+ height: result.height,
535
+ textWidth: result.textWidth,
536
+ text
537
+ });
538
+ totalWidth += result.textWidth;
539
+ if (i < segments.length - 1) {
540
+ totalWidth += splitType === "words" ? (font.size ?? 24) * 0.4 : letterSpacing;
541
+ }
542
+ });
543
+ const totalHeight = meshes[0]?.height ?? 0;
544
+ let currentX = -totalWidth / 2;
545
+ meshes.forEach((item, i) => {
546
+ item.offsetX = currentX + item.textWidth / 2;
547
+ item.offsetY = 0;
548
+ currentX += item.textWidth;
549
+ if (i < meshes.length - 1) {
550
+ currentX += splitType === "words" ? (font.size ?? 24) * 0.4 : letterSpacing;
551
+ }
552
+ });
553
+ return { meshes, totalWidth, totalHeight };
554
+ }
555
+
556
+ // src/renderers/video.ts
557
+ async function renderVideoElement(element, _resolution, THREE) {
558
+ const {
559
+ src,
560
+ size = {},
561
+ loop = true,
562
+ muted = true,
563
+ playbackRate = 1,
564
+ startTime = 0
565
+ } = element;
566
+ let video;
567
+ const cached = AssetCache.getVideo(src);
568
+ if (cached) {
569
+ video = cached.element;
570
+ video.loop = loop;
571
+ video.muted = muted;
572
+ video.playbackRate = playbackRate;
573
+ } else {
574
+ video = document.createElement("video");
575
+ video.crossOrigin = "anonymous";
576
+ video.src = src;
577
+ video.loop = loop;
578
+ video.muted = muted;
579
+ video.playbackRate = playbackRate;
580
+ video.playsInline = true;
581
+ video.preload = "auto";
582
+ await new Promise((resolve, reject) => {
583
+ video.oncanplaythrough = () => resolve();
584
+ video.onerror = reject;
585
+ video.load();
586
+ });
587
+ }
588
+ video.currentTime = startTime;
589
+ video.pause();
590
+ let width = size.width ?? video.videoWidth;
591
+ let height = size.height ?? video.videoHeight;
592
+ if (size.width === "auto" && size.height !== "auto" && size.height) {
593
+ const targetHeight = typeof size.height === "number" ? size.height : video.videoHeight;
594
+ width = video.videoWidth / video.videoHeight * targetHeight;
595
+ height = targetHeight;
596
+ } else if (size.height === "auto" && size.width !== "auto" && size.width) {
597
+ const targetWidth = typeof size.width === "number" ? size.width : video.videoWidth;
598
+ height = video.videoHeight / video.videoWidth * targetWidth;
599
+ width = targetWidth;
600
+ }
601
+ const texture = new THREE.VideoTexture(video);
602
+ texture.minFilter = THREE.LinearFilter;
603
+ texture.magFilter = THREE.LinearFilter;
604
+ texture.format = THREE.RGBAFormat;
605
+ texture.generateMipmaps = false;
606
+ texture.colorSpace = THREE.SRGBColorSpace;
607
+ const material = new THREE.MeshBasicMaterial({
608
+ map: texture,
609
+ transparent: true,
610
+ depthWrite: false
611
+ });
612
+ const geometry = new THREE.PlaneGeometry(width, height);
613
+ const mesh = new THREE.Mesh(geometry, material);
614
+ mesh.userData.video = video;
615
+ mesh.userData.texture = texture;
616
+ return { mesh, width, height, video };
617
+ }
618
+
619
+ // src/renderElements.ts
620
+ function calculatePosition(position, resolution, elementWidth, elementHeight) {
621
+ const { width, height } = resolution;
622
+ const halfW = elementWidth / 2;
623
+ const halfH = elementHeight / 2;
624
+ if (typeof position === "string") {
625
+ switch (position) {
626
+ case "center":
627
+ return { x: width / 2 - halfW, y: height / 2 - halfH };
628
+ case "top-left":
629
+ return { x: 0, y: 0 };
630
+ case "top-center":
631
+ return { x: width / 2 - halfW, y: 0 };
632
+ case "top-right":
633
+ return { x: width - elementWidth, y: 0 };
634
+ case "center-left":
635
+ return { x: 0, y: height / 2 - halfH };
636
+ case "center-right":
637
+ return { x: width - elementWidth, y: height / 2 - halfH };
638
+ case "bottom-left":
639
+ return { x: 0, y: height - elementHeight };
640
+ case "bottom-center":
641
+ return { x: width / 2 - halfW, y: height - elementHeight };
642
+ case "bottom-right":
643
+ return { x: width - elementWidth, y: height - elementHeight };
644
+ default:
645
+ return { x: 0, y: 0 };
646
+ }
647
+ }
648
+ const x = typeof position.x === "string" ? parseFloat(position.x) / 100 * width : position.x;
649
+ const y = typeof position.y === "string" ? parseFloat(position.y) / 100 * height : position.y;
650
+ return { x, y };
651
+ }
652
+ var DESIGN_HEIGHT = 1080;
653
+ async function renderElements(elementsConfig, overlayScenes, resolution, THREE) {
654
+ const getScene = (config) => overlayScenes[config.zIndex ?? 100];
655
+ await preloadAssets(elementsConfig);
656
+ const elementMap = /* @__PURE__ */ new Map();
657
+ const resolutionScale = resolution.height / DESIGN_HEIGHT;
658
+ for (let i = 0; i < elementsConfig.length; i++) {
659
+ const config = elementsConfig[i];
660
+ const id = config.id ?? `element_${i}`;
661
+ try {
662
+ let mesh;
663
+ let canvas = null;
664
+ let elementWidth = 0;
665
+ let elementHeight = 0;
666
+ let segments = null;
667
+ const segmentMeshes = [];
668
+ let videoElement = null;
669
+ if (config.type === "text" && config.split) {
670
+ const splitResult = renderSplitTextElement(config, resolution, THREE);
671
+ elementWidth = splitResult.totalWidth;
672
+ elementHeight = splitResult.totalHeight;
673
+ const scaledWidth = elementWidth * resolutionScale;
674
+ const scaledHeight = elementHeight * resolutionScale;
675
+ const { x, y } = calculatePosition(
676
+ config.position,
677
+ resolution,
678
+ scaledWidth,
679
+ scaledHeight
680
+ );
681
+ const basePosX = x - resolution.width / 2 + scaledWidth / 2;
682
+ const basePosY = -(y - resolution.height / 2 + scaledHeight / 2);
683
+ let transformX = 0;
684
+ let transformY = 0;
685
+ if (config.transform) {
686
+ transformX = (config.transform.translateX ?? 0) * resolutionScale;
687
+ transformY = -((config.transform.translateY ?? 0) * resolutionScale);
688
+ }
689
+ const zIndex = config.zIndex ?? 100;
690
+ segments = splitResult.meshes.map((item, si) => {
691
+ const segMesh = item.mesh;
692
+ segMesh.scale.set(resolutionScale, resolutionScale, 1);
693
+ segMesh.position.x = basePosX + item.offsetX * resolutionScale + transformX;
694
+ segMesh.position.y = basePosY + item.offsetY * resolutionScale + transformY;
695
+ segMesh.position.z = 0;
696
+ segMesh.renderOrder = zIndex + i * 0.01 + si * 1e-3;
697
+ if (config.opacity !== void 0) {
698
+ segMesh.material.opacity = config.opacity;
699
+ }
700
+ getScene(config).add(segMesh);
701
+ segmentMeshes.push(segMesh);
702
+ return createElementProps(
703
+ THREE,
704
+ segMesh,
705
+ segMesh.position.x,
706
+ -segMesh.position.y,
707
+ config.opacity ?? 1
708
+ );
709
+ });
710
+ mesh = splitResult.meshes[0]?.mesh ?? new THREE.Mesh();
711
+ } else if (config.type === "text") {
712
+ const result = renderTextElement(config, resolution, THREE);
713
+ mesh = result.mesh;
714
+ canvas = result.canvas;
715
+ elementWidth = result.width;
716
+ elementHeight = result.height;
717
+ } else if (config.type === "image") {
718
+ const result = await renderImageElement(config, resolution, THREE);
719
+ mesh = result.mesh;
720
+ elementWidth = result.width;
721
+ elementHeight = result.height;
722
+ } else if (config.type === "svg") {
723
+ const result = await renderSVGElement(config, resolution, THREE);
724
+ mesh = result.mesh;
725
+ elementWidth = result.width;
726
+ elementHeight = result.height;
727
+ } else if (config.type === "video") {
728
+ const result = await renderVideoElement(config, resolution, THREE);
729
+ mesh = result.mesh;
730
+ elementWidth = result.width;
731
+ elementHeight = result.height;
732
+ mesh.userData.video = result.video;
733
+ videoElement = result.video;
734
+ videoElement.pause();
735
+ } else {
736
+ mesh = new THREE.Mesh(
737
+ new THREE.PlaneGeometry(100, 100),
738
+ new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })
739
+ );
740
+ console.warn(`Element type "${config.type}" not implemented`);
741
+ }
742
+ if (!config.split) {
743
+ const scaledWidth = elementWidth * resolutionScale;
744
+ const scaledHeight = elementHeight * resolutionScale;
745
+ const { x, y } = calculatePosition(
746
+ config.position,
747
+ resolution,
748
+ scaledWidth,
749
+ scaledHeight
750
+ );
751
+ const posX = x - resolution.width / 2 + scaledWidth / 2;
752
+ const posY = -(y - resolution.height / 2 + scaledHeight / 2);
753
+ mesh.scale.set(resolutionScale, resolutionScale, 1);
754
+ mesh.position.x = posX;
755
+ mesh.position.y = posY;
756
+ mesh.position.z = 0;
757
+ const zIndex = config.zIndex ?? 100;
758
+ mesh.renderOrder = zIndex + i * 0.01;
759
+ if (config.opacity !== void 0) {
760
+ ;
761
+ mesh.material.opacity = config.opacity;
762
+ mesh.material.transparent = true;
763
+ }
764
+ if (config.transform) {
765
+ const t = config.transform;
766
+ if (t.translateX) mesh.position.x += t.translateX * resolutionScale;
767
+ if (t.translateY) mesh.position.y -= t.translateY * resolutionScale;
768
+ if (t.translateZ) mesh.position.z += t.translateZ;
769
+ if (t.scale)
770
+ mesh.scale.set(
771
+ t.scale * resolutionScale,
772
+ t.scale * resolutionScale,
773
+ 1
774
+ );
775
+ if (t.rotateZ || t.rotation) {
776
+ mesh.rotation.z = (t.rotateZ ?? t.rotation ?? 0) * Math.PI / 180;
777
+ }
778
+ }
779
+ getScene(config).add(mesh);
780
+ }
781
+ const props = createElementProps(
782
+ THREE,
783
+ mesh,
784
+ mesh.position.x,
785
+ -mesh.position.y,
786
+ config.opacity ?? 1,
787
+ videoElement
788
+ );
789
+ const elementInstance = {
790
+ config,
791
+ mesh,
792
+ node: null,
793
+ props,
794
+ segments,
795
+ setContent: (_content) => {
796
+ if (config.type === "text" && canvas) {
797
+ console.warn("setContent not fully implemented in inline renderer");
798
+ }
799
+ },
800
+ destroy: () => {
801
+ const targetScene = getScene(config);
802
+ if (segmentMeshes.length > 0) {
803
+ segmentMeshes.forEach((m) => {
804
+ targetScene.remove(m);
805
+ m.geometry.dispose();
806
+ const mat = m.material;
807
+ if (mat.map) mat.map.dispose();
808
+ mat.dispose();
809
+ });
810
+ } else {
811
+ targetScene.remove(mesh);
812
+ mesh.geometry.dispose();
813
+ const mat = mesh.material;
814
+ if (mat.map) mat.map.dispose();
815
+ mat.dispose();
816
+ }
817
+ }
818
+ };
819
+ elementMap.set(id, elementInstance);
820
+ } catch (error) {
821
+ console.warn(
822
+ `[vos] Failed to render element "${id}" (${config.type}):`,
823
+ error
824
+ );
825
+ const fallbackMesh = new THREE.Mesh(
826
+ new THREE.PlaneGeometry(100, 100),
827
+ new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })
828
+ );
829
+ getScene(config).add(fallbackMesh);
830
+ const props = createElementProps(THREE, fallbackMesh, 0, 0, 0);
831
+ elementMap.set(id, {
832
+ config,
833
+ mesh: fallbackMesh,
834
+ node: null,
835
+ props,
836
+ segments: null,
837
+ setContent: () => {
838
+ },
839
+ destroy: () => {
840
+ getScene(config).remove(fallbackMesh);
841
+ fallbackMesh.geometry.dispose();
842
+ fallbackMesh.material.dispose();
843
+ }
844
+ });
845
+ }
846
+ }
847
+ return elementMap;
848
+ }
849
+
850
+ // src/index.ts
851
+ function createVosElements(THREE) {
852
+ return {
853
+ renderElements: (elementsConfig, overlayScenes, resolution) => renderElements(elementsConfig, overlayScenes, resolution, THREE),
854
+ disposeElements: (elementMap) => {
855
+ elementMap.forEach((instance) => instance.destroy?.());
856
+ elementMap.clear();
857
+ }
858
+ };
859
+ }
860
+ export {
861
+ createVosElements,
862
+ renderElements
863
+ };
864
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@vosjs/elements",
3
+ "version": "0.1.0",
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
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Hongbin Li",
8
+ "homepage": "https://github.com/vosjs/vos#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/vosjs/vos.git",
12
+ "directory": "packages/elements"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/vosjs/vos/issues"
16
+ },
17
+ "keywords": [
18
+ "vos",
19
+ "three",
20
+ "threejs",
21
+ "elements",
22
+ "overlay",
23
+ "text",
24
+ "svg"
25
+ ],
26
+ "main": "./src/index.ts",
27
+ "types": "./src/index.ts",
28
+ "exports": {
29
+ ".": "./src/index.ts",
30
+ "./bundle": {
31
+ "types": "./dist/bundle.d.ts",
32
+ "import": "./dist/bundle.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "build": "tsup && node bundle.mjs",
41
+ "typecheck": "tsc --noEmit",
42
+ "lint": "eslint .",
43
+ "clean": "rm -rf dist node_modules"
44
+ },
45
+ "peerDependencies": {
46
+ "three": ">=0.160.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/three": "^0.182.0",
50
+ "esbuild": "^0.27.0",
51
+ "three": "^0.182.0",
52
+ "tsup": "^8.5.0",
53
+ "typescript": "^5"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public",
57
+ "main": "./dist/index.js",
58
+ "types": "./dist/index.d.ts",
59
+ "exports": {
60
+ ".": {
61
+ "types": "./dist/index.d.ts",
62
+ "import": "./dist/index.js"
63
+ },
64
+ "./bundle": {
65
+ "types": "./dist/bundle.d.ts",
66
+ "import": "./dist/bundle.js"
67
+ }
68
+ }
69
+ }
70
+ }