| "use strict"; |
| "use client"; |
| var __create = Object.create; |
| var __defProp = Object.defineProperty; |
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |
| var __getOwnPropNames = Object.getOwnPropertyNames; |
| var __getProtoOf = Object.getPrototypeOf; |
| var __hasOwnProp = Object.prototype.hasOwnProperty; |
| var __export = (target, all) => { |
| for (var name in all) |
| __defProp(target, name, { get: all[name], enumerable: true }); |
| }; |
| var __copyProps = (to, from, except, desc) => { |
| if (from && typeof from === "object" || typeof from === "function") { |
| for (let key of __getOwnPropNames(from)) |
| if (!__hasOwnProp.call(to, key) && key !== except) |
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); |
| } |
| return to; |
| }; |
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( |
| |
| |
| |
| |
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, |
| mod |
| )); |
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); |
|
|
| |
| var next_exports = {}; |
| __export(next_exports, { |
| UnicornScene: () => UnicornScene, |
| default: () => next_default |
| }); |
| module.exports = __toCommonJS(next_exports); |
| var import_react3 = require("react"); |
| var import_script = __toESM(require("next/script")); |
| var import_image = __toESM(require("next/image")); |
|
|
| |
| var import_react2 = require("react"); |
|
|
| |
| var import_react = require("react"); |
|
|
| |
| var UNICORN_STUDIO_VERSION = "2.1.4"; |
| var UNICORN_STUDIO_CDN_URL = `https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v${UNICORN_STUDIO_VERSION}/dist/unicornStudio.umd.js`; |
| var DEFAULT_VALUES = { |
| |
| width: "100%", |
| |
| height: "100%", |
| |
| scale: 1, |
| |
| production: true, |
| |
| dpi: 1.5, |
| |
| fps: 60, |
| |
| altText: "Scene", |
| |
| className: "", |
| |
| paused: false, |
| |
| lazyLoad: true, |
| |
| showPlaceholderOnError: true, |
| |
| showPlaceholderWhileLoading: true |
| }; |
| var VALID_FPS = [15, 24, 30, 60, 120]; |
|
|
| |
| function isWebGLSupported() { |
| if (typeof window === "undefined") return true; |
| try { |
| const canvas = document.createElement("canvas"); |
| const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); |
| return !!gl; |
| } catch (e) { |
| return false; |
| } |
| } |
| function validateFPS(fps) { |
| return VALID_FPS.includes(fps); |
| } |
| function validateScale(scale) { |
| return scale >= 0.25 && scale <= 1; |
| } |
| function validateParameters(scale, fps) { |
| if (!validateScale(scale)) { |
| return `Invalid scale: ${scale}. Scale must be between 0.25 and 1.0`; |
| } |
| if (!validateFPS(fps)) { |
| return `Invalid fps: ${fps}. FPS must be one of: 15, 24, 30, 60, 120`; |
| } |
| return null; |
| } |
|
|
| |
| function sanitizeErrorMessage(message) { |
| if (message.includes("404") || message.includes("Failed to fetch")) { |
| return "Resource not found"; |
| } |
| if (message.includes("Network") || message.includes("network")) { |
| return "Network error occurred"; |
| } |
| if (message.includes("timeout")) { |
| return "Loading timeout"; |
| } |
| const urlPattern = /\bhttps?:\/\/[^\s)]+/gi; |
| const filePathPattern = /\b(?:[A-Za-z]:\\|\/)[^\s)]+/g; |
| let sanitized = message.replaceAll(urlPattern, "[redacted]"); |
| sanitized = sanitized.replaceAll(filePathPattern, "[redacted]"); |
| return sanitized; |
| } |
| function buildSceneConfig(element, params) { |
| const elementId = element.id || `unicorn-${Math.random().toString(36).slice(2, 11)}`; |
| if (!element.id) { |
| element.id = elementId; |
| } |
| const config = { |
| elementId, |
| scale: params.scale, |
| dpi: params.dpi, |
| fps: params.fps, |
| lazyLoad: params.lazyLoad, |
| altText: params.altText, |
| ariaLabel: params.ariaLabel, |
| production: params.production |
| }; |
| if (params.jsonFilePath) { |
| config.filePath = params.jsonFilePath; |
| } else if (params.projectId) { |
| config.projectId = params.projectId; |
| } else { |
| throw new Error("No project ID or JSON file path provided"); |
| } |
| return config; |
| } |
| var INIT_TIMEOUT_MS = 15e3; |
| async function withTimeout(promise, ms = INIT_TIMEOUT_MS) { |
| let timeoutId; |
| const timeoutPromise = new Promise((_, reject) => { |
| timeoutId = setTimeout( |
| () => reject(new Error("Scene initialization timeout")), |
| ms |
| ); |
| }); |
| try { |
| return await Promise.race([promise, timeoutPromise]); |
| } finally { |
| if (timeoutId) clearTimeout(timeoutId); |
| } |
| } |
| function assignSceneRef(ref, value) { |
| if (!ref) return; |
| if (typeof ref === "function") { |
| ref(value); |
| return; |
| } |
| ref.current = value; |
| } |
| function useUnicornScene({ |
| elementRef, |
| projectId, |
| jsonFilePath, |
| production, |
| scale, |
| dpi, |
| fps, |
| lazyLoad, |
| altText, |
| ariaLabel, |
| isScriptLoaded, |
| paused, |
| onLoad, |
| onError, |
| sceneRef |
| }) { |
| const internalSceneRef = (0, import_react.useRef)(null); |
| const [initError, setInitError] = (0, import_react.useState)(null); |
| const initializationKeyRef = (0, import_react.useRef)(""); |
| const isInitializingRef = (0, import_react.useRef)(false); |
| const onLoadRef = (0, import_react.useRef)(onLoad); |
| onLoadRef.current = onLoad; |
| const onErrorRef = (0, import_react.useRef)(onError); |
| onErrorRef.current = onError; |
| const sceneRefRef = (0, import_react.useRef)(sceneRef); |
| const prevSceneRef = (0, import_react.useRef)(sceneRef); |
| if (sceneRef !== prevSceneRef.current) { |
| assignSceneRef(prevSceneRef.current, null); |
| sceneRefRef.current = sceneRef; |
| prevSceneRef.current = sceneRef; |
| assignSceneRef(sceneRef, internalSceneRef.current); |
| } |
| const validationError = (0, import_react.useMemo)(() => { |
| return validateParameters(scale, fps); |
| }, [scale, fps]); |
| const prevValidationError = (0, import_react.useRef)(null); |
| (0, import_react.useEffect)(() => { |
| var _a; |
| if (validationError !== prevValidationError.current) { |
| prevValidationError.current = validationError; |
| if (validationError) { |
| const error = new Error(validationError); |
| setInitError(error); |
| (_a = onErrorRef.current) == null ? void 0 : _a.call(onErrorRef, error); |
| } else { |
| setInitError(null); |
| } |
| } |
| }, [validationError]); |
| const destroyScene = (0, import_react.useCallback)(() => { |
| var _a; |
| if ((_a = internalSceneRef.current) == null ? void 0 : _a.destroy) { |
| internalSceneRef.current.destroy(); |
| internalSceneRef.current = null; |
| assignSceneRef(sceneRefRef.current, null); |
| } |
| isInitializingRef.current = false; |
| }, []); |
| (0, import_react.useEffect)(() => { |
| let ignore = false; |
| async function initializeScene() { |
| var _a, _b, _c; |
| if (!elementRef.current || !isScriptLoaded || validationError) return; |
| if (isInitializingRef.current) return; |
| const currentKey = `${projectId || ""}-${jsonFilePath || ""}-${scale}-${dpi}-${fps}-${production ? "prod" : "dev"}`; |
| if (initializationKeyRef.current === currentKey && internalSceneRef.current) { |
| return; |
| } |
| initializationKeyRef.current = currentKey; |
| try { |
| destroyScene(); |
| isInitializingRef.current = true; |
| if (!((_a = window.UnicornStudio) == null ? void 0 : _a.addScene)) { |
| throw new Error("UnicornStudio.addScene not found"); |
| } |
| const sceneConfig = buildSceneConfig(elementRef.current, { |
| jsonFilePath, |
| projectId, |
| scale, |
| dpi, |
| fps, |
| lazyLoad, |
| altText, |
| ariaLabel, |
| production |
| }); |
| const scene = await withTimeout( |
| window.UnicornStudio.addScene(sceneConfig) |
| ); |
| if (ignore) { |
| scene == null ? void 0 : scene.destroy(); |
| return; |
| } |
| if (scene) { |
| internalSceneRef.current = scene; |
| assignSceneRef(sceneRefRef.current, scene); |
| setInitError(null); |
| isInitializingRef.current = false; |
| (_b = onLoadRef.current) == null ? void 0 : _b.call(onLoadRef); |
| } else { |
| isInitializingRef.current = false; |
| throw new Error("Failed to initialize scene"); |
| } |
| } catch (error) { |
| if (ignore) return; |
| const err = error instanceof Error ? error : new Error("Unknown error"); |
| const sanitizedError = new Error(sanitizeErrorMessage(err.message)); |
| setInitError(sanitizedError); |
| isInitializingRef.current = false; |
| (_c = onErrorRef.current) == null ? void 0 : _c.call(onErrorRef, sanitizedError); |
| } |
| } |
| if (isScriptLoaded) { |
| void initializeScene(); |
| } |
| return () => { |
| ignore = true; |
| destroyScene(); |
| }; |
| }, [ |
| isScriptLoaded, |
| elementRef, |
| jsonFilePath, |
| projectId, |
| production, |
| scale, |
| dpi, |
| fps, |
| lazyLoad, |
| altText, |
| ariaLabel, |
| destroyScene, |
| validationError |
| ]); |
| (0, import_react.useEffect)(() => { |
| if (internalSceneRef.current && paused !== void 0) { |
| internalSceneRef.current.paused = paused; |
| } |
| }, [paused]); |
| (0, import_react.useEffect)(() => { |
| const el = elementRef.current; |
| if (!el || typeof ResizeObserver === "undefined") return; |
| const observer = new ResizeObserver(() => { |
| var _a, _b; |
| (_b = (_a = internalSceneRef.current) == null ? void 0 : _a.resize) == null ? void 0 : _b.call(_a); |
| }); |
| observer.observe(el); |
| return () => { |
| observer.disconnect(); |
| }; |
| }, [elementRef]); |
| return { error: initError }; |
| } |
|
|
| |
| function useUnicornStudioScript() { |
| const [isLoaded, setIsLoaded] = (0, import_react2.useState)(false); |
| const [error, setError] = (0, import_react2.useState)(null); |
| const handleScriptLoad = (0, import_react2.useCallback)(() => { |
| setIsLoaded((prev) => { |
| if (!prev && typeof window !== "undefined" && window.UnicornStudio) { |
| setError(null); |
| return true; |
| } |
| return prev; |
| }); |
| }, []); |
| const handleScriptError = (0, import_react2.useCallback)(() => { |
| setError(new Error("Failed to load UnicornStudio script")); |
| setIsLoaded(false); |
| }, []); |
| (0, import_react2.useEffect)(() => { |
| if (typeof window !== "undefined" && window.UnicornStudio && !isLoaded) { |
| setIsLoaded(true); |
| setError(null); |
| } |
| }, [isLoaded]); |
| return { isLoaded, error, handleScriptLoad, handleScriptError }; |
| } |
|
|
| |
| var unicornStyles = { |
| |
| |
| |
| |
| |
| |
| container: { |
| position: "relative", |
| width: "var(--unicorn-width)", |
| height: "var(--unicorn-height)" |
| }, |
| |
| |
| |
| errorWrapper: { |
| display: "flex", |
| alignItems: "center", |
| justifyContent: "center", |
| height: "100%" |
| }, |
| |
| |
| |
| errorBox: { |
| textAlign: "center", |
| padding: "1rem", |
| borderRadius: "0.5rem", |
| backgroundColor: "rgb(254 242 242)", |
| color: "rgb(239 68 68)" |
| }, |
| |
| |
| |
| errorTitle: { |
| fontWeight: "600", |
| marginBottom: "0.25rem" |
| }, |
| |
| |
| |
| errorMessage: { |
| fontSize: "0.875rem", |
| marginTop: "0.25rem" |
| } |
| }; |
|
|
| |
| var import_jsx_runtime = require("react/jsx-runtime"); |
| function UnicornScene({ |
| projectId, |
| jsonFilePath, |
| sdkUrl = UNICORN_STUDIO_CDN_URL, |
| width = DEFAULT_VALUES.width, |
| height = DEFAULT_VALUES.height, |
| scale = DEFAULT_VALUES.scale, |
| dpi = DEFAULT_VALUES.dpi, |
| fps = DEFAULT_VALUES.fps, |
| altText = DEFAULT_VALUES.altText, |
| ariaLabel, |
| className = DEFAULT_VALUES.className, |
| lazyLoad = DEFAULT_VALUES.lazyLoad, |
| production = DEFAULT_VALUES.production, |
| paused = DEFAULT_VALUES.paused, |
| placeholder, |
| placeholderClassName, |
| showPlaceholderOnError = DEFAULT_VALUES.showPlaceholderOnError, |
| showPlaceholderWhileLoading = DEFAULT_VALUES.showPlaceholderWhileLoading, |
| onLoad, |
| onError, |
| sceneRef |
| }) { |
| const elementRef = (0, import_react3.useRef)(null); |
| const [isSceneLoaded, setIsSceneLoaded] = (0, import_react3.useState)(false); |
| const [webGLSupported, setWebGLSupported] = (0, import_react3.useState)(true); |
| const { |
| isLoaded, |
| error: scriptError, |
| handleScriptLoad, |
| handleScriptError |
| } = useUnicornStudioScript(); |
| const { error: sceneError } = useUnicornScene({ |
| elementRef, |
| projectId, |
| jsonFilePath, |
| production, |
| scale, |
| dpi, |
| fps, |
| lazyLoad, |
| altText, |
| ariaLabel: ariaLabel || altText, |
| isScriptLoaded: isLoaded, |
| paused, |
| sceneRef, |
| onLoad: () => { |
| setIsSceneLoaded(true); |
| onLoad == null ? void 0 : onLoad(); |
| }, |
| onError |
| }); |
| const error = scriptError || sceneError; |
| (0, import_react3.useEffect)(() => { |
| setWebGLSupported(isWebGLSupported()); |
| }, []); |
| const showPlaceholder = (placeholder || placeholderClassName) && (!webGLSupported || showPlaceholderWhileLoading && !isSceneLoaded || showPlaceholderOnError && error); |
| const numericWidth = typeof width === "number" ? width : 0; |
| const numericHeight = typeof height === "number" ? height : 0; |
| const useNumericDimensions = typeof width === "number" && typeof height === "number"; |
| const customProperties = { |
| "--unicorn-width": typeof width === "number" ? `${width}px` : width, |
| "--unicorn-height": typeof height === "number" ? `${height}px` : height |
| }; |
| return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ |
| (0, import_jsx_runtime.jsx)( |
| import_script.default, |
| { |
| src: sdkUrl, |
| strategy: lazyLoad ? "lazyOnload" : "afterInteractive", |
| onLoad: handleScriptLoad, |
| onError: handleScriptError |
| } |
| ), |
| (0, import_jsx_runtime.jsxs)( |
| "div", |
| { |
| ref: elementRef, |
| style: { ...unicornStyles.container, ...customProperties }, |
| className, |
| children: [ |
| showPlaceholder && (placeholder || placeholderClassName) && (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0 }, children: typeof placeholder === "string" ? useNumericDimensions ? (0, import_jsx_runtime.jsx)( |
| import_image.default, |
| { |
| src: placeholder, |
| alt: altText, |
| width: numericWidth, |
| height: numericHeight, |
| style: { objectFit: "cover" }, |
| priority: true |
| } |
| ) : (0, import_jsx_runtime.jsx)( |
| import_image.default, |
| { |
| src: placeholder, |
| alt: altText, |
| fill: true, |
| style: { objectFit: "cover" }, |
| priority: true |
| } |
| ) : placeholder ? placeholder : placeholderClassName ? (0, import_jsx_runtime.jsx)( |
| "div", |
| { |
| className: placeholderClassName, |
| style: { width: "100%", height: "100%" }, |
| "aria-label": altText |
| } |
| ) : null }), |
| error && !showPlaceholder && (0, import_jsx_runtime.jsx)("div", { style: unicornStyles.errorWrapper, children: (0, import_jsx_runtime.jsxs)("div", { style: unicornStyles.errorBox, children: [ |
| (0, import_jsx_runtime.jsx)("p", { style: unicornStyles.errorTitle, children: "Error loading scene" }), |
| (0, import_jsx_runtime.jsx)("p", { style: unicornStyles.errorMessage, children: error.message }) |
| ] }) }) |
| ] |
| } |
| ) |
| ] }); |
| } |
| var next_default = UnicornScene; |
| |
| 0 && (module.exports = { |
| UnicornScene |
| }); |
| |