| {"version":3,"sources":["../src/react/index.tsx","../src/react/hooks.ts","../src/shared/hooks.ts","../src/shared/constants.ts","../src/shared/utils.ts","../src/shared/styles.ts"],"sourcesContent":["import { useRef, useState, useEffect } from \"react\";\nimport type { UnicornSceneProps } from \"../shared/types\";\nimport { useUnicornStudioScript, useUnicornScene } from \"./hooks\";\nimport { UNICORN_STUDIO_CDN_URL, DEFAULT_VALUES } from \"../shared/constants\";\nimport { unicornStyles } from \"../shared/styles\";\nimport { isWebGLSupported } from \"../shared/utils\";\n\n/**\n * React component for rendering Unicorn Studio WebGL animations.\n *\n * @remarks\n * This component wraps Unicorn Studio's WebGL animation system for use in React applications.\n * It handles script loading, scene initialization, placeholder display, and error states.\n *\n * The component requires either a `projectId` (to load from Unicorn Studio's servers)\n * or a `jsonFilePath` (to load from a local JSON file).\n *\n * @param props - The component props\n *\n * @example\n * Basic usage with project ID:\n * ```tsx\n * <UnicornScene\n * projectId=\"your-project-id\"\n * width={800}\n * height={600}\n * />\n * ```\n *\n * @example\n * With placeholder and callbacks:\n * ```tsx\n * <UnicornScene\n * projectId=\"your-project-id\"\n * placeholder=\"/images/loading.png\"\n * onLoad={() => console.log(\"Scene loaded!\")}\n * onError={(err) => console.error(err)}\n * />\n * ```\n *\n * @example\n * Using local JSON file:\n * ```tsx\n * <UnicornScene\n * jsonFilePath=\"/scenes/animation.json\"\n * scale={0.5}\n * fps={30}\n * />\n * ```\n */\nfunction UnicornScene({\n projectId,\n jsonFilePath,\n sdkUrl = UNICORN_STUDIO_CDN_URL,\n width = DEFAULT_VALUES.width,\n height = DEFAULT_VALUES.height,\n scale = DEFAULT_VALUES.scale,\n dpi = DEFAULT_VALUES.dpi,\n fps = DEFAULT_VALUES.fps,\n altText = DEFAULT_VALUES.altText,\n ariaLabel,\n className = DEFAULT_VALUES.className,\n lazyLoad = DEFAULT_VALUES.lazyLoad,\n production = DEFAULT_VALUES.production,\n paused = DEFAULT_VALUES.paused,\n placeholder,\n placeholderClassName,\n showPlaceholderOnError = DEFAULT_VALUES.showPlaceholderOnError,\n showPlaceholderWhileLoading = DEFAULT_VALUES.showPlaceholderWhileLoading,\n onLoad,\n onError,\n sceneRef,\n}: UnicornSceneProps) {\n const elementRef = useRef<HTMLDivElement>(null);\n const [isSceneLoaded, setIsSceneLoaded] = useState(false);\n const [webGLSupported, setWebGLSupported] = useState(true);\n\n const { isLoaded, error: scriptError } = useUnicornStudioScript(sdkUrl);\n\n const { error: sceneError } = useUnicornScene({\n elementRef,\n projectId,\n jsonFilePath,\n production,\n scale,\n dpi,\n fps,\n lazyLoad,\n altText,\n ariaLabel: ariaLabel || altText,\n isScriptLoaded: isLoaded,\n paused,\n sceneRef,\n onLoad: () => {\n setIsSceneLoaded(true);\n onLoad?.();\n },\n onError,\n });\n\n const error = scriptError || sceneError;\n\n // Check WebGL support on mount\n useEffect(() => {\n setWebGLSupported(isWebGLSupported());\n }, []);\n\n // Determine if placeholder should be shown\n const showPlaceholder =\n (placeholder || placeholderClassName) &&\n (!webGLSupported ||\n (showPlaceholderWhileLoading && !isSceneLoaded) ||\n (showPlaceholderOnError && error));\n\n // Calculate dimensions for both container and image\n const numericWidth = typeof width === \"number\" ? width : 0;\n const numericHeight = typeof height === \"number\" ? height : 0;\n const useNumericDimensions =\n typeof width === \"number\" && typeof height === \"number\";\n\n // Build CSS custom properties for dynamic dimensions\n const customProperties = {\n \"--unicorn-width\": typeof width === \"number\" ? `${width}px` : width,\n \"--unicorn-height\": typeof height === \"number\" ? `${height}px` : height,\n } as React.CSSProperties;\n\n return (\n <div\n ref={elementRef}\n style={{ ...unicornStyles.container, ...customProperties }}\n className={className}\n >\n {showPlaceholder && (placeholder || placeholderClassName) && (\n <div style={{ position: \"absolute\", inset: 0 }}>\n {typeof placeholder === \"string\" ? (\n <img\n src={placeholder}\n alt={altText}\n style={{\n width: useNumericDimensions ? `${numericWidth}px` : \"100%\",\n height: useNumericDimensions ? `${numericHeight}px` : \"100%\",\n objectFit: \"cover\",\n }}\n />\n ) : placeholder ? (\n placeholder\n ) : placeholderClassName ? (\n <div\n className={placeholderClassName}\n style={{ width: \"100%\", height: \"100%\" }}\n aria-label={altText}\n />\n ) : null}\n </div>\n )}\n {error && !showPlaceholder && (\n <div style={unicornStyles.errorWrapper}>\n <div style={unicornStyles.errorBox}>\n <p style={unicornStyles.errorTitle}>Error loading scene</p>\n <p style={unicornStyles.errorMessage}>{error.message}</p>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nexport default UnicornScene;\nexport { UnicornScene };\n\n// Re-export types for convenience\nexport type { UnicornSceneProps } from \"../shared/types\";\n","import { useEffect, useState, useCallback } from \"react\";\n\n/**\n * Hook for loading the Unicorn Studio SDK script in React applications.\n *\n * @remarks\n * This hook manages the lifecycle of the Unicorn Studio SDK script element.\n * It handles script loading, error states, and prevents duplicate script tags.\n * The script is loaded asynchronously and persists in the DOM to avoid re-loading on remount.\n *\n * @param scriptUrl - The URL of the Unicorn Studio SDK script to load\n * @returns An object containing loading state, error state, and event handlers\n *\n * @example\n * ```tsx\n * const { isLoaded, error } = useUnicornStudioScript(\n * \"https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v2.0.1/dist/unicornStudio.umd.js\"\n * );\n *\n * if (error) {\n * return <div>Failed to load SDK</div>;\n * }\n *\n * if (!isLoaded) {\n * return <div>Loading...</div>;\n * }\n * ```\n */\nexport function useUnicornStudioScript(scriptUrl: string): {\n /** Whether the script has finished loading successfully */\n isLoaded: boolean;\n /** Error that occurred during script loading, if any */\n error: Error | null;\n /** Callback to handle successful script load */\n handleScriptLoad: () => void;\n /** Callback to handle script loading error */\n handleScriptError: () => void;\n} {\n const [isLoaded, setIsLoaded] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const handleScriptLoad = useCallback(() => {\n setIsLoaded(true);\n }, []);\n\n const handleScriptError = useCallback(() => {\n setError(new Error(\"Failed to load UnicornStudio script\"));\n }, []);\n\n useEffect(() => {\n // Check if script is already loaded\n const existingScript = document.querySelector(\n `script[src=\"${scriptUrl}\"]`,\n ) as HTMLScriptElement;\n\n if (existingScript) {\n if (existingScript.dataset.loaded === \"true\") {\n setIsLoaded(true);\n return;\n }\n\n // If script exists but not loaded, attach listeners\n existingScript.addEventListener(\"load\", handleScriptLoad);\n existingScript.addEventListener(\"error\", handleScriptError);\n\n return () => {\n existingScript.removeEventListener(\"load\", handleScriptLoad);\n existingScript.removeEventListener(\"error\", handleScriptError);\n };\n }\n\n // Create and load the script\n const script = document.createElement(\"script\");\n script.src = scriptUrl;\n script.async = true;\n script.addEventListener(\"load\", () => {\n script.dataset.loaded = \"true\";\n handleScriptLoad();\n });\n script.addEventListener(\"error\", handleScriptError);\n\n document.head.appendChild(script);\n\n return () => {\n if (script.parentNode) {\n script.removeEventListener(\"load\", handleScriptLoad);\n script.removeEventListener(\"error\", handleScriptError);\n // Don't remove script from DOM to avoid re-loading on remount\n }\n };\n }, [scriptUrl, handleScriptLoad, handleScriptError]);\n\n return { isLoaded, error, handleScriptLoad, handleScriptError };\n}\n\n// Re-export shared hooks\nexport { useUnicornScene } from \"../shared/hooks\";\nexport type { UseUnicornSceneParams } from \"../shared/hooks\";\n","import { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport type {\n UnicornStudioScene,\n UnicornSceneConfig,\n ValidFPS,\n ScaleRange,\n} from \"./types\";\nimport { validateParameters } from \"./utils\";\n\n/**\n * Sanitizes error messages to avoid exposing internal URLs or sensitive details.\n */\nfunction sanitizeErrorMessage(message: string): string {\n if (message.includes(\"404\") || message.includes(\"Failed to fetch\")) {\n return \"Resource not found\";\n }\n if (message.includes(\"Network\") || message.includes(\"network\")) {\n return \"Network error occurred\";\n }\n if (message.includes(\"timeout\")) {\n return \"Loading timeout\";\n }\n // For all other errors, redact obvious URLs and file paths to avoid leaking internals.\n const urlPattern = /\\bhttps?:\\/\\/[^\\s)]+/gi;\n const filePathPattern = /\\b(?:[A-Za-z]:\\\\|\\/)[^\\s)]+/g;\n\n let sanitized = message.replaceAll(urlPattern, \"[redacted]\");\n sanitized = sanitized.replaceAll(filePathPattern, \"[redacted]\");\n\n return sanitized;\n}\n\n/**\n * Builds the scene configuration object from the given parameters and DOM element.\n *\n * @throws If neither `jsonFilePath` nor `projectId` is provided\n */\nfunction buildSceneConfig(\n element: HTMLDivElement,\n params: {\n jsonFilePath?: string;\n projectId?: string;\n scale: ScaleRange;\n dpi: number;\n fps: ValidFPS;\n lazyLoad: boolean;\n altText: string;\n ariaLabel: string;\n production?: boolean;\n },\n): UnicornSceneConfig {\n const elementId =\n element.id || `unicorn-${Math.random().toString(36).slice(2, 11)}`;\n\n if (!element.id) {\n element.id = elementId;\n }\n\n const config: UnicornSceneConfig = {\n elementId,\n scale: params.scale,\n dpi: params.dpi,\n fps: params.fps,\n lazyLoad: params.lazyLoad,\n altText: params.altText,\n ariaLabel: params.ariaLabel,\n production: params.production,\n };\n\n if (params.jsonFilePath) {\n config.filePath = params.jsonFilePath;\n } else if (params.projectId) {\n config.projectId = params.projectId;\n } else {\n throw new Error(\"No project ID or JSON file path provided\");\n }\n\n return config;\n}\n\nconst INIT_TIMEOUT_MS = 15000;\n\n/**\n * Wraps a promise with a timeout, rejecting if it doesn't resolve in time.\n * Ensures the timer is always cleaned up.\n */\nasync function withTimeout<T>(\n promise: Promise<T>,\n ms: number = INIT_TIMEOUT_MS,\n): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(\"Scene initialization timeout\")),\n ms,\n );\n });\n\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n}\n\n/**\n * Parameters for the useUnicornScene hook.\n */\nexport interface UseUnicornSceneParams {\n /**\n * React ref to the container element where the scene will be rendered.\n */\n elementRef: React.RefObject<HTMLDivElement | null>;\n\n /**\n * The Unicorn Studio project ID to load.\n */\n projectId?: string;\n\n /**\n * Path to a local JSON file containing the scene data.\n */\n jsonFilePath?: string;\n\n /**\n * Whether to use production mode for the scene.\n */\n production?: boolean;\n\n /**\n * Rendering scale factor (0.25 to 1.0).\n */\n scale: ScaleRange;\n\n /**\n * Device pixel ratio for rendering.\n */\n dpi: number;\n\n /**\n * Target frames per second for the animation.\n */\n fps: ValidFPS;\n\n /**\n * Whether to lazy load the scene when it enters the viewport.\n */\n lazyLoad: boolean;\n\n /**\n * Alt text for accessibility.\n */\n altText: string;\n\n /**\n * ARIA label for the scene container.\n */\n ariaLabel: string;\n\n /**\n * Whether the Unicorn Studio SDK script has finished loading.\n */\n isScriptLoaded: boolean;\n\n /**\n * Whether the scene animation is paused.\n */\n paused?: boolean;\n\n /**\n * Callback fired when the scene has loaded successfully.\n */\n onLoad?: () => void;\n\n /**\n * Callback fired when an error occurs during scene loading.\n *\n * @param error - The error that occurred\n */\n onError?: (error: Error) => void;\n\n /**\n * Optional ref that receives the active Unicorn Studio scene instance.\n */\n sceneRef?: React.Ref<UnicornStudioScene | null>;\n}\n\n/**\n * Synchronizes an external React ref (callback or object ref) with the given scene instance.\n *\n * @param ref - A React ref callback or mutable object ref to receive the scene instance; if `undefined`, no action is taken\n * @param value - The current `UnicornStudioScene` instance or `null` to clear the ref\n */\nfunction assignSceneRef(\n ref: React.Ref<UnicornStudioScene | null> | undefined,\n value: UnicornStudioScene | null,\n) {\n if (!ref) return;\n\n if (typeof ref === \"function\") {\n ref(value);\n return;\n }\n\n (ref as React.MutableRefObject<UnicornStudioScene | null>).current = value;\n}\n\n/**\n * Hook for managing a Unicorn Studio scene lifecycle.\n *\n * @remarks\n * This hook handles scene initialization, configuration updates, pause state synchronization,\n * and cleanup. It is framework-agnostic and used internally by both React and Next.js components.\n *\n * The hook will automatically:\n * - Initialize the scene when the SDK script loads\n * - Re-initialize when configuration changes\n * - Sync the paused state with the scene\n * - Clean up resources on unmount\n *\n * @param params - The hook parameters\n * @returns An object containing any initialization error\n *\n * @example\n * ```tsx\n * const { error } = useUnicornScene({\n * elementRef,\n * projectId: \"my-project-id\",\n * scale: 1,\n * dpi: 1.5,\n * fps: 60,\n * lazyLoad: true,\n * altText: \"My Scene\",\n * ariaLabel: \"Interactive animation\",\n * isScriptLoaded: true,\n * onLoad: () => console.log(\"Scene loaded!\"),\n * onError: (err) => console.error(err),\n * });\n * ```\n */\nexport function useUnicornScene({\n elementRef,\n projectId,\n jsonFilePath,\n production,\n scale,\n dpi,\n fps,\n lazyLoad,\n altText,\n ariaLabel,\n isScriptLoaded,\n paused,\n onLoad,\n onError,\n sceneRef,\n}: UseUnicornSceneParams): { error: Error | null } {\n const internalSceneRef = useRef<UnicornStudioScene | null>(null);\n const [initError, setInitError] = useState<Error | null>(null);\n const initializationKeyRef = useRef<string>(\"\");\n const isInitializingRef = useRef(false);\n\n // Stable refs for callbacks and sceneRef so they never trigger re-initialization\n const onLoadRef = useRef(onLoad);\n onLoadRef.current = onLoad;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n const sceneRefRef = useRef(sceneRef);\n const prevSceneRef = useRef(sceneRef);\n\n // Sync external sceneRef when the prop changes: null the old ref and\n // forward the current scene (if any) to the new one.\n if (sceneRef !== prevSceneRef.current) {\n assignSceneRef(prevSceneRef.current, null);\n sceneRefRef.current = sceneRef;\n prevSceneRef.current = sceneRef;\n assignSceneRef(sceneRef, internalSceneRef.current);\n }\n\n // Validate parameters early and memoize the result to prevent loops\n const validationError = useMemo(() => {\n return validateParameters(scale, fps);\n }, [scale, fps]);\n\n const prevValidationError = useRef<string | null>(null);\n\n useEffect(() => {\n if (validationError !== prevValidationError.current) {\n prevValidationError.current = validationError;\n\n if (validationError) {\n const error = new Error(validationError);\n setInitError(error);\n onErrorRef.current?.(error);\n } else {\n setInitError(null);\n }\n }\n }, [validationError]);\n\n const destroyScene = useCallback(() => {\n if (internalSceneRef.current?.destroy) {\n internalSceneRef.current.destroy();\n internalSceneRef.current = null;\n assignSceneRef(sceneRefRef.current, null);\n }\n isInitializingRef.current = false;\n }, []);\n\n useEffect(() => {\n let ignore = false;\n\n async function initializeScene() {\n if (!elementRef.current || !isScriptLoaded || validationError) return;\n\n // Prevent multiple concurrent initializations\n if (isInitializingRef.current) return;\n\n // Create a unique key for this configuration\n const currentKey = `${projectId || \"\"}-${jsonFilePath || \"\"}-${scale}-${dpi}-${fps}-${production ? \"prod\" : \"dev\"}`;\n\n // Check if we're already initialized with this exact configuration\n if (\n initializationKeyRef.current === currentKey &&\n internalSceneRef.current\n ) {\n return;\n }\n\n // Update the initialization key\n initializationKeyRef.current = currentKey;\n\n try {\n destroyScene();\n\n // Set the flag after destroyScene() which unconditionally clears it\n isInitializingRef.current = true;\n\n if (!window.UnicornStudio?.addScene) {\n throw new Error(\"UnicornStudio.addScene not found\");\n }\n\n const sceneConfig = buildSceneConfig(elementRef.current, {\n jsonFilePath,\n projectId,\n scale,\n dpi,\n fps,\n lazyLoad,\n altText,\n ariaLabel,\n production,\n });\n\n const scene = await withTimeout(\n window.UnicornStudio.addScene(sceneConfig),\n );\n\n // If the effect cleaned up while we were awaiting, destroy and bail\n if (ignore) {\n scene?.destroy();\n return;\n }\n\n if (scene) {\n internalSceneRef.current = scene;\n assignSceneRef(sceneRefRef.current, scene);\n setInitError(null);\n isInitializingRef.current = false;\n onLoadRef.current?.();\n } else {\n isInitializingRef.current = false;\n throw new Error(\"Failed to initialize scene\");\n }\n } catch (error) {\n if (ignore) return;\n\n const err = error instanceof Error ? error : new Error(\"Unknown error\");\n const sanitizedError = new Error(sanitizeErrorMessage(err.message));\n setInitError(sanitizedError);\n isInitializingRef.current = false;\n onErrorRef.current?.(sanitizedError);\n }\n }\n\n if (isScriptLoaded) {\n void initializeScene();\n }\n\n return () => {\n ignore = true;\n destroyScene();\n };\n }, [\n isScriptLoaded,\n elementRef,\n jsonFilePath,\n projectId,\n production,\n scale,\n dpi,\n fps,\n lazyLoad,\n altText,\n ariaLabel,\n destroyScene,\n validationError,\n ]);\n\n // Sync paused state with scene\n useEffect(() => {\n if (internalSceneRef.current && paused !== undefined) {\n internalSceneRef.current.paused = paused;\n }\n }, [paused]);\n\n // Observe container resize and call scene.resize() so the canvas adapts\n useEffect(() => {\n const el = elementRef.current;\n if (!el || typeof ResizeObserver === \"undefined\") return;\n\n const observer = new ResizeObserver(() => {\n internalSceneRef.current?.resize?.();\n });\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n };\n }, [elementRef]);\n\n return { error: initError };\n}\n","import type { ValidFPS, ScaleRange } from \"./types\";\n\n/**\n * Current version of the Unicorn Studio SDK being used.\n */\nexport const UNICORN_STUDIO_VERSION = \"2.1.4\";\n\n/**\n * CDN URL for the Unicorn Studio SDK script.\n *\n * @remarks\n * This URL points to the official Unicorn Studio UMD bundle hosted on jsDelivr.\n */\nexport const UNICORN_STUDIO_CDN_URL = `https://cdn.jsdelivr.net/gh/hiunicornstudio/unicornstudio.js@v${UNICORN_STUDIO_VERSION}/dist/unicornStudio.umd.js`;\n\n/**\n * Default values for UnicornScene component props.\n *\n * @remarks\n * These defaults are used when optional props are not provided to the component.\n */\nexport const DEFAULT_VALUES = {\n /** Default container width */\n width: \"100%\" as const,\n /** Default container height */\n height: \"100%\" as const,\n /** Default rendering scale (0.25 to 1.0) */\n scale: 1 as ScaleRange,\n /** Default production mode setting */\n production: true,\n /** Default device pixel ratio */\n dpi: 1.5,\n /** Default frames per second */\n fps: 60 as ValidFPS,\n /** Default alt text for accessibility */\n altText: \"Scene\",\n /** Default CSS class name */\n className: \"\",\n /** Default paused state */\n paused: false,\n /** Default lazy loading setting */\n lazyLoad: true,\n /** Default setting for showing placeholder on error */\n showPlaceholderOnError: true,\n /** Default setting for showing placeholder while loading */\n showPlaceholderWhileLoading: true,\n} as const;\n\n/**\n * Array of valid FPS values supported by Unicorn Studio.\n *\n * @remarks\n * Only these specific frame rates are supported: 15, 24, 30, 60, and 120.\n */\nexport const VALID_FPS = [15, 24, 30, 60, 120] as const;\n","import { VALID_FPS } from \"./constants\";\nimport type { ScaleRange, ValidFPS } from \"./types\";\n\n/**\n * Checks if WebGL is supported in the current browser environment.\n *\n * @remarks\n * During server-side rendering (SSR), this function returns `true` to assume\n * WebGL support until client-side hydration can verify.\n *\n * @returns `true` if WebGL is supported or during SSR, `false` otherwise\n *\n * @example\n * ```tsx\n * if (!isWebGLSupported()) {\n * return <FallbackContent />;\n * }\n * ```\n */\nexport function isWebGLSupported(): boolean {\n if (typeof window === \"undefined\") return true; // Assume supported during SSR\n\n try {\n const canvas = document.createElement(\"canvas\");\n const gl =\n canvas.getContext(\"webgl\") || canvas.getContext(\"experimental-webgl\");\n return !!gl;\n } catch (e) {\n return false;\n }\n}\n\n/**\n * Validates if the given FPS value is one of the allowed values.\n *\n * @param fps - The frames per second value to validate\n * @returns `true` if the FPS value is valid (15, 24, 30, 60, or 120)\n *\n * @example\n * ```ts\n * validateFPS(60); // true\n * validateFPS(45); // false\n * ```\n */\nexport function validateFPS(fps: number): fps is ValidFPS {\n return VALID_FPS.includes(fps as ValidFPS);\n}\n\n/**\n * Validates if the given scale value is within the allowed range.\n *\n * @param scale - The scale value to validate\n * @returns `true` if the scale is between 0.25 and 1.0 (inclusive)\n *\n * @example\n * ```ts\n * validateScale(1); // true\n * validateScale(0.5); // true\n * validateScale(0.1); // false\n * validateScale(1.5); // false\n * ```\n */\nexport function validateScale(scale: number): scale is ScaleRange {\n return scale >= 0.25 && scale <= 1.0;\n}\n\n/**\n * Validates both scale and FPS parameters and returns an error message if invalid.\n *\n * @param scale - The scale value to validate (must be between 0.25 and 1.0)\n * @param fps - The frames per second value to validate (must be 15, 24, 30, 60, or 120)\n * @returns An error message string if validation fails, or `null` if both parameters are valid\n *\n * @example\n * ```ts\n * validateParameters(1, 60); // null (valid)\n * validateParameters(0.1, 60); // \"Invalid scale: 0.1. Scale must be between 0.25 and 1.0\"\n * validateParameters(1, 45); // \"Invalid fps: 45. FPS must be one of: 15, 24, 30, 60, 120\"\n * ```\n */\nexport function validateParameters(scale: number, fps: number): string | null {\n if (!validateScale(scale)) {\n return `Invalid scale: ${scale}. Scale must be between 0.25 and 1.0`;\n }\n if (!validateFPS(fps)) {\n return `Invalid fps: ${fps}. FPS must be one of: 15, 24, 30, 60, 120`;\n }\n return null;\n}\n","/**\n * Inline styles used by the UnicornScene component.\n *\n * @remarks\n * These styles provide the base layout and error display styling.\n * The container uses CSS custom properties (`--unicorn-width` and `--unicorn-height`)\n * for dynamic dimension handling.\n */\nexport const unicornStyles = {\n /**\n * Styles for the main scene container element.\n *\n * @remarks\n * Uses CSS custom properties for width and height to allow dynamic sizing.\n */\n container: {\n position: \"relative\" as const,\n width: \"var(--unicorn-width)\",\n height: \"var(--unicorn-height)\",\n },\n\n /**\n * Styles for the error message wrapper.\n */\n errorWrapper: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"100%\",\n },\n\n /**\n * Styles for the error message box.\n */\n errorBox: {\n textAlign: \"center\" as const,\n padding: \"1rem\",\n borderRadius: \"0.5rem\",\n backgroundColor: \"rgb(254 242 242)\",\n color: \"rgb(239 68 68)\",\n },\n\n /**\n * Styles for the error title text.\n */\n errorTitle: {\n fontWeight: \"600\",\n marginBottom: \"0.25rem\",\n },\n\n /**\n * Styles for the error message text.\n */\n errorMessage: {\n fontSize: \"0.875rem\",\n marginTop: \"0.25rem\",\n },\n};\n"],"mappings":"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","names":["useRef","useState","useEffect","useEffect","useState","useCallback","useState","useCallback","useEffect","useRef","useState","useEffect"]} |