storysplat-viewer 0.2.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/storysplat-viewer.umd.js +2 -0
- package/dist/storysplat-viewer.umd.js.map +1 -0
- package/dist/types/dynamic-viewer/CameraControls.d.ts +148 -0
- package/dist/types/dynamic-viewer/createViewer.d.ts +15 -0
- package/dist/types/dynamic-viewer/viewerUI.d.ts +97 -0
- package/dist/types/effects/gsplat-reveal-radial.d.ts +18 -0
- package/dist/types/effects/gsplat-shader-effect.d.ts +27 -0
- package/dist/types/effects/index.d.ts +8 -0
- package/dist/types/effects/reveal-presets.d.ts +43 -0
- package/dist/types/html-generation/generateHTML.d.ts +46 -0
- package/dist/types/index.d.ts +20 -56
- package/dist/types/transformers/sceneToConfig.d.ts +56 -0
- package/dist/types/types/index.d.ts +183 -6
- package/package.json +51 -40
- package/dist/esm/dist/styles/viewer.css +0 -2
- package/dist/esm/dist/styles/viewer.css.map +0 -1
- package/dist/esm/index.js +0 -199988
- package/dist/esm/index.js.map +0 -1
- package/dist/types/core/cameraManager.d.ts +0 -36
- package/dist/types/core/lightingManager.d.ts +0 -9
- package/dist/types/core/renderLoopManager.d.ts +0 -24
- package/dist/types/core/sceneManager.d.ts +0 -19
- package/dist/types/core/splatLoader.d.ts +0 -12
- package/dist/types/features/analyticsManager.d.ts +0 -102
- package/dist/types/features/animatedGifTexture.d.ts +0 -37
- package/dist/types/features/audioManager.d.ts +0 -72
- package/dist/types/features/collisionManager.d.ts +0 -51
- package/dist/types/features/customMeshManager.d.ts +0 -15
- package/dist/types/features/customScriptManager.d.ts +0 -34
- package/dist/types/features/hotspotManager.d.ts +0 -16
- package/dist/types/features/htmlMeshManager.d.ts +0 -23
- package/dist/types/features/navigationManager.d.ts +0 -150
- package/dist/types/features/particleManager.d.ts +0 -14
- package/dist/types/features/sceneFeatures.d.ts +0 -14
- package/dist/types/features/skyboxManager.d.ts +0 -3
- package/dist/types/features/splatSwapManager.d.ts +0 -87
- package/dist/types/features/xrManager.d.ts +0 -12
- package/dist/types/types/dataTypes.d.ts +0 -258
- package/dist/types/types/hotspotTypes.d.ts +0 -82
- package/dist/types/types/htmlMeshTypes.d.ts +0 -58
- package/dist/types/types/interactionTypes.d.ts +0 -43
- package/dist/types/types/navigationTypes.d.ts +0 -41
- package/dist/types/types/sceneFeatures.d.ts +0 -186
- package/dist/types/types/viewerOptions.d.ts +0 -249
- package/dist/types/types.d.ts +0 -106
- package/dist/types/ui/helpPanel.d.ts +0 -32
- package/dist/types/ui/muteButton.d.ts +0 -30
- package/dist/types/ui/preloader.d.ts +0 -40
- package/dist/types/ui/startButton.d.ts +0 -29
- package/dist/types/ui/templateManager.d.ts +0 -121
- package/dist/types/ui/uiManager.d.ts +0 -97
- package/dist/types/ui/watermark.d.ts +0 -18
- package/dist/types/utils/helpers.d.ts +0 -81
- package/dist/types/viewer-simple.d.ts +0 -30
- package/dist/types/viewer.d.ts +0 -113
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import*as e from"playcanvas";function t(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function o(e,o={}){const{cdnUrl:n="https://unpkg.com/storysplat-viewer@2/dist/storysplat-viewer.umd.js",title:i=e.name||"StorySplat Scene",description:a=`Interactive 3D scene: ${e.name||"StorySplat"}`,faviconUrl:s,customCSS:l=""}=o,r=s?`<link rel="icon" href="${t(s)}" />`:"",c=l?`<style>${l}</style>`:"",p=JSON.stringify(e,(e,t)=>{if(!(t instanceof HTMLElement)&&"function"!=typeof t)return t},2);return`<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">\n <meta name="description" content="${t(a)}">\n <title>${t(i)}</title>\n ${r}\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: #111;\n }\n #app {\n width: 100%;\n height: 100%;\n position: relative;\n }\n /* Loading state */\n #app:empty::after {\n content: 'Loading...';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #666;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 16px;\n }\n </style>\n ${c}\n</head>\n<body>\n <div id="app"></div>\n\n <script src="${t(n)}"><\/script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${p};\n\n // Wait for DOM and viewer to be ready\n function init() {\n var container = document.getElementById('app');\n if (!container) {\n console.error('[StorySplat] Container #app not found');\n return;\n }\n\n if (typeof StorySplatViewer === 'undefined' || !StorySplatViewer.createViewer) {\n console.error('[StorySplat] Viewer not loaded. Check CDN URL.');\n return;\n }\n\n try {\n var viewer = StorySplatViewer.createViewer(container, sceneData, {\n showUI: true,\n autoPlay: false\n });\n\n viewer.on('ready', function() {\n console.log('[StorySplat] Scene ready');\n });\n\n viewer.on('error', function(err) {\n console.error('[StorySplat] Error:', err);\n });\n\n // Expose viewer globally for debugging\n window.storySplatViewer = viewer;\n } catch (err) {\n console.error('[StorySplat] Failed to create viewer:', err);\n }\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n } else {\n init();\n }\n })();\n <\/script>\n</body>\n</html>`}async function n(e,t={}){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);return o(await n.json(),t)}function i(e){const t=e.loadedModelUrl||e.splatUrl||"",o=e.sogModelUrl||e.sogUrl,n=e.compressedPlyUrl,i=t.split("?")[0].split(".").pop()?.toLowerCase(),a="ply"===i||t.includes(".compressed.ply");let s=t;o?s=o:n?s=n:a||console.warn("[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:",i),console.log("[StorySplat Viewer] URL selection:",{original:t,sogUrl:o,compressedPlyUrl:n,selected:s});const l=(e.waypoints||[]).map(e=>{let t=e.fov||60;t<3.5&&(t*=180/Math.PI);let o=e.info||e.description||"";if(!o&&e.interactions){const t=e.interactions.find(e=>"info"===e.type);t&&t.data&&t.data.text&&(o=t.data.text)}return{position:e.position||{x:e.x||0,y:e.y||0,z:e.z||0},rotation:e.rotation||{x:0,y:0,z:0},fov:t,duration:e.duration||2e3,name:e.name||e.title||"",info:o,interactions:e.interactions||[]}});return{name:e.name||"StorySplat Scene",sceneId:e.sceneId,userId:e.userId,userName:e.userName,thumbnailUrl:e.thumbnailUrl,splatUrl:s,sogUrl:o,fallbackUrls:[...e.fallbackUrls||[],o!==s?o:null,n!==s?n:null,t!==s?t:null].filter(e=>null!=e&&""!==e),scale:e.scale||(null!=e.splatScale?{x:e.splatScale,y:e.splatScale,z:e.splatScale}:{x:1,y:1,z:1}),splatPosition:e.splatPosition||e.position||e.cameraPosition||[0,0,0],splatRotation:e.splatRotation||e.rotation||e.cameraRotation||[0,0,0],invertXScale:e.invertXScale,invertYScale:e.invertYScale,waypoints:l,hotspots:e.hotspots||[],skybox:e.skybox,customMeshes:e.customMeshes||[],htmlMeshes:e.htmlMeshes||[],lights:e.lights||[],particles:e.particles||[],collisionMeshesData:e.collisionMeshesData||[],playerHeight:e.playerHeight,uiColor:e.uiColor||"#ffffff",uiOptions:{showStartExperience:e.uiOptions?.showStartExperience??!0,showWatermark:e.uiOptions?.showWatermark??!0,hideFullscreenButton:e.uiOptions?.hideFullscreenButton??!1,hideInfoButton:e.uiOptions?.hideInfoButton??!1,hideMuteButton:e.uiOptions?.hideMuteButton??!1,hideHelpButton:e.uiOptions?.hideHelpButton??!1,hideWatermark:e.uiOptions?.hideWatermark??!1,watermarkText:e.uiOptions?.watermarkText,watermarkLink:e.uiOptions?.watermarkLink,buttonPosition:e.uiOptions?.buttonPosition||"inline"},defaultCameraMode:e.defaultCameraMode||"orbit",allowedCameraModes:e.allowedCameraModes||["orbit","first-person","drone"],cameraMovementSpeed:e.cameraMovementSpeed,cameraRotationSensitivity:e.cameraRotationSensitivity,includeScrollControls:e.includeScrollControls??!0,scrollButtonMode:e.scrollButtonMode||"continuous",scrollAmount:e.scrollAmount||100,scrollSpeed:e.scrollSpeed,autoPlayEnabled:e.autoPlayEnabled??!1,includeXR:e.includeXR,xrMode:e.xrMode,customScript:e.customScript,templateType:"standard"}}function a(e){const t=e.waypoints?.[0]?.fov||60;return{splatUrl:e.splatUrl,sogUrl:e.sogUrl,fallbackUrls:e.fallbackUrls,scale:e.scale,position:e.splatPosition||[0,0,0],rotation:e.splatRotation||[0,0,0],invertXScale:e.invertXScale,invertYScale:e.invertYScale,waypoints:e.waypoints,hotspots:e.hotspots,skybox:e.skybox,customMeshes:e.customMeshes,htmlMeshes:e.htmlMeshes,lights:e.lights,particles:e.particles,collisionMeshesData:e.collisionMeshesData,cameraMode:e.defaultCameraMode,defaultCameraMode:e.defaultCameraMode,allowedCameraModes:e.allowedCameraModes,autoPlay:e.autoPlayEnabled,uiColor:e.uiColor,uiOptions:e.uiOptions,fov:t,nearClip:e.minClipPlane||.1,farClip:e.maxClipPlane||1e3,playerHeight:e.playerHeight||1.6,cameraMovementSpeed:e.cameraMovementSpeed||1,cameraRotationSensitivity:e.cameraRotationSensitivity||.2}}function s(e,t){const o=document.createElement("div");o.className="storysplat-preloader";const n=!!t;return o.innerHTML=`\n <div class="storysplat-preloader-content">\n <div class="storysplat-preloader-media">\n ${n?`<img class="storysplat-preloader-image" src="${t}" alt="Custom Logo" />`:'<img class="storysplat-preloader-image-inverted" src="https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda" alt="StorySplat Logo" />'}\n ${n?"":'<lottie-player class="storysplat-preloader-lottie"\n src="https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c6e-a94c-cba1d2b65d5e"\n background="transparent"\n speed="1"\n loop\n autoplay>\n </lottie-player>'}\n </div>\n <div class="storysplat-preloader-progress">\n <div class="storysplat-preloader-text">Loading... 0%</div>\n <div class="storysplat-preloader-bar"></div>\n </div>\n </div>\n `,e.appendChild(o),n||new Promise(e=>{if(customElements.get("lottie-player"))return void e();const t=document.querySelector('script[src*="lottie-player"]');if(t)return void t.addEventListener("load",()=>e());const o=document.createElement("script");o.src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js",o.onload=()=>e(),document.head.appendChild(o)}),o}function l(e){e.classList.add("hidden"),setTimeout(()=>e.remove(),500)}function r(e,t,o={}){const{uiColor:n="#4CAF50",showScrollControls:i=!0,showModeToggle:a=!0,showFullscreenButton:l=!0,showHelpButton:r=!1,showPreloader:c=!0,allowedCameraModes:p=["tour","explore"],defaultCameraMode:d="tour",customPreloaderLogoUrl:u}=o,m={};e.querySelectorAll(".storysplat-preloader, .storysplat-scroll-controls, .storysplat-waypoint-info, .storysplat-fullscreen-btn, .storysplat-help-btn, .storysplat-help-panel").forEach(e=>e.remove()),function(e="#4CAF50"){const t=document.getElementById("storysplat-viewer-styles");t&&t.remove();const o=document.createElement("style");o.id="storysplat-viewer-styles",o.textContent=function(e="#4CAF50"){return`\n /* Container - CRITICAL: must be position relative and contained */\n .storysplat-viewer-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-family: system-ui, -apple-system, sans-serif;\n box-sizing: border-box;\n }\n\n .storysplat-viewer-container * {\n box-sizing: border-box;\n }\n\n .storysplat-viewer-container canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100% !important;\n height: 100% !important;\n display: block;\n touch-action: none;\n }\n\n /* Preloader - semi-transparent to see loading/reveal effect */\n .storysplat-preloader {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(30, 30, 30, 0.85);\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n z-index: 10000;\n transition: opacity 0.5s ease-out;\n }\n\n .storysplat-preloader.hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .storysplat-preloader-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 40px;\n gap: 20px;\n }\n\n .storysplat-preloader-media {\n display: flex;\n align-items: center;\n gap: 40px;\n }\n\n .storysplat-preloader-image {\n height: 200px;\n width: auto;\n object-fit: contain;\n }\n\n .storysplat-preloader-image-inverted {\n height: 200px;\n width: auto;\n object-fit: contain;\n filter: invert(1);\n margin-right: -100px;\n }\n\n .storysplat-preloader-lottie {\n height: 200px;\n width: 200px;\n }\n\n @media (max-width: 768px) {\n .storysplat-preloader-image,\n .storysplat-preloader-image-inverted {\n height: 100px;\n }\n .storysplat-preloader-image-inverted {\n margin-right: -50px;\n }\n .storysplat-preloader-lottie {\n height: 100px;\n width: 100px;\n }\n }\n\n .storysplat-preloader-progress {\n width: 200px;\n height: 4px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n margin-top: 20px;\n position: relative;\n }\n\n .storysplat-preloader-bar {\n width: 0%;\n height: 100%;\n background: ${e};\n border-radius: 2px;\n transition: width 0.3s ease-out;\n }\n\n .storysplat-preloader-text {\n position: absolute;\n top: -25px;\n left: 50%;\n transform: translateX(-50%);\n color: white;\n font-size: 14px;\n white-space: nowrap;\n }\n\n /* Minimal Scroll Controls */\n .storysplat-scroll-controls {\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 150px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n z-index: 1000;\n }\n\n .storysplat-scroll-content {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n color: white;\n font-variant-numeric: tabular-nums;\n min-width: 40px;\n text-align: center;\n }\n\n .storysplat-progress-container {\n width: 90%;\n max-width: 150px;\n height: 3px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .storysplat-progress-bar {\n height: 100%;\n background: ${e};\n transition: width 0.05s linear;\n width: 0%;\n }\n\n .storysplat-scroll-buttons {\n display: flex;\n justify-content: center;\n gap: 5px;\n z-index: 1000;\n }\n\n /* Minimal Button Style */\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-btn-play {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-btn-play svg {\n width: 12px;\n height: 12px;\n fill: white;\n }\n\n /* Mode Toggle - Minimal */\n .storysplat-mode-container {\n margin-top: 5px;\n }\n\n .storysplat-mode-toggle {\n display: flex;\n gap: 5px;\n }\n\n .storysplat-mode-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 3px 6px;\n font-size: 11px;\n border-radius: 2px;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .storysplat-mode-btn.selected {\n background: ${e} !important;\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n /* Fullscreen Button - Minimal */\n .storysplat-fullscreen-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(0, 0, 0, 0);\n border: none;\n padding: 5px;\n border-radius: 3px;\n cursor: pointer;\n z-index: 1000;\n }\n\n .storysplat-fullscreen-btn svg {\n width: 20px;\n height: 20px;\n fill: white;\n }\n\n /* Help Button - Minimal */\n .storysplat-help-btn {\n position: absolute;\n top: 10px;\n left: 10px;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.3);\n color: white;\n border: none;\n cursor: pointer;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n transition: all 0.2s ease;\n }\n\n .storysplat-help-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-help-panel {\n position: absolute;\n top: 50px;\n left: 10px;\n background: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 15px;\n border-radius: 5px;\n max-width: 280px;\n z-index: 999;\n display: none;\n font-size: 12px;\n }\n\n .storysplat-help-panel.visible {\n display: block;\n }\n\n .storysplat-help-panel h3 {\n margin: 0 0 10px 0;\n font-size: 14px;\n font-weight: 600;\n }\n\n .storysplat-help-panel p {\n margin: 5px 0;\n line-height: 1.4;\n }\n\n /* Waypoint Info - Top Banner Style (matching BabylonJS minimal export) */\n .storysplat-waypoint-info {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n background: rgba(0, 0, 0, 0.5);\n color: white;\n text-align: center;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-waypoint-info.hasContent {\n display: block;\n padding: 50px 30px 30px 30px;\n }\n\n .storysplat-waypoint-info h2 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: bold;\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: 14px;\n line-height: 1.5;\n opacity: 0.9;\n }\n\n /* Responsive waypoint info */\n @media (max-height: 600px) {\n .storysplat-waypoint-info.hasContent {\n padding: 20px 20px 15px 20px;\n font-size: 13px;\n }\n }\n\n @media (max-height: 500px) {\n .storysplat-waypoint-info.hasContent {\n padding: 10px 15px 10px 15px;\n font-size: 12px;\n }\n }\n\n /* Hotspot Popup - Matching BabylonJS HTML export */\n .storysplat-hotspot-popup {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.9);\n padding: 30px;\n border-radius: 12px;\n max-width: 600px;\n max-height: 80vh;\n overflow: auto;\n z-index: 10000;\n color: white;\n font-family: system-ui, -apple-system, sans-serif;\n display: none;\n }\n\n .storysplat-hotspot-popup.visible {\n display: block;\n }\n\n .storysplat-hotspot-popup h2 {\n margin: 0 0 15px 0;\n font-size: 20px;\n font-weight: 600;\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 15px 0;\n line-height: 1.6;\n font-size: 14px;\n opacity: 0.9;\n }\n\n .storysplat-hotspot-popup img {\n max-width: 100%;\n border-radius: 8px;\n margin-bottom: 15px;\n display: block;\n }\n\n .storysplat-hotspot-popup iframe {\n width: 100%;\n height: 400px;\n border: none;\n border-radius: 8px;\n margin-bottom: 15px;\n }\n\n .storysplat-hotspot-popup-close {\n position: absolute;\n top: 10px;\n right: 10px;\n background: transparent;\n border: none;\n color: white;\n font-size: 24px;\n cursor: pointer;\n padding: 5px;\n line-height: 1;\n opacity: 0.7;\n transition: opacity 0.2s;\n }\n\n .storysplat-hotspot-popup-close:hover {\n opacity: 1;\n }\n\n .storysplat-hotspot-popup-link {\n display: inline-block;\n padding: 12px 24px;\n background: #6366f1;\n color: white;\n text-decoration: none;\n border-radius: 8px;\n font-weight: 600;\n transition: transform 0.2s;\n cursor: pointer;\n }\n\n .storysplat-hotspot-popup-link:hover {\n transform: scale(1.05);\n }\n\n /* Hotspot popup overlay (dim background) */\n .storysplat-hotspot-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9999;\n display: none;\n }\n\n .storysplat-hotspot-overlay.visible {\n display: block;\n }\n\n /* Mobile Responsive */\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n margin-bottom: 10px;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n }\n\n /* Virtual Joystick Overlay - visual indicator only, touches pass through to canvas */\n .storysplat-joystick-container {\n position: absolute;\n bottom: 80px;\n left: 40px;\n width: 120px;\n height: 120px;\n z-index: 2000;\n pointer-events: none;\n touch-action: none;\n display: none;\n }\n\n .storysplat-joystick-container.visible {\n display: block;\n }\n\n .storysplat-joystick-base {\n position: absolute;\n top: 0;\n left: 0;\n width: 120px;\n height: 120px;\n background: rgba(255, 255, 255, 0.2);\n border: 3px solid rgba(255, 255, 255, 0.4);\n border-radius: 50%;\n pointer-events: none;\n }\n\n .storysplat-joystick-thumb {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 50px;\n height: 50px;\n margin-left: -25px;\n margin-top: -25px;\n background: rgba(255, 255, 255, 0.6);\n border: 3px solid rgba(255, 255, 255, 0.8);\n border-radius: 50%;\n pointer-events: none;\n transition: transform 0.05s ease-out;\n }\n\n .storysplat-joystick-thumb.active {\n background: rgba(255, 255, 255, 0.8);\n border-color: white;\n }\n\n /* Look Zone Indicator (right side) */\n .storysplat-look-zone {\n position: absolute;\n bottom: 80px;\n right: 40px;\n width: 100px;\n height: 100px;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-look-zone.visible {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon {\n width: 60px;\n height: 60px;\n background: rgba(255, 255, 255, 0.15);\n border: 2px solid rgba(255, 255, 255, 0.25);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon svg {\n width: 30px;\n height: 30px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n /* Camera Mode Toggle (Orbit/Fly) - Mobile only in explore mode */\n .storysplat-camera-mode-toggle {\n position: absolute;\n bottom: 210px;\n left: 40px;\n width: 44px;\n height: 44px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.5);\n border: 2px solid rgba(255, 255, 255, 0.3);\n cursor: pointer;\n z-index: 2001;\n display: none;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n }\n\n .storysplat-camera-mode-toggle.visible {\n display: flex;\n }\n\n .storysplat-camera-mode-toggle:active {\n background: rgba(0, 0, 0, 0.7);\n transform: scale(0.95);\n }\n\n .storysplat-camera-mode-toggle svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n .storysplat-camera-mode-toggle .orbit-icon,\n .storysplat-camera-mode-toggle .fly-icon {\n display: none;\n }\n\n .storysplat-camera-mode-toggle.orbit .orbit-icon {\n display: block;\n }\n\n .storysplat-camera-mode-toggle.fly .fly-icon {\n display: block;\n }\n `}(e),document.head.appendChild(o)}(n),e.classList.add("storysplat-viewer-container"),c&&(m.preloader=s(e,u));const h=document.createElement("div");if(h.className="storysplat-waypoint-info",h.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',e.appendChild(h),m.waypointInfo=h,i&&t.waypoints&&t.waypoints.length>0){const t=document.createElement("div");t.className="storysplat-scroll-controls",t.innerHTML=`\n <div class="storysplat-scroll-content">\n <div class="storysplat-progress-text">0%</div>\n <div class="storysplat-progress-container">\n <div class="storysplat-progress-bar"></div>\n </div>\n <div class="storysplat-scroll-buttons">\n <button class="storysplat-btn storysplat-btn-prev">Prev</button>\n <button class="storysplat-btn storysplat-btn-play">\n <svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>\n </button>\n <button class="storysplat-btn storysplat-btn-next">Next</button>\n </div>\n ${a&&p.length>1?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${p.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===d?"selected":""}" data-mode="tour">Tour</button>`:""}\n ${p.includes("explore")?`<button class="storysplat-mode-btn ${"explore"===d?"selected":""}" data-mode="explore">Explore</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,e.appendChild(t),m.scrollControls=t,m.progressBar=t.querySelector(".storysplat-progress-bar"),m.progressText=t.querySelector(".storysplat-progress-text")}if(l){const t=document.createElement("button");t.className="storysplat-fullscreen-btn",t.setAttribute("aria-label","Toggle Fullscreen"),t.innerHTML='\n <svg class="storysplat-expand-icon" viewBox="0 0 24 24">\n <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>\n </svg>\n <svg class="storysplat-compress-icon" viewBox="0 0 24 24" style="display: none;">\n <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>\n </svg>\n ',e.appendChild(t),m.fullscreenButton=t}if(r){const t=document.createElement("button");t.className="storysplat-help-btn",t.setAttribute("title","Toggle Help"),t.textContent="?",e.appendChild(t),m.helpButton=t;const o=document.createElement("div");o.className="storysplat-help-panel",o.innerHTML="\n <h3>Explore Mode</h3>\n <p>• LMB Drag - Orbit camera</p>\n <p>• RMB Drag - Fly/look</p>\n <p>• WASD/QE - Move camera</p>\n <p>• Shift - Move fast</p>\n <p>• Scroll/Pinch - Zoom</p>\n <p>• Double-click - Focus</p>\n <h3>Tour Mode</h3>\n <p>• Scroll - Move along path</p>\n ",e.appendChild(o),m.helpPanel=o}const g=document.createElement("div");g.className="storysplat-hotspot-overlay",e.appendChild(g);const y=document.createElement("div");y.className="storysplat-hotspot-popup",y.innerHTML='\n <button class="storysplat-hotspot-popup-close">×</button>\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n ',e.appendChild(y),m.hotspotPopup=y;const f=y.querySelector(".storysplat-hotspot-popup-close");f?.addEventListener("click",()=>{y.classList.remove("visible"),g.classList.remove("visible")}),g.addEventListener("click",()=>{y.classList.remove("visible"),g.classList.remove("visible")});const v=document.createElement("div");v.className="storysplat-joystick-container",v.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',e.appendChild(v),m.joystick=v,m.joystickThumb=v.querySelector(".storysplat-joystick-thumb");const b=document.createElement("div");b.className="storysplat-look-zone",b.innerHTML='\n <div class="storysplat-look-zone-icon">\n <svg viewBox="0 0 24 24">\n <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>\n </svg>\n </div>\n ',e.appendChild(b),m.lookZone=b;const w=document.createElement("button");return w.className="storysplat-camera-mode-toggle fly",w.setAttribute("aria-label","Toggle camera mode"),w.innerHTML='\n <svg class="orbit-icon" viewBox="0 0 24 24">\n <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>\n <path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/>\n </svg>\n <svg class="fly-icon" viewBox="0 0 24 24">\n <path d="M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z"/>\n </svg>\n ',e.appendChild(w),m.cameraModeToggle=w,m}function c(e,t){const o=e.querySelector(".storysplat-hotspot-popup"),n=e.querySelector(".storysplat-hotspot-overlay");if(!o||!n)return;const i=o.querySelector(".storysplat-hotspot-popup-title"),a=o.querySelector(".storysplat-hotspot-popup-content");t.backgroundColor&&(o.style.background=t.backgroundColor),t.textColor&&(o.style.color=t.textColor,i&&(i.style.color=t.textColor)),t.fontFamily&&(o.style.fontFamily=t.fontFamily),t.fontSize&&(o.style.fontSize=`${t.fontSize}px`),i&&(i.textContent=t.title||"Hotspot");let s="";if(t.information&&(s+=`<p>${t.information}</p>`),t.photoUrl&&(s+=`<img src="${t.photoUrl}" alt="${t.title||"Hotspot image"}" />`),"iframe"===t.contentType&&t.iframeUrl&&(s+=`<iframe src="${t.iframeUrl}" title="${t.title||"Embedded content"}"></iframe>`),t.externalLinkUrl){const e=t.externalLinkButtonColor||"#6366f1";s+=`\n <a href="${t.externalLinkUrl}" target="_blank" rel="noopener noreferrer"\n class="storysplat-hotspot-popup-link" style="background: ${e}">\n ${t.externalLinkText||"Visit Link"}\n </a>\n `}a&&(a.innerHTML=s),o.classList.add("visible"),n.classList.add("visible")}function p(e,t){e.joystick&&(t?e.joystick.classList.add("visible"):e.joystick.classList.remove("visible")),e.lookZone&&(t?e.lookZone.classList.add("visible"):e.lookZone.classList.remove("visible"))}function d(e,t,o,n,i){if(e.joystickThumb)if(t){e.joystickThumb.classList.add("active");const t=Math.sqrt(o*o+n*n),a=Math.min(t,i),s=t>0?a/t:0,l=o*s,r=n*s;e.joystickThumb.style.transform=`translate(${l}px, ${r}px)`}else e.joystickThumb.classList.remove("active"),e.joystickThumb.style.transform="translate(0, 0)"}function u(e,t){e.cameraModeToggle&&(t?e.cameraModeToggle.classList.add("visible"):e.cameraModeToggle.classList.remove("visible"))}function m(e,t){e.cameraModeToggle&&(e.cameraModeToggle.classList.remove("orbit","fly"),e.cameraModeToggle.classList.add(t))}const h=new e.Vec3,g=new e.Vec3,y=new e.Pose,f=new e.InputFrame({move:[0,0,0],rotate:[0,0,0]}),v=(e,t,o)=>{const n=Math.sqrt(e[0]*e[0]+e[1]*e[1]);if(n<t)return void e.fill(0);const i=(n-t)/(o-t);e[0]*=i/n,e[1]*=i/n},b=(t,o,n,i,a=new e.Vec3)=>{const{fov:s,aspectRatio:l,horizontalFov:r,projection:c,orthoHeight:p}=t,d=t.system?.app,{width:u,height:m}=d?.graphicsDevice?.clientRect||{width:1920,height:1080};a.set(-o/u*2,n/m*2,0);const h=g.set(0,0,0);if(c===e.PROJECTION_PERSPECTIVE){const t=i*Math.tan(.5*s*e.math.DEG_TO_RAD);r?h.set(t,t/l,0):h.set(t*l,t,0)}else h.set(p*l,p,0);return a.mul(h),a};class w{constructor(t,o,n={}){this.enabled=!0,this._mode="orbit",this._enableOrbit=!0,this._enableFly=!0,this.enablePan=!0,this._pose=new e.Pose,this._startZoomDist=0,this._pitchRange=new e.Vec2(-360,360),this._yawRange=new e.Vec2(-360,360),this._zoomRange=new e.Vec2(.01,1/0),this._state={axis:new e.Vec3,shift:0,ctrl:0,mouse:[0,0,0],touches:0},this.moveSpeed=10,this.moveFastSpeed=20,this.moveSlowSpeed=5,this.rotateSpeed=.2,this.rotateJoystickSens=2,this.zoomSpeed=.001,this.zoomPinchSens=5,this.gamepadDeadZone=new e.Vec2(.3,.6),this.joystickEventName="joystick",this._destroyHandler=null,this.camera=t,this.app=o;const i=t.camera;if(!i)throw new Error("CameraControls: camera component not found on entity");this.cameraComponent=i,this._flyController=new e.FlyController,this._orbitController=new e.OrbitController,this._focusController=new e.FocusController,this._orbitController.zoomRange=new e.Vec2(.01,1/0);const a=o.graphicsDevice.canvas;this._desktopInput=new e.KeyboardMouseSource,this._orbitMobileInput=new e.MultiTouchSource,this._flyMobileInput=new e.DualGestureSource,this._gamepadInput=new e.GamepadSource,this._desktopInput.attach(a),this._orbitMobileInput.attach(a),this._flyMobileInput.attach(a),this._gamepadInput.attach(a),this._flyMobileInput.on("joystick:position:left",([e,t,o,n])=>{"fly"===this._mode&&this.app.fire(`${this.joystickEventName}:left`,e,t,o,n)}),this._flyMobileInput.on("joystick:position:right",([e,t,o,n])=>{"fly"===this._mode&&this.app.fire(`${this.joystickEventName}:right`,e,t,o,n)}),this._pose.look(this.camera.getPosition(),e.Vec3.ZERO),this._setMode("orbit"),this._controller=this._orbitController,void 0!==n.enableOrbit&&(this.enableOrbit=n.enableOrbit),void 0!==n.enableFly&&(this.enableFly=n.enableFly),void 0!==n.enablePan&&(this.enablePan=n.enablePan),n.focusPoint&&(this.focusPoint=n.focusPoint),void 0!==n.moveSpeed&&(this.moveSpeed=n.moveSpeed),void 0!==n.moveFastSpeed&&(this.moveFastSpeed=n.moveFastSpeed),void 0!==n.moveSlowSpeed&&(this.moveSlowSpeed=n.moveSlowSpeed),void 0!==n.rotateSpeed&&(this.rotateSpeed=n.rotateSpeed),void 0!==n.rotateJoystickSens&&(this.rotateJoystickSens=n.rotateJoystickSens),void 0!==n.zoomSpeed&&(this.zoomSpeed=n.zoomSpeed),void 0!==n.zoomPinchSens&&(this.zoomPinchSens=n.zoomPinchSens),void 0!==n.focusDamping&&(this.focusDamping=n.focusDamping),void 0!==n.rotateDamping&&(this.rotateDamping=n.rotateDamping),void 0!==n.moveDamping&&(this.moveDamping=n.moveDamping),void 0!==n.zoomDamping&&(this.zoomDamping=n.zoomDamping),n.pitchRange&&(this.pitchRange=n.pitchRange),n.yawRange&&(this.yawRange=n.yawRange),n.zoomRange&&(this.zoomRange=n.zoomRange),n.gamepadDeadZone&&(this.gamepadDeadZone=n.gamepadDeadZone),n.mobileInputLayout&&(this.mobileInputLayout=n.mobileInputLayout)}set enableFly(e){this._enableFly=e,this._enableFly||"fly"!==this._mode||this._setMode("orbit")}get enableFly(){return this._enableFly}set enableOrbit(e){this._enableOrbit=e,this._enableOrbit||"orbit"!==this._mode||this._setMode("fly")}get enableOrbit(){return this._enableOrbit}set focusDamping(e){this._focusController.focusDamping=e}get focusDamping(){return this._focusController.focusDamping}set moveDamping(e){this._flyController.moveDamping=e}get moveDamping(){return this._flyController.moveDamping}set rotateDamping(e){this._flyController.rotateDamping=e,this._orbitController.rotateDamping=e}get rotateDamping(){return this._orbitController.rotateDamping}set zoomDamping(e){this._orbitController.zoomDamping=e}get zoomDamping(){return this._orbitController.zoomDamping}set focusPoint(e){const t=this.camera.getPosition();this._startZoomDist=t.distance(e),this._controller.attach(this._pose.look(t,e),!1)}get focusPoint(){return this._pose.getFocus(h)}set pitchRange(t){this._pitchRange.x=e.math.clamp(t.x,-360,360),this._pitchRange.y=e.math.clamp(t.y,-360,360),this._flyController.pitchRange=this._pitchRange,this._orbitController.pitchRange=this._pitchRange}get pitchRange(){return this._pitchRange}set yawRange(t){this._yawRange.x=e.math.clamp(t.x,-360,360),this._yawRange.y=e.math.clamp(t.y,-360,360),this._flyController.yawRange=this._yawRange,this._orbitController.yawRange=this._yawRange}get yawRange(){return this._yawRange}set zoomRange(e){this._zoomRange.x=e.x,this._zoomRange.y=e.y<=e.x?1/0:e.y,this._orbitController.zoomRange=this._zoomRange}get zoomRange(){return this._zoomRange}set mobileInputLayout(e){/(?:joystick|touch)-(?:joystick|touch)/.test(e)?this._flyMobileInput.layout=e:console.warn(`CameraControls: invalid mobile input layout: ${e}`)}get mobileInputLayout(){return this._flyMobileInput.layout}get mode(){return this._mode}_setMode(e){if(this._enableFly&&!this._enableOrbit)e="fly";else if(!this._enableFly&&this._enableOrbit)e="orbit";else if(!this._enableFly&&!this._enableOrbit)return void console.warn("CameraControls: both fly and orbit modes are disabled");if(this._mode!==e){switch(this._mode=e,this._controller&&this._controller.detach(),this._mode){case"orbit":this._controller=this._orbitController;break;case"fly":this._controller=this._flyController;break;case"focus":this._controller=this._focusController}this._controller.attach(this._pose,!1),this.app.fire("cameracontrols:modechange",this._mode)}}setMode(e){this._setMode(e)}focus(e,t=!1){this._setMode("focus");const o=t?this._startZoomDist:this.camera.getPosition().distance(e),n=h.copy(this.camera.forward).mulScalar(-o).add(e);this._controller.attach(y.look(n,e))}look(e,t=!1){this._setMode("focus");const o=t?h.copy(this.camera.getPosition()).sub(e).normalize().mulScalar(this._startZoomDist).add(e):this.camera.getPosition();this._controller.attach(y.look(o,e))}reset(e,t){this._setMode("focus"),this._controller.attach(y.look(t,e))}syncFromCamera(){const e=this.camera.getPosition().clone(),t=this.camera.forward.clone(),o=e.clone().add(t.mulScalar(5));this._pose.look(e,o),this._startZoomDist=5,this._setMode("orbit"),this._controller.attach(this._pose,!1)}enable(){this.enabled=!0}disable(){this.enabled=!1,this._desktopInput.read(),this._orbitMobileInput.read(),this._flyMobileInput.read(),this._gamepadInput.read()}update(t){if(!this.enabled)return;const{keyCode:o}=e.KeyboardMouseSource,{key:n,button:i,mouse:a,wheel:s}=this._desktopInput.read(),{touch:l,pinch:r,count:c}=this._orbitMobileInput.read(),{leftInput:p,rightInput:d}=this._flyMobileInput.read(),{leftStick:u,rightStick:m}=this._gamepadInput.read();v(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),v(m,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(h.set(n[o.D]-n[o.A]+(n[o.RIGHT]-n[o.LEFT]),n[o.E]-n[o.Q],n[o.W]-n[o.S]+(n[o.UP]-n[o.DOWN])));for(let e=0;e<this._state.mouse.length;e++)this._state.mouse[e]+=i[e];this._state.shift+=n[o.SHIFT],this._state.ctrl+=n[o.CTRL],this._state.touches+=c[0],1===i[0]||1===i[1]||0!==s[0]?this._setMode("orbit"):(1===i[2]||this._state.axis.length()>0)&&this._setMode("fly");const y=+("orbit"===this._mode),w=+("fly"===this._mode),x=+(this._state.touches>1),S=+(this._state.shift||this._state.mouse[1]),_=+this._flyMobileInput.layout.endsWith("joystick"),C=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*t,M=60*this.zoomSpeed*t,E=M*this.zoomPinchSens,P=60*this.rotateSpeed*t,A=this.rotateSpeed*this.rotateJoystickSens*60*t,{deltas:T}=f,k=h.set(0,0,0),L=this._state.axis.clone().normalize();k.add(L.mulScalar(w*C));const z=b(this.cameraComponent,a[0],a[1],this._pose.distance);k.add(z.mulScalar(y*S*+this.enablePan));const R=g.set(0,0,s[0]);k.add(R.mulScalar(y*M)),T.move.append([k.x,k.y,k.z]),k.set(0,0,0);const I=g.set(a[0],a[1],0);k.add(I.mulScalar((1-y*S)*P)),T.rotate.append([k.x,k.y,k.z]),k.set(0,0,0);const D=g.set(p[0],0,-p[1]);k.add(D.mulScalar(w*C));const F=b(this.cameraComponent,l[0],l[1],this._pose.distance);k.add(F.mulScalar(y*x*+this.enablePan));const V=g.set(0,0,r[0]);k.add(V.mulScalar(y*x*E)),T.move.append([k.x,k.y,k.z]),k.set(0,0,0);const $=g.set(l[0],l[1],0);k.add($.mulScalar(y*(1-x)*P));const B=g.set(d[0],d[1],0);k.add(B.mulScalar(w*(_?A:P))),T.rotate.append([k.x,k.y,k.z]),k.set(0,0,0);const O=g.set(u[0],0,-u[1]);k.add(O.mulScalar(w*C)),T.move.append([k.x,k.y,k.z]),k.set(0,0,0);const U=g.set(m[0],m[1],0);if(k.add(U.mulScalar(w*A)),T.rotate.append([k.x,k.y,k.z]),this.app.xr?.active)f.read();else{if("focus"===this._mode){const e=T.move.length()+T.rotate.length()>0,t=this._focusController.complete?.()??!1;(e||t)&&this._setMode("orbit")}this._pose.copy(this._controller.update(f,t)),this.camera.setPosition(this._pose.position),this.camera.setEulerAngles(this._pose.angles)}}destroy(){this._desktopInput.destroy(),this._orbitMobileInput.destroy(),this._flyMobileInput.destroy(),this._gamepadInput.destroy(),this._flyController.destroy(),this._orbitController.destroy()}}let x=null;function S(){if(console.log("[RevealEffect] getGsplatRevealRadialClass called, cached:",!!x),x)return x;console.log("[RevealEffect] Creating new script class via pc.createScript");const t=e.createScript("gsplatRevealRadial");return console.log("[RevealEffect] Script class created:",t),Object.assign(t.prototype,{effectTime:0,_materialsApplied:null,_shadersApplied:!1,_retryCount:0,_maxRetries:100,_materialCreatedHandler:null,_centerArray:[0,0,0],_dotTintArray:[0,0,0],_waveTintArray:[0,0,0],center:null,speed:5,acceleration:0,delay:2,dotTint:null,waveTint:null,oscillationIntensity:.2,endRadius:25,initialize(){console.log("[RevealEffect] initialize() called"),this.effectTime=0,this._materialsApplied=new Set,this._shadersApplied=!1,this._retryCount=0,this._centerArray=[0,0,0],this._dotTintArray=[0,0,0],this._waveTintArray=[0,0,0],this.center||(this.center=new e.Vec3(0,0,0)),this.dotTint||(this.dotTint=new e.Color(0,1,1)),this.waveTint||(this.waveTint=new e.Color(1,.5,0)),this.on("enable",()=>{console.log("[RevealEffect] enabled event fired"),this.effectTime=0,this._applyShaders()}),this.on("disable",()=>{console.log("[RevealEffect] disabled event fired"),this._removeShaders()}),this.enabled?(console.log("[RevealEffect] Starting enabled, applying shaders"),this._applyShaders()):console.log("[RevealEffect] Starting disabled, waiting for enable")},update(e){if(!this._shadersApplied&&this._retryCount<this._maxRetries&&(this._retryCount++,this._retryCount%20==0&&console.log(`[RevealEffect] Retry ${this._retryCount}/${this._maxRetries} to apply shaders`),this._applyShaders()),this.effectTime+=e,Math.floor(this.effectTime)!==Math.floor(this.effectTime-e)&&console.log(`[RevealEffect] effectTime: ${this.effectTime.toFixed(2)}s, shadersApplied: ${this._shadersApplied}, materialsCount: ${this._materialsApplied?.size||0}`),this._isEffectComplete())return console.log("[RevealEffect] Effect complete, disabling"),void(this.enabled=!1);this._updateUniforms()},_updateUniforms(){this._setUniform("uTime",this.effectTime),this._centerArray[0]=this.center.x,this._centerArray[1]=this.center.y,this._centerArray[2]=this.center.z,this._setUniform("uCenter",this._centerArray),this._setUniform("uSpeed",this.speed),this._setUniform("uAcceleration",this.acceleration),this._setUniform("uDelay",this.delay),this._dotTintArray[0]=this.dotTint.r,this._dotTintArray[1]=this.dotTint.g,this._dotTintArray[2]=this.dotTint.b,this._setUniform("uDotTint",this._dotTintArray),this._waveTintArray[0]=this.waveTint.r,this._waveTintArray[1]=this.waveTint.g,this._waveTintArray[2]=this.waveTint.b,this._setUniform("uWaveTint",this._waveTintArray),this._setUniform("uOscillationIntensity",this.oscillationIntensity),this._setUniform("uEndRadius",this.endRadius)},_getCompletionTime(){const e=this.delay;if(0===this.acceleration)return e+this.endRadius/this.speed;const t=this.speed*this.speed+2*this.acceleration*this.endRadius;if(t<0)return 1/0;return e+(-this.speed+Math.sqrt(t))/this.acceleration},_isEffectComplete(){return this.effectTime>=this._getCompletionTime()},getShaderGLSL:()=>"\nuniform float uTime;\nuniform vec3 uCenter;\nuniform float uSpeed;\nuniform float uAcceleration;\nuniform float uDelay;\nuniform vec3 uDotTint;\nuniform vec3 uWaveTint;\nuniform float uOscillationIntensity;\nuniform float uEndRadius;\n\n// Shared globals (initialized once per vertex)\nfloat g_dist;\nfloat g_dotWavePos;\nfloat g_liftTime;\nfloat g_liftWavePos;\n\nvoid initShared(vec3 center) {\n g_dist = length(center - uCenter);\n g_dotWavePos = uSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n g_liftTime = max(0.0, uTime - uDelay);\n g_liftWavePos = uSpeed * g_liftTime + 0.5 * uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfloat hash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nvoid modifyCenter(inout vec3 center) {\n initShared(center);\n\n // Early exit optimization\n if (g_dist > uEndRadius) return;\n\n // Only apply oscillation if lift wave hasn't fully passed\n bool wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n float phase = hash(center) * 6.28318;\n center.y += sin(uTime * 3.0 + phase) * uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n float distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n float liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n center.y += liftAmount * uOscillationIntensity * 0.9;\n }\n}\n\nvoid modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) {\n // Early exit for distant splats - hide them\n if (g_dist > uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n float scale;\n bool isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = (g_liftWavePos >= g_dist + 2.0) ? 1.0 : mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n float distToWave = abs(g_dist - g_dotWavePos);\n scale = (distToWave < 0.5)\n ? mix(0.1, 0.2, 1.0 - distToWave * 2.0)\n : mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist));\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n float t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n float dotSize = scale * 0.05;\n float originalSize = gsplatExtractSize(covA, covB);\n float finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n vec3 origCovA = covA * (scale * scale);\n vec3 origCovB = covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n covA = mix(covA, origCovA, t);\n covB = mix(covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n float originalSize = gsplatExtractSize(covA, covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nvoid modifyColor(vec3 center, inout vec4 color) {\n // Use shared globals\n if (g_dist > uEndRadius) return;\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n float distToLift = abs(g_dist - g_liftWavePos);\n float liftIntensity = smoothstep(1.5, 0.0, distToLift);\n color.rgb += uWaveTint * liftIntensity;\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n float distToDot = abs(g_dist - g_dotWavePos);\n float dotIntensity = smoothstep(1.0, 0.0, distToDot);\n color.rgb += uDotTint * dotIntensity;\n }\n}\n",getShaderWGSL:()=>"\nuniform uTime: f32;\nuniform uCenter: vec3f;\nuniform uSpeed: f32;\nuniform uAcceleration: f32;\nuniform uDelay: f32;\nuniform uDotTint: vec3f;\nuniform uWaveTint: vec3f;\nuniform uOscillationIntensity: f32;\nuniform uEndRadius: f32;\n\n// Shared globals (initialized once per vertex)\nvar<private> g_dist: f32;\nvar<private> g_dotWavePos: f32;\nvar<private> g_liftTime: f32;\nvar<private> g_liftWavePos: f32;\n\nfn initShared(center: vec3f) {\n g_dist = length(center - uniform.uCenter);\n g_dotWavePos = uniform.uSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n g_liftTime = max(0.0, uniform.uTime - uniform.uDelay);\n g_liftWavePos = uniform.uSpeed * g_liftTime + 0.5 * uniform.uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn modifyCenter(center: ptr<function, vec3f>) {\n initShared(*center);\n\n // Early exit optimization\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Only apply oscillation if lift wave hasn't fully passed\n let wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n let phase = hash(*center) * 6.28318;\n (*center).y += sin(uniform.uTime * 3.0 + phase) * uniform.uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n let distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n let liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n (*center).y += liftAmount * uniform.uOscillationIntensity * 0.9;\n }\n}\n\nfn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>) {\n // Early exit for distant splats - hide them\n if (g_dist > uniform.uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n var scale: f32;\n let isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = select(mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5), 1.0, g_liftWavePos >= g_dist + 2.0);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n let distToWave = abs(g_dist - g_dotWavePos);\n scale = select(\n mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist)),\n mix(0.1, 0.2, 1.0 - distToWave * 2.0),\n distToWave < 0.5\n );\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n let t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n let dotSize = scale * 0.05;\n let originalSize = gsplatExtractSize(*covA, *covB);\n let finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n let origCovA = *covA * (scale * scale);\n let origCovB = *covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n *covA = mix(*covA, origCovA, t);\n *covB = mix(*covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n let originalSize = gsplatExtractSize(*covA, *covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nfn modifyColor(center: vec3f, color: ptr<function, vec4f>) {\n // Use shared globals\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n let distToLift = abs(g_dist - g_liftWavePos);\n let liftIntensity = smoothstep(1.5, 0.0, distToLift);\n (*color) = vec4f((*color).rgb + uniform.uWaveTint * liftIntensity, (*color).a);\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n let distToDot = abs(g_dist - g_dotWavePos);\n let dotIntensity = smoothstep(1.0, 0.0, distToDot);\n (*color) = vec4f((*color).rgb + uniform.uDotTint * dotIntensity, (*color).a);\n }\n}\n",_applyShaders(){const e=this.entity.gsplat;if(!e)return void console.log("[RevealEffect] _applyShaders: No gsplat component found on entity");const t=e.instance||e._instance;if(t)return console.log("[RevealEffect] Instance found via component:",t),void this._applyToInstance(t);const o=this.app;if(o&&o.scene){const e=o.scene.layers?.layerList||[];for(const t of e)if(t.meshInstances)for(const e of t.meshInstances){if(e.gsplatInstance||e._gsplatInstance){const t=e.gsplatInstance||e._gsplatInstance;return console.log("[RevealEffect] Found gsplat instance via mesh instance:",t),void this._applyToInstance(t)}const t=e.material;if(t&&t.gsplat)return console.log("[RevealEffect] Found gsplat material via mesh instance:",t),this._applyShaderToMaterial(t),void(this._shadersApplied=!0)}const t=e=>{if(e.gsplat){const t=e.gsplat.instance||e.gsplat._instance;if(t)return console.log("[RevealEffect] Found gsplat instance via entity search:",t),this._applyToInstance(t),!0}for(const o of e.children||[])if(t(o))return!0;return!1};o.root&&t(o.root)}if(this._retryCount%50==0&&(console.log("[RevealEffect] Still searching for gsplat materials..."),console.log("[RevealEffect] gsplatComponent._instance:",e._instance),console.log("[RevealEffect] gsplatComponent.asset:",e.asset),o&&o.assets&&"number"==typeof e.asset&&e.asset>0)){const t=o.assets.get(e.asset);console.log("[RevealEffect] Asset from registry:",t),t&&t.resource&&console.log("[RevealEffect] Asset resource:",t.resource)}},_applyToInstance(e){if(this._shadersApplied)return;console.log("[RevealEffect] Applying shaders to instance"),console.log("[RevealEffect] Instance type:",e.constructor?.name),console.log("[RevealEffect] Instance keys:",Object.keys(e));const t=e.materials||e._materials;if(console.log("[RevealEffect] Instance materials:",t),t&&(t instanceof Map||t.forEach&&void 0!==t.size?(console.log("[RevealEffect] Materials is a Map/Set with size:",t.size),t.size>0&&(t.forEach(e=>{this._applyShaderToMaterial(e)}),this._shadersApplied=!0,console.log("[RevealEffect] SUCCESS: Shaders applied to",t.size,"materials"))):Array.isArray(t)&&(console.log("[RevealEffect] Materials is array with length:",t.length),t.forEach(e=>{this._applyShaderToMaterial(e)}),this._shadersApplied=!0)),!this._shadersApplied){const t=e.material||e._material;t&&(console.log("[RevealEffect] Found single material on instance"),this._applyShaderToMaterial(t),this._shadersApplied=!0)}e.on&&!this._materialCreatedHandler&&(this._materialCreatedHandler=e=>{console.log("[RevealEffect] material:created event received"),this._applyShaderToMaterial(e),this._shadersApplied=!0},e.on("material:created",this._materialCreatedHandler),console.log("[RevealEffect] Subscribed to material:created event"))},_applyShaderToMaterial(e){if(this._materialsApplied.has(e))return void console.log("[RevealEffect] Material already has shader applied, skipping");console.log("[RevealEffect] Applying shader to material:",e),console.log("[RevealEffect] Material constructor:",e.constructor?.name),console.log("[RevealEffect] Material keys:",Object.keys(e));const t=[];let o=e;for(;o&&o!==Object.prototype;){const e=Object.getOwnPropertyNames(o);for(const n of e)try{"function"!=typeof o[n]||t.includes(n)||t.push(n)}catch(e){}o=Object.getPrototypeOf(o)}console.log("[RevealEffect] Material methods:",t.filter(e=>!e.startsWith("_")).join(", "));const n=this.getShaderGLSL(),i=this.getShaderWGSL(),a="function"==typeof e.setShaderChunk;console.log("[RevealEffect] material.setShaderChunk exists:",a),a?(n&&(e.setShaderChunk("gsplatEffectGLSL",n),console.log("[RevealEffect] GLSL shader chunk set via setShaderChunk")),i&&(e.setShaderChunk("gsplatEffectWGSL",i),console.log("[RevealEffect] WGSL shader chunk set via setShaderChunk"))):(e.chunks&&(console.log("[RevealEffect] Material has chunks property"),e.chunks.gsplatEffectGLSL=n,e.chunks.gsplatEffectWGSL=i,console.log("[RevealEffect] Set chunks directly")),e.options&&console.log("[RevealEffect] Material has options:",e.options),e.shader&&console.log("[RevealEffect] Material has shader:",e.shader),"function"==typeof e.setParameter&&console.log("[RevealEffect] material.setParameter exists - will use for uniforms")),e.update?.(),this._materialsApplied.add(e),console.log("[RevealEffect] Material added to applied set, total:",this._materialsApplied.size)},_removeShaders(){if(this._materialsApplied&&(this._materialsApplied.forEach(e=>{e.setShaderChunk?.("gsplatEffectGLSL",""),e.setShaderChunk?.("gsplatEffectWGSL",""),e.update?.()}),this._materialsApplied.clear()),this._shadersApplied=!1,this._materialCreatedHandler){const e=this.entity.gsplat,t=e?.instance;t?.off&&t.off("material:created",this._materialCreatedHandler),this._materialCreatedHandler=null}},_setUniform(e,t){this._materialsApplied&&this._materialsApplied.forEach(o=>{o.setParameter?.(e,t)})},destroy(){this._removeShaders()}}),x=t,t}const _={fast:{speed:10,acceleration:2,delay:.5,oscillationIntensity:.1,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:50},medium:{speed:5,acceleration:0,delay:2,oscillationIntensity:.2,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:50},slow:{speed:3,acceleration:0,delay:3,oscillationIntensity:.25,dotTint:{r:0,g:1,b:1},waveTint:{r:1,g:.5,b:0},endRadius:50}};function C(e){if("none"!==e)return _[e]}class M{constructor(){this.listeners=new Map}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,...t){this.listeners.get(e)?.forEach(e=>e(...t))}}function E(t,o,n={}){const s=new M,h=a(i(o));console.log("[StorySplat Viewer] Creating viewer with config:",h),console.log("[StorySplat Viewer] Scale config:",h.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:o.splatScale,scale:o.scale});const g=!1!==n.showUI,y=h.uiColor||"#4CAF50",f=h.uiOptions||{},v=e=>"first-person"===e?"tour":"drone"===e?"explore":e,b=(h.allowedCameraModes||["orbit","first-person","drone"]).map(v).filter((e,t,o)=>o.indexOf(e)===t),x=v(h.defaultCameraMode||"orbit");let _={};g&&(_=r(t,h,{uiColor:y,showScrollControls:h.waypoints&&h.waypoints.length>0,showModeToggle:b.length>1,showFullscreenButton:!f.hideFullscreenButton,showHelpButton:!f.hideHelpButton&&!f.hideInfoButton,showPreloader:!0,allowedCameraModes:b,defaultCameraMode:x}));const E=document.createElement("canvas");E.id="storysplat-viewer-canvas",E.style.width="100%",E.style.height="100%",E.style.display="block",t.appendChild(E);const P=new e.Application(E,{graphicsDeviceOptions:{antialias:!1,alpha:!1,powerPreference:"high-performance"},mouse:new e.Mouse(E),touch:new e.TouchDevice(E),keyboard:new e.Keyboard(window)});P.setCanvasFillMode(e.FILLMODE_FILL_WINDOW),P.setCanvasResolution(e.RESOLUTION_AUTO),P.start(),console.log("[StorySplat Viewer] App started");let T=0,k=!1,L=null,z=null,R=!1;const I=new e.Entity("camera");I.addComponent("camera",{clearColor:new e.Color(.1,.1,.1),fov:h.fov||60,nearClip:h.nearClip||.1,farClip:h.farClip||1e3}),I.addComponent("audiolistener"),console.log("[StorySplat Viewer] Camera settings:",{fov:h.fov,nearClip:h.nearClip,farClip:h.farClip,playerHeight:h.playerHeight});const D=h.playerHeight||1.6;if(I.setPosition(0,D,5),P.root.addChild(I),h.waypoints&&h.waypoints.length>0){const e=h.waypoints[0];console.log("[StorySplat Viewer] First waypoint raw:",e),setTimeout(()=>{if(e.position){const t=e.position;I.setPosition(t.x,t.y,-t.z),console.log("[StorySplat Viewer] Camera position (Z negated):",{x:t.x,y:t.y,z:-t.z})}if(e.rotation){const t=H(e.rotation);I.setRotation(t),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}},0)}else I.lookAt(new e.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)");const F=new e.Entity("light");F.addComponent("light",{type:e.LIGHTTYPE_DIRECTIONAL,color:new e.Color(1,1,1),intensity:1,castShadows:!1}),F.setEulerAngles(45,45,0),P.root.addChild(F),console.log("[StorySplat Viewer] Light added");const V=new w(I,P,{moveSpeed:5*(h.cameraMovementSpeed||1),moveFastSpeed:10*(h.cameraMovementSpeed||1),moveSlowSpeed:2.5*(h.cameraMovementSpeed||1),rotateSpeed:.001*(h.cameraRotationSensitivity||.2),enableOrbit:!0,enableFly:!0,enablePan:!0});let $=x,B=!0;"tour"===x&&V.disable();const O=new e.Picker(P,1,1);function U(e){$=e,console.log("[StorySplat Viewer] Switching to mode:",e);const t=function(){const e=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(e)}();"explore"===e?(B=!1,V.syncFromCamera(),V.enable(),V.enableOrbit=!0,V.enableFly=!0,V.enablePan=!0,t&&(p(_,!0),u(_,!0),V.setMode("fly"),m(_,"fly"))):(V.disable(),B=!0,p(_,!1),u(_,!1)),s.emit("modeChange",{mode:e})}P.on("update",e=>{V.update(e),B||function(){const e=I.getPosition();ue.forEach(t=>{const o=t.hotspotData;if(!o||"video"!==o.type||!t.videoElement)return;if("proximity"!==(t.mediaTriggerMode||"click"))return;const n=t.getPosition(),i=e.distance(n),a=t.proximityDistance||5;i<=a&&!t.isVideoPlaying?ze(t,o):i>a&&t.isVideoPlaying&&Re(t)})}(),function(){if(0===ge.size)return;const e=I.getPosition();ge.forEach((t,o)=>{const{entity:n,config:i,slotId:a,assetReady:s,playing:l}=t;if(!s)return;const r=n.sound?.slot(a);if(r&&i.spatialSound){const a=n.getPosition(),s=e.distance(a),c=i.maxDistance||20;s<=c&&!l?(console.log(`[Audio] Proximity play: ${o}, distance=${s.toFixed(2)}, maxDistance=${c}`),r.play(),t.playing=!0):s>c&&l&&i.stopOnExit&&(console.log(`[Audio] Proximity stop: ${o}, distance=${s.toFixed(2)}`),r.stop(),t.playing=!1)}})}()}),P.on("joystick:left",(e,t,o,n)=>{if(e<0||t<0)d(_,!1,0,0,60);else{d(_,!0,o-e,n-t,60)}}),_.cameraModeToggle&&_.cameraModeToggle.addEventListener("click",()=>{if("explore"!==$)return;const e=V.mode;"orbit"===e||"focus"===e?(V.setMode("fly"),m(_,"fly")):(V.setMode("orbit"),m(_,"orbit"))}),P.on("cameracontrols:modechange",e=>{"orbit"!==e&&"fly"!==e||m(_,e)});const W=(e,t)=>{_.preloader&&function(e,t,o){const n=e.querySelector(".storysplat-preloader-bar"),i=e.querySelector(".storysplat-preloader-text"),a=Math.max(0,Math.min(100,100*t));n&&(n.style.width=`${a}%`),i&&(i.textContent=o||`Loading... ${Math.round(a)}%`)}(_.preloader,e,t),s.emit("progress",{progress:e,text:t})};function H(t){if("_w"in t||"w"in t){const o=t._x??t.x??0,n=t._y??t.y??0,i=t._z??t.z??0,a=t._w??t.w??1;return new e.Quat(-o,-n,i,a)}if("x"in t&&"y"in t&&"z"in t){const o=new e.Quat;return o.setFromEulerAngles(t.x||0,t.y||0,t.z||0),o}return new e.Quat}let N=0;const j=1e3/(h.waypoints?.reduce((e,t)=>e+(t.duration||2e3),0)||1);let G=null,q=null;const X=new e.Quat;let Y=!1,Z=0,Q=0;const J=[],K=[];function ee(t){if(!B||J.length<2)return;t=Math.max(0,Math.min(1,t));const o=J.length,n=t*(o-1),i=Math.min(Math.floor(n),o-2),a=n-i,l=J[i],r=J[i+1];G=new e.Vec3(A(l.x,r.x,a),A(l.y,r.y,a),A(l.z,r.z,a));const c=K[i],p=K[i+1];q=new e.Quat,q.slerp(c,p,a);const d=Math.round(t*(o-1));if(d!==T){const e=T;T=d,s.emit("waypointChange",{index:d,waypoint:h.waypoints[d],prevIndex:e}),u=d,m=e,ge.forEach((e,t)=>{const{entity:o,waypointIndex:n,config:i,slotId:a,autoplayTriggered:s}=e,l=o.sound?.slot(a);if(!l)return;const r=m===n&&u!==n;u===n&&m!==n&&i.autoplay&&!s&&(console.log(`[Audio] Autoplay waypoint audio: ${t} at waypoint ${n}`),l.isPlaying||(l.play(),e.playing=!0,e.autoplayTriggered=!0)),r&&i.stopOnExit&&e.playing&&(console.log(`[Audio] Stopping waypoint audio on exit: ${t}`),l.isPlaying&&(l.stop(),e.playing=!1,e.autoplayTriggered=!1))})}var u,m;s.emit("progressUpdate",{progress:Math.max(0,Math.min(1,t)),index:T})}function te(e,t=!1){t?oe(e):(N=Math.max(0,Math.min(1,e)),ee(N))}function oe(e,t=500){const o=N,n=performance.now(),i=()=>{const a=performance.now()-n,s=Math.min(a/t,1);N=o+(e-o)*(s<.5?2*s*s:(4-2*s)*s-1),ee(N),s<1&&requestAnimationFrame(i)};requestAnimationFrame(i)}function ne(e){if(!h.waypoints||e<0||e>=h.waypoints.length)return;if(!B)return;oe(e/Math.max(1,h.waypoints.length-1))}function ie(){if(!h.waypoints||0===h.waypoints.length)return;ne(Math.min(T+1,h.waypoints.length-1))}function ae(){if(!h.waypoints||0===h.waypoints.length)return;ne(Math.max(T-1,0))}h.waypoints&&h.waypoints.length>0&&(h.waypoints.forEach(t=>{const o=t.position?new e.Vec3(t.position.x,t.position.y,-t.position.z):new e.Vec3(0,h.playerHeight||1.6,0);J.push(o);const n=t.rotation?H(t.rotation):new e.Quat;K.push(n)}),J.length>0&&(G=J[0].clone(),q=K[0].clone())),P.on("update",function(){if(!G||!q)return;const t=I.getPosition(),o=new e.Vec3;if(o.lerp(t,G,.1),I.setPosition(o.x,o.y,o.z),!Y){const t=new e.Quat;X.slerp(X,t,1-.95)}const n=new e.Quat;n.mul2(q,X);const i=I.getRotation(),a=new e.Quat;a.slerp(i,n,.1),I.setRotation(a)});let se=0,le=null;function re(e){if(!k)return;0===se&&(se=e);const t=(e-se)/1e3;se=e,N+=j*t,N>=1&&(N=0),ee(N),le=requestAnimationFrame(re)}function ce(){k||!h.waypoints||h.waypoints.length<2||(k=!0,se=0,s.emit("playbackStart"),le=requestAnimationFrame(re))}function pe(){k=!1,le&&(cancelAnimationFrame(le),le=null),s.emit("playbackStop")}const de=P.graphicsDevice.canvas;de.addEventListener("wheel",e=>{if(!B)return;e.preventDefault();const t=e.deltaY>0?.005:-.005;te(Math.max(0,Math.min(1,N+t)))},{passive:!1}),de.addEventListener("pointerdown",e=>{B&&(Y=!0,Z=e.clientX,Q=e.clientY)},{capture:!0}),de.addEventListener("pointermove",t=>{if(!B||!Y)return;const o=t.clientX-Z,n=t.clientY-Q;Z=t.clientX,Q=t.clientY;const i=.3*-o*.01,a=.3*-n*.01,s=new e.Quat;s.setFromAxisAngle(e.Vec3.UP,i*e.math.RAD_TO_DEG);const l=new e.Quat;l.setFromAxisAngle(e.Vec3.RIGHT,a*e.math.RAD_TO_DEG),X.mul(s),X.mul(l),X.normalize()},{capture:!0}),de.addEventListener("pointerup",()=>{Y=!1},{capture:!0}),de.addEventListener("pointerleave",()=>{Y=!1},{capture:!0});const ue=[];function me(t){const o=parseInt(t.slice(1,3),16)/255,n=parseInt(t.slice(3,5),16)/255,i=parseInt(t.slice(5,7),16)/255;return new e.Color(o,n,i)}const he=[],ge=new Map,ye=new Map,fe=new Map,ve={flare:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fflare.png?alt=media&token=ce114781-2ac3-41b2-b9c2-34bda0f6eb13",circle:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fcircle.png?alt=media&token=fd01b475-2b94-4c24-bc83-2a907045715f",spark:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fspark.png?alt=media",rain:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Frain.png?alt=media",smoke:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsmoke.png?alt=media"};function be(t,o){return new Promise((n,i)=>{if(fe.has(t))return void n(fe.get(t));const a=new e.Asset(t,"texture",{url:o});a.on("load",()=>{if(R)return console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${t}`),void i(new Error("Viewer destroyed"));const e=a.resource;fe.set(t,e),n(e)}),a.on("error",e=>{console.error(`[Particle] Failed to load texture: ${t}`,e),i(e)}),P.assets.add(a),P.assets.load(a)})}function we(t){const o=new e.Entity(t.name||"particle-system"),n=(e,t=0)=>({x:e?._x??e?.x??t,y:e?._y??e?.y??t,z:e?._z??e?.z??t}),i=e=>e||{r:1,g:1,b:1,a:1},a=n(t.emitterPosition,0);n(t.gravity,0);const s=i(t.color1),l=i(t.color2),r=t.minLifeTime&&t.maxLifeTime?(t.minLifeTime+t.maxLifeTime)/2:2;return o.addComponent("particlesystem",{numParticles:t.numParticles||500,lifetime:r,rate:t.emitRate||50,emitterShape:e.EMITTERSHAPE_BOX,emitterExtents:new e.Vec3(t.emitterExtents?.x||.1,t.emitterExtents?.y||.1,t.emitterExtents?.z||.1),startAngle:0,startAngle2:360,radialSpeedGraph:new e.Curve([0,t.minEmitPower||1,1,t.maxEmitPower||2]),localVelocityGraph:new e.CurveSet([[0,0],[0,t.minEmitPower||1],[0,0]]),scaleGraph:new e.Curve([0,t.minSize||.1,1,t.maxSize||.3]),colorGraph:new e.CurveSet([[0,s.r,1,l.r],[0,s.g,1,l.g],[0,s.b,1,l.b]]),alphaGraph:new e.Curve([0,s.a||1,.5,l.a||.5,1,0]),blend:e.BLEND_ADDITIVEALPHA,depthWrite:!1,lighting:!1,halfLambert:!1,alignToMotion:!1,preWarm:!1,loop:!0,autoPlay:!0}),o.particlesystem&&(o.particlesystem.localSpace=!1),o.setPosition(a.x,a.y,-a.z),console.log(`[Particle] Entity configured at position: (${a.x}, ${a.y}, ${-a.z})`),o}const xe=new Map;function Se(t){const{type:o,component:n,node:i,animations:a}=t;console.log("[CustomMesh] Playing animation, type:",o,"node:",i?.name,"animations:",a?.length);try{if("pc-anim"===o)console.log("[CustomMesh] Using simple pc-anim approach (like HTML export)"),n.playing=!0,t.isPlaying=!0,console.log("[CustomMesh] Animation started - playing:",n.playing);else if("animation"===o){if(n.playing=!0,n.loop=!0,"function"==typeof n.play){const e=Object.keys(n.animations||{});e.length>0?(n.play(e[0],1),console.log("[CustomMesh] Playing legacy animation clip:",e[0])):n.play()}}else if("glb-skeleton"===o){console.log("[CustomMesh] Starting GLB skeleton animation playback");const{modelEntity:o,asset:n,animations:i}=t;if(i&&i.length>0){const n=i[0];console.log("[CustomMesh] Animation clip:",n),console.log("[CustomMesh] Animation name:",n._name||n.name),console.log("[CustomMesh] Animation duration:",n._duration||n.duration);try{if(!o.anim){console.log("[CustomMesh] Adding AnimComponent to modelEntity");const e={layers:[{name:"Base",states:[{name:"START",speed:1},{name:"Idle",speed:1,loop:!0}],transitions:[{from:"START",to:"Idle",time:0,conditions:[]}]}]};if(o.addComponent("anim",{activate:!0,speed:1}),o.anim){o.anim.loadStateGraph(e);const t=n;o.anim.assignAnimation("Base.Idle",t,"Base"),console.log("[CustomMesh] AnimComponent added and animation assigned")}}if(o.anim)o.anim.playing=!0,o.anim.speed=1,t.animComponent=o.anim,t.isPlaying=!0,console.log("[CustomMesh] GLB animation playing via AnimComponent");else{console.log("[CustomMesh] Falling back to manual curve sampling"),t.isPlaying=!0,t.startTime=Date.now();const i=i=>{if(!t.isPlaying)return;const a=(Date.now()-t.startTime)/1e3%(n._duration||n.duration||1);try{(n._curves||[]).forEach((t,i)=>{if(!t)return;const s=n._paths?.[i];if(!s||!s.entityPath)return;let l=o.findByPath(s.entityPath.join("/"));if(l||(l=o.findByName(s.entityPath[s.entityPath.length-1])),!l)return;const r=t.evaluate?.(a);if(null==r)return;const c=s.component||s.propertyPath?.[0];"localPosition"===c||"position"===c?Array.isArray(r)&&l.setLocalPosition(r[0],r[1],r[2]):"localRotation"===c||"rotation"===c?Array.isArray(r)&&r.length>=4&&l.setLocalRotation(new e.Quat(r[0],r[1],r[2],r[3])):"localScale"!==c&&"scale"!==c||Array.isArray(r)&&l.setLocalScale(r[0],r[1],r[2])})}catch(e){}};t.updateHandler=i,P.on("update",i),console.log("[CustomMesh] Manual animation playback started")}}catch(e){console.error("[CustomMesh] Error setting up GLB animation:",e)}}}else if("anim"===o&&(n.playing=!0,n.speed=1,n.baseLayer)){const e=(n.baseLayer.states||[]).find(e=>"START"!==e&&"END"!==e&&"ANY"!==e);e&&(n.baseLayer.play(e),console.log("[CustomMesh] Playing anim state:",e))}n&&console.log("[CustomMesh] Animation component state - playing:",n.playing,"speed:",n.speed)}catch(e){console.error("[CustomMesh] Error in playAnimComponentV2:",e)}}function _e(t,o){if(console.log("[CustomMesh] Loading mesh",o,":",t.name,t),!t.modelUrl||"string"!=typeof t.modelUrl||""===t.modelUrl.trim())return console.warn("[CustomMesh] Skipping mesh",t.name,"- no valid modelUrl provided. Config:",t),null;const n=new e.Entity("custom-mesh-"+o),i=t.position||{x:0,y:0,z:0};if(n.setPosition(i._x??i.x??0,i._y??i.y??0,-(i._z??i.z??0)),t.rotation){const e=t.rotation,o=180/Math.PI;n.setEulerAngles((e._x??e.x??0)*o,(e._y??e.y??0)*o,(e._z??e.z??0)*o)}if(t.scale){const e=t.scale;n.setLocalScale(e._x??e.x??1,e._y??e.y??1,e._z??e.z??1)}const a=t.modelUrl.trim();console.log("[CustomMesh] Loading model from URL:",a);const s=new e.Asset("mesh-model-"+o,"container",{url:a});P.assets.add(s);const l=t.id||`mesh-${o}`,r={entity:n,config:t,isAnimPlaying:!1,audioPlaying:!1};return xe.set(l,r),s.ready(o=>{try{if(console.log("[CustomMesh] Model loaded:",t.name),!o||!o.resource)return void console.error("[CustomMesh] Invalid asset resource for mesh:",t.name);const i=o.resource.instantiateRenderEntity();if(!i)return void console.error("[CustomMesh] Failed to instantiate render entity for mesh:",t.name);function a(e){e.enabled=!0,e.children&&e.children.forEach(e=>a(e))}n.addChild(i),n.modelEntity=i,a(i);let s=0;i.forEach(()=>{s++}),console.log("[CustomMesh] Enabled all children for:",t.name,"- Total nodes:",s),t.billboard&&(P.on("update",()=>{if(!n.enabled)return;const e=I.getPosition(),t=n.getPosition(),o=e.x-t.x,i=e.z-t.z,a=Math.atan2(o,i)*(180/Math.PI),s=n.getEulerAngles();n.setEulerAngles(s.x,a,s.z)}),console.log("[CustomMesh] Billboard enabled for:",t.name));const l=o.resource?.animations?.length>0;if(console.log("[CustomMesh] Asset resource for",t.name,":",{hasAnimations:l,animationCount:o.resource?.animations?.length||0,animations:o.resource?.animations?.map(e=>e?.name||e?.resource?.name||"unnamed")||[],interactionConfig:t.interaction}),l||t.interaction&&t.interaction.playModelAnimation){const c=[],p=i.anim;if(p&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),c.push({type:"pc-anim",component:p,modelEntity:i})),0===c.length){function d(e,t=0){e.anim&&!c.find(t=>t.component===e.anim)&&(c.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(c.push({type:"animation",component:e.animation,node:e}),console.log("[CustomMesh] Found ANIMATION (legacy) component on:",e.name)),e.children&&e.children.forEach(e=>d(e,t+1))}d(i)}if(0===c.length&&l){const u=o.resource.animations;console.log("[CustomMesh] No anim component found - falling back to GLB skeleton approach"),console.log("[CustomMesh] GLB has",u.length,"embedded animations");try{c.push({type:"glb-skeleton",modelEntity:i,asset:o,animations:u,animationNames:u.map(e=>e.resource?.name||e.name||"Animation"),isPlaying:!1,currentTime:0}),console.log("[CustomMesh] GLB skeleton animations stored:",u.map(e=>e.resource?.name||e.name))}catch(m){console.error("[CustomMesh] Error setting up GLB animations:",m)}}if(c.length>0){r.allAnimComponents=c,r.animComponent=c[0].component,console.log("[CustomMesh] Total animation components for",t.name,":",c.length,"- Types:",c.map(e=>e.type).join(", "));const h=t.interaction?.animationAutoPlay;h&&(c.forEach(e=>{Se(e)}),r.isAnimPlaying=!0,console.log("[CustomMesh] Auto-playing all animations for:",t.name))}else console.warn("[CustomMesh] No animation components could be set up for mesh:",t.name)}else console.log("[CustomMesh] No animations to setup for:",t.name,"(no GLB animations and playModelAnimation not enabled)");t.interaction&&t.interaction.playAudio&&t.interaction.audioUrl&&function(t,o,n){const i=o.interaction,a=o.id||o.name,s=`mesh-audio-${a}`;t.addComponent("sound",{positional:i.audioSpatial||!1,distanceModel:(i.audioSpatial,"linear"),refDistance:i.audioRefDistance||5,maxDistance:i.audioMaxDistance||100,rollOffFactor:i.audioRollOffFactor||1,slots:{[s]:{name:s,loop:i.audioLoop||!1,autoPlay:!1,volume:void 0!==i.audioVolume?i.audioVolume:1,pitch:1}}}),n.audioSlotId=s;const l=new e.Asset(`mesh-audio-asset-${a}`,"audio",{url:i.audioUrl});P.assets.add(l),l.ready(()=>{const e=t.sound?.slot(s);e&&(e.asset=l.id),console.log("[CustomMesh] Audio loaded for mesh:",o.name,"Spatial:",i.audioSpatial)}),l.on("error",e=>{console.error("[CustomMesh] Failed to load mesh audio:",o.name,e)}),P.assets.load(l)}(n,t,r),t.interaction&&function(t,o,n){const i=o.interaction?.animationTriggerMode||"click";if("click"!==i)return;const a=P.graphicsDevice.canvas,s=(o,n)=>{const i=a.getBoundingClientRect(),s=o-i.left,l=n-i.top,r=I.camera.screenToWorld(s,l,I.camera.nearClip),c=I.camera.screenToWorld(s,l,I.camera.farClip),p=(new e.Vec3).sub2(c,r).normalize(),d=t.modelEntity;if(d&&Ce(d,r,p))return!0;if(Ce(t,r,p))return!0;const u=t.getPosition(),m=(new e.Vec3).sub2(u,r).dot(p);if(m>0){const o=(new e.Vec3).add2(r,p.clone().mulScalar(m)).distance(u),n=t.getLocalScale();if(o<1.5*Math.max(n.x,n.y,n.z))return!0}return!1},l=()=>{console.log("[CustomMesh] Clicked mesh:",o.name),console.log("[CustomMesh] meshData:",n),console.log("[CustomMesh] allAnimComponents:",n.allAnimComponents);const e=n.allAnimComponents;if(o.interaction.playModelAnimation&&e&&e.length>0?n.isAnimPlaying?(e.forEach(e=>{!function(e){const{type:t,component:o}=e;try{"pc-anim"===t?(o.playing=!1,e.isPlaying=!1,console.log("[CustomMesh] pc-anim animation paused")):"glb-skeleton"===t?(e.isPlaying=!1,e.animComponent&&(e.animComponent.playing=!1,console.log("[CustomMesh] GLB skeleton animation paused via AnimComponent")),e.updateHandler&&(P.off("update",e.updateHandler),e.updateHandler=null,console.log("[CustomMesh] GLB manual animation paused"))):o&&(o.playing=!1,o.speed=0,"animation"===t&&"function"==typeof o.pause&&o.pause())}catch(e){console.error("[CustomMesh] Error pausing animation:",e)}}(e)}),n.isAnimPlaying=!1,console.log("[CustomMesh] Paused",e.length,"animations for:",o.name)):(e.forEach(e=>{Se(e)}),n.isAnimPlaying=!0,console.log("[CustomMesh] Playing",e.length,"animations for:",o.name)):o.interaction.playModelAnimation&&(console.warn("[CustomMesh] No animation components found for click on:",o.name),console.warn("[CustomMesh] meshData.allAnimComponents:",n.allAnimComponents)),o.interaction.playAudio&&n.audioSlotId){const e=t.sound?.slot(n.audioSlotId);e&&(e.isPlaying?(e.pause(),n.audioPlaying=!1,console.log("[CustomMesh] Paused audio for:",o.name)):(e.play(),n.audioPlaying=!0,console.log("[CustomMesh] Playing audio for:",o.name)))}o.interaction.showPopup&&function(e){const t=document.querySelector(".storysplat-mesh-popup");t&&t.remove();const o=document.createElement("div");o.className="storysplat-mesh-popup",o.style.cssText="\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.9);\n padding: 30px;\n border-radius: 12px;\n max-width: 600px;\n max-height: 80vh;\n overflow: auto;\n z-index: 10000;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ";const n=document.createElement("h2");if(n.textContent=e.name||"Custom Mesh",n.style.cssText="margin-top: 0; margin-bottom: 16px;",o.appendChild(n),e.interaction.popupContent){const t=document.createElement("p");t.textContent=e.interaction.popupContent,t.style.cssText="margin: 0; line-height: 1.6;",o.appendChild(t)}const i=document.createElement("button");i.textContent="× Close",i.style.cssText="\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(255, 255, 255, 0.2);\n border: none;\n color: white;\n padding: 8px 16px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 16px;\n ",i.onclick=()=>o.remove(),o.appendChild(i),document.body.appendChild(o)}(o)},r=e=>{s(e.clientX,e.clientY)&&l()};let c=!1;const p=e=>{const t=s(e.clientX,e.clientY);t&&!c?(a.style.cursor="pointer",c=!0):!t&&c&&(a.style.cursor="",c=!1)},d=()=>{c&&(a.style.cursor="",c=!1)};a.addEventListener("click",r),a.addEventListener("mousemove",p),a.addEventListener("mouseleave",d),t.meshClickHandler=r,t.meshHoverHandler=p,t.meshLeaveHandler=d}(n,t,r),console.log("[CustomMesh] Mesh fully initialized:",t.name)}catch(g){console.error("[CustomMesh] Error processing loaded mesh:",t.name,g)}}),s.on("error",e=>{console.error("[CustomMesh] Failed to load mesh:",t.name,e),P.assets.remove(s)}),P.assets.load(s),t.visibilityRange?(n.visibilityRange=t.visibilityRange,n.enabled=!1!==t.enabled):n.enabled=!1!==t.enabled,n.opacityConfig={mode:t.opacityMode||"static",value:void 0!==t.opacity?t.opacity:1},P.root.addChild(n),n}function Ce(t,o,n){const i=t.render;if(i&&i.meshInstances)for(const e of i.meshInstances)if(e.aabb){if(Me(o,n,e.aabb))return!0}const a=t.model;if(a&&a.meshInstances)for(const e of a.meshInstances)if(e.aabb){if(Me(o,n,e.aabb))return!0}const s=t.children;if(s)for(const t of s)if(t instanceof e.Entity&&Ce(t,o,n))return!0;return!1}function Me(e,t,o){const n=o.getMin?o.getMin():o.min,i=o.getMax?o.getMax():o.max;if(!n||!i)return!1;let a=-1/0,s=1/0;for(let o=0;o<3;o++){const l=["x","y","z"][o],r=e[l],c=t[l],p=n[l]??n.data?.[o],d=i[l]??i.data?.[o];if(Math.abs(c)<1e-8){if(r<p||r>d)return!1}else{let e=(p-r)/c,t=(d-r)/c;if(e>t&&([e,t]=[t,e]),a=Math.max(a,e),s=Math.min(s,t),a>s)return!1}}return s>=0}function Ee(){const t=100*N,o=h.waypoints?.length||1,n=Math.round(N*Math.max(1,o-1));xe.forEach(o=>{const{entity:i,config:a}=o,s=i.visibilityRange;if(s){let e=!0;"waypoint"===s.type?e=n>=s.start&&n<=s.end:"percentage"===s.type&&(e=t>=s.start&&t<=s.end),i.enabled=e}if("animated"===a.opacityMode&&a.opacityAnimation){const o=a.opacityAnimation;if(t>=o.startPercent&&t<=o.endPercent){const n=(t-o.startPercent)/(o.endPercent-o.startPercent);!function(t,o){function n(t){t.render&&t.render.meshInstances&&t.render.meshInstances.forEach(t=>{t.material&&(t.material._isCloned||(t.material=t.material.clone(),t.material._isCloned=!0),t.material.opacity=o,t.material.blendType=e.BLEND_NORMAL,t.material.update())}),t.children.forEach(e=>n(e))}n(t)}(i,o.startOpacity+(o.endOpacity-o.startOpacity)*n)}}})}function Pe(t){const o=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return o?new e.Color(parseInt(o[1],16)/255,parseInt(o[2],16)/255,parseInt(o[3],16)/255):new e.Color(1,1,1)}function Ae(){h.lights&&0!==h.lights.length?(console.log(`[StorySplat Viewer] Creating ${h.lights.length} custom lights...`),h.lights.forEach((t,o)=>{let n=null;switch(t.type){case"point":n=function(t){const o=new e.Entity(t.name||"Point Light");o.addComponent("light",{type:e.LIGHTTYPE_POINT,color:Pe(t.color||"#ffffff"),intensity:t.intensity||1,range:t.range||10,castShadows:t.castShadows||!1});const n=t.position;return n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0)),o.enabled=!1!==t.enabled,o}(t);break;case"directional":n=function(t){const o=new e.Entity(t.name||"Directional Light");o.addComponent("light",{type:e.LIGHTTYPE_DIRECTIONAL,color:Pe(t.color||"#ffffff"),intensity:t.intensity||1,castShadows:t.castShadows||!1});const n=t.position;n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0));const i=t.rotation;if(i){const e=180/Math.PI;o.setEulerAngles((i._x??i.x??0)*e,(i._y??i.y??0)*e,(i._z??i.z??0)*e)}else o.setEulerAngles(45,0,0);return o.enabled=!1!==t.enabled,o}(t);break;case"hemispheric":n=function(t){const o=new e.Entity(t.name||"Hemispheric Light");o.addComponent("light",{type:e.LIGHTTYPE_DIRECTIONAL,color:Pe(t.color||"#ffffff"),intensity:t.intensity||1,castShadows:!1});const n=t.position;if(n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0)),o.setEulerAngles(-90,0,0),o.enabled=!1!==t.enabled,t.groundColor){const o=Pe(t.groundColor),n=Pe(t.color||"#ffffff");P.scene.ambientLight=new e.Color((n.r+.3*o.r)/1.3,(n.g+.3*o.g)/1.3,(n.b+.3*o.b)/1.3)}return o}(t);break;case"ambient":!function(t){const o=Pe(t.color||"#404040"),n=t.intensity||.4;P.scene.ambientLight=new e.Color(o.r*n,o.g*n,o.b*n),console.log("[StorySplat Viewer] Set ambient light:",t.name||"Ambient Light")}(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}n&&(P.root.addChild(n),console.log(`[StorySplat Viewer] Created ${t.type} light:`,t.name||`Light ${o}`))}),console.log("[StorySplat Viewer] Lighting setup complete")):console.log("[StorySplat Viewer] No custom lights to create")}function Te(e){switch(e){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}function ke(){h.hotspots&&0!==h.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${h.hotspots.length} hotspots...`),h.hotspots.forEach((t,o)=>{const n=new e.Entity(`hotspot-${o}`),i=t.position||{_x:0,_y:0,_z:0};n.setPosition(i._x??i.x??0,i._y??i.y??0,-(i._z??i.z??0));const a=t.scale||{_x:1,_y:1,_z:1},s=Math.abs(a._x??a.x??1),l=Math.abs(a._y??a.y??1),r=a._z??a.z??1,c=t.rotation||{_x:0,_y:0,_z:0},p=180/Math.PI,d=(c._x??c.x??0)*p,u=(c._y??c.y??0)*p+180,m=(c._z??c.z??0)*p;if(n.setEulerAngles(d,u,m),"sphere"===t.type){n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new e.StandardMaterial,i=me(t.color||"#ffffff");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5),o.opacity=t.opacity??.8,o.blendType=e.BLEND_NORMAL,o.update(),n.render.material=o,n.setLocalScale(.2*s,.2*l,.2*r)}else if("image"===t.type&&t.imageUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let o=1;"animated"===t.opacityMode&&t.opacityAnimation?o=void 0!==t.opacityAnimation.startOpacity?t.opacityAnimation.startOpacity:1:void 0!==t.opacity&&(o=t.opacity),n.targetOpacity=o,n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const i=!0===t.useLighting,a=function(t,o=!1,n=1,i){const a=new e.StandardMaterial;a.blendType=e.BLEND_NORMAL,a.depthWrite=!1,a.cull=e.CULLFACE_NONE,o?a.diffuse=new e.Color(1,1,1):(a.diffuse=new e.Color(0,0,0),a.emissive=new e.Color(1,1,1),a.specular=new e.Color(0,0,0),a.useLighting=!1),a.opacity=n,a.update();const s=new Image;return s.crossOrigin="anonymous",s.onload=()=>{if(R)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${t}`);const n=new e.Texture(P.graphicsDevice,{width:s.width,height:s.height,format:e.PIXELFORMAT_RGBA8,mipmaps:!0,minFilter:e.FILTER_LINEAR_MIPMAP_LINEAR,magFilter:e.FILTER_LINEAR,addressU:e.ADDRESS_CLAMP_TO_EDGE,addressV:e.ADDRESS_CLAMP_TO_EDGE});n.setSource(s),o?(a.diffuseMap=n,a.opacityMap=n):(a.emissiveMap=n,a.opacityMap=n),a.update(),console.log(`[Hotspot] Texture loaded for: ${t}, useLighting=${o}`),i&&i()},s.onerror=o=>{console.error(`[Hotspot] Failed to load texture: ${t}`,o),a.opacity=.3,a.emissive=new e.Color(1,0,0),a.update(),i&&i()},s.src=t,a}(t.imageUrl,i,o,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=a,n.setLocalScale(s,l,r),n.rotateLocal(90,180,0),n.hotspotMaterial=a,console.log(`[Hotspot] Created image hotspot: ${t.title}, opacity=${o}, opacityMode=${t.opacityMode}, useLighting=${i}`)}else if("video"===t.type&&t.videoUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const o=/iPad|iPhone|iPod/.test(navigator.userAgent)&&t.useIOSVideoAlphaMethod||t.forceIOSVideoAlphaMethodForAllDevices,i=o&&t.iosMainVideoUrl?t.iosMainVideoUrl:t.videoUrl,a=o&&t.alphaMaskVideoUrl||null,c=(o,n)=>{const i=document.createElement("video");i.src=o,i.loop=!1!==t.videoLoop,i.crossOrigin="anonymous",i.playsInline=!0,i.muted=!!n||!1!==t.videoMuted,"autoplay"===t.mediaTriggerMode&&(i.autoplay=!0,i.muted=!0);const a=new e.Texture(P.graphicsDevice,{format:e.PIXELFORMAT_R8_G8_B8,mipmaps:!1,minFilter:e.FILTER_LINEAR,magFilter:e.FILTER_LINEAR,addressU:e.ADDRESS_CLAMP_TO_EDGE,addressV:e.ADDRESS_CLAMP_TO_EDGE});return a.setSource(i),{video:i,texture:a}},p=c(i,!1),d=p.video,u=p.texture,m=new e.StandardMaterial;m.diffuseMap=u,m.emissiveMap=u,m.emissive=new e.Color(1,1,1);let h=null,g=null;if(a){const t=c(a,!0);h=t.video,g=t.texture,m.opacityMap=g,m.blendType=e.BLEND_NORMAL,m.alphaTest=.01}m.update(),P.on("update",()=>{d.readyState===d.HAVE_ENOUGH_DATA&&u.upload(),h&&g&&h.readyState===h.HAVE_ENOUGH_DATA&&g.upload()});const y={x:s,y:l,z:r};if(d.addEventListener("loadedmetadata",()=>{const e=d.videoWidth,t=d.videoHeight;if(e>0&&t>0){const o=e/t;1===y.x&&1===y.y&&n.setLocalScale(o*y.y,y.y,y.z)}}),n.render.material=m,n.setLocalScale(s,l,r),n.rotateLocal(90,180,0),n.videoElement=d,n.alphaVideoElement=h,n.hotspotMaterial=m,n.mediaTriggerMode=t.mediaTriggerMode||"click",n.proximityDistance=t.proximityDistance||5,n.isVideoPlaying=!1,!0!==t.videoMuted){const e=function(e,t,o){if(!o.videoSpatialAudio&&!1!==o.videoMuted)return null;try{const n=new(window.AudioContext||window.webkitAudioContext);he.push(n);const i=n.createMediaElementSource(t),a=n.createPanner();a.panningModel="HRTF",a.distanceModel=o.videoDistanceModel||"inverse",a.refDistance=void 0!==o.videoRefDistance?o.videoRefDistance:1,a.maxDistance=void 0!==o.videoMaxDistance?o.videoMaxDistance:100,a.rolloffFactor=void 0!==o.videoRolloffFactor?o.videoRolloffFactor:1;const s=e.getPosition();return a.setPosition(s.x,s.y,s.z),i.connect(a),a.connect(n.destination),P.on("update",()=>{if(!e||!e.getPosition)return;const t=e.getPosition();if(a.setPosition(t.x,t.y,t.z),I&&I.getPosition){const e=I.getPosition(),t=I.forward,o=I.up;n.listener.positionX?(n.listener.positionX.value=e.x,n.listener.positionY.value=e.y,n.listener.positionZ.value=e.z,n.listener.forwardX.value=t.x,n.listener.forwardY.value=t.y,n.listener.forwardZ.value=t.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(e.x,e.y,e.z),n.listener.setOrientation(t.x,t.y,t.z,o.x,o.y,o.z))}}),console.log(`[Audio] Video spatial audio setup for hotspot: ${o.title}, refDist=${a.refDistance}, maxDist=${a.maxDistance}`),{audioCtx:n,source:i,panner:a}}catch(e){return console.warn("[Audio] Failed to setup video spatial audio:",e),null}}(n,d,t);e&&(n.videoSpatialAudio=e)}console.log(`[Hotspot] Created video hotspot: ${t.title}, mode=${t.mediaTriggerMode}, useAlpha=${!!a}, spatialAudio=${!!n.videoSpatialAudio}`)}else{n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new e.StandardMaterial;o.diffuse=me(t.color||"#4CAF50"),o.emissive=me(t.color||"#4CAF50"),o.emissive.mulScalar(.5),o.opacity=.8,o.blendType=e.BLEND_NORMAL,o.update(),n.render.material=o,n.setLocalScale(.2*s,.2*l,.2*r)}n.addComponent("collision",{type:"sphere"===t.type?"sphere":"box",radius:.1,halfExtents:new e.Vec3(.5,.5,.05)}),n.hotspotData=t;const h=function(e,t){if(!t.audioUrl)return null;const o=document.createElement("audio");if(o.src=t.audioUrl,o.loop=t.audioLoop||!1,o.volume=void 0!==t.audioVolume?t.audioVolume:1,o.crossOrigin="anonymous",t.audioSpatial){const n=new(window.AudioContext||window.webkitAudioContext);he.push(n);const i=n.createMediaElementSource(o),a=n.createPanner();a.panningModel="HRTF",a.distanceModel=t.audioDistanceModel||"inverse",a.refDistance=void 0!==t.audioRefDistance?t.audioRefDistance:1,a.maxDistance=void 0!==t.audioMaxDistance?t.audioMaxDistance:100,a.rolloffFactor=void 0!==t.audioRolloffFactor?t.audioRolloffFactor:1;const s=e.getPosition();a.setPosition(s.x,s.y,s.z),i.connect(a),a.connect(n.destination);const l=()=>{if(!e||!e.getPosition)return;const t=e.getPosition();if(a.setPosition(t.x,t.y,t.z),I&&I.getPosition){const e=I.getPosition(),t=I.forward,o=I.up;n.listener.positionX?(n.listener.positionX.value=e.x,n.listener.positionY.value=e.y,n.listener.positionZ.value=e.z,n.listener.forwardX.value=t.x,n.listener.forwardY.value=t.y,n.listener.forwardZ.value=t.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(e.x,e.y,e.z),n.listener.setOrientation(t.x,t.y,t.z,o.x,o.y,o.z))}};return P.on("update",l),console.log(`[Audio] Spatial audio setup for hotspot: ${t.title}, refDist=${a.refDistance}, maxDist=${a.maxDistance}`),{audio:o,audioCtx:n,source:i,panner:a,updateAudioPosition:l}}return console.log(`[Audio] Non-spatial audio setup for hotspot: ${t.title}`),{audio:o}}(n,t);h&&(n.audioElements=h,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${t.title||"Untitled"}`)),t.billboard&&P.on("update",()=>{n.lookAt(I.getPosition()),n.rotateLocal(90,180,0)}),t.visibilityRange&&(n.visibilityRange=t.visibilityRange,n.enabled=!1),P.root.addChild(n),ue.push(n),console.log(`[StorySplat Viewer] Created hotspot: ${t.title||"Untitled"}`)})):console.log("[StorySplat Viewer] No hotspots to create")}function Le(){const e=100*N,t=h.waypoints?.length||1,o=Math.round(N*Math.max(1,t-1)),n=I.getPosition();ue.forEach(t=>{const i=t.hotspotData;if(!i)return;let a=!0;const s=t.visibilityRange;if(s&&("waypoint"===s.type?a=o>=s.start&&o<=s.end:"percentage"===s.type&&(a=e>=s.start&&e<=s.end)),t.shouldBeVisible=a,t.hiddenUntilTextureLoaded?t.enabled=!1:t.enabled=a,t.videoElement&&"video"===i.type){const o=t.mediaTriggerMode||"click";if("proximity"===o){const e=t.getPosition(),o=n.distance(e),a=t.proximityDistance||5;o<=a&&!t.isVideoPlaying?(ze(t,i),console.log(`[Hotspot] Proximity play: ${i.title}, distance=${o.toFixed(2)}`)):o>a&&t.isVideoPlaying&&(Re(t),console.log(`[Hotspot] Proximity pause: ${i.title}, distance=${o.toFixed(2)}`))}"scroll"===o&&(a&&!t.isVideoPlaying?(ze(t,i),console.log(`[Hotspot] Scroll play: ${i.title}, scroll=${e.toFixed(1)}%`)):!a&&t.isVideoPlaying&&(Re(t),console.log(`[Hotspot] Scroll pause: ${i.title}, scroll=${e.toFixed(1)}%`)))}if("animated"===i.opacityMode&&i.opacityAnimation){if("image"===i.type&&!t.textureLoaded)return;const o=i.opacityAnimation,n=o.startPercent??0,a=o.endPercent??100,s=void 0!==o.startOpacity?o.startOpacity:1,l=void 0!==o.endOpacity?o.endOpacity:1;let r;if(e<=n)r=s;else if(e>=a)r=l;else{r=s+(l-s)*((e-n)/(a-n))}r=Math.max(0,Math.min(1,r)),t.hotspotMaterial?(t.hotspotMaterial.opacity=r,t.hotspotMaterial.update()):t.render&&t.render.material&&(t.render.material.opacity=r,t.render.material.update())}})}function ze(e,t){const o=e.videoElement,n=e.alphaVideoElement;if(o){if("autoplay"!==e.mediaTriggerMode&&(o.muted=!1!==t.videoMuted),e.videoSpatialAudio&&e.videoSpatialAudio.audioCtx){const t=e.videoSpatialAudio.audioCtx;"suspended"===t.state&&t.resume().then(()=>{console.log("[Audio] Video spatial audio context resumed")}).catch(e=>console.warn("[Audio] Failed to resume video audio context:",e))}o.play().catch(e=>console.warn("Video play failed:",e)),n&&n.play().catch(e=>console.warn("Alpha video play failed:",e)),e.isVideoPlaying=!0}}function Re(e){const t=e.videoElement,o=e.alphaVideoElement;t&&(t.pause(),o&&o.pause(),e.isVideoPlaying=!1)}s.on("progressUpdate",()=>{Ee()}),s.on("progressUpdate",()=>{Le()}),setTimeout(()=>{Le()},100);const Ie=P.graphicsDevice.canvas;function De(t,o){const n=I.camera.screenToWorld(t,o,I.camera.nearClip),i=I.camera.screenToWorld(t,o,I.camera.farClip);let a=null;if(ue.forEach(t=>{if(!t.enabled)return;const o=t.getPosition(),s=(new e.Vec3).sub2(i,n).normalize(),l=(new e.Vec3).sub2(o,n).dot(s);if(l<0)return;(new e.Vec3).add2(n,s.clone().mulScalar(l)).distance(o)<.5&&(!a||l<a.distance)&&(a={entity:t,distance:l})}),null!==a){const e=a.entity;return{entity:e,hotspot:e.hotspotData}}return null}let Fe=null,Ve=!1;const $e=t.querySelector(".storysplat-hotspot-popup"),Be=t.querySelector(".storysplat-hotspot-overlay");async function Oe(t,o){if("explore"!==$)return;console.log("[StorySplat Viewer] Double-click focus at:",t,o);const n=De(t,o);if(n){const e=n.entity.getPosition();return console.log("[StorySplat Viewer] Focusing on hotspot at:",e.x,e.y,e.z),void V.focus(e,!1)}try{const n=1;O.resize(Math.floor(Ie.clientWidth*n),Math.floor(Ie.clientHeight*n));const i=P.scene.layers.getLayerByName("World");if(!i)return void console.warn("[StorySplat Viewer] World layer not found");O.prepare(I.camera,P.scene,[i]);const a=21,s=Math.floor(t*n)-Math.floor(a/2),l=Math.floor(o*n)-Math.floor(a/2),r=await O.getSelectionAsync(s,l,a,a);if(r.length>0){const n=r[0];if(L&&L.gsplat?.instance?.meshInstance===n||r.length>0){const i=I.camera.screenToWorld(t,o,I.camera.nearClip),a=I.camera.screenToWorld(t,o,I.camera.farClip),s=(new e.Vec3).sub2(a,i).normalize(),l=n.aabb.center,r=(new e.Vec3).sub2(l,i).dot(s),c=Math.max(1,Math.min(r,100)),p=(new e.Vec3).add2(i,s.clone().mulScalar(c));console.log("[StorySplat Viewer] Focusing on GSplat at:",p.x.toFixed(2),p.y.toFixed(2),p.z.toFixed(2),"depth:",c.toFixed(2)),V.focus(p,!1)}}}catch(e){console.warn("[StorySplat Viewer] Picking failed:",e)}}$e&&($e.addEventListener("mouseenter",()=>{Ve=!0}),$e.addEventListener("mouseleave",()=>{Ve=!1,Fe&&"hover"===Fe.activationMode&&($e.classList.remove("visible"),Be&&Be.classList.remove("visible"),Fe=null)})),Ie.addEventListener("mousemove",e=>{const o=Ie.getBoundingClientRect(),n=De(e.clientX-o.left,e.clientY-o.top);if(n&&n.hotspot){const e=n.hotspot;"click"===e.activationMode||"hover"===e.activationMode||"video"===e.type?Ie.style.cursor="pointer":Ie.style.cursor="default","hover"===e.activationMode&&Fe!==e&&(Fe=e,(e.information||e.photoUrl||e.iframeUrl||e.externalLinkUrl)&&c(t,e))}else if(Ie.style.cursor="default",Fe&&"hover"===Fe.activationMode&&!Ve){const e=t.querySelector(".storysplat-hotspot-popup"),o=t.querySelector(".storysplat-hotspot-overlay");e&&e.classList.remove("visible"),o&&o.classList.remove("visible"),Fe=null}}),Ie.addEventListener("click",e=>{const o=Ie.getBoundingClientRect(),n=De(e.clientX-o.left,e.clientY-o.top);if(null!==n){const e=n.entity,o=n.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",o.title),e.audioElements&&e.audioElements.audio){const t=e.audioElements,n=t.audio;n.paused?(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),n.play().catch(e=>console.error("[Audio] Hotspot audio play failed:",e)),console.log("[Audio] Hotspot audio started:",o.title)):(n.pause(),console.log("[Audio] Hotspot audio paused:",o.title))}if(e.videoElement&&("click"===e.mediaTriggerMode||"autoplay"===e.mediaTriggerMode)){const t=e.videoElement;e.alphaVideoElement,"autoplay"===e.mediaTriggerMode&&!t.paused&&t.muted?(t.muted=(o.videoMuted,!1),console.log("[Hotspot] Video unmuted by click")):t.paused?(ze(e,o),console.log("[Hotspot] Video started")):(Re(e),console.log("[Hotspot] Video paused"))}"click"===o.activationMode&&(o.information||o.photoUrl||o.iframeUrl||o.externalLinkUrl)&&c(t,o)}}),Ie.addEventListener("dblclick",e=>{const t=Ie.getBoundingClientRect();Oe(e.clientX-t.left,e.clientY-t.top)});let Ue=0;Ie.addEventListener("touchend",e=>{if(1!==e.changedTouches.length)return;const t=Date.now();if(t-Ue<300){const t=e.changedTouches[0],o=Ie.getBoundingClientRect();Oe(t.clientX-o.left,t.clientY-o.top),Ue=0}else Ue=t}),W(.2,"Initializing..."),async function(){const t=[];h.sogUrl&&t.push(h.sogUrl),h.splatUrl&&t.push(h.splatUrl),h.fallbackUrls&&t.push(...h.fallbackUrls),console.log("[StorySplat Viewer] Loading splat from URLs:",t),W(.3,"Loading splat...");for(const i of t)if(i)try{console.log("[StorySplat Viewer] Trying URL:",i);i.split(".").pop()?.toLowerCase();const t="gsplat",a=new e.Asset("splat-"+Date.now(),t,{url:i});return await new Promise((t,i)=>{a.ready(()=>{if(R)return console.log("[StorySplat Viewer] Ignoring splat load - viewer was destroyed"),void i(new Error("Viewer destroyed"));console.log("[StorySplat Viewer] Asset ready (loaded + resources ready)"),L=new e.Entity("splat"),L.addComponent("gsplat",{asset:a,unified:!0});const s=h.scale||{x:1,y:1,z:1},l=h.invertXScale||!1,r=h.invertYScale||!1,c={x:l?-s.x:s.x,y:r?-s.y:s.y,z:l!==r?-s.z:s.z};L.setLocalScale(c.x,c.y,c.z);const p=h.position||[0,0,0];L.setPosition(p[0],p[1],-p[2]);const d=h.rotation||[0,0,0],u=0!==d[0]||0!==d[1]||0!==d[2];let m;m=u?[d[0]*(180/Math.PI),d[1]*(180/Math.PI),-d[2]*(180/Math.PI)]:[180,0,0],L.setEulerAngles(m[0],m[1],m[2]),console.log("[StorySplat Viewer] Splat transform applied:",{scale:c,position:[p[0],p[1],-p[2]],rotation:m,hasUserRotation:u,invertXScale:l,invertYScale:r}),P.root.addChild(L),console.log("[StorySplat Viewer] Splat entity added to scene"),L.gsplat?.material&&(L.gsplat.material.setParameter("alphaClip",.1),console.log("[StorySplat Viewer] GSplat alphaClip set for picking support"));const g=n.revealEffect||o.revealEffect||"medium",y=C(g);if(y){L.addComponent("script");const e=S();z=L.script?.create(e),z&&(z.enabled=!1,z.center.set(0,0,0),z.speed=y.speed,z.acceleration=y.acceleration,z.delay=y.delay,z.oscillationIntensity=y.oscillationIntensity,z.dotTint.set(y.dotTint.r,y.dotTint.g,y.dotTint.b),z.waveTint.set(y.waveTint.r,y.waveTint.g,y.waveTint.b),z.endRadius=y.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",g))}else console.log("[StorySplat Viewer] Reveal effect disabled");t()}),a.on("error",e=>{console.error("[StorySplat Viewer] Asset error:",e),i(e)}),P.assets.add(a),P.assets.load(a)}),s.emit("loaded"),void console.log("[StorySplat Viewer] Splat loaded successfully")}catch(e){console.warn("[StorySplat Viewer] Failed to load:",i,e)}console.error("[StorySplat Viewer] Failed to load splat from any URL"),s.emit("error",new Error("Failed to load splat from any URL"))}().then(()=>{W(1,"Ready!"),ke(),h.waypoints&&0!==h.waypoints.length&&(h.waypoints.forEach((t,o)=>{t.interactions&&Array.isArray(t.interactions)&&t.interactions.forEach(n=>{if("audio"===n.type&&n.data){const i=n.data,a=n.id,s=new e.Entity(`waypoint-audio-${a}`),l=t.position||{x:0,y:0,z:0};s.setPosition(l._x??l.x??t.x??0,l._y??l.y??t.y??1.6,-(l._z??l.z??t.z??0));const r={slots:{[a]:{name:a,loop:i.loop||!1,autoPlay:!1,volume:void 0!==i.volume?i.volume:1,pitch:1,positional:i.spatialSound||!1,distanceModel:Te(i.distanceModel||"exponential"),maxDistance:i.maxDistance||1e4,refDistance:i.refDistance||1,rollOffFactor:i.rolloffFactor||1}}};s.addComponent("sound",r);const c=new e.Asset(`waypoint-audio-asset-${a}`,"audio",{url:i.url});P.assets.add(c),c.ready(()=>{const e=s.sound?.slot(a);e&&(e.asset=c.id);const t=ge.get(a);t&&(t.assetReady=!0),console.log(`[Audio] Waypoint audio loaded: ${a}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),P.assets.load(c),P.root.addChild(s),ge.set(a,{entity:s,waypointIndex:o,config:i,slotId:a,playing:!1,autoplayTriggered:!1,assetReady:!1}),console.log(`[Audio] Waypoint audio created: ${a} at waypoint ${o}, spatial=${i.spatialSound}`)}})}),ge.size>0&&console.log(`[StorySplat Viewer] Setup ${ge.size} waypoint audio sources`)),console.log("[StorySplat Viewer] 🎆 Calling initParticleSystems..."),async function(){if(console.log("═══════════════════════════════════════"),console.log("🎆 PARTICLE SYSTEM INITIALIZATION"),console.log("═══════════════════════════════════════"),console.log("[Particle] config.particles:",h.particles),console.log("[Particle] Type:",typeof h.particles),console.log("[Particle] Is Array:",Array.isArray(h.particles)),!h.particles||0===h.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${h.particles.length} particle system(s) to create`);for(let e=0;e<h.particles.length;e++){const t=h.particles[e];console.log(`[Particle] --- Particle System ${e+1}/${h.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(t,null,2));try{const e=t.particleTexture||"flare",o=ve[e]||ve.flare;console.log(`[Particle] Texture: ${e} -> ${o.substring(0,60)}...`),console.log("[Particle] Loading texture...");const n=await be(e,o);console.log("[Particle] ✅ Texture loaded:",n.name),console.log("[Particle] Creating entity...");const i=we(t);console.log("[Particle] ✅ Entity created:",i.name),i.particlesystem?(i.particlesystem.colorMap=n,console.log("[Particle] ✅ Texture applied to particle system"),console.log("[Particle] Particle system properties:",{numParticles:i.particlesystem.numParticles,lifetime:i.particlesystem.lifetime,rate:i.particlesystem.rate,loop:i.particlesystem.loop,autoPlay:i.particlesystem.autoPlay})):console.warn("[Particle] ⚠️ Entity has no particlesystem component!"),P.root.addChild(i),console.log("[Particle] ✅ Entity added to scene");const a=i.getPosition();console.log(`[Particle] Position: (${a.x.toFixed(2)}, ${a.y.toFixed(2)}, ${a.z.toFixed(2)})`);const s=(t.id||t.name||`particle-${ye.size}`).replace(/[^a-zA-Z0-9]/g,"_");ye.set(s,i),console.log(`[Particle] ✅ SUCCESS: Created "${t.name||s}"`)}catch(e){console.error(`[Particle] ❌ FAILED to create particle system: ${t.name}`),console.error(`[Particle] Error message: ${e?.message||"No message"}`),console.error(`[Particle] Error stack: ${e?.stack||"No stack"}`),console.error("[Particle] Error object:",e)}}console.log("═══════════════════════════════════════"),console.log(`[Particle] 🎉 COMPLETE: ${ye.size}/${h.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(ye.keys())),console.log("═══════════════════════════════════════")}(),async function(){h.customMeshes&&0!==h.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${h.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",h.customMeshes),setTimeout(()=>{let e=0,t=0;h.customMeshes.forEach((o,n)=>{if(console.log(`[CustomMesh] Processing mesh ${n}:`,{name:o.name,enabled:o.enabled,hasModelUrl:!!o.modelUrl,modelUrl:o.modelUrl?.substring(0,100)+"..."}),!1!==o.enabled)try{_e(o,n)?e++:(t++,console.warn(`[CustomMesh] Mesh ${o.name} returned null (likely missing modelUrl)`))}catch(e){console.error("[CustomMesh] Error loading mesh",o.name,":",e),t++}else console.log("[CustomMesh] Skipping disabled mesh:",o.name,"(enabled =",o.enabled,")"),t++}),console.log(`[CustomMesh] Summary: ${e} loaded, ${t} skipped`)},100),console.log(`[StorySplat Viewer] ${h.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),function(){if(!h.skybox||!h.skybox.url)return void console.log("[StorySplat Viewer] No skybox configured");const t=h.skybox.url,o=h.skybox.rotation??0;console.log("[StorySplat Viewer] Creating skybox:",t);const n=new e.Entity("skybox"),i=new e.StandardMaterial;i.useLighting=!1,i.cull=e.CULLFACE_FRONT;const a=new e.Asset("skybox-texture","texture",{url:t});if(P.assets.add(a),a.ready(t=>{i.emissiveMap=t.resource,i.emissive=new e.Color(1,1,1),i.update(),console.log("[StorySplat Viewer] Skybox texture loaded")}),a.on("error",e=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",e)}),P.assets.load(a),n.addComponent("render",{type:"sphere",material:i,castShadows:!1,receiveShadows:!1}),n.setLocalScale(500,500,500),0!==o){const e=o*(180/Math.PI);n.setEulerAngles(0,e,0)}P.on("update",()=>{const e=I.getPosition();n.setPosition(e.x,e.y,e.z)}),P.root.addChild(n),console.log("[StorySplat Viewer] Skybox created with rotation:",o)}(),Ae(),_.preloader&&l(_.preloader),z&&setTimeout(()=>{z&&(z.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started"))},1e3),s.emit("ready"),console.log("[StorySplat Viewer] Ready"),g&&(!function(e,t,o="tour"){const n=e.scrollControls?.querySelector(".storysplat-btn-prev"),i=e.scrollControls?.querySelector(".storysplat-btn-next"),a=e.scrollControls?.querySelector(".storysplat-btn-play");if(n&&n.addEventListener("click",()=>t.prevWaypoint()),i&&i.addEventListener("click",()=>t.nextWaypoint()),a&&a.addEventListener("click",()=>{t.isPlaying()?(t.pause(),a.innerHTML='<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>'):(t.play(),a.innerHTML='<svg viewBox="0 0 24 24"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg>')}),e.helpButton&&e.helpPanel&&e.helpButton.addEventListener("click",()=>{e.helpPanel.classList.toggle("visible")}),e.fullscreenButton){const t=e.fullscreenButton.parentElement;e.fullscreenButton.addEventListener("click",()=>{if(document.fullscreenElement){document.exitFullscreen();const t=e.fullscreenButton.querySelector(".storysplat-expand-icon"),o=e.fullscreenButton.querySelector(".storysplat-compress-icon");t&&(t.style.display="block"),o&&(o.style.display="none")}else{t?.requestFullscreen();const o=e.fullscreenButton.querySelector(".storysplat-expand-icon"),n=e.fullscreenButton.querySelector(".storysplat-compress-icon");o&&(o.style.display="none"),n&&(n.style.display="block")}})}const s=t=>{const o=e.scrollControls?.querySelector(".storysplat-progress-text"),n=e.scrollControls?.querySelector(".storysplat-progress-container"),i=e.scrollControls?.querySelector(".storysplat-scroll-buttons"),a=t?"":"none";o&&(o.style.display=a),n&&(n.style.display=a),i&&(i.style.display=a)};if(s("tour"===o),t.setCameraMode){const o=e.scrollControls?.querySelectorAll(".storysplat-mode-btn");o?.forEach(e=>{e.addEventListener("click",()=>{const n=e.getAttribute("data-mode");n&&t.setCameraMode&&(t.setCameraMode(n),o.forEach(e=>e.classList.remove("selected")),e.classList.add("selected"),s("tour"===n))})}),t.on("modeChange",({mode:e})=>{s("tour"===e),o?.forEach(t=>{const o=t.getAttribute("data-mode");t.classList.toggle("selected",o===e)})})}let l=0,r=-1;t.on("progressUpdate",({progress:t})=>{const o=100*Math.max(0,Math.min(1,t)),n=Math.round(o),i=performance.now();i-l<33||(l=i,e.progressBar&&(e.progressBar.style.width=`${o}%`),e.progressText&&n!==r&&(r=n,e.progressText.innerHTML="",e.progressText.textContent=`${n}%`))});let c=-1;t.on("waypointChange",({index:o,waypoint:n})=>{if(o===c)return;if(c=o,!e.waypointInfo)return;const i=e.waypointInfo.querySelector(".storysplat-waypoint-title"),a=e.waypointInfo.querySelector(".storysplat-waypoint-description");if(!i||!a)return;const s=n||t.getWaypoints?.()[o];s&&(s.name||s.info)?(i.textContent=s.name||"",a.textContent=s.info||"",s.name||s.info?e.waypointInfo.classList.add("hasContent"):e.waypointInfo.classList.remove("hasContent")):(i.textContent="",a.textContent="",e.waypointInfo.classList.remove("hasContent"))})}(_,{nextWaypoint:ie,prevWaypoint:ae,play:ce,pause:pe,isPlaying:()=>k,getCurrentWaypointIndex:()=>T,getWaypointCount:()=>h.waypoints?.length||0,getWaypoints:()=>h.waypoints||[],setCameraMode:U,on:(e,t)=>s.on(e,t)},x),s.emit("progressUpdate",{progress:N,index:T}),h.waypoints&&h.waypoints.length>0&&s.emit("waypointChange",{index:0,waypoint:h.waypoints[0],prevIndex:-1})),n.autoPlay&&ce()}).catch(e=>{console.error("[StorySplat Viewer] Failed to initialize:",e),_.preloader&&l(_.preloader),s.emit("error",e)});const We=()=>{P.resizeCanvas()};window.addEventListener("resize",We);return{app:P,canvas:E,goToWaypoint:ne,nextWaypoint:ie,prevWaypoint:ae,getCurrentWaypointIndex:()=>T,getWaypointCount:()=>h.waypoints?.length||0,setPosition:(e,t,o)=>I.setPosition(e,t,o),setRotation:(e,t,o)=>I.setEulerAngles(e,t,o),getPosition:()=>{const e=I.getPosition();return{x:e.x,y:e.y,z:e.z}},getRotation:()=>{const e=I.getEulerAngles();return{x:e.x,y:e.y,z:e.z}},play:ce,pause:pe,stop:function(){pe(),te(0)},isPlaying:()=>k,destroy:()=>{R=!0,pe(),window.removeEventListener("resize",We),_.preloader&&_.preloader.remove(),_.scrollControls&&_.scrollControls.remove(),_.fullscreenButton&&_.fullscreenButton.remove(),_.helpButton&&_.helpButton.remove(),_.helpPanel&&_.helpPanel.remove(),_.waypointInfo&&_.waypointInfo.remove();const e=document.getElementById("storysplat-viewer-styles");e&&e.remove(),t.classList.remove("storysplat-viewer-container"),function(){const e=P.graphicsDevice.canvas;xe.forEach(t=>{const o=t.entity;o.meshClickHandler&&e.removeEventListener("click",o.meshClickHandler),o.destroy()}),xe.clear()}(),P.destroy(),E.remove()},resize:We,on:(e,t)=>s.on(e,t),off:(e,t)=>s.off(e,t)}}async function P(e,t,o){console.log("[StorySplat Viewer] Fetching scene from:",t);const n=await fetch(t);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.statusText}`);const i=await n.json();return console.log("[StorySplat Viewer] Scene data loaded:",i),E(e,i,o)}function A(e,t,o){return e+(t-e)*o}export{_ as REVEAL_PRESETS,E as createViewer,P as createViewerFromUrl,a as exportPropsToViewerConfig,o as generateHTML,n as generateHTMLFromUrl,C as getRevealPreset,i as transformSceneToExportProps};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|