"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( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/next/index.tsx 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")); // src/next/hooks.ts var import_react2 = require("react"); // src/shared/hooks.ts var import_react = require("react"); // src/shared/constants.ts 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 = { /** Default container width */ width: "100%", /** Default container height */ height: "100%", /** Default rendering scale (0.25 to 1.0) */ scale: 1, /** Default production mode setting */ production: true, /** Default device pixel ratio */ dpi: 1.5, /** Default frames per second */ fps: 60, /** Default alt text for accessibility */ altText: "Scene", /** Default CSS class name */ className: "", /** Default paused state */ paused: false, /** Default lazy loading setting */ lazyLoad: true, /** Default setting for showing placeholder on error */ showPlaceholderOnError: true, /** Default setting for showing placeholder while loading */ showPlaceholderWhileLoading: true }; var VALID_FPS = [15, 24, 30, 60, 120]; // src/shared/utils.ts 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; } // src/shared/hooks.ts 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 }; } // src/next/hooks.ts 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 }; } // src/shared/styles.ts var unicornStyles = { /** * Styles for the main scene container element. * * @remarks * Uses CSS custom properties for width and height to allow dynamic sizing. */ container: { position: "relative", width: "var(--unicorn-width)", height: "var(--unicorn-height)" }, /** * Styles for the error message wrapper. */ errorWrapper: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, /** * Styles for the error message box. */ errorBox: { textAlign: "center", padding: "1rem", borderRadius: "0.5rem", backgroundColor: "rgb(254 242 242)", color: "rgb(239 68 68)" }, /** * Styles for the error title text. */ errorTitle: { fontWeight: "600", marginBottom: "0.25rem" }, /** * Styles for the error message text. */ errorMessage: { fontSize: "0.875rem", marginTop: "0.25rem" } }; // src/next/index.tsx 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 /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_script.default, { src: sdkUrl, strategy: lazyLoad ? "lazyOnload" : "afterInteractive", onLoad: handleScriptLoad, onError: handleScriptError } ), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)( "div", { ref: elementRef, style: { ...unicornStyles.container, ...customProperties }, className, children: [ showPlaceholder && (placeholder || placeholderClassName) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { position: "absolute", inset: 0 }, children: typeof placeholder === "string" ? useNumericDimensions ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_image.default, { src: placeholder, alt: altText, width: numericWidth, height: numericHeight, style: { objectFit: "cover" }, priority: true } ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)( import_image.default, { src: placeholder, alt: altText, fill: true, style: { objectFit: "cover" }, priority: true } ) : placeholder ? placeholder : placeholderClassName ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)( "div", { className: placeholderClassName, style: { width: "100%", height: "100%" }, "aria-label": altText } ) : null }), error && !showPlaceholder && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: unicornStyles.errorWrapper, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: unicornStyles.errorBox, children: [ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: unicornStyles.errorTitle, children: "Error loading scene" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: unicornStyles.errorMessage, children: error.message }) ] }) }) ] } ) ] }); } var next_default = UnicornScene; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { UnicornScene }); //# sourceMappingURL=next.js.map