storysplat-viewer 2.7.7 → 2.7.8
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 +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/storysplat-viewer.bundled.umd.js +1 -1
- package/dist/storysplat-viewer.bundled.umd.js.map +1 -1
- package/dist/storysplat-viewer.umd.js +1 -1
- package/dist/storysplat-viewer.umd.js.map +1 -1
- package/dist/types/dynamic-viewer/CameraControls.d.ts +5 -0
- package/dist/types/dynamic-viewer/viewerUI.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import*as t from"playcanvas";function e(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function o(t){return t.replace(/<\/script/gi,"<\\/script")}function n(t,n={}){const{cdnUrl:i="https://unpkg.com/storysplat-viewer@2/dist/storysplat-viewer.umd.js",title:s=t.name||"StorySplat Scene",description:a=`Interactive 3D scene: ${t.name||"StorySplat"}`,faviconUrl:r,customCSS:l="",lazyLoad:c,lazyLoadButtonText:p}=n,d=c??t.uiOptions?.lazyLoad??!1,h=p??t.uiOptions?.lazyLoadButtonText,u=r?`<link rel="icon" href="${e(r)}" />`:"",m=l?`<style>${l}</style>`:"",y=o(JSON.stringify(t,(t,e)=>{if(!("undefined"!=typeof HTMLElement&&e instanceof HTMLElement||"function"==typeof e||e&&"object"==typeof e&&"nodeType"in e))return e},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="${e(a)}">\n <title>${e(s)}</title>\n ${u}\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n overflow: hidden;\n background: #111;\n }\n #app {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\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 ${m}\n</head>\n<body>\n <div id="app"></div>\n\n <script src="https://cdn.jsdelivr.net/npm/playcanvas@2.14.3/build/playcanvas.min.js"><\/script>\n <script src="${e(i)}"><\/script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${y};\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 lazyLoad: ${d},\n lazyLoadButtonText: ${h?`'${o(h)}'`:"undefined"}\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 i(t,e={}){const o=await fetch(t);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.statusText}`);return n(await o.json(),e)}function s(t){const e=t.loadedModelUrl||t.splatUrl||"",o=t.sogModelUrl||t.sogUrl,n=t.compressedPlyUrl,i=t.lodMetaUrl,s="string"==typeof t.activeSkyboxUrl&&t.activeSkyboxUrl?t.activeSkyboxUrl:"string"==typeof t.skyboxUrl&&t.skyboxUrl?t.skyboxUrl:void 0,a=t.skybox||(s?{url:s,rotation:t.skyboxRotation}:void 0),r=e.split("?")[0].split(".").pop()?.toLowerCase(),l="ply"===r||e.includes(".compressed.ply");let c=e;o?c=o:n?c=n:l||console.warn("[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:",r),console.log("[StorySplat Viewer] URL selection:",{original:e,lodMetaUrl:i,sogUrl:o,compressedPlyUrl:n,selected:c});const p=(t.waypoints||[]).map(t=>{let e=t.fov||60;e<3.5&&(e*=180/Math.PI);let o=t.info||t.description||"";if(!o&&t.interactions){const e=t.interactions.find(t=>"info"===t.type),n=function(t){if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.text?e.text:void 0}(e?.data);n&&(o=n)}return{position:t.position||{x:t.x||0,y:t.y||0,z:t.z||0},rotation:t.rotation||{x:0,y:0,z:0},fov:e,duration:t.duration||2e3,name:t.name||t.title||"",info:o,interactions:t.interactions||[],triggerDistance:t.triggerDistance??1}}),d=Array.isArray(t.particleSystems)&&t.particleSystems.length>0?t.particleSystems:t.particles||[];return{name:t.name||"StorySplat Scene",sceneId:t.sceneId,userId:t.userId,userName:t.userName||"Unknown",thumbnailUrl:t.thumbnailUrl,splatUrl:c,sogUrl:o,lodMetaUrl:i,fallbackUrls:[...t.fallbackUrls||[],o!==c?o:null,n!==c?n:null,e!==c?e:null].filter(t=>null!=t&&""!==t),scale:t.scale||(null!=t.splatScale?{x:t.splatScale,y:t.splatScale,z:t.splatScale}:{x:1,y:1,z:1}),splatPosition:t.splatPosition||t.position||t.cameraPosition||[0,0,0],splatRotation:t.splatRotation||t.rotation||t.cameraRotation||[0,0,0],invertXScale:t.invertXScale,invertYScale:t.invertYScale,waypoints:p,hotspots:t.hotspots||[],portals:t.portals||[],skybox:a,skyboxUrl:a?.url,skyboxRotation:a?.rotation,customMeshes:t.customMeshes||[],htmlMeshes:t.htmlMeshes||[],lights:t.lights||[],particles:d,collisionMeshesData:t.collisionMeshesData||[],playerHeight:t.playerHeight,uiColor:t.uiColor||"#ffffff",uiOptions:{showStartExperience:t.uiOptions?.showStartExperience??!0,showWatermark:t.uiOptions?.showWatermark??!0,hideFullscreenButton:t.uiOptions?.hideFullscreenButton??!1,hideInfoButton:t.uiOptions?.hideInfoButton??!1,hideMuteButton:t.uiOptions?.hideMuteButton??!1,hideHelpButton:t.uiOptions?.hideHelpButton??!1,hideNavigator:t.uiOptions?.hideNavigator??!1,showWaypointList:t.uiOptions?.showWaypointList??!0,hideWatermark:t.uiOptions?.hideWatermark??!1,watermarkText:t.uiOptions?.watermarkText,watermarkLink:t.uiOptions?.watermarkLink,buttonPosition:t.uiOptions?.buttonPosition||"inline",buttonLabels:t.uiOptions?.buttonLabels,customPreloaderLogoUrl:t.uiOptions?.customPreloaderLogoUrl,lazyLoad:t.uiOptions?.lazyLoad,lazyLoadButtonText:t.uiOptions?.lazyLoadButtonText,lazyLoadThumbnailUrl:t.uiOptions?.lazyLoadThumbnailUrl,lazyLoadThumbnailType:t.uiOptions?.lazyLoadThumbnailType,uiType:t.uiOptions?.uiType,debugMode:t.uiOptions?.debugMode},defaultCameraMode:t.defaultCameraMode||"orbit",allowedCameraModes:t.allowedCameraModes||["orbit","first-person","drone"],cameraMovementSpeed:t.cameraMovementSpeed,cameraRotationSensitivity:t.cameraRotationSensitivity,cameraDamping:t.cameraDamping,invertCameraRotation:t.invertCameraRotation,includeScrollControls:t.includeScrollControls??!0,scrollButtonMode:t.scrollButtonMode||"continuous",scrollAmount:t.scrollAmount||100,scrollSpeed:t.scrollSpeed,transitionSpeed:t.transitionSpeed,autoPlayEnabled:t.autoPlayEnabled??!1,autoplaySpeed:t.autoplaySpeed,loopMode:t.loopMode,includeXR:t.includeXR,xrMode:t.xrMode,customScript:t.customScript,templateType:t.uiOptions?.uiType||"minimal",additionalSplats:t.additionalSplats||[],keepMeshesInMemory:t.keepMeshesInMemory??!1,initialSplatExploreMode:t.initialSplatExploreMode,audioEmitters:t.audioEmitters||[],frameSequence:t.frameSequence}}function a(t){const e=t.waypoints?.[0]?.fov||60;return{splatUrl:t.splatUrl,sogUrl:t.sogUrl,lodMetaUrl:t.lodMetaUrl,fallbackUrls:t.fallbackUrls,scale:t.scale,position:t.splatPosition||[0,0,0],rotation:t.splatRotation||[0,0,0],invertXScale:t.invertXScale,invertYScale:t.invertYScale,waypoints:t.waypoints,hotspots:t.hotspots,portals:t.portals,skybox:t.skybox,skyboxUrl:t.skyboxUrl,skyboxRotation:t.skyboxRotation,customMeshes:t.customMeshes,htmlMeshes:t.htmlMeshes,lights:t.lights,particles:t.particles,collisionMeshesData:t.collisionMeshesData,cameraMode:t.defaultCameraMode,defaultCameraMode:t.defaultCameraMode,allowedCameraModes:t.allowedCameraModes,autoPlay:t.autoPlayEnabled,uiColor:t.uiColor,uiOptions:t.uiOptions,fov:e,nearClip:t.minClipPlane||.1,farClip:t.maxClipPlane||1e3,playerHeight:t.playerHeight||1.6,cameraMovementSpeed:t.cameraMovementSpeed||1,cameraRotationSensitivity:t.cameraRotationSensitivity||.2,cameraDamping:t.cameraDamping||.75,invertCameraRotation:t.invertCameraRotation,scrollSpeed:t.scrollSpeed,scrollAmount:t.scrollAmount,scrollButtonMode:t.scrollButtonMode,transitionSpeed:t.transitionSpeed,autoplaySpeed:t.autoplaySpeed,loopMode:t.loopMode,additionalSplats:t.additionalSplats,keepMeshesInMemory:t.keepMeshesInMemory??!1,initialSplatExploreMode:t.initialSplatExploreMode,includeXR:t.includeXR,xrMode:t.xrMode,customScript:t.customScript,audioEmitters:t.audioEmitters||[],frameSequence:t.frameSequence}}const r={tour:"Tour",explore:"Explore",hybrid:"Hybrid",walk:"Walk",orbit:"Orbit",fly:"Fly",next:"Next",previous:"Prev",startExperience:"Start Experience",fullscreen:"Fullscreen",mute:"Mute",unmute:"Unmute",waypoints:"Waypoints",close:"Close",yes:"Yes",cancel:"Cancel",switchScenes:"Switch scenes?",hotspotDefaultTitle:"Hotspot",openExternalLink:"Open External Link",vr:"VR",ar:"AR",exitVr:"Exit VR",exitAr:"Exit AR",loading:"Loading...",loadingScene:"Loading {name}...",helpTitle:"Controls & Help",helpCameraModes:"Camera Modes:",helpTourDesc:"Follow predefined path",helpExploreDesc:"Free movement",helpWalkDesc:"First-person walking",errorWebGLTitle:"Unable to Initialize 3D Graphics",errorWebGLMessage:"Your browser or device may not support WebGL. Please try a different browser or device.",percentageFormat:"{n}%"};function l(t,e){return t?.[e]||r[e]}function c(t="#4CAF50",e="minimal"){const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();const n=document.createElement("style");return n.id="storysplat-viewer-styles",n.textContent=function(t="#4CAF50",e="minimal"){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 /* Style isolation: reset common elements to prevent parent page styles from leaking in.\n Uses :where() for zero specificity so individual class rules always win. */\n .storysplat-viewer-container :where(button, a) {\n all: unset;\n box-sizing: border-box;\n display: inline-block;\n font-family: inherit;\n font-size: inherit;\n line-height: normal;\n cursor: pointer;\n }\n\n .storysplat-viewer-container button:focus-visible,\n .storysplat-viewer-container a:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\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: 100000;\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: ${t};\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: ${t};\n transition: width 0.3s ease-out;\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 text-align: center;\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 /* Explore Controls (Orbit/Fly toggle) */\n .storysplat-explore-controls {\n display: none;\n gap: 5px;\n width: 100%;\n justify-content: center;\n }\n\n .storysplat-explore-controls.visible {\n display: flex;\n }\n\n .storysplat-explore-btn {\n flex: 1;\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 6px 12px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-explore-btn.selected {\n background: ${t} !important;\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.5);\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 text-align: center;\n }\n\n .storysplat-mode-btn.selected {\n background: ${t} !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 /* XR (VR/AR) Buttons - Minimal */\n .storysplat-xr-btn {\n position: absolute;\n top: 10px;\n right: 45px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n z-index: 1000;\n color: white;\n font-size: 11px;\n font-weight: bold;\n text-transform: uppercase;\n transition: all 0.2s ease;\n display: none;\n }\n\n .storysplat-xr-btn.available {\n display: block;\n }\n\n .storysplat-xr-btn.active {\n background: ${t};\n }\n\n .storysplat-xr-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-xr-btn.active:hover {\n background: ${t};\n opacity: 0.9;\n }\n\n .storysplat-ar-btn {\n right: 85px;\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 styles */\n /* Uses position: absolute so popup stays within container when embedded */\n .storysplat-hotspot-popup {\n position: absolute;\n background-color: rgba(0, 0, 0, 0.75);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100001;\n box-shadow: 0 0 10px rgba(0,0,0,0.5);\n display: none;\n font-size: 14px;\n flex-direction: column;\n font-family: system-ui, -apple-system, sans-serif;\n /* Default centered positioning */\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 80%;\n max-height: 80%;\n overflow: auto;\n }\n\n .storysplat-hotspot-popup.visible {\n display: flex;\n }\n\n /* Fullscreen mode for click activation with image/iframe content */\n .storysplat-hotspot-popup.fullscreen {\n top: 20px !important;\n left: 20px !important;\n right: 20px !important;\n bottom: 20px !important;\n transform: none !important;\n width: auto !important;\n height: auto !important;\n max-width: none !important;\n max-height: none !important;\n margin: 0 !important;\n border-radius: 10px !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n flex-direction: column !important;\n padding: 0 !important;\n overflow: hidden !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: 18px;\n font-weight: 600;\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: 1.5;\n font-size: 14px;\n white-space: pre-wrap;\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup-content {\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-title {\n text-align: center;\n width: 100%;\n padding: 20px 20px 10px 20px;\n padding-right: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n width: 100%;\n max-width: none;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n\n .storysplat-hotspot-popup.fullscreen p {\n text-align: center;\n max-width: 80%;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-link {\n margin: 4px auto;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-close {\n display: block;\n margin: 4px auto 20px auto;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup img {\n max-width: 100%;\n height: auto;\n border-radius: 5px;\n margin-bottom: 10px;\n display: block;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n width: auto !important;\n height: auto !important;\n max-width: 90% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n margin: 10px 0;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup iframe {\n width: 100%;\n max-width: 100%;\n aspect-ratio: 16 / 9;\n height: auto;\n border: none;\n border-radius: 5px;\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 90% !important;\n max-width: 800px !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 100% !important;\n margin: 10px 0;\n border-radius: 8px;\n flex-shrink: 1;\n min-height: 0;\n }\n\n /* Portal confirmation popup */\n .storysplat-portal-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100002;\n display: none;\n flex-direction: column;\n gap: 15px;\n min-width: 240px;\n max-width: 80%;\n text-align: center;\n box-shadow: 0 0 12px rgba(0, 0, 0, 0.6);\n }\n\n .storysplat-portal-popup.visible {\n display: flex;\n }\n\n .storysplat-portal-popup-title {\n font-size: 16px;\n font-weight: 600;\n }\n\n .storysplat-portal-popup-actions {\n display: flex;\n justify-content: center;\n gap: 10px;\n }\n\n .storysplat-portal-popup-btn {\n padding: 8px 14px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n }\n\n .storysplat-portal-popup-confirm {\n background: ${t};\n color: white;\n }\n\n .storysplat-portal-popup-cancel {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n }\n\n /* Video container - matches HTML export sizing */\n .storysplat-hotspot-popup .video-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 300px;\n max-width: 100%;\n height: auto;\n margin: 10px 0;\n }\n\n .storysplat-hotspot-popup video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 5px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n }\n\n /* Fullscreen popup video - constrained so title+description+buttons always fit */\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 90% !important;\n max-width: 800px !important;\n max-height: 100% !important;\n height: auto !important;\n margin: 10px 0 !important;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen video {\n max-width: 100% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n }\n\n /* Close button - matching BabylonJS export (green button at bottom) */\n .storysplat-hotspot-popup-close {\n display: inline-block;\n width: auto;\n padding: 10px 20px;\n background-color: #4CAF50;\n border: none;\n color: white;\n cursor: pointer;\n border-radius: 5px;\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: system-ui, -apple-system, sans-serif;\n text-align: center;\n transition: background-color 0.2s;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup-close:hover {\n background-color: #45a049;\n }\n\n /* Error Popup - for outdated/broken scenes */\n .storysplat-error-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.95);\n border-radius: 12px;\n padding: 30px 40px;\n max-width: 450px;\n width: 90%;\n text-align: center;\n z-index: 10001;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 100, 100, 0.3);\n }\n\n .storysplat-error-popup-icon {\n font-size: 48px;\n margin-bottom: 15px;\n }\n\n .storysplat-error-popup-title {\n color: #ff6b6b;\n font-size: 20px;\n font-weight: 600;\n margin: 0 0 15px 0;\n }\n\n .storysplat-error-popup-message {\n color: #ccc;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 20px 0;\n }\n\n .storysplat-error-popup-action {\n display: inline-block;\n padding: 12px 24px;\n background: linear-gradient(135deg, #4CAF50, #45a049);\n color: white;\n text-decoration: none;\n border-radius: 6px;\n font-weight: 500;\n font-size: 14px;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .storysplat-error-popup-action:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);\n }\n\n .storysplat-hotspot-popup-link {\n display: block;\n padding: 8px 16px;\n background: #007bff;\n color: white;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n font-weight: 500;\n margin: 0 0 4px 0;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .storysplat-hotspot-popup-link:hover {\n background: #0056b3;\n }\n\n /* Mobile Responsive */\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen {\n top: 10px !important;\n left: 10px !important;\n right: 10px !important;\n bottom: 10px !important;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n padding: 10px 15px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 98% !important;\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 98% !important;\n max-width: 98% !important;\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 will-change: transform;\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 /* Look Zone Active State */\n .storysplat-look-zone.active .storysplat-look-zone-icon {\n background: rgba(255, 255, 255, 0.25);\n border-color: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-look-zone.active .storysplat-look-zone-icon svg {\n fill: rgba(255, 255, 255, 0.8);\n }\n\n /* Lazy Load Container */\n .storysplat-lazy-load-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #1a1a1a;\n z-index: 10001;\n }\n\n .storysplat-lazy-load-thumbnail {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-thumbnail-video {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.3) 0%,\n rgba(0, 0, 0, 0.5) 50%,\n rgba(0, 0, 0, 0.7) 100%\n );\n }\n\n .storysplat-lazy-load-content {\n position: relative;\n z-index: 1;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n }\n\n .storysplat-lazy-load-start-btn {\n padding: 16px 32px;\n background: ${t};\n border: none;\n border-radius: 50px;\n color: white;\n font-size: 18px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n transition: all 0.3s ease;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n }\n\n .storysplat-lazy-load-start-btn:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 30px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-lazy-load-start-btn:active {\n transform: scale(0.98);\n }\n\n .storysplat-lazy-load-start-btn svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n @media (max-width: 768px) {\n .storysplat-lazy-load-start-btn {\n padding: 12px 24px;\n font-size: 16px;\n }\n }\n\n /* Waypoint List Dropdown - Glassmorphic style */\n .storysplat-waypoint-list-container {\n position: absolute;\n top: 10px;\n right: 45px;\n z-index: 1000;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n transition: background-color 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-waypoint-list-toggle:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: white;\n transition: transform 0.3s ease;\n }\n\n .storysplat-waypoint-list-toggle.open svg {\n transform: rotate(180deg);\n }\n\n .storysplat-waypoint-list-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n min-width: 200px;\n max-height: 300px;\n overflow-y: auto;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n display: none;\n flex-direction: column;\n }\n\n .storysplat-waypoint-list-dropdown.open {\n display: flex;\n }\n\n .storysplat-waypoint-item {\n padding: 12px 16px;\n color: white;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n transition: background-color 0.2s ease;\n font-size: 14px;\n }\n\n .storysplat-waypoint-item:last-child {\n border-bottom: none;\n }\n\n .storysplat-waypoint-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-waypoint-item.active {\n background: rgba(76, 175, 80, 0.3);\n }\n\n /* Adjust position when fullscreen button is present */\n .storysplat-waypoint-list-container.with-fullscreen {\n right: 45px;\n }\n\n .storysplat-waypoint-list-container.no-fullscreen {\n right: 10px;\n }\n\n @media (max-width: 768px) {\n .storysplat-waypoint-list-container {\n right: 40px;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 6px 12px;\n font-size: 12px;\n }\n\n .storysplat-waypoint-list-dropdown {\n min-width: 160px;\n max-height: 250px;\n }\n\n .storysplat-waypoint-item {\n padding: 10px 12px;\n font-size: 12px;\n }\n }\n\n /* Watermark - matches BabylonJS HTML export */\n .storysplat-watermark {\n position: absolute;\n bottom: 10px;\n right: 10px;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-size: 12px;\n z-index: 1000;\n pointer-events: auto; /* Allow pointer events for the entire watermark so links work */\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n }\n\n .storysplat-watermark a {\n color: ${t};\n text-decoration: none;\n cursor: pointer;\n }\n\n .storysplat-watermark a:hover {\n text-decoration: underline;\n }\n\n .storysplat-fps-counter {\n position: absolute;\n top: 8px;\n left: 8px;\n background: rgba(0, 0, 0, 0.6);\n color: #0f0;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-family: monospace;\n z-index: 1000;\n pointer-events: none;\n line-height: 1;\n }\n `+function(t,e){return"standard"===e?`\n /* ===== STANDARD (Centered) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: auto;\n max-width: 350px;\n padding: 15px 20px;\n background: rgba(0, 0, 0, 0.7);\n border-radius: 10px;\n bottom: 20px;\n gap: 8px;\n }\n\n .storysplat-scroll-content {\n gap: 8px;\n }\n\n .storysplat-progress-container {\n max-width: 300px;\n height: 10px;\n border-radius: 5px;\n }\n\n .storysplat-progress-bar {\n border-radius: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 16px;\n }\n\n .storysplat-scroll-buttons {\n gap: 8px;\n }\n\n .storysplat-btn {\n background: ${t};\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n }\n\n .storysplat-btn:hover {\n opacity: 0.85;\n background: ${t};\n }\n\n .storysplat-btn-play {\n padding: 10px 12px;\n background: ${t};\n border-radius: 5px;\n }\n\n .storysplat-btn-play svg {\n width: 16px;\n height: 16px;\n }\n\n .storysplat-btn-play:hover {\n opacity: 0.85;\n background: ${t};\n }\n\n .storysplat-mode-container {\n margin-top: 8px;\n }\n\n .storysplat-mode-toggle {\n gap: 8px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 14px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-btn {\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n max-width: 280px;\n padding: 12px 15px;\n }\n\n .storysplat-btn {\n padding: 8px 16px;\n font-size: 14px;\n }\n\n .storysplat-progress-container {\n height: 8px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n }\n }\n `:"pro"===e?"\n /* ===== PRO (Dark) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: 80%;\n max-width: 600px;\n padding: 12px 20px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 8px;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n gap: 6px;\n }\n\n .storysplat-scroll-content {\n gap: 6px;\n }\n\n .storysplat-progress-container {\n max-width: 100%;\n height: 2px;\n border-radius: 1px;\n }\n\n .storysplat-progress-bar {\n border-radius: 1px;\n }\n\n .storysplat-progress-text {\n font-size: 13px;\n opacity: 0.8;\n }\n\n .storysplat-scroll-buttons {\n gap: 6px;\n }\n\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.5);\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-btn-play {\n padding: 8px 10px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn-play svg {\n width: 14px;\n height: 14px;\n }\n\n .storysplat-btn-play:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n /* Pro: mode toggle on the left side, vertical stack */\n .storysplat-mode-container {\n position: absolute;\n bottom: 20px;\n left: 20px;\n margin-top: 0;\n z-index: 1000;\n }\n\n .storysplat-mode-toggle {\n flex-direction: column;\n gap: 4px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 12px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-controls {\n flex-direction: column;\n }\n\n .storysplat-explore-btn {\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n width: 70%;\n max-width: none;\n padding: 10px 15px;\n bottom: 10px;\n }\n\n .storysplat-mode-container {\n bottom: 10px;\n left: 10px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n ":""}(t,e)}(t,e),document.head.appendChild(n),n}function p(t,e,o){const n=document.createElement("div");n.className="storysplat-preloader";const i=!!e;return n.innerHTML=`\n <div class="storysplat-preloader-content">\n <div class="storysplat-preloader-media">\n ${i?`<img class="storysplat-preloader-image" src="${e}" 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 ${i?"":'<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">${l(o,"loading")} 0%</div>\n <div class="storysplat-preloader-bar"></div>\n </div>\n </div>\n `,t.appendChild(n),i||new Promise(t=>{if(customElements.get("lottie-player"))return void t();const e=document.querySelector('script[src*="lottie-player"]');if(e)return void e.addEventListener("load",()=>t());const o=document.createElement("script");o.src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js",o.onload=()=>t(),document.head.appendChild(o)}),n}function d(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function h(t,e,o="tour",n){const i=t.scrollControls?.querySelector(".storysplat-btn-prev"),s=t.scrollControls?.querySelector(".storysplat-btn-next"),a=t.scrollControls?.querySelector(".storysplat-btn-play");if(i&&i.addEventListener("click",()=>e.prevWaypoint()),s&&s.addEventListener("click",()=>e.nextWaypoint()),a){const t=t=>{a.innerHTML=t?'<svg viewBox="0 0 24 24"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg>':'<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>'};a.addEventListener("click",()=>{e.isPlaying()?e.pause():e.play()}),e.on("playbackStart",()=>t(!0)),e.on("playbackStop",()=>t(!1)),t(e.isPlaying())}if(t.helpButton&&t.helpPanel&&t.helpButton.addEventListener("click",()=>{t.helpPanel.classList.toggle("visible")}),t.fullscreenButton){if(/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1)t.fullscreenButton.style.display="none",console.log("[StorySplat Viewer] Fullscreen button hidden on iOS (API not supported)");else{const e=t.fullscreenButton.parentElement;t.fullscreenButton.addEventListener("click",()=>{const o=document;if(o.fullscreenElement||o.webkitFullscreenElement){o.exitFullscreen?o.exitFullscreen():o.webkitExitFullscreen&&o.webkitExitFullscreen();const e=t.fullscreenButton.querySelector(".storysplat-expand-icon"),n=t.fullscreenButton.querySelector(".storysplat-compress-icon");e&&(e.style.display="block"),n&&(n.style.display="none")}else{e?.requestFullscreen?e.requestFullscreen():e?.webkitRequestFullscreen&&e.webkitRequestFullscreen?.();const o=t.fullscreenButton.querySelector(".storysplat-expand-icon"),n=t.fullscreenButton.querySelector(".storysplat-compress-icon");o&&(o.style.display="none"),n&&(n.style.display="block")}});const o=()=>{const e=document,o=e.fullscreenElement||e.webkitFullscreenElement,n=t.fullscreenButton.querySelector(".storysplat-expand-icon"),i=t.fullscreenButton.querySelector(".storysplat-compress-icon");n&&(n.style.display=o?"none":"block"),i&&(i.style.display=o?"block":"none")};document.addEventListener("fullscreenchange",o),document.addEventListener("webkitfullscreenchange",o)}}const l=e=>{const o=t.scrollControls?.querySelector(".storysplat-progress-text"),n=t.scrollControls?.querySelector(".storysplat-progress-container"),i=t.scrollControls?.querySelector(".storysplat-scroll-buttons"),s=e?"":"none";o&&(o.style.display=s),n&&(n.style.display=s),i&&(i.style.display=s)},c=e=>{const o=t.scrollControls?.querySelector(".storysplat-explore-controls");o&&(e?o.classList.add("visible"):o.classList.remove("visible"))};if(l("tour"===o),c("explore"===o),e.setCameraMode){const o=t.scrollControls?.parentElement||t.scrollControls,n=o?.querySelectorAll(".storysplat-mode-btn");n?.forEach(t=>{t.addEventListener("click",()=>{const o=t.getAttribute("data-mode");o&&e.setCameraMode&&(e.setCameraMode(o),n.forEach(t=>t.classList.remove("selected")),t.classList.add("selected"),l("tour"===o),c("explore"===o))})}),e.on("modeChange",({mode:t})=>{l("tour"===t),c("explore"===t),n?.forEach(e=>{const o=e.getAttribute("data-mode");e.classList.toggle("selected",o===t)})})}let p=0,d=-1;e.on("progressUpdate",({progress:e})=>{const o=100*Math.max(0,Math.min(1,e)),i=Math.round(o),s=performance.now();var a,l;s-p<33||(p=s,t.progressBar&&(t.progressBar.style.width=`${o}%`),t.progressText&&i!==d&&(d=i,t.progressText.innerHTML="",t.progressText.textContent=(a=n,l=i,(a?.percentageFormat||r.percentageFormat).replace("{n}",String(l)))))});let h=-1;e.on("waypointChange",({index:o,waypoint:n})=>{if(o===h)return;if(h=o,!t.waypointInfo)return;const i=t.waypointInfo.querySelector(".storysplat-waypoint-title"),s=t.waypointInfo.querySelector(".storysplat-waypoint-description");if(!i||!s)return;const a=n||e.getWaypoints?.()[o];a&&(a.name||a.info)?(i.textContent=a.name||"",s.textContent=a.info||"",a.name||a.info?t.waypointInfo.classList.add("hasContent"):t.waypointInfo.classList.remove("hasContent")):(i.textContent="",s.textContent="",t.waypointInfo.classList.remove("hasContent"))})}function u(t,e,o){const n=t.querySelector(".storysplat-hotspot-popup");if(!n)return;const i=n.querySelector(".storysplat-hotspot-popup-title"),s=n.querySelector(".storysplat-hotspot-popup-content"),a=n.querySelector(".storysplat-hotspot-popup-close");n.style.cssText="",n.classList.remove("fullscreen");const r=e.activationMode||"click",c=e.photoUrl||e.popupVideoUrl||"iframe"===e.contentType&&e.iframeUrl;"click"===r&&c&&n.classList.add("fullscreen");const p=e.backgroundAlpha??.75;if(e.backgroundColor){const t=e.backgroundColor.replace("#","");if(6===t.length){const e=parseInt(t.substring(0,2),16),o=parseInt(t.substring(2,4),16),i=parseInt(t.substring(4,6),16);n.style.backgroundColor=`rgba(${e}, ${o}, ${i}, ${p})`}else n.style.backgroundColor=e.backgroundColor}else n.style.backgroundColor=`rgba(0, 0, 0, ${p})`;e.textColor&&(n.style.color=e.textColor,i&&(i.style.color=e.textColor)),e.fontFamily&&(n.style.fontFamily=e.fontFamily),e.fontSize&&(n.style.fontSize=`${e.fontSize}px`),a&&e.closeButtonColor&&(a.style.backgroundColor=e.closeButtonColor),i&&(i.textContent=e.title||l(o,"hotspotDefaultTitle"));let d="";if("iframe"===e.contentType&&e.iframeUrl&&(d+=`<iframe src="${e.iframeUrl}" title="${e.title||"Embedded content"}"></iframe>`),e.popupVideoUrl&&(d+=`<div class="video-container"><video src="${e.popupVideoUrl}" controls playsinline webkit-playsinline preload="metadata"></video></div>`),e.photoUrl&&(d+=`<img src="${e.photoUrl}" alt="${e.title||"Hotspot image"}" />`),e.information&&(d+=`<p>${e.information}</p>`),e.externalLinkUrl){const t=e.externalLinkButtonColor||"#007bff";d+=`\n <div onclick="window.open('${e.externalLinkUrl}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${e.externalLinkText||l(o,"openExternalLink")}\n </div>\n `}s&&(s.innerHTML=d),n.classList.add("visible")}function m(t,e){t.joystick&&(e?(t.joystick.classList.add("visible"),t.joystickThumb&&(t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)")):t.joystick.classList.remove("visible")),t.lookZone&&(e?t.lookZone.classList.add("visible"):t.lookZone.classList.remove("visible"))}function y(t,e,o,n,i){if(t.joystickThumb)if(e){t.joystickThumb.classList.add("active");const e=Math.sqrt(o*o+n*n),s=Math.min(e,i),a=e>0?s/e:0,r=o*a,l=n*a;t.joystickThumb.style.transform=`translate(${r}px, ${l}px)`}else t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)"}function g(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}function f(t,e){if(!t.waypointListContainer)return;t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item").forEach((t,o)=>{o===e?t.classList.add("active"):t.classList.remove("active")})}const v=new t.Vec3,b=new t.Vec3,x=new t.Pose,w=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),S=(t,e,o)=>{const n=Math.sqrt(t[0]*t[0]+t[1]*t[1]);if(n<e)return void t.fill(0);const i=(n-e)/(o-e);t[0]*=i/n,t[1]*=i/n},E=(e,o,n,i,s=new t.Vec3)=>{const{fov:a,aspectRatio:r,horizontalFov:l,projection:c,orthoHeight:p}=e,d=e.system?.app,{width:h,height:u}=d?.graphicsDevice?.clientRect||{width:1920,height:1080};s.set(-o/h*2,n/u*2,0);const m=b.set(0,0,0);if(c===t.PROJECTION_PERSPECTIVE){const e=i*Math.tan(.5*a*t.math.DEG_TO_RAD);l?m.set(e,e/r,0):m.set(e*r,e,0)}else m.set(p*r,p,0);return s.mul(m),s};class C{constructor(e,o,n={}){this.enabled=!0,this._mode="orbit",this._enableOrbit=!0,this._enableFly=!0,this.enablePan=!0,this._pose=new t.Pose,this._preFocusMode="orbit",this._startZoomDist=0,this._pitchRange=new t.Vec2(-89,89),this._yawRange=new t.Vec2(-1/0,1/0),this._lastFocusPoint=new t.Vec3(0,0,0),this._zoomRange=new t.Vec2(.01,1/0),this._state={axis:new t.Vec3,shift:0,ctrl:0,mouse:[0,0,0],touches:0},this.moveSpeed=25,this.moveFastSpeed=50,this.moveSlowSpeed=10,this.rotateSpeed=.05,this.rotateTouchSens=1,this.rotateJoystickSens=1,this.zoomSpeed=.001,this.zoomPinchSens=5,this.keyboardSpeedMultiplier=1.5,this.gamepadDeadZone=new t.Vec2(.3,.6),this.invertRotation=!1,this.joystickEventName="joystick",this._collisionEntities=[],this._collisionRadius=.3,this._prevPosition=new t.Vec3,this._prevPositionValid=!1,this._collisionTestVec=new t.Vec3,this._destroyHandler=null,this.camera=e,this.app=o;const i=e.camera;if(!i)throw new Error("CameraControls: camera component not found on entity");this.cameraComponent=i,this._flyController=new t.FlyController,this._orbitController=new t.OrbitController,this._focusController=new t.FocusController,this._flyController.moveDamping=.75,this._flyController.rotateDamping=.75,this._orbitController.rotateDamping=.75,this._orbitController.zoomDamping=.8,this._orbitController.zoomRange=new t.Vec2(.01,1/0);const s=o.graphicsDevice.canvas;this._desktopInput=new t.KeyboardMouseSource,this._orbitMobileInput=new t.MultiTouchSource,this._flyMobileInput=new t.DualGestureSource,this._gamepadInput=new t.GamepadSource,this._desktopInput.attach(s),this._orbitMobileInput.attach(s),this._flyMobileInput.attach(s),this._gamepadInput.attach(s),this._flyMobileInput.on("joystick:position:left",([t,e,o,n])=>{(t<0||"fly"===this._mode)&&this.app.fire(`${this.joystickEventName}:left`,t,e,o,n)}),this._flyMobileInput.on("joystick:position:right",([t,e,o,n])=>{(t<0||"fly"===this._mode)&&this.app.fire(`${this.joystickEventName}:right`,t,e,o,n)}),this._pose.look(this.camera.getPosition(),t.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.rotateTouchSens&&(this.rotateTouchSens=n.rotateTouchSens),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),void 0!==n.invertRotation&&(this.invertRotation=n.invertRotation)}set enableFly(t){this._enableFly=t,this._enableFly||"fly"!==this._mode||this._setMode("orbit")}get enableFly(){return this._enableFly}set enableOrbit(t){this._enableOrbit=t,this._enableOrbit||"orbit"!==this._mode||this._setMode("fly")}get enableOrbit(){return this._enableOrbit}set focusDamping(t){this._focusController.focusDamping=t}get focusDamping(){return this._focusController.focusDamping}set moveDamping(t){this._flyController.moveDamping=t}get moveDamping(){return this._flyController.moveDamping}set rotateDamping(t){this._flyController.rotateDamping=t,this._orbitController.rotateDamping=t}get rotateDamping(){return this._orbitController.rotateDamping}set zoomDamping(t){this._orbitController.zoomDamping=t}get zoomDamping(){return this._orbitController.zoomDamping}set focusPoint(t){const e=this.camera.getPosition();this._startZoomDist=e.distance(t),this._controller.attach(this._pose.look(e,t),!1)}get focusPoint(){return this._pose.getFocus(v)}set pitchRange(t){this._pitchRange.copy(t),this._flyController.pitchRange=this._pitchRange,this._orbitController.pitchRange=this._pitchRange}get pitchRange(){return this._pitchRange}set yawRange(e){this._yawRange.x=t.math.clamp(e.x,-360,360),this._yawRange.y=t.math.clamp(e.y,-360,360),this._flyController.yawRange=this._yawRange,this._orbitController.yawRange=this._yawRange}get yawRange(){return this._yawRange}set zoomRange(t){this._zoomRange.x=t.x,this._zoomRange.y=t.y<=t.x?1/0:t.y,this._orbitController.zoomRange=this._zoomRange}get zoomRange(){return this._zoomRange}set mobileInputLayout(t){/(?:joystick|touch)-(?:joystick|touch)/.test(t)?this._flyMobileInput.layout=t:console.warn(`CameraControls: invalid mobile input layout: ${t}`)}get mobileInputLayout(){return this._flyMobileInput.layout}get mode(){return this._mode}_setMode(t){if(this._enableFly&&!this._enableOrbit)t="fly";else if(!this._enableFly&&this._enableOrbit)t="orbit";else if(!this._enableFly&&!this._enableOrbit)return void console.warn("CameraControls: both fly and orbit modes are disabled");const e=this._mode;if(e!==t){switch(this._mode=t,this._controller&&this._controller.detach(),this._mode){case"orbit":if(this._controller=this._orbitController,"focus"===e){const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint)}this._lastYaw=this._pose.angles.y;break;case"fly":if(this._controller=this._flyController,"focus"===e){const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint)}break;case"focus":this._controller=this._focusController}this._controller.attach(this._pose,!1),this.app.fire("cameracontrols:modechange",this._mode)}}setMode(t){this._setMode(t)}focus(t,e=!1){this._lastFocusPoint.copy(t),"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const o=e?this._startZoomDist:this.camera.getPosition().distance(t);this._startZoomDist=o;const n=v.copy(this.camera.forward).mulScalar(-o).add(t);this._controller.attach(x.look(n,t))}look(t,e=!1){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const o=e?v.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(x.look(o,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(x.look(e,t))}syncFromCamera(t){const e=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const o=e.distance(t);this._startZoomDist=o,this._pose.distance=o,this._pose.look(e,t);let n=this._pose.angles.y;for(;n>180;)n-=360;for(;n<-180;)n+=360;this._pose.angles.y=n,this._lastYaw=n,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),this._pose.angles.z=0}else{const t=this.camera.getEulerAngles();this._pose.position.copy(e),this._pose.angles.x=t.x,this._pose.angles.y=t.y,this._pose.angles.z=0;let o=this._pose.angles.y;for(;o>180;)o-=360;for(;o<-180;)o+=360;this._pose.angles.y=o,this._lastYaw=o,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x));const n=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(n.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,!1)}syncFromPose(e,o,n){this._prevPositionValid=!1,this.camera.setPosition(e),this.camera.setRotation(o);const i=new t.Entity;i.setRotation(o);const s=i.getEulerAngles();this._pose.position.copy(e),this._pose.angles.x=s.x,this._pose.angles.y=s.y,this._pose.angles.z=0;let a=this._pose.angles.y;for(;a>180;)a-=360;for(;a<-180;)a+=360;if(this._pose.angles.y=a,this._lastYaw=a,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),n)this._lastFocusPoint.copy(n);else{const t=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(t.mulScalar(10))}const r=e.distance(this._lastFocusPoint);this._startZoomDist=r,this._pose.distance=r,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(e){if(!this.enabled)return;const{keyCode:o}=t.KeyboardMouseSource,{key:n,button:i,mouse:s,wheel:a}=this._desktopInput.read(),{touch:r,pinch:l,count:c}=this._orbitMobileInput.read(),{leftInput:p,rightInput:d}=this._flyMobileInput.read(),{leftStick:h,rightStick:u}=this._gamepadInput.read();S(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),S(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(v.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 t=0;t<this._state.mouse.length;t++)this._state.mouse[t]+=i[t];this._state.shift+=n[o.SHIFT],this._state.ctrl+=n[o.CTRL],this._state.touches+=c[0];const m=+("orbit"===this._mode),y=+("fly"===this._mode),g=+(this._state.touches>1),f=+(this._state.shift||this._state.mouse[1]),x=+this._flyMobileInput.layout.endsWith("joystick"),C=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,M=60*this.zoomSpeed*e,_=M*this.zoomPinchSens,L=60*this.rotateSpeed*e,k=L*this.rotateTouchSens,A=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:P}=w,T=v.set(0,0,0),z=this._state.axis.clone().normalize();T.add(z.mulScalar(y*C*this.keyboardSpeedMultiplier));const R=E(this.cameraComponent,s[0],s[1],this._pose.distance);T.add(R.mulScalar(m*f*+this.enablePan));const I=b.set(0,0,a[0]);T.add(I.mulScalar(m*M)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const D=b.set(s[0],s[1],0);T.add(D.mulScalar((1-m*f)*L)),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),T.set(0,0,0);const F=b.set(p[0],0,-p[1]);T.add(F.mulScalar(y*C));const U=E(this.cameraComponent,r[0],r[1],this._pose.distance);T.add(U.mulScalar(m*g*+this.enablePan));const $=b.set(0,0,l[0]);T.add($.mulScalar(m*g*_)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const V=b.set(r[0],r[1],0);T.add(V.mulScalar(m*(1-g)*k));const B=b.set(d[0],d[1],0);T.add(B.mulScalar(y*(x?A:k))),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),T.set(0,0,0);const O=b.set(h[0],0,-h[1]);T.add(O.mulScalar(y*C)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const H=b.set(u[0],u[1],0);if(T.add(H.mulScalar(y*A)),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),this.app.xr?.active)w.read();else{if("focus"===this._mode){const t=P.move.length()+P.rotate.length()>0,e=this._focusController.complete?.()??!1;(t||e)&&this._setMode(this._preFocusMode)}if(this._pose.copy(this._controller.update(w,e)),("fly"===this._mode||"orbit"===this._mode)&&this._collisionEntities.length>0){if(this._prevPositionValid){const t=this._pose.position,e=this._prevPosition,o=this._collisionTestVec;let n=!1;o.set(t.x,e.y,e.z),this.checkCollision(o)&&(t.x=e.x,n=!0),o.set(t.x,t.y,e.z),this.checkCollision(o)&&(t.y=e.y,n=!0),o.set(t.x,t.y,t.z),this.checkCollision(o)&&(t.z=e.z,n=!0),n&&this._controller.attach(this._pose,!1)}this._prevPosition.copy(this._pose.position),this._prevPositionValid=!0}else"fly"!==this._mode&&"orbit"!==this._mode&&(this._prevPositionValid=!1);if("orbit"===this._mode){let t=this._pose.angles.y;for(;t>180;)t-=360;for(;t<-180;)t+=360;if(void 0!==this._lastYaw){const e=t-this._lastYaw;e>180?t-=360:e<-180&&(t+=360)}this._pose.angles.y=t,this._lastYaw=t}this.camera.setPosition(this._pose.position),this.camera.setEulerAngles(this._pose.angles)}}setCollisionEntities(t,e){this._collisionEntities=t,void 0!==e&&(this._collisionRadius=e),this._prevPositionValid=!1}checkCollision(t){const e=this._collisionRadius;for(const o of this._collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):"floor"===s||"plane"===s?(c=n.x,p=n.y,d=n.z,a=i.x/2,r=.05,l=i.z/2):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=Math.abs(t.x-c),m=Math.abs(t.y-p),y=Math.abs(t.z-d);if(u<a+e&&m<r+e&&y<l+e)return!0}return!1}destroy(){this._desktopInput.destroy(),this._orbitMobileInput.destroy(),this._flyMobileInput.destroy(),this._gamepadInput.destroy(),this._flyController.destroy(),this._orbitController.destroy()}}function M(t,e){return t?Array.isArray(t)?[t[0]??e[0],t[1]??e[1],t[2]??e[2]]:[t.x??e[0],t.y??e[1],t.z??e[2]]:e}class _{constructor(e,o,n={}){this.enabled=!1,this.velocity=new t.Vec3,this.isGrounded=!1,this.yaw=0,this.pitch=0,this.keys={},this.mouseLocked=!1,this.moveSpeed=8,this.sprintMultiplier=2,this.lookSensitivity=.002,this.playerHeight=1.6,this.gravity=20,this.maxFallSpeed=50,this.jumpVelocity=8,this.collisionRadius=.3,this.stepHeight=.3,this.groundCheckDistance=.1,this.moveDamping=.9,this.horizontalVelocity=new t.Vec3,this.targetVelocity=new t.Vec3,this.collisionEntities=[],this.floorEntity=null,this.keydownHandler=null,this.keyupHandler=null,this.mousemoveHandler=null,this.clickHandler=null,this.pointerlockchangeHandler=null,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.forward=new t.Vec3,this.right=new t.Vec3,this.camera=e,this.app=o,void 0!==n.moveSpeed&&(this.moveSpeed=n.moveSpeed),void 0!==n.sprintMultiplier&&(this.sprintMultiplier=n.sprintMultiplier),void 0!==n.lookSensitivity&&(this.lookSensitivity=n.lookSensitivity),void 0!==n.playerHeight&&(this.playerHeight=n.playerHeight),void 0!==n.gravity&&(this.gravity=n.gravity),void 0!==n.maxFallSpeed&&(this.maxFallSpeed=n.maxFallSpeed),void 0!==n.jumpVelocity&&(this.jumpVelocity=n.jumpVelocity),void 0!==n.collisionRadius&&(this.collisionRadius=n.collisionRadius),void 0!==n.stepHeight&&(this.stepHeight=n.stepHeight),void 0!==n.groundCheckDistance&&(this.groundCheckDistance=n.groundCheckDistance),void 0!==n.moveDamping&&(this.moveDamping=n.moveDamping);const i=this.camera.getEulerAngles();this.pitch=i.x,this.yaw=i.y}async createCollisionMeshes(e){if(!e||0===e.length)return;console.log("[CharacterController] Creating collision meshes:",e.length);const o=[];e.forEach((e,n)=>{if("custom"===e.meshType&&e.customMeshUrl){const t=this.loadCustomCollisionMesh(e,n);return void o.push(t)}let i=null;switch(e.meshType){case"cube":i=new t.Entity(`collision-cube-${n}`),i.addComponent("render",{type:"box"});break;case"sphere":i=new t.Entity(`collision-sphere-${n}`),i.addComponent("render",{type:"sphere"});break;case"floor":i=new t.Entity(`collision-floor-${n}`),i.addComponent("render",{type:"plane"}),this.floorEntity=i;break;default:i=new t.Entity(`collision-plane-${n}`),i.addComponent("render",{type:"plane"})}i&&this.configureCollisionEntity(i,e)}),o.length>0&&await Promise.all(o),console.log("[CharacterController] Created",this.collisionEntities.length,"collision entities")}async loadCustomCollisionMesh(e,o){const n=e.customMeshUrl;if(n){console.log("[CharacterController] Loading custom collision mesh:",n);try{const i=n.split("?")[0].split(".").pop()?.toLowerCase()||"glb",s="gltf"===i||"glb"===i?"container":"model",a=new t.Asset(`collision-custom-${o}`,s,{url:n});await new Promise((i,r)=>{a.ready(()=>{try{const n=new t.Entity(`collision-custom-${o}`);if("container"===s){const t=a.resource;if(t&&t.instantiateRenderEntity){const e=t.instantiateRenderEntity();for(;e.children.length>0;)n.addChild(e.children[0]);e.destroy()}}else n.addComponent("model",{asset:a});this.configureCollisionEntity(n,e),this.computeAndStoreBounds(n),i()}catch(t){console.error("[CharacterController] Error setting up custom mesh:",t),r(t)}}),a.on("error",t=>{console.error("[CharacterController] Error loading custom mesh:",n,t),r(t)}),this.app.assets.add(a),this.app.assets.load(a)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",n,t)}}}computeAndStoreBounds(e){const o=new t.BoundingBox;let n=!1;const i=e=>{if(e.render&&e.render.meshInstances)for(const t of e.render.meshInstances)t.aabb&&(n?o.add(t.aabb):(o.copy(t.aabb),n=!0));for(const o of e.children)o instanceof t.Entity&&i(o)};i(e),n&&(e._collisionBounds=o)}configureCollisionEntity(t,e){const o=M(e.position,[0,0,0]);t.setPosition(o[0],o[1],-o[2]);const n=M(e.rotation,[0,0,0]);t.setEulerAngles(n[0]*(180/Math.PI),n[1]*(180/Math.PI),-n[2]*(180/Math.PI));const i=M(e.scaling,[1,1,1]);t.setLocalScale(i[0],i[1],i[2]),this.setEntityVisibility(t,!1),t._collisionMeshType=e.meshType,this.app.root.addChild(t),this.collisionEntities.push(t)}setEntityVisibility(e,o){e.render&&(e.render.enabled=o);for(const n of e.children)n instanceof t.Entity&&this.setEntityVisibility(n,o)}enable(){if(this.enabled)return;this.enabled=!0;const t=this.camera.getEulerAngles();this.pitch=t.x,this.yaw=t.y,this.velocity.set(0,0,0),this.horizontalVelocity.set(0,0,0),this.targetVelocity.set(0,0,0),this.setupInputHandlers(),console.log("[CharacterController] Enabled")}disable(){this.enabled&&(this.enabled=!1,this.removeInputHandlers(),document.pointerLockElement&&document.exitPointerLock(),console.log("[CharacterController] Disabled"))}setupInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler=t=>{this.keys[t.code]=!0,"Space"===t.code&&this.isGrounded&&(this.velocity.y=this.jumpVelocity,this.isGrounded=!1)},this.keyupHandler=t=>{this.keys[t.code]=!1},this.mousemoveHandler=t=>{this.mouseLocked&&(this.yaw-=t.movementX*this.lookSensitivity*100,this.pitch-=t.movementY*this.lookSensitivity*100,this.pitch=Math.max(-89,Math.min(89,this.pitch)))},this.clickHandler=()=>{this.mouseLocked||t.requestPointerLock()},this.pointerlockchangeHandler=()=>{this.mouseLocked=document.pointerLockElement===t},document.addEventListener("keydown",this.keydownHandler),document.addEventListener("keyup",this.keyupHandler),document.addEventListener("mousemove",this.mousemoveHandler),t.addEventListener("click",this.clickHandler),document.addEventListener("pointerlockchange",this.pointerlockchangeHandler)}removeInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler&&document.removeEventListener("keydown",this.keydownHandler),this.keyupHandler&&document.removeEventListener("keyup",this.keyupHandler),this.mousemoveHandler&&document.removeEventListener("mousemove",this.mousemoveHandler),this.clickHandler&&t.removeEventListener("click",this.clickHandler),this.pointerlockchangeHandler&&document.removeEventListener("pointerlockchange",this.pointerlockchangeHandler),this.keys={}}checkCollision(t,e){for(const o of this.collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;if("floor"===s||"plane"===s)continue;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=Math.abs(t.x-c),m=Math.abs(t.y-p),y=Math.abs(t.z-d);if(u<a+e&&m<r+this.playerHeight/2&&y<l+e)return!0}return!1}checkGround(t){if(this.floorEntity){const e=this.floorEntity.getPosition(),o=this.floorEntity.getLocalScale(),n=o.x/2*100,i=o.z/2*100;if(Math.abs(t.x-e.x)<n&&Math.abs(t.z-e.z)<i)return e.y}let e=null;for(const o of this.collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=p+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-d)<l+this.collisionRadius&&t.y>=u-this.stepHeight&&(null===e||u>e)&&(e=u)}return e}update(e){if(!this.enabled)return;const o=(this.keys.KeyD||this.keys.ArrowRight?1:0)-(this.keys.KeyA||this.keys.ArrowLeft?1:0),n=(this.keys.KeyW||this.keys.ArrowUp?1:0)-(this.keys.KeyS||this.keys.ArrowDown?1:0),i=this.keys.ShiftLeft||this.keys.ShiftRight,s=this.yaw*(Math.PI/180);this.forward.set(-Math.sin(s),0,-Math.cos(s)),this.right.set(Math.cos(s),0,-Math.sin(s));const a=this.moveSpeed*(i?this.sprintMultiplier:1);this.targetVelocity.set(0,0,0),this.targetVelocity.add(this.tmpVec2.copy(this.forward).mulScalar(n*a)),this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(o*a));const r=((t,e)=>1-Math.pow(t,1e3*e))(this.moveDamping,e);this.horizontalVelocity.lerp(this.horizontalVelocity,this.targetVelocity,r),this.isGrounded||(this.velocity.y-=this.gravity*e,this.velocity.y=Math.max(-this.maxFallSpeed,this.velocity.y));const l=this.camera.getPosition().clone();l.y,this.playerHeight;const c=new t.Vec3;c.x=l.x+this.horizontalVelocity.x*e,c.y=l.y+this.velocity.y*e,c.z=l.z+this.horizontalVelocity.z*e,this.tmpVec2.set(c.x,l.y,l.z),this.checkCollision(this.tmpVec2,this.collisionRadius)&&(c.x=l.x),this.tmpVec2.set(c.x,l.y,c.z),this.checkCollision(this.tmpVec2,this.collisionRadius)&&(c.z=l.z);const p=this.checkGround(c),d=c.y-this.playerHeight;null!==p&&d<=p+this.groundCheckDistance?(c.y=p+this.playerHeight,this.velocity.y=0,this.isGrounded=!0):(null===p||d>p+this.stepHeight)&&(this.isGrounded=!1),this.camera.setPosition(c.x,c.y,c.z),this.camera.setEulerAngles(this.pitch,this.yaw,0)}destroy(){this.disable();for(const t of this.collisionEntities)t.destroy();this.collisionEntities=[],this.floorEntity=null}get collisionMeshEntities(){return this.collisionEntities}get grounded(){return this.isGrounded}getVelocity(){return this.velocity.clone()}setPosition(t,e,o){this.camera.setPosition(t,e,o)}setRotation(t,e){this.pitch=t,this.yaw=e,this.camera.setEulerAngles(t,e,0)}}let L=null;function k(){if(console.log("[RevealEffect] getGsplatRevealRadialClass called, cached:",!!L),L)return L;console.log("[RevealEffect] Creating new script class via pc.createScript");const e=t.createScript("gsplatRevealRadial");return console.log("[RevealEffect] Script class created:",e),Object.assign(e.prototype,{effectTime:0,_materialsApplied:null,_shadersApplied:!1,_retryCount:0,_maxRetries:100,_materialCreatedHandler:null,_systemMaterialHandler: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 t.Vec3(0,0,0)),this.dotTint||(this.dotTint=new t.Color(0,1,1)),this.waveTint||(this.waveTint=new t.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(t){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+=t,Math.floor(this.effectTime)!==Math.floor(this.effectTime-t)&&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 t=this.delay;if(0===this.acceleration)return t+this.endRadius/this.speed;const e=this.speed*this.speed+2*this.acceleration*this.endRadius;if(e<0)return 1/0;return t+(-this.speed+Math.sqrt(e))/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 t=this.entity.gsplat;if(!t)return void console.log("[RevealEffect] _applyShaders: No gsplat component found on entity");const e=t,o=!0===e.unified,n=this.app;if(o){console.log("[RevealEffect] Unified mode detected, using GSplatComponentSystem");const t=n?.systems?.gsplat;if(t){this._systemMaterialHandler||(this._systemMaterialHandler=(t,e,o)=>{console.log("[RevealEffect] material:created event from GSplatComponentSystem"),this._applyShaderToMaterial(t),this._shadersApplied=!0},t.on?.("material:created",this._systemMaterialHandler),console.log("[RevealEffect] Subscribed to GSplatComponentSystem material:created event"));const o=n.root?.findComponents("camera")||[],i=e.layers||[0];for(const e of o)for(const o of i){const i=n.scene?.layers?.getLayerById(o);if(i){const o=t.getGSplatMaterial?.(e.camera,i);o&&(console.log("[RevealEffect] Found unified material via getGSplatMaterial:",o),this._applyShaderToMaterial(o),this._shadersApplied=!0)}}}return}const i=e.instance||e._instance;if(i)return console.log("[RevealEffect] Instance found via component:",i),void this._applyToInstance(i);if(n&&n.scene){const t=n.scene.layers?.layerList||[];for(const e of t)if(e.meshInstances)for(const t of e.meshInstances){const e=t;if(t.gsplatInstance||e._gsplatInstance){const o=t.gsplatInstance||e._gsplatInstance;return console.log("[RevealEffect] Found gsplat instance via mesh instance:",o),void this._applyToInstance(o)}const o=t.material;if(o&&o.gsplat)return console.log("[RevealEffect] Found gsplat material via mesh instance:",o),this._applyShaderToMaterial(o),void(this._shadersApplied=!0)}const e=t=>{const o=t;if(o.gsplat){const t=o.gsplat.instance||o.gsplat._instance;if(t)return console.log("[RevealEffect] Found gsplat instance via entity search:",t),this._applyToInstance(t),!0}for(const o of t.children||[])if(e(o))return!0;return!1};n.root&&e(n.root)}this._retryCount%50==0&&(console.log("[RevealEffect] Still searching for gsplat materials..."),console.log("[RevealEffect] gsplatComponent.unified:",e.unified),console.log("[RevealEffect] gsplatComponent.asset:",t.asset))},_applyToInstance(t){if(this._shadersApplied)return;console.log("[RevealEffect] Applying shaders to instance"),console.log("[RevealEffect] Instance type:",t.constructor?.name),console.log("[RevealEffect] Instance keys:",Object.keys(t));const e=t.materials||t._materials;if(console.log("[RevealEffect] Instance materials:",e),e){const t=e;e 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(t=>{this._applyShaderToMaterial(t)}),this._shadersApplied=!0,console.log("[RevealEffect] SUCCESS: Shaders applied to",t.size,"materials"))):Array.isArray(e)&&(console.log("[RevealEffect] Materials is array with length:",e.length),e.forEach(t=>{this._applyShaderToMaterial(t)}),this._shadersApplied=!0)}if(!this._shadersApplied){const e=t.material||t._material;e&&(console.log("[RevealEffect] Found single material on instance"),this._applyShaderToMaterial(e),this._shadersApplied=!0)}t.on&&!this._materialCreatedHandler&&(this._materialCreatedHandler=t=>{console.log("[RevealEffect] material:created event received"),this._applyShaderToMaterial(t),this._shadersApplied=!0},t.on("material:created",this._materialCreatedHandler),console.log("[RevealEffect] Subscribed to material:created event"))},_applyShaderToMaterial(t){if(this._materialsApplied?.has(t))return void console.log("[RevealEffect] Material already has shader applied, skipping");console.log("[RevealEffect] Applying shader to material:",t),console.log("[RevealEffect] Material constructor:",t.constructor?.name),console.log("[RevealEffect] Material keys:",Object.keys(t));const e=[];let o=t;for(;o&&o!==Object.prototype;){const t=Object.getOwnPropertyNames(o);for(const n of t)try{"function"!=typeof o[n]||e.includes(n)||e.push(n)}catch(t){}o=Object.getPrototypeOf(o)}console.log("[RevealEffect] Material methods:",e.filter(t=>!t.startsWith("_")).join(", "));const n=this.getShaderGLSL(),i=this.getShaderWGSL(),s=t,a="function"==typeof s.setShaderChunk;console.log("[RevealEffect] material.setShaderChunk exists:",a),a?(n&&(s.setShaderChunk?.("gsplatEffectGLSL",n),console.log("[RevealEffect] GLSL shader chunk set via setShaderChunk")),i&&(s.setShaderChunk?.("gsplatEffectWGSL",i),console.log("[RevealEffect] WGSL shader chunk set via setShaderChunk"))):(s.chunks&&(console.log("[RevealEffect] Material has chunks property"),s.chunks.gsplatEffectGLSL=n,s.chunks.gsplatEffectWGSL=i,console.log("[RevealEffect] Set chunks directly")),s.options&&console.log("[RevealEffect] Material has options:",s.options),s.shader&&console.log("[RevealEffect] Material has shader:",s.shader),"function"==typeof t.setParameter&&console.log("[RevealEffect] material.setParameter exists - will use for uniforms")),t.update?.(),this._materialsApplied?.add(t),console.log("[RevealEffect] Material added to applied set, total:",this._materialsApplied?.size)},_removeShaders(){if(this._materialsApplied&&(this._materialsApplied.forEach(t=>{const e=t;e.setShaderChunk?.("gsplatEffectGLSL",""),e.setShaderChunk?.("gsplatEffectWGSL",""),t.update?.()}),this._materialsApplied.clear()),this._shadersApplied=!1,this._materialCreatedHandler){const t=this.entity.gsplat,e=t?.instance;e?.off&&e.off("material:created",this._materialCreatedHandler),this._materialCreatedHandler=null}if(this._systemMaterialHandler){const t=this.app?.systems?.gsplat;t?.off&&t.off("material:created",this._systemMaterialHandler),this._systemMaterialHandler=null}},_setUniform(t,e){this._materialsApplied&&this._materialsApplied.forEach(o=>{o.setParameter?.(t,e)})},destroy(){this._removeShaders()}}),L=e,e}const A={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 P(t){if("none"!==t)return A[t]}var T,z={},R={},I={};function D(){if(T)return I;T=1,Object.defineProperty(I,"__esModule",{value:!0}),I.loop=I.conditional=I.parse=void 0;I.parse=function t(e,o){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:n;if(Array.isArray(o))o.forEach(function(o){return t(e,o,n,i)});else if("function"==typeof o)o(e,n,i,t);else{var s=Object.keys(o)[0];Array.isArray(o[s])?(i[s]={},t(e,o[s],n,i[s])):i[s]=o[s](e,n,i,t)}return n};I.conditional=function(t,e){return function(o,n,i,s){e(o,n,i)&&s(o,t,n,i)}};return I.loop=function(t,e){return function(o,n,i,s){for(var a=[],r=o.pos;e(o,n,i);){var l={};if(s(o,t,n,l),o.pos===r)break;r=o.pos,a.push(l)}return a}},I}var F,U,$={};function V(){if(F)return $;F=1,Object.defineProperty($,"__esModule",{value:!0}),$.readBits=$.readArray=$.readUnsigned=$.readString=$.peekBytes=$.readBytes=$.peekByte=$.readByte=$.buildStream=void 0;$.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};$.readByte=t;$.peekByte=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return function(e){return e.data[e.pos+t]}};var e=function(t){return function(e){return e.data.subarray(e.pos,e.pos+=t)}};$.readBytes=e;$.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};$.readString=function(t){return function(o){return Array.from(e(t)(o)).map(function(t){return String.fromCharCode(t)}).join("")}};$.readUnsigned=function(t){return function(o){var n=e(2)(o);return t?(n[1]<<8)+n[0]:(n[0]<<8)+n[1]}};$.readArray=function(t,o){return function(n,i,s){for(var a="function"==typeof o?o(n,i,s):o,r=e(t),l=new Array(a),c=0;c<a;c++)l[c]=r(n);return l}};return $.readBits=function(t){return function(e){for(var o=function(t){return t.data[t.pos++]}(e),n=new Array(8),i=0;i<8;i++)n[7-i]=!!(o&1<<i);return Object.keys(t).reduce(function(e,o){var i=t[o];return i.length?e[o]=function(t,e,o){for(var n=0,i=0;i<o;i++)n+=t[e+i]&&Math.pow(2,o-i-1);return n}(n,i.index,i.length):e[o]=n[i.index],e},{})}},$}var B,O={};var H,W,G={};var N=function(){if(W)return z;W=1,Object.defineProperty(z,"__esModule",{value:!0}),z.decompressFrames=z.decompressFrame=z.parseGIF=void 0;var t,e=(U||(U=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=D(),o=V(),n={blocks:function(t){for(var e=[],n=t.data.length,i=0,s=(0,o.readByte)()(t);0!==s&&s;s=(0,o.readByte)()(t)){if(t.pos+s>=n){var a=n-t.pos;e.push((0,o.readBytes)(a)(t)),i+=a;break}e.push((0,o.readBytes)(s)(t)),i+=s}for(var r=new Uint8Array(i),l=0,c=0;c<e.length;c++)r.set(e[c],l),l+=e[c].length;return r}},i=(0,e.conditional)({gce:[{codes:(0,o.readBytes)(2)},{byteSize:(0,o.readByte)()},{extras:(0,o.readBits)({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:(0,o.readUnsigned)(!0)},{transparentColorIndex:(0,o.readByte)()},{terminator:(0,o.readByte)()}]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&249===e[1]}),s=(0,e.conditional)({image:[{code:(0,o.readByte)()},{descriptor:[{left:(0,o.readUnsigned)(!0)},{top:(0,o.readUnsigned)(!0)},{width:(0,o.readUnsigned)(!0)},{height:(0,o.readUnsigned)(!0)},{lct:(0,o.readBits)({exists:{index:0},interlaced:{index:1},sort:{index:2},future:{index:3,length:2},size:{index:5,length:3}})}]},(0,e.conditional)({lct:(0,o.readArray)(3,function(t,e,o){return Math.pow(2,o.descriptor.lct.size+1)})},function(t,e,o){return o.descriptor.lct.exists}),{data:[{minCodeSize:(0,o.readByte)()},n]}]},function(t){return 44===(0,o.peekByte)()(t)}),a=(0,e.conditional)({text:[{codes:(0,o.readBytes)(2)},{blockSize:(0,o.readByte)()},{preData:function(t,e,n){return(0,o.readBytes)(n.text.blockSize)(t)}},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&1===e[1]}),r=(0,e.conditional)({application:[{codes:(0,o.readBytes)(2)},{blockSize:(0,o.readByte)()},{id:function(t,e,n){return(0,o.readString)(n.blockSize)(t)}},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&255===e[1]}),l=(0,e.conditional)({comment:[{codes:(0,o.readBytes)(2)},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&254===e[1]}),c=[{header:[{signature:(0,o.readString)(3)},{version:(0,o.readString)(3)}]},{lsd:[{width:(0,o.readUnsigned)(!0)},{height:(0,o.readUnsigned)(!0)},{gct:(0,o.readBits)({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:(0,o.readByte)()},{pixelAspectRatio:(0,o.readByte)()}]},(0,e.conditional)({gct:(0,o.readArray)(3,function(t,e){return Math.pow(2,e.lsd.gct.size+1)})},function(t,e){return e.lsd.gct.exists}),{frames:(0,e.loop)([i,r,l,s,a],function(t){var e=(0,o.peekByte)()(t);return 33===e||44===e})}];t.default=c}(R)),(t=R)&&t.__esModule?t:{default:t}),o=D(),n=V(),i=(B||(B=1,Object.defineProperty(O,"__esModule",{value:!0}),O.deinterlace=void 0,O.deinterlace=function(t,e){for(var o=new Array(t.length),n=t.length/e,i=function(n,i){var s=t.slice(i*e,(i+1)*e);o.splice.apply(o,[n*e,e].concat(s))},s=[0,4,2,1],a=[8,8,4,2],r=0,l=0;l<4;l++)for(var c=s[l];c<n;c+=a[l])i(c,r),r++;return o}),O),s=(H||(H=1,Object.defineProperty(G,"__esModule",{value:!0}),G.lzw=void 0,G.lzw=function(t,e,o){var n,i,s,a,r,l,c,p,d,h,u,m,y,g,f,v,b=4096,x=o,w=new Array(o),S=new Array(b),E=new Array(b),C=new Array(4097);for(r=1+(i=1<<(h=t)),n=i+2,c=-1,s=(1<<(a=h+1))-1,p=0;p<i;p++)S[p]=0,E[p]=p;for(u=m=y=g=f=v=0,d=0;d<x;){if(0===g){if(m<a){u+=e[v]<<m,m+=8,v++;continue}if(p=u&s,u>>=a,m-=a,p>n||p==r)break;if(p==i){s=(1<<(a=h+1))-1,n=i+2,c=-1;continue}if(-1==c){C[g++]=E[p],c=p,y=p;continue}for(l=p,p==n&&(C[g++]=y,p=c);p>i;)C[g++]=E[p],p=S[p];y=255&E[p],C[g++]=y,n<b&&(S[n]=c,E[n]=y,0===(++n&s)&&n<b&&(a++,s+=n)),c=l}g--,w[f++]=C[g],d++}for(d=f;d<x;d++)w[d]=0;return w}),G);z.parseGIF=function(t){var i=new Uint8Array(t);return(0,o.parse)((0,n.buildStream)(i),e.default)};var a=function(t,e,o){if(t.image){var n=t.image,a=n.descriptor.width*n.descriptor.height,r=(0,s.lzw)(n.data.minCodeSize,n.data.blocks,a);n.descriptor.lct.interlaced&&(r=(0,i.deinterlace)(r,n.descriptor.width));var l={pixels:r,dims:{top:t.image.descriptor.top,left:t.image.descriptor.left,width:t.image.descriptor.width,height:t.image.descriptor.height}};return n.descriptor.lct&&n.descriptor.lct.exists?l.colorTable=n.lct:l.colorTable=e,t.gce&&(l.delay=10*(t.gce.delay||10),l.disposalType=t.gce.extras.disposal,t.gce.extras.transparentColorGiven&&(l.transparentIndex=t.gce.transparentColorIndex)),o&&(l.patch=function(t){for(var e=t.pixels.length,o=new Uint8ClampedArray(4*e),n=0;n<e;n++){var i=4*n,s=t.pixels[n],a=t.colorTable[s]||[0,0,0];o[i]=a[0],o[i+1]=a[1],o[i+2]=a[2],o[i+3]=s!==t.transparentIndex?255:0}return o}(l)),l}console.warn("gif frame does not have associated image.")};return z.decompressFrame=a,z.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(o){return a(o,t.gct,e)})},z}();class q{constructor(t,e,o={}){this.frames=[],this.currentFrameIndex=0,this.isPlaying=!1,this.isLoaded=!1,this.lastFrameTime=0,this.updateHandler=null,this.texture=null,this.gifWidth=0,this.gifHeight=0,this.update=()=>{if(!this.isPlaying||!this.isLoaded||this.frames.length<=1)return;const t=performance.now(),e=this.frames[this.currentFrameIndex].delay||100;t-this.lastFrameTime>=e&&(this.currentFrameIndex=(this.currentFrameIndex+1)%this.frames.length,this.drawFrame(this.currentFrameIndex),this.lastFrameTime=t)},this.app=t,this.url=e,this.options=o,this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d",{willReadFrequently:!0}),this.load()}async load(){try{const e=await fetch(this.url);if(!e.ok)throw new Error(`Failed to fetch GIF: ${e.statusText}`);const o=await e.arrayBuffer(),n=N.parseGIF(o);if(this.frames=N.decompressFrames(n,!0),0===this.frames.length)throw new Error("GIF has no frames");this.gifWidth=n.lsd.width,this.gifHeight=n.lsd.height,this.canvas.width=this.gifWidth,this.canvas.height=this.gifHeight,this.texture=new t.Texture(this.app.graphicsDevice,{width:this.gifWidth,height:this.gifHeight,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.drawFrame(0),this.isLoaded=!0,console.log(`[AnimatedGif] Loaded GIF: ${this.url}, ${this.frames.length} frames, ${this.gifWidth}x${this.gifHeight}`),this.options.onReady&&this.options.onReady(),this.options.autoPlay&&this.play()}catch(t){console.error("[AnimatedGif] Error loading GIF:",t),this.options.onError&&this.options.onError(t instanceof Error?t:new Error(String(t)))}}drawFrame(t){if(!this.texture||t>=this.frames.length)return;const e=this.frames[t],o=t>0?this.frames[t-1]:null;o&&2===o.disposalType&&this.ctx.clearRect(o.dims.left,o.dims.top,o.dims.width,o.dims.height);const n=new ImageData(new Uint8ClampedArray(e.patch),e.dims.width,e.dims.height),i=document.createElement("canvas");i.width=e.dims.width,i.height=e.dims.height;i.getContext("2d").putImageData(n,0,0),this.ctx.drawImage(i,e.dims.left,e.dims.top),this.updateTexture()}updateTexture(){if(!this.texture)return;const t=this.ctx.getImageData(0,0,this.gifWidth,this.gifHeight),e=this.texture.lock();e&&e.set(t.data),this.texture.unlock(),this.texture.upload()}play(){this.isPlaying||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.updateHandler||(this.updateHandler=this.update,this.app.on("update",this.updateHandler)))}pause(){this.isPlaying&&(this.isPlaying=!1,this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null))}stop(){this.pause(),this.currentFrameIndex=0,this.isLoaded&&(this.ctx.clearRect(0,0,this.gifWidth,this.gifHeight),this.drawFrame(0))}get playing(){return this.isPlaying}get loaded(){return this.isLoaded}destroy(){this.pause(),this.texture&&(this.texture.destroy(),this.texture=null),this.frames=[],this.isLoaded=!1,this.canvas=null,this.ctx=null}}class j{constructor(e){this.meshes=new Map,this.updateHandler=null,this.useTexElement2D=!1,this.app=e,this.canvas=e.graphicsDevice.canvas,function(){const e=t.GraphicsDevice;if(!e)return void console.warn("[HtmlMeshHelper] Could not find pc.GraphicsDevice to patch");if("function"==typeof e.prototype._isHTMLElementInterface)return;e.prototype._isHTMLElementInterface=function(t){return!(!("undefined"!=typeof HTMLElement&&t instanceof HTMLElement)||t instanceof HTMLImageElement||t instanceof HTMLCanvasElement||t instanceof HTMLVideoElement)};const o=e.prototype._isBrowserInterface;o&&(e.prototype._isBrowserInterface=function(t){return o.call(this,t)||this._isHTMLElementInterface(t)}),console.log("[HtmlMeshHelper] Patched PlayCanvas GraphicsDevice for HTML-in-Canvas support")}();const o=e.graphicsDevice;this.useTexElement2D=!0===o.supportsTexElement2D,console.log(`[HtmlMeshManager] texElement2D support: ${this.useTexElement2D}`)}createMesh(t){const e=t.width||512,o=t.height||512,n=this.createHtmlElement(t,e,o),i=this.createTexture(n,e,o,t),s=this.createMaterial(i,t),a={entity:this.createEntity(t,s,e,o),texture:i,material:s,htmlElement:n,config:t,destroy:()=>this.destroyMesh(t.id),update:()=>this.updateMeshTexture(t.id)};return this.meshes.set(t.id,a),t.animated&&!this.updateHandler&&this.startUpdateLoop(),a}createHtmlElement(t,e,o){const n=document.createElement("div");if(n.id=`html-mesh-${t.id}`,n.style.width=`${e}px`,n.style.height=`${o}px`,n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.pointerEvents="none",n.style.zIndex="-1",n.style.overflow="hidden",t.css){const e=document.createElement("style");e.textContent=t.css,n.appendChild(e)}return n.innerHTML+=t.html,this.useTexElement2D?(this.canvas.setAttribute("layoutsubtree",""),this.canvas.setAttribute("data-layoutsubtree",""),this.canvas.appendChild(n)):(n.style.visibility="hidden",document.body.appendChild(n)),n}createTexture(e,o,n,i){const s=new t.Texture(this.app.graphicsDevice,{width:o,height:n,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE,name:`htmlMesh-${i.id}`});if(this.useTexElement2D)try{s.setSource(e),console.log(`[HtmlMeshManager] Using texElement2D for mesh ${i.id}`)}catch(t){console.warn(`[HtmlMeshManager] texElement2D failed, falling back to canvas: ${t}`),this.renderToCanvas(s,e,o,n)}else this.renderToCanvas(s,e,o,n);return s}renderToCanvas(t,e,o,n){const i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d",{willReadFrequently:!0}),a=`\n <svg xmlns="http://www.w3.org/2000/svg" width="${o}" height="${n}">\n <foreignObject width="100%" height="100%">\n <div xmlns="http://www.w3.org/1999/xhtml" style="width:${o}px;height:${n}px;">\n ${e.innerHTML}\n </div>\n </foreignObject>\n </svg>\n `,r=new Image,l=new Blob([a],{type:"image/svg+xml;charset=utf-8"}),c=URL.createObjectURL(l);r.onload=()=>{s.drawImage(r,0,0),URL.revokeObjectURL(c),t.setSource(i)},r.onerror=()=>{s.fillStyle="#333",s.fillRect(0,0,o,n),s.fillStyle="#fff",s.font="20px Arial",s.textAlign="center",s.fillText("HTML Mesh",o/2,n/2),URL.revokeObjectURL(c),t.setSource(i)},r.src=c}createMaterial(e,o){const n=new t.StandardMaterial;return n.diffuseMap=e,n.emissiveMap=e,n.emissive=new t.Color(.5,.5,.5),n.opacity=o.opacity??1,n.blendType=void 0!==o.opacity&&o.opacity<1?t.BLEND_NORMAL:t.BLEND_NONE,n.cull=o.doubleSided?t.CULLFACE_NONE:t.CULLFACE_BACK,n.update(),n}createEntity(e,o,n,i){const s=new t.Entity(`htmlMesh-${e.id}`),a=n/i;s.addComponent("render",{type:"plane",material:o,castShadows:e.castShadows??!1,receiveShadows:e.receiveShadows??!1}),s.setPosition(e.position.x,e.position.y,e.position.z);const r=e.rotation||{x:0,y:0,z:0};s.setEulerAngles(r.x,r.y,r.z);const l=e.scale||{x:1,y:1};return s.setLocalScale(l.x*a,1,l.y),this.app.root.addChild(s),e.billboard&&this.app.on("update",()=>{if(!s.enabled)return;const t=this.app.root.findComponent("camera")?.entity;t&&s.lookAt(t.getPosition())}),s}updateMeshTexture(t){const e=this.meshes.get(t);e&&(this.useTexElement2D?e.texture.upload():this.renderToCanvas(e.texture,e.htmlElement,e.config.width||512,e.config.height||512))}startUpdateLoop(){let t={};this.updateHandler=()=>{const e=performance.now();this.meshes.forEach((o,n)=>{if(!o.config.animated)return;const i=o.config.updateRate||100,s=t[n]||0;e-s>=i&&(this.updateMeshTexture(n),t[n]=e)})},this.app.on("update",this.updateHandler)}destroyMesh(t){const e=this.meshes.get(t);if(!e)return;e.entity.destroy(),e.texture.destroy(),e.htmlElement.remove(),this.meshes.delete(t);!Array.from(this.meshes.values()).some(t=>t.config.animated)&&this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null)}getMesh(t){return this.meshes.get(t)}updateVisibility(t,e){this.meshes.forEach(o=>{const n=o.config;if(n.visibilityRange){const i=n.visibilityRange;let s=!0;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),o.entity.enabled=s}if(n.billboard&&n.billboardRange){const i=n.billboardRange;let s=!1;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),o.entity._billboardActive=s}else n.billboard&&(o.entity._billboardActive=!0)})}getAllMeshes(){return this.meshes}destroy(){this.meshes.forEach((t,e)=>this.destroyMesh(e)),this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null)}}function Y(t,e){const o=new j(t);for(const t of e)o.createMesh(t);return o}class X{constructor(t){this.isInitialized=!1,this.scriptCleanup=[],this.lastError=null,this.updateCallbacks=[],this.customScript=t,this.api={}}initialize(t){this.api={...t,registerCleanup:t=>this.addCleanup(t)},this.isInitialized=!0}updateScript(t){t!==this.customScript&&(this.customScript=t,this.execute())}addCleanup(t){"function"==typeof t&&this.scriptCleanup.push(t)}sanitizeScript(t){if(!t)return"";let e=t.normalize("NFC");return e=e.replace(/\uFEFF/g,""),e=e.replace(/\u00A0/g," "),e=e.replace(/[\u2028\u2029]/g,"\n"),e=e.replace(/[\u200B-\u200D\u2060]/g,""),e=e.replace(/[\u2018\u2019\u201B]/g,"'").replace(/[\u201C\u201D\u201E]/g,'"'),e}preprocessScript(t){if(!t)return"";let e=this.sanitizeScript(t).trim().replace(/\r\n/g,"\n");return/console\/log\s*\(/.test(e)&&(console.warn("[Custom Script] Detected 'console/log(...)'. Did you mean 'console.log(...)'?"),e=e.replace(/console\/log\s*\(/g,"console.log(")),e="try {\n"+e+"\n} catch (error) {\n console.error('[Custom Script] Runtime error:', error);\n}",e}execute(){if(!this.isInitialized||!this.customScript)return;if(this.customScript.length>2e5)return void console.warn("[Custom Script] Script too large, aborting execution.");this.cleanup();const t=this.preprocessScript(this.customScript);try{const e=this.api.app;let o=!1;const n=()=>{o||(this.updateCallbacks.length>200?(console.warn("[Custom Script] Too many update callbacks; further callbacks ignored."),o=!0):requestAnimationFrame(n))};requestAnimationFrame(n);const i=Object.create(e);i.registerUpdate=t=>{if("function"!=typeof t||o)return;const n=()=>{try{t()}catch(t){console.error("[Custom Script] Error in update callback:",t)}};this.updateCallbacks.push(n),e.on("update",n),this.addCleanup(()=>{e.off("update",n);const t=this.updateCallbacks.indexOf(n);-1!==t&&this.updateCallbacks.splice(t,1)})},i.registerBeforeRender=i.registerUpdate;const{camera:s,pc:a,canvas:r,getScrollPercentage:l,getCurrentWaypointIndex:c,getHotspots:p,getSplats:d,getHTMLMeshes:h}=this.api,u=new Function("app","camera","pc","canvas","getScrollPercentage","getCurrentWaypointIndex","getHotspots","getSplats","getHTMLMeshes","registerCleanup","registerUpdate","exports","module","require","globalThis","window","document","self","'use strict';\n"+t),m={},y={exports:m},g=()=>{throw new Error("require not available in custom script")},f=new Proxy({},{get:()=>{},set:()=>!1}),v=u(i,s,a,r,l,c,p,d,h,t=>this.addCleanup(t),i.registerUpdate.bind(i),m,y,g,f,void 0,void 0,void 0)||y.exports||m.default||m.cleanup;"function"==typeof v&&this.addCleanup(v),console.log("[Custom Script] Executed successfully")}catch(t){this.lastError=t,console.error("[Custom Script] Execution error:",t)}}cleanup(){this.scriptCleanup.forEach(t=>{try{t()}catch(t){console.error("[Custom Script] Cleanup error:",t)}}),this.scriptCleanup=[],this.updateCallbacks=[]}getLastError(){return this.lastError}dispose(){this.cleanup(),this.isInitialized=!1}}class Z{constructor(t,e,o={}){this.options=o,this.frameAssets=new Map,this.activeEntityIndex=0,this.currentFrame=0,this.isPlaying=!1,this.lastFrameTime=0,this.loadingFrames=new Set,this.destroyed=!1,this.isDisplaying=!1,this.listeners=new Map,this.app=t,this.frameUrls=e.frameUrls,this.fps=e.fps||24,this.loop=!1!==e.loop,this.preloadCount=e.preloadCount||5,this.frameInterval=1e3/this.fps,this.entities=[this.createSplatEntity("frameEntityA"),this.createSplatEntity("frameEntityB")],this.entities[0].enabled=!1,this.entities[1].enabled=!1,this.preloadInitialFrames(),e.autoplay&&this.preloadFrame(0).then(()=>{this.destroyed||this.play()})}createSplatEntity(e){const o=new t.Entity(e);return o.addComponent("gsplat",{}),this.app.root.addChild(o),o}async preloadInitialFrames(){const t=Math.min(this.preloadCount,this.frameUrls.length),e=[];for(let o=0;o<t;o++)e.push(this.preloadFrame(o));await Promise.all(e),this.options.onLoadProgress?.(t,this.frameUrls.length)}async preloadFrame(e){return this.destroyed||e<0||e>=this.frameUrls.length?null:this.frameAssets.has(e)?this.frameAssets.get(e):this.loadingFrames.has(e)?null:(this.loadingFrames.add(e),new Promise(o=>{const n=this.frameUrls[e],i=new t.Asset(`frame_${e}`,"gsplat",{url:n});i.on("load",()=>{this.destroyed||(this.frameAssets.set(e,i),this.loadingFrames.delete(e)),o(i)}),i.on("error",t=>{console.error(`Failed to load frame ${e}:`,t),this.loadingFrames.delete(e),this.options.onError?.(`Failed to load frame ${e}: ${t}`),o(null)}),this.app.assets.add(i),this.app.assets.load(i)}))}unloadFrame(t){const e=this.frameAssets.get(t);e&&(this.app.assets.remove(e),e.unload(),this.frameAssets.delete(t))}updatePreloadWindow(){for(let t=1;t<=this.preloadCount;t++){const e=this.loop?(this.currentFrame+t)%this.frameUrls.length:this.currentFrame+t;e<this.frameUrls.length&&this.preloadFrame(e)}for(const[t]of this.frameAssets){const e=this.currentFrame-t;e>2&&e<this.frameUrls.length-this.preloadCount&&this.unloadFrame(t)}}async displayFrame(t){if(!this.destroyed&&!this.isDisplaying){this.isDisplaying=!0;try{let e=this.frameAssets.get(t);if(!e&&(e=await this.preloadFrame(t),!e||this.destroyed))return;const o=(this.activeEntityIndex+1)%2,n=this.entities[this.activeEntityIndex],i=this.entities[o],s=i.gsplat;s&&(s.asset=e),i.enabled=!0,n.enabled=!1,this.activeEntityIndex=o,this.currentFrame=t,this.emit("frameChange",t,this.frameUrls.length),this.options.onFrameChange?.(t,this.frameUrls.length),this.updatePreloadWindow()}finally{this.isDisplaying=!1}}}update(t){if(!this.isPlaying||this.destroyed)return;const e=performance.now(),o=e-this.lastFrameTime;if(o>=this.frameInterval){this.lastFrameTime=e-o%this.frameInterval;let t=this.currentFrame+1;if(t>=this.frameUrls.length){if(!this.loop)return this.pause(),void this.emit("complete");t=0}this.displayFrame(t)}}play(){this.isPlaying||this.destroyed||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.entities[this.activeEntityIndex].enabled||this.displayFrame(this.currentFrame),this.emit("play"))}pause(){this.isPlaying=!1,this.emit("pause")}stop(){this.isPlaying=!1,this.currentFrame=0,this.displayFrame(0),this.emit("stop")}setFrame(t){t<0&&(t=0),t>=this.frameUrls.length&&(t=this.frameUrls.length-1),this.displayFrame(t)}nextFrame(){let t=this.currentFrame+1;t>=this.frameUrls.length&&(t=this.loop?0:this.frameUrls.length-1),this.setFrame(t)}previousFrame(){let t=this.currentFrame-1;t<0&&(t=this.loop?this.frameUrls.length-1:0),this.setFrame(t)}getCurrentFrame(){return this.currentFrame}getTotalFrames(){return this.frameUrls.length}getProgress(){return this.frameUrls.length>1?this.currentFrame/(this.frameUrls.length-1):0}setProgress(t){const e=Math.round(t*(this.frameUrls.length-1));this.setFrame(e)}getFps(){return this.fps}setFps(t){this.fps=t,this.frameInterval=1e3/t}getIsPlaying(){return this.isPlaying}setLoop(t){this.loop=t}getLoop(){return this.loop}setPosition(t,e,o){this.entities[0].setPosition(t,e,o),this.entities[1].setPosition(t,e,o)}setRotation(t,e,o){this.entities[0].setEulerAngles(t,e,o),this.entities[1].setEulerAngles(t,e,o)}setScale(t,e,o){this.entities[0].setLocalScale(t,e,o),this.entities[1].setLocalScale(t,e,o)}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e))}destroy(){this.destroyed=!0,this.isPlaying=!1;for(const[t]of this.frameAssets)this.unloadFrame(t);this.entities[0].destroy(),this.entities[1].destroy(),this.listeners.clear()}}class J{constructor(){this.listeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e))}}function K(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const Q={"desktop-max":{range:[0,5],lodDistances:[15,30,80,250,300]},desktop:{range:[0,2],lodDistances:[15,30,80,250,300]},"mobile-max":{range:[1,2],lodDistances:[15,30,80,250,300]},mobile:{range:[2,5],lodDistances:[15,30,80,250,300]}};function tt(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function et(e,o,n={}){if(console.log("[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3"),n.lazyLoad){const t=new J;let i,s=null;const a=n.lazyLoadThumbnail||o.uiOptions?.lazyLoadThumbnailUrl||o.thumbnailUrl,r=n.lazyLoadButtonText||o.uiOptions?.lazyLoadButtonText||l(o.uiOptions?.buttonLabels,"startExperience"),p=o.uiColor||"#4CAF50";console.log("[StorySplat Viewer] Lazy loading enabled, showing start button"),function(t,e){const{thumbnailUrl:o,thumbnailType:n,buttonText:i="Start Experience",uiColor:s="#4CAF50",onStart:a}=e;c(s),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=n||(o?function(t){const e=t.toLowerCase();return e.includes(".mp4")||e.includes(".webm")||e.includes(".mov")||e.includes(".ogg")?"video":e.includes(".gif")?"gif":"image"}(o):"image");let p="";o&&(p+="video"===l?`<video class="storysplat-lazy-load-thumbnail-video" src="${o}" autoplay muted loop playsinline webkit-playsinline></video>`:`<img class="storysplat-lazy-load-thumbnail" src="${o}" alt="Scene preview" />`),p+='<div class="storysplat-lazy-load-overlay"></div>',p+=`\n <div class="storysplat-lazy-load-content">\n <button class="storysplat-lazy-load-start-btn" style="background: ${s}">\n <svg viewBox="0 0 24 24">\n <path d="M8 5v14l11-7z"/>\n </svg>\n ${i}\n </button>\n </div>\n `,r.innerHTML=p,t.appendChild(r);const d=r.querySelector(".storysplat-lazy-load-start-btn");d?.addEventListener("click",()=>{const t=r.querySelector("video");t&&(t.pause(),t.src=""),r.style.transition="opacity 0.3s ease-out",r.style.opacity="0",setTimeout(()=>{r.remove(),a()},300)})}(e,{thumbnailUrl:a,thumbnailType:n.lazyLoadThumbnailType||o.uiOptions?.lazyLoadThumbnailType,buttonText:r,uiColor:p,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),s=et(e,o,{...n,lazyLoad:!1}),i&&(s.setButtonLabels(i),i=void 0),s.on("ready",()=>t.emit("ready")),s.on("error",e=>t.emit("error",e)),s.on("waypointChange",e=>t.emit("waypointChange",e)),s.on("playbackStart",()=>t.emit("playbackStart")),s.on("playbackStop",()=>t.emit("playbackStop")),s.on("loaded",()=>t.emit("loaded")),s.on("progress",e=>t.emit("progress",e))}});return{app:null,canvas:null,goToWaypoint:t=>s?.goToWaypoint(t),nextWaypoint:()=>s?.nextWaypoint(),prevWaypoint:()=>s?.prevWaypoint(),getCurrentWaypointIndex:()=>s?.getCurrentWaypointIndex()??0,getWaypointCount:()=>s?.getWaypointCount()??0,setPosition:(t,e,o)=>s?.setPosition(t,e,o),setRotation:(t,e,o)=>s?.setRotation(t,e,o),getPosition:()=>s?.getPosition()??{x:0,y:0,z:0},getRotation:()=>s?.getRotation()??{x:0,y:0,z:0},play:()=>s?.play(),pause:()=>s?.pause(),stop:()=>s?.stop(),isPlaying:()=>s?.isPlaying()??!1,setCameraMode:t=>s?.setCameraMode(t),getCameraMode:()=>s?.getCameraMode()??"tour",setExploreMode:t=>s?.setExploreMode(t),goToOriginalSplat:()=>s?.goToOriginalSplat(),goToSplat:async t=>s?.goToSplat(t),getCurrentSplatUrl:()=>s?.getCurrentSplatUrl()??"",isShowingOriginalSplat:()=>s?.isShowingOriginalSplat()??!0,getAdditionalSplats:()=>s?.getAdditionalSplats()??[],setProgress:t=>s?.setProgress(t),getProgress:()=>s?.getProgress()??0,muteAll:()=>s?.muteAll(),unmuteAll:()=>s?.unmuteAll(),isMuted:()=>s?.isMuted()??!1,getHotspots:()=>s?.getHotspots()??[],triggerHotspot:t=>s?.triggerHotspot(t),closeHotspot:()=>s?.closeHotspot(),destroy:()=>{s?s.destroy():(e.querySelectorAll(".storysplat-lazy-load-container, .storysplat-viewer-container").forEach(t=>t.remove()),e.classList.remove("storysplat-viewer-container"))},resize:()=>s?.resize(),navigateToScene:async t=>{if(s)return s.navigateToScene(t)},setButtonLabels:t=>{s?s.setButtonLabels(t):i={...i,...t}},on:(e,o)=>t.on(e,o),off:(e,o)=>t.off(e,o)}}const i=new J;if(!n.allowParentStyles){const t=e.style.width,o=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=t||"100%",e.style.height=o||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),function(t,e){t.querySelector(".storysplat-error-popup")?.remove();const o=t.querySelector(".storysplat-preloader");o&&d(o);const n=e.includes("Failed to load splat from any URL"),i=document.createElement("div");i.className="storysplat-error-popup",i.innerHTML=n?'\n <div class="storysplat-error-popup-icon">⚠️</div>\n <h3 class="storysplat-error-popup-title">Scene Needs Update</h3>\n <p class="storysplat-error-popup-message">\n This scene was created with an older version of StorySplat and needs to be re-exported to work with the latest viewer.\n <br><br>\n Please ask the scene creator to re-export it from the StorySplat editor.\n </p>\n <a href="https://storysplat.com" target="_blank" class="storysplat-error-popup-action">\n Visit StorySplat\n </a>\n ':`\n <div class="storysplat-error-popup-icon">❌</div>\n <h3 class="storysplat-error-popup-title">Failed to Load Scene</h3>\n <p class="storysplat-error-popup-message">\n ${e||"An error occurred while loading this scene. Please try refreshing the page."}\n </p>\n `,t.appendChild(i)}(e,t.message)});const v=a(s(o));console.log("[StorySplat Viewer] Creating viewer with config:",v),console.log("[StorySplat Viewer] Scale config:",v.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:o.splatScale,scale:o.scale});const b=!0===n.editor,x=b?!n.editorSkipUI&&!1!==n.showUI:!1!==n.showUI,w=v.uiColor||"#4CAF50",S=v.uiOptions||{},E=n.template||S.uiType||"minimal";let M=S.buttonLabels;const L=t=>"first-person"===t?"tour":"drone"===t?"explore":t,A=v.collisionMeshesData&&v.collisionMeshesData.length>0;let T=(v.allowedCameraModes||["orbit","first-person","drone"]).map(L).filter((t,e,o)=>o.indexOf(t)===e);A&&!T.includes("walk")&&T.push("walk"),!A&&T.includes("walk")&&(T=T.filter(t=>"walk"!==t));const z=L(v.defaultCameraMode||"orbit");let R={};x&&(R=function(t,e,o={}){const{uiColor:n="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showPreloader:d=!0,allowedCameraModes:h=["tour","explore"],defaultCameraMode:u="tour",customPreloaderLogoUrl:m,buttonLabels:y,hideWatermark:g=!1,watermarkText:f,watermarkLink:v,sceneId:b,showWaypointList:x=!0,template:w="minimal"}=o,S={tour:l(y,"tour"),explore:l(y,"explore"),walk:l(y,"walk"),orbit:l(y,"orbit"),fly:l(y,"fly"),previous:l(y,"previous"),next:l(y,"next"),fullscreen:l(y,"fullscreen"),waypoints:l(y,"waypoints"),close:l(y,"close"),yes:l(y,"yes"),cancel:l(y,"cancel"),vr:l(y,"vr"),ar:l(y,"ar"),loading:l(y,"loading"),helpTitle:l(y,"helpTitle"),helpCameraModes:l(y,"helpCameraModes"),helpTourDesc:l(y,"helpTourDesc"),helpExploreDesc:l(y,"helpExploreDesc"),helpWalkDesc:l(y,"helpWalkDesc"),hotspotDefaultTitle:l(y,"hotspotDefaultTitle"),openExternalLink:l(y,"openExternalLink")},E={};t.querySelectorAll(".storysplat-preloader, .storysplat-scroll-controls, .storysplat-waypoint-info, .storysplat-fullscreen-btn, .storysplat-help-btn, .storysplat-help-panel, .storysplat-watermark, .storysplat-waypoint-list-container, .storysplat-hotspot-popup, .storysplat-portal-popup").forEach(t=>t.remove()),c(n,w),t.classList.add("storysplat-viewer-container"),d&&(E.preloader=p(t,m,y));const C=document.createElement("div");if(C.className="storysplat-waypoint-info",C.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(C),E.waypointInfo=C,i&&e.waypoints&&e.waypoints.length>0){const e=document.createElement("div");if(e.className="storysplat-scroll-controls",e.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">${S.previous}</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">${S.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n <button class="storysplat-explore-btn" data-explore-mode="orbit">${S.orbit}</button>\n <button class="storysplat-explore-btn" data-explore-mode="fly">${S.fly}</button>\n </div>\n ${s&&h.length>1?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${h.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===u?"selected":""}" data-mode="tour">${S.tour}</button>`:""}\n ${h.includes("explore")?`<button class="storysplat-mode-btn ${"explore"===u?"selected":""}" data-mode="explore">${S.explore}</button>`:""}\n ${h.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===u?"selected":""}" data-mode="walk">${S.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),E.scrollControls=e,E.progressBar=e.querySelector(".storysplat-progress-bar"),E.progressText=e.querySelector(".storysplat-progress-text"),"pro"===w){const o=e.querySelector(".storysplat-mode-container");o&&t.appendChild(o)}}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",S.fullscreen),e.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 ',t.appendChild(e),E.fullscreenButton=e}if(x&&e.waypoints&&e.waypoints.length>0){const o=document.createElement("div");o.className="storysplat-waypoint-list-container "+(a?"with-fullscreen":"no-fullscreen");const n=e.waypoints.map((t,e)=>`<div class="storysplat-waypoint-item" data-waypoint-index="${e}">${t.name||`Waypoint ${e+1}`}</div>`).join("");o.innerHTML=`\n <button class="storysplat-waypoint-list-toggle" aria-label="${S.waypoints}">\n ${S.waypoints}\n <svg viewBox="0 0 24 24">\n <path d="M7 10l5 5 5-5z"/>\n </svg>\n </button>\n <div class="storysplat-waypoint-list-dropdown">\n ${n}\n </div>\n `,t.appendChild(o),E.waypointListContainer=o;const i=o.querySelector(".storysplat-waypoint-list-toggle"),s=o.querySelector(".storysplat-waypoint-list-dropdown");i?.addEventListener("click",t=>{t.stopPropagation(),i.classList.toggle("open"),s?.classList.toggle("open")}),document.addEventListener("click",t=>{o.contains(t.target)||(i?.classList.remove("open"),s?.classList.remove("open"))})}const M=document.createElement("button");M.className="storysplat-xr-btn storysplat-vr-btn",M.setAttribute("aria-label",S.vr),M.textContent=S.vr,t.appendChild(M),E.vrButton=M;const _=document.createElement("button");if(_.className="storysplat-xr-btn storysplat-ar-btn",_.setAttribute("aria-label",S.ar),_.textContent=S.ar,t.appendChild(_),E.arButton=_,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",S.helpTitle),e.textContent="?",t.appendChild(e),E.helpButton=e;const o=document.createElement("div");o.className="storysplat-help-panel",o.innerHTML=`\n <h3>${S.helpTitle}</h3>\n <p><strong>${S.helpCameraModes}</strong></p>\n <p>• ${S.tour} - ${S.helpTourDesc}</p>\n <p>• ${S.explore} - ${S.helpExploreDesc}</p>\n <p>• ${S.walk} - ${S.helpWalkDesc}</p>\n <br/>\n <p><strong>${S.tour} Mode:</strong></p>\n <p>• Scroll - Move along path</p>\n <p>• Drag - Look around</p>\n <br/>\n <p><strong>${S.explore} Mode:</strong></p>\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 <br/>\n <p><strong>${S.walk} Mode:</strong></p>\n <p>• Click to lock mouse</p>\n <p>• WASD/Arrows - Move</p>\n <p>• Mouse - Look around</p>\n <p>• Shift - Sprint</p>\n <p>• Space - Jump</p>\n `,t.appendChild(o),E.helpPanel=o}const L=document.createElement("div");L.className="storysplat-hotspot-popup",L.id="hotspotContent",L.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${S.close}</button>\n `,t.appendChild(L),E.hotspotPopup=L;const k=L.querySelector(".storysplat-hotspot-popup-close");k?.addEventListener("click",()=>{L.classList.remove("visible","fullscreen")});const A=document.createElement("div");A.className="storysplat-portal-popup",A.innerHTML=`\n <div class="storysplat-portal-popup-title"></div>\n <div class="storysplat-portal-popup-actions">\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-confirm">${S.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${S.cancel}</button>\n </div>\n `,t.appendChild(A),E.portalPopup=A;const P=document.createElement("div");P.className="storysplat-joystick-container",P.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(P),E.joystick=P,E.joystickThumb=P.querySelector(".storysplat-joystick-thumb");const T=document.createElement("div");if(T.className="storysplat-look-zone",T.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 ',t.appendChild(T),E.lookZone=T,!g){const e=document.createElement("div");e.className="storysplat-watermark";const o=v||(b?`https://storysplat.com?ref=${b}`:"https://storysplat.com");e.innerHTML=f?`<a href="${o}" target="_blank">${f}</a>`:`Created with <a href="${o}" target="_blank">StorySplat</a>`,t.appendChild(e),E.watermark=e}if(o.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),E.fpsCounter=e}return E}(e,v,{uiColor:w,showScrollControls:v.waypoints&&v.waypoints.length>0,showModeToggle:T.length>1,showFullscreenButton:!S.hideFullscreenButton,showHelpButton:!S.hideHelpButton&&!S.hideInfoButton,showPreloader:!0,allowedCameraModes:T,defaultCameraMode:z,buttonLabels:M,customPreloaderLogoUrl:S.customPreloaderLogoUrl,hideWatermark:S.hideWatermark,watermarkText:S.watermarkText,watermarkLink:S.watermarkLink,sceneId:o.sceneId,showWaypointList:S.showWaypointList,template:E,debugMode:S.debugMode}));const I=document.createElement("canvas");let D;I.id="storysplat-viewer-canvas",I.style.width="100%",I.style.height="100%",I.style.display="block",e.appendChild(I);const F={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{D=new t.Application(I,{graphicsDeviceOptions:F,mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(o){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",o);try{D=new t.Application(I,{graphicsDeviceOptions:{...F,preferWebGl2:!1},mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(t){console.error("[StorySplat Viewer] WebGL initialization failed completely:",t);const o=document.createElement("div");o.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:rgba(0,0,0,0.8);padding:20px;border-radius:10px;text-align:center;font-family:sans-serif;";const n=document.createElement("h3");n.style.cssText="margin:0 0 10px 0;",n.textContent=l(M,"errorWebGLTitle");const i=document.createElement("p");throw i.style.cssText="margin:0;",i.textContent=l(M,"errorWebGLMessage"),o.appendChild(n),o.appendChild(i),e.appendChild(o),new Error("WebGL initialization failed - browser may not support WebGL")}}I.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),I.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),D.setCanvasFillMode(t.FILLMODE_FILL_WINDOW),D.setCanvasResolution(t.RESOLUTION_AUTO),D.start(),console.log("[StorySplat Viewer] App started");const U=K(),$=U?"mobile":"desktop",V=Q[$];console.log("[SPLAT] Initializing LOD system for device:",U?"mobile":"desktop"),D.scene.gsplat?(D.scene.gsplat.lodUpdateAngle=90,D.scene.gsplat.lodBehindPenalty=2,D.scene.gsplat.radialSorting=!0,D.scene.gsplat.lodUpdateDistance=1,D.scene.gsplat.lodUnderfillLimit=10,D.scene.gsplat.lodRangeMin=V.range[0],D.scene.gsplat.lodRangeMax=V.range[1],D.scene.gsplat.colorUpdateDistance=1,D.scene.gsplat.colorUpdateAngle=4,D.scene.gsplat.colorUpdateDistanceLodScale=2,D.scene.gsplat.colorUpdateAngleLodScale=2,console.log("[SPLAT] LOD system configured:",{preset:$,lodRangeMin:V.range[0],lodRangeMax:V.range[1],lodDistances:V.lodDistances,lodUpdateAngle:90,lodUpdateDistance:1,isMobile:U})):console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let B=0,O=!1,H=null,W=null,G=!1,N=null;const j=v.additionalSplats||[],ot=v.keepMeshesInMemory??!1,it=v.initialSplatExploreMode||"fly";let st=null,at=!1;const rt=new Map;let lt=-1,ct=-1;const pt=new t.Entity("camera");let dt=new t.Color(.1,.1,.1);if(v.backgroundColor){const e=v.backgroundColor.replace("#","");if(6===e.length){const o=parseInt(e.substring(0,2),16)/255,n=parseInt(e.substring(2,4),16)/255,i=parseInt(e.substring(4,6),16)/255;dt=new t.Color(o,n,i),console.log("[StorySplat Viewer] Background color set from config:",v.backgroundColor)}}pt.addComponent("camera",{clearColor:dt,fov:v.fov||60,nearClip:v.nearClip||.1,farClip:v.farClip||1e3}),pt.addComponent("audiolistener"),console.log("[StorySplat Viewer] Camera settings:",{fov:v.fov,nearClip:v.nearClip,farClip:v.farClip,playerHeight:v.playerHeight});const ht=v.playerHeight||1.6;if(v.waypoints&&v.waypoints.length>0){const t=v.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",t),t.position){const e=t.position;pt.setPosition(e.x,e.y,-e.z),console.log("[StorySplat Viewer] Camera position (Z negated):",{x:e.x,y:e.y,z:-e.z})}else pt.setPosition(0,ht,5);if(t.rotation){const e=zt(t.rotation);pt.setRotation(e),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else pt.setPosition(0,ht,5),pt.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)");D.root.addChild(pt);const ut=new t.Entity("light");ut.addComponent("light",{type:t.LIGHTTYPE_DIRECTIONAL,color:new t.Color(1,1,1),intensity:1,castShadows:!1}),ut.setEulerAngles(45,45,0),D.root.addChild(ut),console.log("[StorySplat Viewer] Light added");const mt=5*(v.cameraMovementSpeed||1),yt=new C(pt,D,{moveSpeed:mt,moveFastSpeed:2.5*mt,moveSlowSpeed:.5*mt,rotateSpeed:800/(v.cameraRotationSensitivity||4e3),enableOrbit:!0,enableFly:!0,enablePan:!0,invertRotation:v.invertCameraRotation,moveDamping:.75,rotateDamping:.75,zoomDamping:.8});let gt=null;v.collisionMeshesData&&v.collisionMeshesData.length>0&&(gt=new _(pt,D,{moveSpeed:mt,sprintMultiplier:2,lookSensitivity:1/(v.cameraRotationSensitivity||4e3)*10,playerHeight:v.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),gt.createCollisionMeshes(v.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),yt.setCollisionEntities(gt.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}));let ft=z,vt=!0;"tour"===z&&yt.disable();const bt=new t.Picker(D,1,1,!0);let xt=!1,wt=null;let St=null,Et=null,Ct=!1;function Mt(){St&&(St.enabled=!1,Ct=!1)}let _t=0;D.on("update",e=>{var o,n;R.fpsCounter&&(_t+=e,_t>=.5&&(_t=0,n=1/e,(o=R).fpsCounter&&(o.fpsCounter.textContent=`${Math.round(n)} FPS`))),"walk"===ft&>?gt.update(e):yt.update(e),vt||function(){const t=pt.getPosition();Ce.forEach(e=>{const o=e.hotspotData;if(!o)return;if("proximity"!==(e.mediaTriggerMode||"click"))return;const n=e.getPosition(),i=t.distance(n)<=(e.proximityDistance||5),s=e.wasInProximity||!1;if(i&&!s){if("video"===o.type&&e.videoElement)go(e,o);else if("gif"===o.type)e.enabled=!0;else if(("sphere"===o.type||"image"===o.type)&&e.audioElements){const t=e.audioElements,n=t.audio;n&&n.paused&&(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),n.play().catch(t=>console.error("[Audio] Hotspot audio play failed (proximity):",t)),console.log("[Audio] Hotspot audio started (proximity):",o.title))}e.wasInProximity=!0}else if(!i&&s){if("video"===o.type&&e.videoElement)fo(e);else if("gif"===o.type)e.enabled=!1;else if(("sphere"===o.type||"image"===o.type)&&e.audioElements){const t=e.audioElements.audio;t&&!t.paused&&(t.pause(),console.log("[Audio] Hotspot audio stopped (proximity):",o.title))}e.wasInProximity=!1}})}(),function(){if(0===Ie.size)return;const t=pt.getPosition();Ie.forEach((e,o)=>{const{entity:n,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=n.sound?.slot(s);if(l&&i.spatialSound){const s=n.getPosition(),a=t.distance(s),c=i.maxDistance||20;a<=c&&!r?(console.log(`[Audio] Proximity play: ${o}, distance=${a.toFixed(2)}, maxDistance=${c}`),l.play(),e.playing=!0):a>c&&r&&i.stopOnExit&&(console.log(`[Audio] Proximity stop: ${o}, distance=${a.toFixed(2)}`),l.stop(),e.playing=!1)}})}(),function(){if(0===co.size)return;const t=pt.getPosition();co.forEach((e,o)=>{const{entity:n,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=n.sound?.slot(s);if(l&&!1!==i.spatialSound){const o=n.getPosition();t.distance(o)<=(i.maxDistance||100)&&!r&&!1!==i.autoplay&&(l.isPlaying||(l.play(),e.playing=!0))}})}(),function(){if(!v.waypoints?.length)return;const e=pt.getPosition();v.waypoints.forEach((o,n)=>{const i=new t.Vec3(o.position?.x??0,o.position?.y??0,-(o.position?.z??0)),s=e.distance(i),a=o.triggerDistance??1;s<=a?po.has(n)||(po.add(n),console.log(`[StorySplat] Waypoint ${n} triggered (distance: ${s.toFixed(2)}, threshold: ${a})`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{if("audio"===t.type){const e=t.id;if(!e)return;const o=Ie.get(e);if(o&&o.assetReady&&!o.playing){const t=o.entity.sound?.slot(o.slotId);t&&(t.play(),o.playing=!0)}}})}(o)):po.has(n)&&(po.delete(n),console.log(`[StorySplat] Waypoint ${n} exited`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{if("audio"===t.type){if(t.data?.stopOnExit??!1){const e=t.id;if(!e)return;const o=Ie.get(e);if(o&&o.playing){const t=o.entity.sound?.slot(o.slotId);t&&(t.stop(),o.playing=!1)}}}})}(o))})}(),xt&&Ct&&function(){if(St?.enabled&&pt){const t=pt.forward.clone(),e=pt.getPosition(),o=e.clone().add(t.mulScalar(1.5));o.y-=.1,St.setPosition(o),St.lookAt(e),St.rotateLocal(90,0,0)}}()}),D.on("joystick:left",(t,e,o,n)=>{if(t<0||e<0)y(R,!1,0,0,60);else{y(R,!0,o-t,n-e,60)}}),D.on("joystick:right",(t,e,o,n)=>{g(R,!(t<0||e<0))});const Lt=R.scrollControls?.querySelectorAll(".storysplat-explore-btn"),kt=t=>{Lt?.forEach(e=>{const o=e.getAttribute("data-explore-mode");e.classList.toggle("selected",o===t)})};function At(t){"walk"===ft&>?gt.disable():"explore"===ft&&yt.disable(),ft=t,console.log("[StorySplat Viewer] Switching to mode:",t);const e=K();if("walk"===t&>)vt=!1,yt.disable(),gt.enable(),m(R,!1);else if("explore"===t){gt&>.disable(),ee=0,oe=0,ne=!1,vt=!1,yt.disable(),yt.enable(),yt.enableOrbit=!0,yt.enableFly=!0,yt.enablePan=!0;const t=it;Kt&&Qt?(yt.syncFromPose(Kt,Qt),console.log("[StorySplat Viewer] Synced camera from waypoint pose for explore mode")):yt.syncFromCamera();(async()=>{try{const t=.25;bt.resize(Math.floor(Eo.clientWidth*t),Math.floor(Eo.clientHeight*t));const e=D.scene.layers.getLayerByName("World");if(e){bt.prepare(pt.camera,D.scene,[e]);const o=Math.floor(.5*Eo.clientWidth*t),n=Math.floor(.5*Eo.clientHeight*t),i=await bt.getWorldPointAsync(o,n);if(i){const t=pt.getPosition().distance(i);t>.5&&t<500&&(yt.syncFromCamera(i),console.log("[StorySplat Viewer] Updated focus target at distance:",t.toFixed(2)))}}}catch(t){}})(),yt.setMode(t),kt(t),e&&m(R,"fly"===t)}else yt.disable(),gt&>.disable(),vt=!0,m(R,!1);i.emit("modeChange",{mode:t})}Lt?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==ft)return;"orbit"===t.getAttribute("data-explore-mode")?(yt.setMode("orbit"),kt("orbit"),U&&m(R,!1)):(yt.setMode("fly"),kt("fly"),U&&m(R,!0))})}),D.on("cameracontrols:modechange",t=>{"orbit"!==t&&"fly"!==t||(kt(t),U&&"explore"===ft&&m(R,"fly"===t))});const Pt=(t,e)=>{R.preloader&&function(t,e,o,n){const i=t.querySelector(".storysplat-preloader-bar"),s=t.querySelector(".storysplat-preloader-text"),a=Math.max(0,Math.min(100,100*e));i&&(i.style.width=`${a}%`),s&&(s.textContent=o||`${n||r.loading} ${Math.round(a)}%`)}(R.preloader,t,e,l(M,"loading")),i.emit("progress",{progress:t,text:e})};function Tt(t){return["firebasestorage.googleapis.com/v0/b/story-splat","storage.googleapis.com/story-splat","storysplat.com","discover.storysplat.com"].some(e=>t.includes(e))}function zt(e){if("_w"in e||"w"in e){const o=e._x??e.x??0,n=e._y??e.y??0,i=e._z??e.z??0,s=e._w??e.w??1;return new t.Quat(-o,-n,i,s)}if("x"in e&&"y"in e&&"z"in e){const o=new t.Quat;return o.setFromEulerAngles(e.x||0,e.y||0,e.z||0),o}return new t.Quat}function Rt(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function It(t){const e=rt.get(t);e&&(e.destroy(),rt.delete(t),console.log("[SplatSwap] Splat disposed:",t))}function Dt(t){const e=rt.get(t);return!!e&&(e.enabled=!0,st=t,console.log("[SplatSwap] Splat shown:",t),!0)}async function Ft(e){if(!rt.has(e)&&e!==st&&!G){console.log("[SplatSwap] Preloading splat:",e);try{const o=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((n,i)=>{o.ready(()=>{if(G)return void i(new Error("Viewer destroyed"));const s=new t.Entity("splat-preload");s.addComponent("gsplat",{asset:o,unified:!0});const a=v.scale||{x:1,y:1,z:1},r=v.invertXScale||!1,l=v.invertYScale||!1,c={x:r?-a.x:a.x,y:l?-a.y:a.y,z:r!==l?-a.z:a.z};s.setLocalScale(c.x,c.y,c.z);const p=v.position||[0,0,0];s.setPosition(p[0],p[1],-p[2]);const d=v.rotation||[0,0,0],h=[180+d[0]*(180/Math.PI),d[1]*(180/Math.PI),-d[2]*(180/Math.PI)];s.setEulerAngles(h[0],h[1],h[2]),s.enabled=!1,D.root.addChild(s),rt.set(e,s),console.log("[SplatSwap] Preload complete:",e),n()}),o.on("error",t=>{console.error("[SplatSwap] Preload error:",t),i(t)}),D.assets.add(o),D.assets.load(o)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function Ut(t,e=!1){if("explore"!==ft)return;const o=e?it:t||it;if(yt){yt.mode!==o&&(yt.setMode(o),console.log(`[SplatSwap] Switching explore sub-mode to: ${o}`),kt(o),U&&m(R,"fly"===o))}}async function $t(e){if(e!==st&&!at&&!G){at=!0,console.log("[SplatSwap] Switching to splat:",e);try{if(st&&rt.has(st))if(ot){const t=rt.get(st);t&&Rt(t)}else It(st);else H&&(H.enabled=!1);if(rt.has(e))Dt(e),i.emit("splatChange",{url:e,isOriginal:!1});else{const o=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((n,s)=>{o.ready(()=>{if(G)return void s(new Error("Viewer destroyed"));const a=new t.Entity("splat-swap");a.addComponent("gsplat",{asset:o,unified:!0});const r=v.scale||{x:1,y:1,z:1},l=v.invertXScale||!1,c=v.invertYScale||!1,p={x:l?-r.x:r.x,y:c?-r.y:r.y,z:l!==c?-r.z:r.z};a.setLocalScale(p.x,p.y,p.z);const d=v.position||[0,0,0];a.setPosition(d[0],d[1],-d[2]);const h=v.rotation||[0,0,0],u=[180+h[0]*(180/Math.PI),h[1]*(180/Math.PI),-h[2]*(180/Math.PI)];a.setEulerAngles(u[0],u[1],u[2]),D.root.addChild(a),rt.set(e,a),st=e,i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e),n()}),o.on("error",t=>{console.error("[SplatSwap] Load error:",t),s(t)}),D.assets.add(o),D.assets.load(o)})}const o=j.findIndex(t=>t.url===e);-1!==o&&async function(t){if(!j||0===j.length)return;const e=(t+1)%j.length,o=j[e];o&&o.url&&await Ft(o.url)}(o)}catch(t){console.error("[SplatSwap] Error switching splat:",t)}finally{at=!1}}}function Vt(){return v.sogUrl?v.sogUrl:v.splatUrl?v.splatUrl:v.fallbackUrls&&v.fallbackUrls.length>0?v.fallbackUrls[0]:""}function Bt(){if(!j||0===j.length)return;const e=v.waypoints?.length||1,o=100*Ht,n=Math.round(Ht*Math.max(1,e-1));if(Math.abs(o-lt)<.1&&n===ct)return;lt=o,ct=n;let s=null,a=null,r=-1/0,l=-1/0;for(const t of j)-1!==t.waypointIndex?n>=t.waypointIndex&&t.waypointIndex>r&&(r=t.waypointIndex,s=t):-1!==t.percentage&&o>=t.percentage&&t.percentage>l&&(l=t.percentage,a=t);const c=a||s,p=c&&"__ORIGINAL__"===c.url,d=Vt(),h=c?p?d:c.url:d;h&&h!==st&&(h===d&&H&&!st?st=d:h===d&&H?(rt.forEach((t,e)=>{e!==d&&(ot?Rt(t):It(e))}),H&&(H.enabled=!0),st=d,i.emit("splatChange",{url:d,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),Ut(void 0,!0)):($t(h),c&&Ut(c.defaultExploreMode,!1)),c&&c.skyboxUrl&&!p&&function(e,o=0){console.log("[SplatSwap] Applying skybox:",e,"rotation:",o);const n=new t.Asset("skybox-swap-"+Date.now(),"cubemap",{url:e},{type:t.TEXTURETYPE_RGBM,mipmaps:!0});n.ready(()=>{if(!G)try{if(D.scene.skybox=n.resource,D.scene.skyboxMip=0,0!==o){const e=new t.Quat;e.setFromEulerAngles(0,o*(180/Math.PI),0),D.scene.skyboxRotation=e}console.log("[SplatSwap] Skybox applied successfully")}catch(t){console.error("[SplatSwap] Error applying skybox:",t)}}),n.on("error",t=>{console.error("[SplatSwap] Skybox load error:",t)}),D.assets.add(n),D.assets.load(n)}(c.skyboxUrl,c.skyboxRotation||0))}function Ot(){const t=Vt();st!==t&&(rt.forEach((e,o)=>{o!==t&&(ot?Rt(e):It(o))}),H&&(H.enabled=!0),st=t,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),Ut(void 0,!0))}let Ht=0,Wt=0,Gt=!1;const Nt=v.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,qt=v.waypoints?.length||1,jt=Math.max(1,20*(qt-1)),Yt=void 0!==v.autoplaySpeed?60*v.autoplaySpeed/jt:1e3/Nt,Xt=v.loopMode;let Zt;Zt=!0===Xt?"loop":!1===Xt?"none":"loop"===Xt||"pingpong"===Xt||"none"===Xt?Xt:"loop";let Jt=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:Zt,playbackSpeed:Yt,totalDuration:Nt,autoPlay:v.autoPlay,rawLoopMode:v.loopMode});let Kt=null,Qt=null;const te=.01+.1*(v.transitionSpeed||1);let ee=0,oe=0;let ne=!1,ie=0,se=0;const ae=[],re=[],le=[],ce=v.fov||60;let pe=ce;function de(o){if(!vt||ae.length<2)return;o=Math.max(0,Math.min(1,o));const n=ae.length,s=o*(n-1),a=Math.min(Math.floor(s),n-2),r=s-a,l=ae[a],c=ae[a+1];Kt=new t.Vec3(nt(l.x,c.x,r),nt(l.y,c.y,r),nt(l.z,c.z,r));const p=re[a],d=re[a+1];Qt=new t.Quat,Qt.slerp(p,d,r);const h=le[a],u=le[a+1];pe=nt(h,u,r);const m=Math.round(o*(n-1));if(m!==B){const o=B;B=m;const n=v.waypoints[m],s=n.cameraMode||"first-person";"orbit"===s&&(n.orbitTarget?new t.Vec3(n.orbitTarget.x,n.orbitTarget.y,-(n.orbitTarget.z||0)):new t.Vec3(ae[m].x,ae[m].y,ae[m].z)),i.emit("waypointChange",{index:m,waypoint:n,prevIndex:o,cameraMode:s}),y=m,g=o,Ie.forEach((t,e)=>{const{entity:o,waypointIndex:n,config:i,slotId:s,autoplayTriggered:a}=t,r=o.sound?.slot(s);if(!r)return;const l=g===n&&y!==n;y===n&&g!==n&&i.autoplay&&!a&&(console.log(`[Audio] Autoplay waypoint audio: ${e} at waypoint ${n}`),r.isPlaying||(r.play(),t.playing=!0,t.autoplayTriggered=!0)),l&&i.stopOnExit&&t.playing&&(console.log(`[Audio] Stopping waypoint audio on exit: ${e}`),r.isPlaying&&(r.stop(),t.playing=!1,t.autoplayTriggered=!1))}),De(),Fe(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}var y,g;i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,o)),index:B}),Bt()}function he(t,e=!1){const o=Math.max(0,Math.min(1,t));Wt=o,e?me(o):(Ht=o,de(Ht))}v.waypoints&&v.waypoints.length>0&&(v.waypoints.forEach(e=>{const o=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,v.playerHeight||1.6,0);ae.push(o);const n=e.rotation?zt(e.rotation):new t.Quat;re.push(n);let i=e.fov||ce;i<3.5&&(i*=180/Math.PI),le.push(i)}),ae.length>0&&(Kt=ae[0].clone(),Qt=re[0].clone(),pe=le[0])),D.on("update",function(){if(!vt)return;if(!Gt){const t=Wt-Ht;Math.abs(t)>1e-4&&(Ht+=t*te,de(Ht))}if(!Kt||!Qt)return;const e=pt.getPosition(),o=new t.Vec3;o.lerp(e,Kt,te),pt.setPosition(o.x,o.y,o.z);const n=pt.camera;if(n&&le.length>0){const t=nt(n.fov,pe,te);n.fov=t}ne||(ee*=.95,oe*=.95,Math.abs(ee)<.01&&(ee=0),Math.abs(oe)<.01&&(oe=0));const i=new t.Quat;i.setFromEulerAngles(oe,ee,0);const s=new t.Quat;s.mul2(Qt,i);const a=pt.getRotation(),r=new t.Quat;r.slerp(a,s,te),pt.setRotation(r)});const ue=500*(v.transitionSpeed||1);function me(t,e=ue){const o=Ht,n=performance.now();Gt=!0;const i=()=>{const s=performance.now()-n,a=Math.min(s/e,1);Ht=o+(t-o)*(a<.5?2*a*a:(4-2*a)*a-1),de(Ht),a<1?requestAnimationFrame(i):Gt=!1};requestAnimationFrame(i)}function ye(t){if(!v.waypoints||t<0||t>=v.waypoints.length)return;if(!vt)return;const e=t/Math.max(1,v.waypoints.length-1);Wt=e,me(e)}function ge(){if(!v.waypoints||0===v.waypoints.length)return;const t=v.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=v.scrollAmount||10;let e=Ht+t/100;e>1&&(e="loop"===Zt?0:1),Wt=e,me(e)}else{let t=B+1;t>=v.waypoints.length&&(t="loop"===Zt?0:v.waypoints.length-1),ye(t)}}function fe(){if(!v.waypoints||0===v.waypoints.length)return;const t=v.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=v.scrollAmount||10;let e=Ht-t/100;e<0&&(e="loop"===Zt?1:0),Wt=e,me(e)}else{let t=B-1;t<0&&(t="loop"===Zt?v.waypoints.length-1:0),ye(t)}}let ve=0,be=null;function xe(t){if(!O)return;0===ve&&(ve=t);const e=(t-ve)/1e3;ve=t;const o=Yt*e*Jt;if(Ht+=o,Wt+=o,Ht>=1)switch(console.log("[StorySplat Viewer] End of tour reached, loopMode:",Zt),Zt){case"loop":console.log("[StorySplat Viewer] Looping - restarting at beginning"),Ht=0,Wt=0;break;case"pingpong":console.log("[StorySplat Viewer] Pingpong - reversing direction"),Ht=1,Wt=1,Jt=-1;break;case"none":return console.log("[StorySplat Viewer] No loop - stopping playback"),Ht=1,Wt=1,Se(),void i.emit("playbackComplete");default:return console.log("[StorySplat Viewer] Unknown loopMode, stopping:",Zt),Ht=1,Wt=1,Se(),void i.emit("playbackComplete")}else if(Ht<=0)if("pingpong"===Zt)console.log("[StorySplat Viewer] Pingpong - reversing to forward"),Ht=0,Wt=0,Jt=1;else Ht=0,Wt=0;de(Ht),be=requestAnimationFrame(xe)}function we(){if(N)return N.play(),void i.emit("playbackStart");O||!v.waypoints||v.waypoints.length<2||(O=!0,ve=0,Jt=1,i.emit("playbackStart"),be=requestAnimationFrame(xe))}function Se(){if(N)return N.pause(),void i.emit("playbackStop");O=!1,be&&(cancelAnimationFrame(be),be=null),i.emit("playbackStop")}const Ee=D.graphicsDevice.canvas;Ee.addEventListener("wheel",t=>{if(!vt)return;t.preventDefault();const e=v.scrollSpeed||.1,o=v.scrollAmount||100,n=v.waypoints?.length||2,i=Math.max(20,20*(n-1)),s=100*(Math.abs(t.deltaY)/100)*e*(o/100)/i,a=t.deltaY>0?s:-s;Wt=Math.max(0,Math.min(1,Wt+a))},{passive:!1}),Ee.addEventListener("pointerdown",t=>{vt&&(ne=!0,ie=t.clientX,se=t.clientY)},{capture:!0}),Ee.addEventListener("pointermove",t=>{if(!vt||!ne)return;const e=t.clientX-ie,o=t.clientY-se;ie=t.clientX,se=t.clientY;ee+=.3*-e,oe+=.3*-o,oe=Math.max(-60,Math.min(60,oe))},{capture:!0}),Ee.addEventListener("pointerup",()=>{ne=!1},{capture:!0}),Ee.addEventListener("pointerleave",()=>{ne=!1},{capture:!0});const Ce=[],Me=[],_e=R.portalPopup;let Le=null;const ke=()=>{_e&&(_e.classList.remove("visible"),Le=null)};if(_e){const t=_e.querySelector(".storysplat-portal-popup-confirm"),e=_e.querySelector(".storysplat-portal-popup-cancel");t?.addEventListener("click",()=>{if(Le){const t=Le;ke(),wo(t)}}),e?.addEventListener("click",()=>{ke()})}function Ae(e){const o=parseInt(e.slice(1,3),16)/255,n=parseInt(e.slice(3,5),16)/255,i=parseInt(e.slice(5,7),16)/255;return new t.Color(o,n,i)}const Pe=[],Te=[];let ze=!1;const Re=new Map,Ie=new Map;function De(){Ce.forEach(t=>{if(t.audioElements){const e=t.audioElements.audio;e&&!e.paused&&(e.pause(),e.currentTime=0)}if(t.videoElement){const e=t.videoElement;e.paused||e.pause()}t.wasInProximity=!1})}function Fe(t){const e=t.querySelector(".storysplat-hotspot-popup");e&&(e.querySelectorAll("video").forEach(t=>{t.pause(),t.currentTime=0}),e.querySelectorAll("iframe").forEach(t=>{t.src=""}))}const Ue=new Map,$e=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%2Fsparkle.png?alt=media&token=466739a4-ccd7-4295-88c2-dedd681a34f0",rain:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Frain.png?alt=media&token=13478487-6259-4906-838c-ad592db893e4",smoke:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsmoke.png?alt=media&token=6cece4f8-87cf-47f9-974d-c4dd6a649f0f"};function Be(e,o){return new Promise((n,i)=>{if($e.has(e))return void n($e.get(e));const s=new t.Asset(e,"texture",{url:o});s.on("load",()=>{if(G)return console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${e}`),void i(new Error("Viewer destroyed"));const t=s.resource;$e.set(e,t),n(t)}),s.on("error",t=>{console.error(`[Particle] Failed to load texture: ${e}`,t),i(t)}),D.assets.add(s),D.assets.load(s)})}function Oe(e){const o=new t.Entity(e.name||"particle-system"),n=(t,e=0)=>({x:t?._x??t?.x??e,y:t?._y??t?.y??e,z:t?._z??t?.z??e}),i=t=>({r:t?.r??1,g:t?.g??1,b:t?.b??1,a:t?.a??1}),s=n(e.emitterPosition,0),a=n(e.gravity,0),r=i(e.color1),l=i(e.color2),c=i(e.colorDead),p=n(e.direction1,0),d=n(e.direction2,0),h=((e.minLifeTime??1)+(e.maxLifeTime??3))/2;let u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.1,.1,.1),y=new t.Vec3(0,0,0);if("sphere"===e.emitterType||"sphere"===e.emitterShape){u=t.EMITTERSHAPE_SPHERE;const o=e.emitterRadius||.1;m=new t.Vec3(o,o,o)}else if("box"===e.emitterShape&&e.emitBoxMin&&e.emitBoxMax){u=t.EMITTERSHAPE_BOX;const o=n(e.emitBoxMin,0),i=n(e.emitBoxMax,0);m=new t.Vec3(Math.abs(i.x-o.x)/2,Math.abs(i.y-o.y)/2,Math.abs(i.z-o.z)/2),y=new t.Vec3((o.x+i.x)/2,(o.y+i.y)/2,-(o.z+i.z)/2)}else"point"===e.emitterShape?(u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.01,.01,.01)):m=new t.Vec3(e.emitterExtents?.x||e.emitterRadius||.1,e.emitterExtents?.y||e.emitterRadius||.1,e.emitterExtents?.z||e.emitterRadius||.1);let g=t.BLEND_ADDITIVEALPHA;const f=e.blendMode||"";"BLENDMODE_STANDARD"===f||"normal"===f||"alpha"===f?g=t.BLEND_NORMAL:"BLENDMODE_MULTIPLY"===f||"multiply"===f?g=t.BLEND_MULTIPLICATIVE:"BLENDMODE_ONEONE"===f?g=t.BLEND_ADDITIVE:"BLENDMODE_ADD"!==f&&"BLENDMODE_MULTIPLYADD"!==f||(g=t.BLEND_ADDITIVEALPHA);const v=a.x||0,b=a.y||0,x=-(a.z||0),w=v*h,S=b*h,E=x*h,C=e.minAngularSpeed??0,M=e.maxAngularSpeed??e.angularSpeed??0,_=e.minInitialRotation??0,L=e.maxInitialRotation??0,k=e.minSize??.1,A=e.maxSize??.3,P=e.minScaleX??1,T=e.maxScaleX??1,z=e.minScaleY??1,R=e.maxScaleY??1,I=e.minEmitPower??1,D=e.maxEmitPower??2,F=(p.x+d.x)/2,U=(p.y+d.y)/2,$=-(p.z+d.z)/2;o.addComponent("particlesystem",{numParticles:e.numParticles||500,lifetime:h,rate:e.emitRate||50,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:_,startAngle2:0!==L?L:360,radialSpeedGraph:new t.Curve([0,I,1,D]),localVelocityGraph:new t.CurveSet([[0,F*I],[0,U*I],[0,$*I]]),localVelocityGraph2:new t.CurveSet([[0,F*D],[0,U*D],[0,$*D]]),velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,E]]),scaleGraph:new t.Curve([0,k*P,1,A*T]),scaleGraph2:new t.Curve([0,k*z,1,A*R]),rotationSpeedGraph:new t.Curve([0,C]),rotationSpeedGraph2:M!==C?new t.Curve([0,M]):void 0,colorGraph:new t.CurveSet([[0,r.r,.5,l.r,1,c.r],[0,r.g,.5,l.g,1,c.g],[0,r.b,.5,l.b,1,c.b]]),alphaGraph:new t.Curve([0,r.a??1,.5,l.a??.8,1,c.a??0]),blend:g,depthWrite:e.depthWrite??!1,depthSoftening:e.softParticles??0,lighting:e.lighting??!1,halfLambert:e.halfLambert??!1,alignToMotion:e.alignToMotion??!1,stretch:e.stretch||0,preWarm:e.preWarm??!1,loop:e.loop??!0,autoPlay:e.autoPlay??!0,sort:e.sort??0,orientation:e.orientation??0}),o.particlesystem&&(o.particlesystem.localSpace=e.localSpace??!1);const V=e.translationPivot?.x??0,B=e.translationPivot?.y??0;o.setPosition(s.x+y.x+V,s.y+y.y+B,-s.z+y.z);const O=e.renderingGroupId??3;return o.particlesystem&&void 0!==O&&(o.particlesystem.drawOrder=O),console.log(`[Particle] Entity configured at position: (${s.x+y.x+V}, ${s.y+y.y+B}, ${-s.z+y.z})`),console.log(`[Particle] Emitter shape: ${e.emitterShape||"box"}, extents: ${m.x}, ${m.y}, ${m.z}`),console.log(`[Particle] Gravity: (${v}, ${b}, ${x})`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${C} - ${M}, Initial rotation: ${_} - ${L}`),console.log(`[Particle] TranslationPivot: (${V}, ${B}), RenderingGroupId: ${O}`),o}const He=new Map;function We(e){const{type:o,component:n,node:i,animations:s}=e;console.log("[CustomMesh] Playing animation, type:",o,"node:",i?.name,"animations:",s?.length);try{if("pc-anim"===o){if(console.log("[CustomMesh] Using simple pc-anim approach (like HTML export)"),!n)return;n.playing=!0,e.isPlaying=!0,console.log("[CustomMesh] Animation started - playing:",n.playing)}else if("animation"===o){if(!n)return;if(n.playing=!0,n.loop=!0,"function"==typeof n.play){const t=Object.keys(n.animations||{});t.length>0?(n.play(t[0],1),console.log("[CustomMesh] Playing legacy animation clip:",t[0])):n.play()}}else if("glb-skeleton"===o){console.log("[CustomMesh] Starting GLB skeleton animation playback");const{modelEntity:o,asset:n,animations:i}=e;if(!o)return;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 t={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){const e=o.anim;e.loadStateGraph?.(t);const i=n;e.assignAnimation?.("Base.Idle",i,"Base"),console.log("[CustomMesh] AnimComponent added and animation assigned")}}if(o.anim)o.anim.playing=!0,o.anim.speed=1,e.animComponent=o.anim,e.isPlaying=!0,console.log("[CustomMesh] GLB animation playing via AnimComponent");else{console.log("[CustomMesh] Falling back to manual curve sampling"),e.isPlaying=!0,e.startTime=Date.now();const i=i=>{if(!e.isPlaying)return;const s=(Date.now()-(e.startTime||Date.now()))/1e3%(n._duration||n.duration||1);try{(n._curves||[]).forEach((e,i)=>{if(!e)return;const a=n._paths?.[i];if(!a||!a.entityPath)return;let r=o.findByPath(a.entityPath.join("/"));if(r||(r=o.findByName(a.entityPath[a.entityPath.length-1])),!r)return;const l=e.evaluate?.(s);if(null==l)return;const c=a.component||a.propertyPath?.[0];"localPosition"===c||"position"===c?Array.isArray(l)&&r.setLocalPosition(l[0],l[1],l[2]):"localRotation"===c||"rotation"===c?Array.isArray(l)&&l.length>=4&&r.setLocalRotation(new t.Quat(l[0],l[1],l[2],l[3])):"localScale"!==c&&"scale"!==c||Array.isArray(l)&&r.setLocalScale(l[0],l[1],l[2])})}catch(t){}};e.updateHandler=i,D.on("update",i),console.log("[CustomMesh] Manual animation playback started")}}catch(t){console.error("[CustomMesh] Error setting up GLB animation:",t)}}}else if("anim"===o&&n&&(n.playing=!0,n.speed=1,n.baseLayer)){const t=(n.baseLayer.states||[]).find(t=>"START"!==t&&"END"!==t&&"ANY"!==t);t&&(n.baseLayer.play?.(t),console.log("[CustomMesh] Playing anim state:",t))}n&&console.log("[CustomMesh] Animation component state - playing:",n.playing,"speed:",n.speed)}catch(t){console.error("[CustomMesh] Error in playAnimComponentV2:",t)}}function Ge(t){const{type:e,component:o}=t;if(o||"glb-skeleton"===e)try{"pc-anim"===e&&o?(o.playing=!1,t.isPlaying=!1,console.log("[CustomMesh] pc-anim animation paused")):"glb-skeleton"===e?(t.isPlaying=!1,t.animComponent&&(t.animComponent.playing=!1,console.log("[CustomMesh] GLB skeleton animation paused via AnimComponent")),t.updateHandler&&(D.off("update",t.updateHandler),t.updateHandler=null,console.log("[CustomMesh] GLB manual animation paused"))):o&&(o.playing=!1,o.speed=0,"animation"===e&&"function"==typeof o.pause&&o.pause())}catch(t){console.error("[CustomMesh] Error pausing animation:",t)}}function Ne(e,o){if(console.log("[CustomMesh] Loading mesh",o,":",e.name,e),!e.modelUrl||"string"!=typeof e.modelUrl||""===e.modelUrl.trim())return console.warn("[CustomMesh] Skipping mesh",e.name,"- no valid modelUrl provided. Config:",e),null;const n=new t.Entity("custom-mesh-"+o),i=e.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)),e.rotation){const t=e.rotation,o=180/Math.PI;n.setEulerAngles((t._x??t.x??0)*o,(t._y??t.y??0)*o,(t._z??t.z??0)*o)}if(e.scale){const t=e.scale;n.setLocalScale(t._x??t.x??1,t._y??t.y??1,t._z??t.z??1)}const s=e.modelUrl.trim();console.log("[CustomMesh] Loading model from URL:",s);const a=new t.Asset("mesh-model-"+o,"container",{url:s});D.assets.add(a);const r=e.id||`mesh-${o}`,l={entity:n,config:e,isAnimPlaying:!1,audioPlaying:!1};return He.set(r,l),a.ready(o=>{try{if(console.log("[CustomMesh] Model loaded:",e.name),!o||!o.resource)return void console.error("[CustomMesh] Invalid asset resource for mesh:",e.name);const i=o.resource,s=i?.instantiateRenderEntity();if(!s)return void console.error("[CustomMesh] Failed to instantiate render entity for mesh:",e.name);function a(e){e.enabled=!0,e.children&&e.children.forEach(e=>{e instanceof t.Entity&&a(e)})}n.addChild(s),n.modelEntity=s,a(s);let r=0;if(s.forEach(()=>{r++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",r),"animated"!==e.opacityMode&&void 0!==e.opacity&&e.opacity<1&&(Ze(n,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name)),e.billboard){const p=n.getEulerAngles().clone();n._billboardActive=!e.billboardRange,D.on("update",()=>{if(!n.enabled)return;if(!n._billboardActive)return void n.setEulerAngles(p.x,p.y,p.z);const t=pt.getPosition(),e=n.getPosition(),o=t.x-e.x,i=t.z-e.z,s=Math.atan2(o,i)*(180/Math.PI),a=n.getEulerAngles();n.setEulerAngles(a.x,s,a.z)}),console.log("[CustomMesh] Billboard enabled for:",e.name,e.billboardRange?"(with range control)":"(always active)")}const c=(i?.animations?.length??0)>0;if(console.log("[CustomMesh] Asset resource for",e.name,":",{hasAnimations:c,animationCount:i?.animations?.length||0,animations:i?.animations?.map(t=>t?.name||t?.resource?.name||"unnamed")||[],interactionConfig:e.interaction}),c||e.interaction&&e.interaction.playModelAnimation){const d=[],h=s.anim;if(h&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),d.push({type:"pc-anim",component:h,modelEntity:s})),0===d.length){function u(e,o=0){e.anim&&!d.find(t=>t.component===e.anim)&&(d.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(d.push({type:"animation",component:e.animation,node:e}),console.log("[CustomMesh] Found ANIMATION (legacy) component on:",e.name)),e.children&&e.children.forEach(e=>{e instanceof t.Entity&&u(e,o+1)})}u(s)}if(0===d.length&&c){const m=i?.animations||[];console.log("[CustomMesh] No anim component found - falling back to GLB skeleton approach"),console.log("[CustomMesh] GLB has",m.length,"embedded animations");try{d.push({type:"glb-skeleton",modelEntity:s,asset:o,animations:m,animationNames:m.map(t=>t.resource?.name||t.name||"Animation"),isPlaying:!1,currentTime:0}),console.log("[CustomMesh] GLB skeleton animations stored:",m.map(t=>t.resource?.name||t.name))}catch(y){console.error("[CustomMesh] Error setting up GLB animations:",y)}}if(d.length>0){l.allAnimComponents=d,l.animComponent=d[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",d.length,"- Types:",d.map(t=>t.type).join(", "));const g=e.interaction?.animationAutoPlay;g&&(d.forEach(t=>{We(t)}),l.isAnimPlaying=!0,console.log("[CustomMesh] Auto-playing all animations for:",e.name))}else console.warn("[CustomMesh] No animation components could be set up for mesh:",e.name)}else console.log("[CustomMesh] No animations to setup for:",e.name,"(no GLB animations and playModelAnimation not enabled)");e.interaction&&e.interaction.playAudio&&e.interaction.audioUrl&&function(e,o,n){const i=o.interaction;if(!i)return;const s=o.id||o.name,a=`mesh-audio-${s}`;e.addComponent("sound",{positional:i.audioSpatial||!1,distanceModel:i.audioDistanceModel||"exponential",refDistance:i.audioRefDistance||1,maxDistance:i.audioMaxDistance||100,rollOffFactor:i.audioRolloffFactor||1,slots:{[a]:{name:a,loop:i.audioLoop||!1,autoPlay:!1,volume:void 0!==i.audioVolume?i.audioVolume:1,pitch:1}}}),n.audioSlotId=a;const r=new t.Asset(`mesh-audio-asset-${s}`,"audio",{url:i.audioUrl});D.assets.add(r),r.ready(()=>{const t=e.sound?.slot(a);t&&(t.asset=r.id),console.log("[CustomMesh] Audio loaded for mesh:",o.name,"Spatial:",i.audioSpatial)}),r.on("error",t=>{console.error("[CustomMesh] Failed to load mesh audio:",o.name,t)}),D.assets.load(r)}(n,e,l),e.interaction&&function(e,o,n){if(!function(t){const e=t.interaction;return!!e&&!!(e.triggerUIPopup||e.playAudio||e.playModelAnimation||e.triggerDirectLink)}(o))return void console.log("[CustomMesh] Skipping interaction setup - no active interactions for:",o.name);const i=o.interaction?.activationMode||"click",s=D.graphicsDevice.canvas,a=(o,n)=>{const i=s.getBoundingClientRect(),a=o-i.left,r=n-i.top,l=pt.camera.screenToWorld(a,r,pt.camera.nearClip),c=pt.camera.screenToWorld(a,r,pt.camera.farClip),p=(new t.Vec3).sub2(c,l).normalize(),d=e.modelEntity;if(d&&qe(d,l,p))return!0;if(qe(e,l,p))return!0;const h=e.getPosition(),u=(new t.Vec3).sub2(h,l).dot(p);if(u>0){const o=(new t.Vec3).add2(l,p.clone().mulScalar(u)).distance(h),n=e.getLocalScale();if(o<1.5*Math.max(n.x,n.y,n.z))return!0}return!1},r=o.interaction?.popupTriggerMode||i,l=o.interaction?.audioTriggerMode||i,c=o.interaction?.animationTriggerMode||i,p=o.interaction?.directLinkTriggerMode||i;let d=!1;const h=()=>{if(s.style.cursor="pointer","hover"===r&&o.interaction?.triggerUIPopup&&Ye(o),"hover"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&!t.isPlaying&&(t.play(),n.audioPlaying=!0,console.log("[CustomMesh] Playing audio on hover for:",o.name))}if("hover"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&!n.isAnimPlaying&&(t.forEach(t=>We(t)),n.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",o.name))}"hover"===p&&o.interaction?.triggerDirectLink&&Xe(o)},u=()=>{if(s.style.cursor="","hover"===r&&o.interaction?.triggerUIPopup&&function(){const t=document.querySelector(".storysplat-hotspot-popup");t&&t.remove();const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove()}(),"hover"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&t.isPlaying&&(t.stop(),n.audioPlaying=!1,console.log("[CustomMesh] Stopped audio on hover out for:",o.name))}if("hover"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&n.isAnimPlaying&&(t.forEach(t=>Ge(t)),n.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",o.name))}},m=()=>{if(console.log("[CustomMesh] Clicked mesh:",o.name),"click"===r&&o.interaction?.triggerUIPopup&&Ye(o),"click"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&(n.isAnimPlaying?(t.forEach(t=>Ge(t)),n.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",o.name)):(t.forEach(t=>We(t)),n.isAnimPlaying=!0,console.log("[CustomMesh] Playing",t.length,"animations on click for:",o.name)))}if("click"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&(t.isPlaying?(t.pause(),n.audioPlaying=!1,console.log("[CustomMesh] Paused audio on click for:",o.name)):(t.play(),n.audioPlaying=!0,console.log("[CustomMesh] Playing audio on click for:",o.name)))}"click"===p&&o.interaction?.triggerDirectLink&&Xe(o)},y=t=>{a(t.clientX,t.clientY)&&m()},g=t=>{const e=a(t.clientX,t.clientY);e&&!d?(d=!0,h()):!e&&d&&(d=!1,u())},f=()=>{d&&(d=!1,u())};s.addEventListener("click",y),s.addEventListener("mousemove",g),s.addEventListener("mouseleave",f),e.meshClickHandler=y,e.meshHoverHandler=g,e.meshLeaveHandler=f}(n,e,l),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(f){console.error("[CustomMesh] Error processing loaded mesh:",e.name,f)}}),a.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),D.assets.remove(a)}),D.assets.load(a),e.visibilityRange?(n.visibilityRange=e.visibilityRange,n.enabled=!1!==e.enabled):n.enabled=!1!==e.enabled,n.opacityConfig={mode:e.opacityMode||"static",value:void 0!==e.opacity?e.opacity:1},D.root.addChild(n),n}function qe(e,o,n){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(je(o,n,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(je(o,n,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&qe(e,o,n))return!0;return!1}function je(t,e,o){const n=o.getMin?o.getMin():o.min,i=o.getMax?o.getMax():o.max;if(!n||!i)return!1;let s=-1/0,a=1/0;const r=["x","y","z"],l=(t,e,o)=>{const n=t[e];if("number"==typeof n)return n;const i=t[`_${e}`];return"number"==typeof i?i:t.data?.[o]??0};for(let o=0;o<3;o++){const c=r[o],p=t[c],d=e[c],h=l(n,c,o),u=l(i,c,o);if(Math.abs(d)<1e-8){if(p<h||p>u)return!1}else{let t=(h-p)/d,e=(u-p)/d;if(t>e&&([t,e]=[e,t]),s=Math.max(s,t),a=Math.min(a,e),s>a)return!1}}return a>=0}function Ye(t){if(!t.interaction)return;const e={id:`mesh-content-${t.id}`,title:t.interaction.title||t.name,information:t.interaction.information,photoUrl:t.interaction.photoUrl,iframeUrl:t.interaction.iframeUrl,externalLinkUrl:t.interaction.externalLinkUrl,externalLinkText:t.interaction.externalLinkText,backgroundColor:t.interaction.backgroundColor||"#000000",textColor:t.interaction.textColor||"#ffffff"};R.showHotspotPopup?R.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.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: 100001;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ";const n=document.createElement("h2");if(n.textContent=t.name||"Custom Mesh",n.style.cssText="margin-top: 0; margin-bottom: 16px;",o.appendChild(n),t.interaction?.popupContent){const e=document.createElement("p");e.textContent=t.interaction.popupContent,e.style.cssText="margin: 0; line-height: 1.6;",o.appendChild(e)}const i=document.createElement("button");i.textContent=`× ${l(M,"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)}(t)}function Xe(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function Ze(e,o){!function e(n){n.render&&n.render.meshInstances&&n.render.meshInstances.forEach(e=>{if(e.material){if(!e.material._isCloned){const t=e.material.clone();t._isCloned=!0,e.material=t}e.material.opacity=o,e.material.blendType=t.BLEND_PREMULTIPLIED,e.material.depthTest=!0,e.material.depthWrite=!0,e.material.alphaTest=.01,e.material.update()}}),n.children.forEach(o=>{o instanceof t.Entity&&e(o)})}(e)}function Je(){const t=D.graphicsDevice.canvas;He.forEach(e=>{const o=e.entity,n=o.meshClickHandler;n&&t.removeEventListener("click",n),o.destroy()}),He.clear()}i.on("progressUpdate",()=>{!function(){const t=100*Ht,e=v.waypoints?.length||1,o=Math.round(Ht*Math.max(1,e-1));He.forEach(e=>{const{entity:n,config:i}=e,s=n.visibilityRange;if(s){let e=!0;"waypoint"===s.type?e=o>=s.start&&o<=s.end:"percentage"===s.type&&(e=t>=s.start&&t<=s.end),n.enabled=e}if("animated"===i.opacityMode&&i.opacityAnimation){const e=i.opacityAnimation;if(t>=e.startPercent&&t<=e.endPercent){const o=(t-e.startPercent)/(e.endPercent-e.startPercent);Ze(n,e.startOpacity+(e.endOpacity-e.startOpacity)*o)}}if(i.billboard&&i.billboardRange){const e=i.billboardRange;let s=!1;"percentage"===e.type?s=t>=e.start&&t<=e.end:"waypoint"===e.type&&(s=o>=e.start&&o<=e.end),n._billboardActive=s}else i.billboard&&(n._billboardActive=!0)})}()});let Ke=null,Qe=null;function to(){const e=v.skybox?.url||v.skyboxUrl;if(!e)return void console.log("[StorySplat Viewer] No skybox configured");const o=v.skybox?.rotation??v.skyboxRotation??0,n=v.skybox?.intensity??1,i=v.skybox?.enableIBL??!0;console.log("[StorySplat Viewer] Creating skybox:",e,"rotation:",o,"rad =",o*(180/Math.PI),"deg","IBL:",i);const s=e.toLowerCase().includes(".hdr")||e.toLowerCase().includes(".exr");Ke&&(Ke.destroy(),Ke=null),Qe&&(D.off("update",Qe),Qe=null);const a=new t.Entity("skybox"),r=new t.StandardMaterial;r.useLighting=!1,r.cull=t.CULLFACE_BACK,r.depthTest=!1,r.depthWrite=!1;const l=new t.Asset(`skybox-texture-${Date.now()}`,"texture",{url:e},s?{type:t.TEXTURETYPE_RGBM,mipmaps:!0}:{mipmaps:!0});if(D.assets.add(l),l.ready(e=>{const o=e.resource;try{o.minFilter=t.FILTER_LINEAR_MIPMAP_LINEAR,o.magFilter=t.FILTER_LINEAR}catch{}if(r.emissiveMap=o,r.emissive=new t.Color(n,n,n),r.update(),console.log("[StorySplat Viewer] Skybox texture loaded"),i)try{s&&(D.scene.exposure=n,D.scene.toneMapping=t.TONEMAP_ACES,console.log("[StorySplat Viewer] HDR tone mapping enabled")),o&&(D.scene.ambientLight=new t.Color(.3*n,.3*n,.35*n),console.log("[StorySplat Viewer] IBL ambient lighting applied with intensity:",n))}catch(t){console.warn("[StorySplat Viewer] Failed to apply IBL:",t)}}),l.on("error",t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",t)}),D.assets.load(l),a.addComponent("render",{type:"sphere",material:r,castShadows:!1,receiveShadows:!1}),a.render&&a.render.meshInstances.length>0&&(a.render.meshInstances[0].drawOrder=-1e4),a.setLocalScale(-500,500,500),0!==o){const t=o*(180/Math.PI);a.setEulerAngles(0,t,0)}Qe=()=>{const t=pt.getPosition();a.setPosition(t.x,t.y,t.z)},D.on("update",Qe),D.root.addChild(a),Ke=a,console.log("[StorySplat Viewer] Skybox created with rotation:",o,"intensity:",n)}const eo=[];function oo(e){const o=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return o?new t.Color(parseInt(o[1],16)/255,parseInt(o[2],16)/255,parseInt(o[3],16)/255):new t.Color(1,1,1)}function no(e){const o=new t.Entity(e.name||"Point Light");o.addComponent("light",{type:t.LIGHTTYPE_POINT,color:oo(e.color||"#ffffff"),intensity:e.intensity||1,range:e.range||10,castShadows:e.castShadows||!1});const n=e.position;return n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0)),o.enabled=!1!==e.enabled,o}function io(e){const o=new t.Entity(e.name||"Directional Light");o.addComponent("light",{type:t.LIGHTTYPE_DIRECTIONAL,color:oo(e.color||"#ffffff"),intensity:e.intensity||1,castShadows:e.castShadows||!1});const n=e.position;n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0));const i=e.rotation;if(i){const t=180/Math.PI;o.setEulerAngles((i._x??i.x??0)*t,(i._y??i.y??0)*t,(i._z??i.z??0)*t)}else o.setEulerAngles(45,0,0);return o.enabled=!1!==e.enabled,o}function so(e){const o=new t.Entity(e.name||"Hemispheric Light");o.addComponent("light",{type:t.LIGHTTYPE_DIRECTIONAL,color:oo(e.color||"#ffffff"),intensity:e.intensity||1,castShadows:!1});const n=e.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!==e.enabled,e.groundColor){const o=oo(e.groundColor),n=oo(e.color||"#ffffff");D.scene.ambientLight=new t.Color((n.r+.3*o.r)/1.3,(n.g+.3*o.g)/1.3,(n.b+.3*o.b)/1.3)}return o}function ao(e){const o=oo(e.color||"#404040"),n=e.intensity||.4;return D.scene.ambientLight=new t.Color(o.r*n,o.g*n,o.b*n),console.log("[StorySplat Viewer] Set ambient light:",e.name||"Ambient Light"),null}function ro(e){const o=new t.Entity(e.name||"Spot Light"),n=180/Math.PI,i=(e.angle??45*Math.PI/180)*n;e.exponent;const s=e.innerConeAngle??e.innerAngle??.8*i,a=e.outerConeAngle??e.outerAngle??i;o.addComponent("light",{type:t.LIGHTTYPE_SPOT,color:oo(e.color||"#ffffff"),intensity:e.intensity||1,range:e.range||10,innerConeAngle:s,outerConeAngle:a,castShadows:e.castShadows||!1,shadowBias:e.shadowBias??.05,normalOffsetBias:e.normalOffsetBias??.05});const r=e.position;r&&o.setPosition(r._x??r.x??0,r._y??r.y??0,-(r._z??r.z??0));const l=e.rotation;if(l){const t=180/Math.PI;o.setEulerAngles((l._x??l.x??0)*t,(l._y??l.y??0)*t,(l._z??l.z??0)*t)}else if(e.direction){const n=e.direction,i=new t.Vec3(n._x??n.x??0,n._y??n.y??-1,-(n._z??n.z??0)).normalize();o.lookAt(o.getPosition().x+i.x,o.getPosition().y+i.y,o.getPosition().z+i.z)}else o.setEulerAngles(90,0,0);return o.enabled=!1!==e.enabled,o}function lo(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}const co=new Map;const po=new Set;const ho=[];function uo(e,o=!1,n=1,i){const s=new t.StandardMaterial;if(s.blendType=t.BLEND_PREMULTIPLIED,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.01,o?s.diffuse=new t.Color(1,1,1):(s.diffuse=new t.Color(0,0,0),s.emissive=new t.Color(1,1,1),s.specular=new t.Color(0,0,0),s.useLighting=!1),s.opacity=n,s.update(),function(t){const e=t.toLowerCase();return e.endsWith(".gif")||e.includes("image/gif")||e.includes("format=gif")}(e)){console.log(`[Hotspot] Loading animated GIF: ${e}`);const n=new q(D,e,{autoPlay:!0,onReady:()=>{if(G)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void n.destroy();n.texture&&(n.texture.premultiplyAlpha=!0,o?(s.diffuseMap=n.texture,s.opacityMap=n.texture):(s.emissiveMap=n.texture,s.opacityMap=n.texture),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] GIF texture loaded: ${e}, useLighting=${o}`),i&&i())},onError:o=>{console.error(`[Hotspot] Failed to load GIF: ${e}`,o),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()}});ho.push(n)}else{const n=new Image;n.crossOrigin="anonymous",n.onload=()=>{if(G)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const a=new t.Texture(D.graphicsDevice,{width:n.width,height:n.height,format:t.PIXELFORMAT_RGBA8,mipmaps:!0,minFilter:t.FILTER_LINEAR_MIPMAP_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});a.setSource(n),a.premultiplyAlpha=!0,o?(s.diffuseMap=a,s.opacityMap=a):(s.emissiveMap=a,s.opacityMap=a),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] Texture loaded for: ${e}, useLighting=${o}`),i&&i()},n.onerror=o=>{console.error(`[Hotspot] Failed to load texture: ${e}`,o),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()},n.src=e}return s}function mo(e,o){const n=new t.Entity(`hotspot-${e.id||o}`),i=e.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 s=e.scale||{_x:1,_y:1,_z:1},a="number"==typeof s?{x:s,y:s,z:s}:s,r=Math.abs(a._x??a.x??1),l=Math.abs(a._y??a.y??1),c=a._z??a.z??1,p=e.rotation||{_x:0,_y:0,_z:0},d=180/Math.PI,h=(p._x??p.x??0)*d,u=(p._y??p.y??0)*d,m=(p._z??p.z??0)*d;if(n.setEulerAngles(h,u,-m),"sphere"===e.type){n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Ae(e.color||"#ffffff");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(o.opacity=1,o.blendType=t.BLEND_NONE,o.depthTest=!0,o.depthWrite=!0):(o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0),o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let t=1;"animated"===e.opacityMode&&e.opacityAnimation?t=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(t=e.opacity),n.targetOpacity=t,n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const o=!0===e.useLighting,i=uo(e.imageUrl,o,t,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),n.hotspotMaterial=i,console.log(`[Hotspot] Created image hotspot: ${e.title}, opacity=${t}, opacityMode=${e.opacityMode}, useLighting=${o}`)}else if("video"===e.type&&e.videoUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const o=/iPad|iPhone|iPod/.test(navigator.userAgent)&&e.useIOSVideoAlphaMethod||e.forceIOSVideoAlphaMethodForAllDevices,i=o&&e.iosMainVideoUrl?e.iosMainVideoUrl:e.videoUrl,s=o&&e.alphaMaskVideoUrl||null,a=(t=>{const e=t.toLowerCase();return e.endsWith(".webm")||e.includes("format=webm")||e.includes("video/webm")})(i)&&!1!==e.webmHasAlpha,p=(o,n,i=!1)=>{const s=document.createElement("video");s.src=o,s.loop=!1!==e.videoLoop,s.crossOrigin="anonymous",s.playsInline=!0,s.muted=!!n||!1!==e.videoMuted,"autoplay"===e.mediaTriggerMode&&(s.autoplay=!0,s.muted=!0);const a=new t.Texture(D.graphicsDevice,{format:i?t.PIXELFORMAT_R8_G8_B8_A8:t.PIXELFORMAT_R8_G8_B8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});return a.setSource(s),{video:s,texture:a}},d=p(i,!1,a),h=d.video,u=d.texture;if(e.videoBackupUrl){const t=e.videoBackupUrl;h.onerror=()=>{console.log(`[Hotspot] Main video failed, trying backup: ${t}`),h.src=t,h.load()}}const m=new t.StandardMaterial;m.diffuseMap=u,m.emissiveMap=u,m.emissive=new t.Color(1,1,1),m.depthTest=!0,m.depthWrite=!0,m.cull=t.CULLFACE_NONE,m.twoSidedLighting=!0,m.blendType=t.BLEND_PREMULTIPLIED,m.alphaTest=.01,a&&(m.opacityMap=u,m.blendType=t.BLEND_PREMULTIPLIED,m.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let y=null,g=null;if(s){const o=p(s,!0,!1);y=o.video,g=o.texture,m.opacityMap=g,m.opacityMapChannel="r",m.blendType=t.BLEND_PREMULTIPLIED,m.alphaTest=.01,h.addEventListener("play",()=>{y&&y.paused&&(y.currentTime=h.currentTime,y.play().catch(console.warn))}),h.addEventListener("pause",()=>{y&&!y.paused&&y.pause()}),h.addEventListener("seeked",()=>{y&&(y.currentTime=h.currentTime)}),console.log(`[Hotspot] iOS alpha mask video enabled: ${e.title}, alphaUrl=${s.substring(0,50)}...`)}m.update(),D.on("update",()=>{h.readyState===h.HAVE_ENOUGH_DATA&&u.upload(),y&&g&&y.readyState===y.HAVE_ENOUGH_DATA&&g.upload()});const f={x:r,y:l,z:c};if(h.addEventListener("loadedmetadata",()=>{const t=h.videoWidth,e=h.videoHeight;if(t>0&&e>0){const o=t/e;1===f.x&&1===f.y&&n.setLocalScale(o*f.y,f.y,f.z)}}),n.render.material=m,n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),n.videoElement=h,n.alphaVideoElement=y,n.hotspotMaterial=m,n.mediaTriggerMode=e.mediaTriggerMode||"click",n.proximityDistance=e.proximityDistance||5,n.isVideoPlaying=!1,!0!==e.videoMuted){const t=function(t,e,o){if(!o.videoSpatialAudio&&!1!==o.videoMuted)return null;try{const n=new(window.AudioContext||window.webkitAudioContext);Pe.push(n);const i=n.createMediaElementSource(e),s=n.createPanner();s.panningModel="HRTF",s.distanceModel=o.videoDistanceModel||"linear",s.refDistance=void 0!==o.videoRefDistance?o.videoRefDistance:1,s.maxDistance=void 0!==o.videoMaxDistance?o.videoMaxDistance:100,s.rolloffFactor=void 0!==o.videoRolloffFactor?o.videoRolloffFactor:1;const a=t.getPosition();return s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(n.destination),D.on("update",()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),pt&&pt.getPosition){const t=pt.getPosition(),e=pt.forward,o=pt.up;n.listener.positionX?(n.listener.positionX.value=t.x,n.listener.positionY.value=t.y,n.listener.positionZ.value=t.z,n.listener.forwardX.value=e.x,n.listener.forwardY.value=e.y,n.listener.forwardZ.value=e.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(t.x,t.y,t.z),n.listener.setOrientation(e.x,e.y,e.z,o.x,o.y,o.z))}}),console.log(`[Audio] Video spatial audio setup for hotspot: ${o.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audioCtx:n,source:i,panner:s}}catch(t){return console.warn("[Audio] Failed to setup video spatial audio:",t),null}}(n,h,e);t&&(n.videoSpatialAudio=t)}console.log(`[Hotspot] Created video hotspot: ${e.title}, mode=${e.mediaTriggerMode}, useAlpha=${!!s}, spatialAudio=${!!n.videoSpatialAudio}`)}else if("gif"===e.type&&e.gifUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let o=1;"animated"===e.opacityMode&&e.opacityAnimation?o=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(o=e.opacity),n.targetOpacity=o,n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const i=!0===e.useLighting,s=new t.StandardMaterial;s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=o,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.01,i||(s.emissive=new t.Color(1,1,1),s.diffuse=new t.Color(0,0,0));const a=async a=>{try{const p=document.createElement("canvas"),d=p.getContext("2d"),h=new Image;h.crossOrigin="anonymous",h.onload=()=>{p.width=h.width||256,p.height=h.height||256,d.drawImage(h,0,0);const u=new t.Texture(D.graphicsDevice,{format:t.PIXELFORMAT_R8_G8_B8_A8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});u.setSource(p),s.diffuseMap=u,i||(s.emissiveMap=u),s.opacityMap=u,s.alphaTest=.01,s.update(),n.render.material=s;const m=p.width/p.height;1===r&&1===l?n.setLocalScale(m,1,c):n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),n.textureLoaded=!0,n.gifCanvas=p,n.gifTexture=u,n.hotspotMaterial=s,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1,fetch(a).then(t=>t.arrayBuffer()).then(t=>{const e=()=>{n.enabled&&n.gifTexture&&(d.clearRect(0,0,p.width,p.height),d.drawImage(h,0,0),n.gifTexture?.upload(),requestAnimationFrame(e))};n.gifAnimationFrame=requestAnimationFrame(e)}).catch(t=>{console.warn("[Hotspot] GIF animation load failed, using static frame:",t)}),console.log(`[Hotspot] Created GIF hotspot: ${e.title}, opacity=${o}, useLighting=${i}`)},h.onerror=o=>{console.error("[Hotspot] Failed to load GIF:",e.gifUrl,o),n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const i=new t.StandardMaterial;i.diffuse=Ae(e.color||"#FF00FF"),i.opacity=.8,i.blendType=t.BLEND_NORMAL,i.update(),n.render.material=i,n.setLocalScale(.2*r,.2*l,.2*c),n.enabled=!0,n.hiddenUntilTextureLoaded=!1},h.src=a}catch(t){console.error("[Hotspot] GIF hotspot creation failed:",t)}};a(e.gifUrl)}else{n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Ae(e.color||"#4CAF50");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0,o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}n.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),n.hotspotData=e;const y=function(t,e){if(!e.audioUrl)return null;const o=document.createElement("audio");if(o.src=e.audioUrl,o.loop=e.audioLoop||!1,o.volume=void 0!==e.audioVolume?e.audioVolume:1,o.crossOrigin="anonymous",Te.push(o),e.audioSpatial){const n=new(window.AudioContext||window.webkitAudioContext);Pe.push(n);const i=n.createMediaElementSource(o),s=n.createPanner();s.panningModel="HRTF",s.distanceModel=e.audioDistanceModel||"linear",s.refDistance=void 0!==e.audioRefDistance?e.audioRefDistance:1,s.maxDistance=void 0!==e.audioMaxDistance?e.audioMaxDistance:100,s.rolloffFactor=void 0!==e.audioRolloffFactor?e.audioRolloffFactor:1;const a=t.getPosition();s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(n.destination);const r=()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),pt&&pt.getPosition){const t=pt.getPosition(),e=pt.forward,o=pt.up;n.listener.positionX?(n.listener.positionX.value=t.x,n.listener.positionY.value=t.y,n.listener.positionZ.value=t.z,n.listener.forwardX.value=e.x,n.listener.forwardY.value=e.y,n.listener.forwardZ.value=e.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(t.x,t.y,t.z),n.listener.setOrientation(e.x,e.y,e.z,o.x,o.y,o.z))}};return D.on("update",r),console.log(`[Audio] Spatial audio setup for hotspot: ${e.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audio:o,audioCtx:n,source:i,panner:s,updateAudioPosition:r}}return console.log(`[Audio] Non-spatial audio setup for hotspot: ${e.title}`),{audio:o}}(n,e);if(y&&(n.audioElements=y,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}`)),e.billboard){const t=n.getEulerAngles().clone(),o=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;n._billboardActive=!o,n._billboardOriginalRotation=t,D.on("update",()=>{n._billboardActive&&(n.lookAt(pt.getPosition()),n.rotateLocal(90,0,0))})}return e.visibilityRange&&(n.visibilityRange=e.visibilityRange,n.enabled=!1),D.root.addChild(n),Ce.push(n),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),n}function yo(){const t=100*Ht,e=v.waypoints?.length||1,o=Math.round(Ht*Math.max(1,e-1)),n=pt.getPosition();Ce.forEach(e=>{const i=e.hotspotData;if(!i)return;let s=!0;if(i.alwaysVisible)s=!0;else{const n=e.visibilityRange;n&&("waypoint"===n.type?s=o>=n.start&&o<=n.end:"percentage"===n.type&&(s=t>=n.start&&t<=n.end))}if(e.shouldBeVisible=s,i.billboard&&(void 0!==i.billboardRangeStart||void 0!==i.billboardRangeEnd)){const o=i.billboardRangeStart??0,n=i.billboardRangeEnd??100,s=t>=o&&t<=n;if(e._billboardActive=s,!s&&e._billboardOriginalRotation){const t=e._billboardOriginalRotation;e.setEulerAngles(t.x,t.y,t.z)}}if(e.hiddenUntilTextureLoaded?e.enabled=!1:e.enabled=s,e.videoElement&&"video"===i.type){const o=e.mediaTriggerMode||"click";if("proximity"===o){const t=e.getPosition(),o=n.distance(t),s=e.proximityDistance||5;o<=s&&!e.isVideoPlaying?(go(e,i),console.log(`[Hotspot] Proximity play: ${i.title}, distance=${o.toFixed(2)}`)):o>s&&e.isVideoPlaying&&(fo(e),console.log(`[Hotspot] Proximity pause: ${i.title}, distance=${o.toFixed(2)}`))}"scroll"===o&&(s&&!e.isVideoPlaying?(go(e,i),console.log(`[Hotspot] Scroll play: ${i.title}, scroll=${t.toFixed(1)}%`)):!s&&e.isVideoPlaying&&(fo(e),console.log(`[Hotspot] Scroll pause: ${i.title}, scroll=${t.toFixed(1)}%`)))}if("animated"===i.opacityMode&&i.opacityAnimation){if("image"===i.type&&!e.textureLoaded)return;const o=i.opacityAnimation,n=o.startPercent??0,s=o.endPercent??100,a=void 0!==o.startOpacity?o.startOpacity:1,r=void 0!==o.endOpacity?o.endOpacity:1;let l;if(t<=n)l=a;else if(t>=s)l=r;else{l=a+(r-a)*((t-n)/(s-n))}if(l=Math.max(0,Math.min(1,l)),e.hotspotMaterial)e.hotspotMaterial.opacity=l,e.hotspotMaterial.update();else if(e.render&&e.render.material){const t=e.render.material;t.opacity=l,t.update?.()}}})}function go(t,e){const o=t.videoElement,n=t.alphaVideoElement;if(o){if("autoplay"!==t.mediaTriggerMode&&(o.muted=!1!==e.videoMuted),t.videoSpatialAudio&&t.videoSpatialAudio.audioCtx){const e=t.videoSpatialAudio.audioCtx;"suspended"===e.state&&e.resume().then(()=>{console.log("[Audio] Video spatial audio context resumed")}).catch(t=>console.warn("[Audio] Failed to resume video audio context:",t))}o.play().catch(t=>console.warn("Video play failed:",t)),n&&n.play().catch(t=>console.warn("Alpha video play failed:",t)),t.isVideoPlaying=!0}}function fo(t){const e=t.videoElement,o=t.alphaVideoElement;e&&(e.pause(),o&&o.pause(),t.isVideoPlaying=!1)}function vo(e,o){const n=new t.Entity(`portal-${o}`),i=e.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 s=e.scale||{_x:1,_y:1,_z:1},a="number"==typeof s?{x:s,y:s,z:s}:s,r=Math.abs(a._x??a.x??1),l=Math.abs(a._y??a.y??1),c=a._z??a.z??1,p=e.rotation||{_x:0,_y:0,_z:0},d=180/Math.PI,h=(p._x??p.x??0)*d,u=(p._y??p.y??0)*d,m=(p._z??p.z??0)*d;if(n.setEulerAngles(h,u,-m),"sphere"===e.type){n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Ae(e.color||"#9C27B0");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(o.opacity=1,o.blendType=t.BLEND_NONE,o.depthTest=!0,o.depthWrite=!0):(o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0),o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,o=e.opacity??1;n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const i=uo(e.imageUrl,t,o,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),n.portalMaterial=i,console.log(`[Portal] Created image portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("video"===e.type&&e.videoUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const o=document.createElement("video");o.src=e.videoUrl,o.loop=!0,o.muted=!0,o.crossOrigin="anonymous",o.playsInline=!0,o.autoplay=!0;const i=new t.Texture(D.graphicsDevice,{format:t.PIXELFORMAT_R8_G8_B8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});i.setSource(o);const s=new t.StandardMaterial;s.diffuseMap=i,s.emissiveMap=i,s.emissive=new t.Color(1,1,1),s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=e.opacity??1,s.update(),n.render.material=s,n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),D.on("update",()=>{o.readyState>=o.HAVE_CURRENT_DATA&&i.setSource(o)}),o.play().catch(t=>console.log("[Portal] Video autoplay blocked:",t)),n.videoElement=o,n.portalMaterial=s,console.log(`[Portal] Created video portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("gif"===e.type&&e.gifUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,o=e.opacity??1,i=uo(e.gifUrl,t,o,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,l,c),n.rotateLocal(90,0,0),n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1,n.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else{n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Ae(e.color||"#9C27B0");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0,o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}return n.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),n.portalData=e,e.billboard&&D.on("update",()=>{n.enabled&&(n.lookAt(pt.getPosition()),n.rotateLocal(90,0,0))}),e.visibilityRange&&(n.visibilityRange=e.visibilityRange,n.enabled=!1),D.root.addChild(n),Me.push(n),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),n}function bo(){const t=100*Ht,e=v.waypoints?.length||1,o=Math.round(Ht*Math.max(1,e-1)),n=pt.getPosition();Me.forEach(e=>{const i=e.portalData;if(!i)return;let s=!0;const a=e.visibilityRange;if(a&&("waypoint"===a.type?s=o>=a.start&&o<=a.end:"percentage"===a.type&&(s=t>=a.start&&t<=a.end)),e.shouldBeVisible=s,e.hiddenUntilTextureLoaded?e.enabled=!1:e.enabled=s,"proximity"===i.activationMode&&s){const t=e.getPosition(),o=n.distance(t),s=i.proximityDistance||2;o<=s&&!e.proximityTriggered?(e.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${i.title||i.targetSceneName}, navigating to scene ${i.targetSceneId}`),wo(i)):o>s&&(e.proximityTriggered=!1)}})}function xo(e,o){const n=pt.camera.screenToWorld(e,o,pt.camera.nearClip),i=pt.camera.screenToWorld(e,o,pt.camera.farClip);let s=null;Me.forEach(e=>{if(!e.enabled)return;const o=e.getPosition(),a=(new t.Vec3).sub2(i,n).normalize(),r=(new t.Vec3).sub2(o,n).dot(a);if(r<0)return;const l=(new t.Vec3).add2(n,a.clone().mulScalar(r)).distance(o),c=e.getLocalScale();l<.6*Math.max(c.x,c.y,.3)&&(!s||r<s.distance)&&(s={entity:e,distance:r})});if(null!==s){const t=s.entity;return{entity:t,portal:t.portalData}}return null}async function wo(t){if(t.targetSceneId){console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${t.targetSceneId}`),i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName}),function(t,e){const o=t.querySelector(".storysplat-portal-loading");o&&o.remove();try{"static"===window.getComputedStyle(t).position&&(t.style.position="relative")}catch{}const n=document.createElement("div");n.className="storysplat-portal-loading";const i=document.createElement("div");i.className="storysplat-portal-spinner";const s=document.createElement("div");if(s.className="storysplat-portal-loading-text",s.textContent=l(M,"loadingScene").replace("{name}",e),n.appendChild(i),n.appendChild(s),Object.assign(n.style,{position:"absolute",inset:"0",background:"rgba(0, 0, 0, 0.85)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",zIndex:"100003",fontFamily:"system-ui, sans-serif"}),Object.assign(i.style,{width:"50px",height:"50px",border:"4px solid rgba(255, 255, 255, 0.2)",borderTop:`4px solid ${w}`,borderRadius:"50%",animation:"storysplat-portal-spin 1s linear infinite",marginBottom:"20px"}),Object.assign(s.style,{color:"#ffffff",fontSize:"18px"}),!document.getElementById("storysplat-portal-styles")){const t=document.createElement("style");t.id="storysplat-portal-styles",t.textContent="\n @keyframes storysplat-portal-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n ",document.head.appendChild(t)}t.appendChild(n)}(e,t.targetSceneName||t.title||"scene");try{const o=`https://discover.storysplat.com/api/scene/${t.targetSceneId}`;console.log(`[Portal] Fetching scene from: ${o}`);const n=await fetch(o);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.status} ${n.statusText}`);const i=await n.json(),s=i.data||i;s.name&&function(t,e){const o=t.querySelector(".storysplat-portal-loading-text");o&&(o.textContent=l(M,"loadingScene").replace("{name}",e))}(e,s.name);!function(){console.log("[Portal] Cleaning up current scene for navigation..."),Se(),Ce.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),ho.forEach(t=>t.destroy()),ho.length=0,Je(),Ce.forEach(t=>{t.destroy()}),Ce.length=0,Me.forEach(t=>{t.destroy()}),Me.length=0,H&&(H.destroy(),H=null);const t=D.__htmlMeshManager;t&&t.destroy();const e=D.__customScriptSystem;e&&e.dispose();console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),I&&I.parentNode&&I.remove();await et(e,s,{});return So(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),So(e);const o=document.createElement("div");o.className="storysplat-portal-error",o.textContent=`Failed to load scene: ${t.message}`,Object.assign(o.style,{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",background:"rgba(0,0,0,0.9)",color:"#ff6b6b",padding:"20px",borderRadius:"8px",zIndex:"100004",fontFamily:"system-ui, sans-serif"}),e.appendChild(o),setTimeout(()=>o.remove(),3e3)}}else console.warn("[Portal] No target scene ID specified")}function So(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{yo()}),setTimeout(()=>{yo()},100),i.on("progressUpdate",()=>{bo()}),setTimeout(()=>{bo()},100);const Eo=D.graphicsDevice.canvas;let Co=!1,Mo=!1,_o=0,Lo=0;Eo.addEventListener("pointerdown",t=>{0===t.button&&(Mo=!0,Co=!1,_o=t.clientX,Lo=t.clientY)}),Eo.addEventListener("pointermove",t=>{if(!Mo)return;const e=t.clientX-_o,o=t.clientY-Lo;e*e+o*o>25&&(Co=!0)});const ko=()=>{Mo=!1};function Ao(e,o){const n=pt.camera.screenToWorld(e,o,pt.camera.nearClip),i=pt.camera.screenToWorld(e,o,pt.camera.farClip);let s=null;Ce.forEach(e=>{if(!e.enabled)return;const o=e.getPosition(),a=(new t.Vec3).sub2(i,n).normalize(),r=(new t.Vec3).sub2(o,n).dot(a);if(r<0)return;const l=(new t.Vec3).add2(n,a.clone().mulScalar(r)).distance(o),c=e.getLocalScale();l<.6*Math.max(c.x,c.y,.3)&&(!s||r<s.distance)&&(s={entity:e,distance:r})});if(null!==s){const t=s.entity;return{entity:t,hotspot:t.hotspotData}}return null}Eo.addEventListener("pointerup",ko),Eo.addEventListener("pointercancel",ko);let Po=null,To=!1;const zo=e.querySelector(".storysplat-hotspot-popup"),Ro=e.querySelector(".storysplat-hotspot-overlay");async function Io(t,e){if("explore"!==ft)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const o=Ao(t,e);if(o){const t=o.entity.getPosition();return console.log("[StorySplat Viewer] Focusing on hotspot at:",t.x,t.y,t.z),void yt.focus(t,!1)}try{const o=.25;bt.resize(Math.floor(Eo.clientWidth*o),Math.floor(Eo.clientHeight*o));const n=D.scene.layers.getLayerByName("World");if(!n)return void console.warn("[StorySplat Viewer] World layer not found");bt.prepare(pt.camera,D.scene,[n]);const i=Math.floor(t*o),s=Math.floor(e*o),a=await bt.getWorldPointAsync(i,s);if(a){const t=pt.getPosition().distance(a);if(t>.1&&t<1e3)return console.log("[StorySplat Viewer] Focusing on GSplat at:",a.x.toFixed(2),a.y.toFixed(2),a.z.toFixed(2),"distance:",t.toFixed(2)),void yt.focus(a,!1)}const r=await bt.getSelectionAsync(i,s,1,1);if(r.length>0){const t=r[0];if(!t.aabb)return void console.log("[StorySplat Viewer] No AABB available for picked object");const e=t.aabb.center.clone();console.log("[StorySplat Viewer] Focusing on mesh AABB center:",e.x.toFixed(2),e.y.toFixed(2),e.z.toFixed(2)),yt.focus(e,!1)}else console.log("[StorySplat Viewer] No pick result at click point")}catch(t){console.warn("[StorySplat Viewer] Picking failed:",t)}}zo&&(zo.addEventListener("mouseenter",()=>{To=!0}),zo.addEventListener("mouseleave",()=>{To=!1,Po&&"hover"===Po.activationMode&&(zo.classList.remove("visible"),Ro&&Ro.classList.remove("visible"),Po=null)})),Eo.addEventListener("mousemove",t=>{const o=Eo.getBoundingClientRect(),n=t.clientX-o.left,i=t.clientY-o.top,s=xo(n,i);if(s&&s.portal){const t=s.portal.activationMode||"click";return void(Eo.style.cursor="click"===t?"pointer":"default")}const a=Ao(n,i);if(a&&a.hotspot){const t=a.hotspot;"click"===t.activationMode||"hover"===t.activationMode||"video"===t.type?Eo.style.cursor="pointer":Eo.style.cursor="default","hover"===t.activationMode&&Po!==t&&(Po=t,(t.information||t.photoUrl||t.iframeUrl||t.externalLinkUrl)&&u(e,t,M))}else if(Eo.style.cursor="default",Po&&"hover"===Po.activationMode&&!To){const t=e.querySelector(".storysplat-hotspot-popup"),o=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),o&&o.classList.remove("visible"),Po=null}}),Eo.addEventListener("click",o=>{if(Co)return void(Co=!1);const n=Eo.getBoundingClientRect(),i=o.clientX-n.left,s=o.clientY-n.top,a=xo(i,s);if(null!==a&&a.portal){const t=a.portal;console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName);if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&_e?(t=>{if(!_e)return;const e=_e.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${l(M,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=l(M,"switchScenes")),Le=t,_e.classList.add("visible")})(t):wo(t)}return}const r=Ao(i,s);if(null!==r){const o=r.entity,n=r.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",n.title),o.audioElements&&o.audioElements.audio){const t=o.audioElements,e=t.audio;e.paused?(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),e.play().catch(t=>console.error("[Audio] Hotspot audio play failed:",t)),console.log("[Audio] Hotspot audio started:",n.title)):(e.pause(),console.log("[Audio] Hotspot audio paused:",n.title))}if(o.videoElement&&("click"===o.mediaTriggerMode||"autoplay"===o.mediaTriggerMode)){const t=o.videoElement;o.alphaVideoElement,"autoplay"===o.mediaTriggerMode&&!t.paused&&t.muted?(t.muted=(n.videoMuted,!1),console.log("[Hotspot] Video unmuted by click")):t.paused?(go(o,n),console.log("[Hotspot] Video started")):(fo(o),console.log("[Hotspot] Video paused"))}const i=n.activationMode||"click",s=n.title||n.information||n.photoUrl||n.iframeUrl||n.externalLinkUrl;"click"===i&&s&&(xt?Ct?Mt():function(e){if(!xt)return;St||(St=new t.Entity("arContentPlane"),St.addComponent("render",{type:"plane"}),St.setLocalScale(.8,1,.45),D.root.addChild(St));const o=512,n=288,i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d"),a=e.backgroundColor||"rgba(20, 20, 20, 0.95)";s.fillStyle=a,s.fillRect(0,0,o,n),s.strokeStyle="rgba(255, 255, 255, 0.3)",s.lineWidth=2,s.strokeRect(1,1,510,286);const r=e.textColor||"#ffffff";let l=35;e.title&&(s.fillStyle=r,s.font="bold 28px Arial, sans-serif",s.fillText(e.title,20,l),l+=40),e.information&&(s.fillStyle=r,s.font="18px Arial, sans-serif",l=function(t,e,o,n,i,s){const a=e.split(" ");let r="",l=n;for(const e of a){const n=r+e+" ";t.measureText(n).width>i&&r?(t.fillText(r.trim(),o,l),r=e+" ",l+=s):r=n}return t.fillText(r.trim(),o,l),l+s}(s,e.information,20,l,472,24)),s.fillStyle="rgba(255, 255, 255, 0.5)",s.font="14px Arial, sans-serif",s.textAlign="center",s.fillText("Tap to close",256,273),s.textAlign="left",Et||(Et=new t.Texture(D.graphicsDevice,{width:o,height:n,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),Et.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=Et,c.emissive=new t.Color(1,1,1),c.emissiveMap=Et,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),St.render&&St.render.meshInstances[0]&&(St.render.meshInstances[0].material=c),St.enabled=!0,Ct=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(n):u(e,n,M));const a=n.teleportWaypoint??n.teleportToWaypoint,l=n.teleportPercent??n.teleportToPercent,c=n.teleportMode||"animate";let p=null;if(void 0!==a&&-1!==a){const t=v.waypoints?.length||1,e=Math.max(0,Math.min(a,t-1));p=t>1?e/(t-1):0,console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",p,", mode:",c,")")}else void 0!==l&&-1!==l&&(p=Math.max(0,Math.min(l/100,1)),console.log("[Hotspot] Teleporting to percent:",l,"(progress:",p,", mode:",c,")"));null!==p&&("instant"===c?(Ht=p,Wt=p,de(Ht)):(Wt=p,me(p,800)))}}),Eo.addEventListener("dblclick",t=>{const e=Eo.getBoundingClientRect();Io(t.clientX-e.left,t.clientY-e.top)});let Do=0;Eo.addEventListener("touchend",t=>{if(1!==t.changedTouches.length)return;const e=Date.now();if(e-Do<300){const e=t.changedTouches[0],o=Eo.getBoundingClientRect();Io(e.clientX-o.left,e.clientY-o.top),Do=0}else Do=e}),Pt(.2,"Initializing...");const Fo=o.frameSequence&&o.frameSequence.frameUrls&&o.frameSequence.frameUrls.length>0?async function(){if(!o.frameSequence||!o.frameSequence.frameUrls||0===o.frameSequence.frameUrls.length)throw new Error("No frame sequence URLs provided");console.log("[StorySplat Viewer] Loading 4DGS frame sequence:",o.frameSequence.frameUrls.length,"frames"),Pt(.3,"Loading 4DGS frames..."),N=new Z(D,{frameUrls:o.frameSequence.frameUrls,fps:o.frameSequence.fps||24,loop:!1!==o.frameSequence.loop,preloadCount:o.frameSequence.preloadCount||5,autoplay:o.frameSequence.autoplay||!1},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Pt(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),N.on("complete",()=>{i.emit("frameComplete")}),D.on("update",t=>{N&&!G&&N.update(t)}),console.log("[StorySplat Viewer] 4DGS frame sequence player initialized"),i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:!1})}:async function(){let e=0,s="";const a=[];console.log("[SPLAT] Building URL priority list..."),console.log("[SPLAT] Available URLs:",{lodMetaUrl:v.lodMetaUrl||"(none)",sogUrl:v.sogUrl||"(none)",splatUrl:v.splatUrl||"(none)",fallbackUrls:v.fallbackUrls?.length||0}),v.lodMetaUrl&&(a.push(v.lodMetaUrl),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",v.lodMetaUrl)),v.sogUrl&&(a.push(v.sogUrl),console.log("[SPLAT] ✓ SOG URL added (second priority):",v.sogUrl)),v.splatUrl&&(a.push(v.splatUrl),console.log("[SPLAT] ✓ Original splat URL added (third priority):",v.splatUrl)),v.fallbackUrls&&(a.push(...v.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",v.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other"),Pt(.3,l(M,"loading"));for(const r of a)if(r)try{const a=r.split(".").pop()?.toLowerCase()||"splat",c="gsplat",p=r.includes("lod-meta.json"),d=r.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",r),console.log("[SPLAT] Format detection:",{extension:a,isLodStreaming:p,isSogFormat:d,assetType:c});const h=new t.Asset("splat-"+Date.now(),c,{url:r});h.on("progress",(t,o)=>{if(o>0){Tt(r)&&(e=t);const n=.3+t/o*.6,i=Math.round(t/o*100);i%25!=0&&100!==i||console.log(`[SPLAT] Loading progress: ${i}% (${(t/1024/1024).toFixed(2)}MB / ${(o/1024/1024).toFixed(2)}MB)`),Pt(n,`${l(M,"loading")} ${i}%`)}}),s=r,await new Promise((e,s)=>{let a=!1;const l=d?t=>{if(a)return;const e=t.reason?.message||String(t.reason);(e.includes("shape")||e.includes("upgradeMeta"))&&(console.warn("[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):",e),a=!0,t.preventDefault(),i.emit("warning",{type:"deprecated_format",message:"This scene uses an outdated SOG format. Please re-upload your scene with the latest tools for better performance.",details:"SOG v1 format is deprecated. Use splat-transform v2+ to convert your PLY files.",url:r}),console.log("[SPLAT] Will try fallback URL if available..."),s(new Error(`SOG parse error: ${e}`)))}:null;d&&l&&(console.log("[SPLAT] Registered error handler for SOG format parsing"),window.addEventListener("unhandledrejection",l));const u=()=>{d&&l&&window.removeEventListener("unhandledrejection",l)};if(h.ready(()=>{if(G)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),u(),void s(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{H=new t.Entity("splat"),H.addComponent("gsplat",{asset:h,unified:!0});const i=H.gsplat;i&&tt(r)?(i.lodDistances=[...V.lodDistances],console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD distances configured:",{distances:V.lodDistances,description:"Quality levels switch at these camera distances",preset:$})):d?console.log("[SPLAT] ✓ Single SOG file loaded (no LOD streaming)"):console.log("[SPLAT] Standard format loaded (PLY/SPLAT)");const s=v.scale||{x:1,y:1,z:1},l=v.invertXScale||!1,c=v.invertYScale||!1,p={x:l?-s.x:s.x,y:c?-s.y:s.y,z:l!==c?-s.z:s.z};H.setLocalScale(p.x,p.y,p.z);const m=v.position||[0,0,0];H.setPosition(m[0],m[1],-m[2]);const y=v.rotation||[0,0,0],g=[180+y[0]*(180/Math.PI),y[1]*(180/Math.PI),-y[2]*(180/Math.PI)];H.setEulerAngles(g[0],g[1],g[2]),console.log("[StorySplat Viewer] Splat transform applied:",{scale:p,position:[m[0],m[1],-m[2]],rotation:g,userRotation:y,invertXScale:l,invertYScale:c}),D.root.addChild(H),console.log("[StorySplat Viewer] Splat entity added to scene"),H.gsplat?.material&&(H.gsplat.material.setParameter("alphaClip",.01),console.log("[StorySplat Viewer] GSplat alphaClip set for picking support"));const f=n.revealEffect||o.revealEffect||"medium",b=P(f);if(b){H.addComponent("script");const t=k();W=H.script?.create?.(t)??null,W&&(W.enabled=!1,W.center.set(0,0,0),W.speed=b.speed,W.acceleration=b.acceleration,W.delay=b.delay,W.oscillationIntensity=b.oscillationIntensity,W.dotTint.set(b.dotTint.r,b.dotTint.g,b.dotTint.b),W.waveTint.set(b.waveTint.r,b.waveTint.g,b.waveTint.b),W.endRadius=b.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",f))}else console.log("[StorySplat Viewer] Reveal effect disabled");setTimeout(()=>{a||(u(),e())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),u(),s(t)}}),h.on("error",t=>{const e=t;console.error("[SPLAT] ✗ Asset load error:",{url:r,assetType:c,isSogFormat:d,isLodFormat:p,error:t,message:e?.message||"Unknown error",status:e?.status||e?.statusCode||"N/A"}),u(),s(t)}),D.assets.add(h),p){const t=D.loader.getHandler("gsplat"),e=t?.parsers?.octree;if(e){console.log("[SPLAT] Using direct octree parser for LOD URL (bypassing basename routing)");const t={load:r,original:r};e.load(t,(t,e)=>{if(t)return console.error("[SPLAT] Octree parser error:",t),void h.fire("error",t);h.resource=e,h.loaded=!0,h.fire("load",h)},h)}else console.warn("[SPLAT] Could not access octree parser, falling back to standard load"),D.assets.load(h)}else D.assets.load(h)});const u=Tt(s);return i.emit("loaded",{bandwidthUsed:u?e:0,isStorySplatHosted:u}),console.log("[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓"),void console.log("[SPLAT] Load summary:",{url:s,format:p?"LOD streaming (lod-meta.json)":d?"SOG (compressed)":"Standard (PLY/SPLAT)",lodEnabled:p,lodPreset:p?$:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:u,bandwidthCounted:u?"Yes":"No (self-hosted)"})}catch(t){console.warn("[SPLAT] ✗ Failed to load URL, trying next fallback:",r),console.warn("[SPLAT] Error:",t)}console.error("[SPLAT] ✗✗✗ FAILED TO LOAD SPLAT FROM ANY URL ✗✗✗"),console.error("[SPLAT] Tried URLs:",a),i.emit("error",new Error("Failed to load splat from any URL"))};Fo().then(()=>{if(Pt(1,"Ready!"),v.hotspots&&0!==v.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${v.hotspots.length} hotspots...`),v.hotspots.forEach((t,e)=>{mo(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),v.portals&&0!==v.portals.length?(console.log(`[StorySplat Viewer] Creating ${v.portals.length} portals...`),v.portals.forEach((t,e)=>{vo(t,e)})):console.log("[StorySplat Viewer] No portals to create"),v.waypoints&&0!==v.waypoints.length&&(v.waypoints.forEach((e,o)=>{e.interactions&&Array.isArray(e.interactions)&&e.interactions.forEach(n=>{if("audio"===n.type&&n.data){const i=n.data,s=String(n.id||`audio-${o}`),a=new t.Entity(`waypoint-audio-${s}`),r=e.position||{x:0,y:0,z:0};a.setPosition(r._x??r.x??e.x??0,r._y??r.y??e.y??1.6,-(r._z??r.z??e.z??0));const l={slots:{[s]:{name:s,loop:i.loop||!1,autoPlay:!1,volume:void 0!==i.volume?i.volume:1,pitch:1,positional:i.spatialSound||!1,distanceModel:lo(i.distanceModel||"exponential"),maxDistance:i.maxDistance||1e4,refDistance:i.refDistance||1,rollOffFactor:i.rolloffFactor||1}}};a.addComponent("sound",l);const c=new t.Asset(`waypoint-audio-asset-${s}`,"audio",{url:i.url});D.assets.add(c),c.ready(()=>{const t=a.sound?.slot(s);t&&(t.asset=c.id);const e=Ie.get(s);e&&(e.assetReady=!0),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),D.assets.load(c),D.root.addChild(a),Ie.set(s,{entity:a,waypointIndex:o,config:i,slotId:s,playing:!1,autoplayTriggered:!1,assetReady:!1}),console.log(`[Audio] Waypoint audio created: ${s} at waypoint ${o}, spatial=${i.spatialSound}`)}})}),Ie.size>0&&console.log(`[StorySplat Viewer] Setup ${Ie.size} waypoint audio sources`)),function(){const e=v.audioEmitters;e&&0!==e.length&&(console.log(`[Audio] Setting up ${e.length} standalone audio emitters`),e.forEach(e=>{if(!e.enabled)return void console.log(`[Audio] Skipping disabled emitter: ${e.name||e.id}`);if(!e.url)return void console.warn(`[Audio] Emitter has no URL: ${e.name||e.id}`);const o=e.id||`emitter-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,n=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${o}`);i.setPosition(n.x,n.y,n.z);const s=lo(e.distanceModel||"linear");i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||100,rollOffFactor:e.rolloffFactor||1,distanceModel:s,volume:e.volume??.5}),i.sound?.addSlot(o,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1});const a=new t.Asset(`audio-emitter-${o}`,"audio",{url:e.url});a.on("load",()=>{if(G)return;const t=i.sound?.slot(o);t&&(t.asset=a.id);const n=co.get(o);n&&(n.assetReady=!0,!1!==e.autoplay&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||o}`),t.play(),n.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||o}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||100}`)}),D.assets.add(a),D.assets.load(a),D.root.addChild(i),co.set(o,{entity:i,config:e,slotId:o,playing:!1,assetReady:!1}),console.log(`[Audio] Emitter created: ${e.name||o} at (${n.x}, ${n.y}, ${n.z})`)}),co.size>0&&console.log(`[StorySplat Viewer] Setup ${co.size} standalone audio emitters`))}(),console.log("[StorySplat Viewer] 🎆 Calling initParticleSystems..."),async function(){if(console.log("═══════════════════════════════════════"),console.log("🎆 PARTICLE SYSTEM INITIALIZATION"),console.log("═══════════════════════════════════════"),console.log("[Particle] config.particles:",v.particles),console.log("[Particle] Type:",typeof v.particles),console.log("[Particle] Is Array:",Array.isArray(v.particles)),!v.particles||0===v.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${v.particles.length} particle system(s) to create`);for(let t=0;t<v.particles.length;t++){const e=v.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${v.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const o=e.particleTexture||"flare";let n,i;"custom"===o&&e.customTextureUrl?(n=e.customTextureUrl,i=`custom_${e.id||e.name||t}`,console.log(`[Particle] Custom texture: ${n.substring(0,60)}...`)):(n=Ve[o]||Ve.flare,i=o,console.log(`[Particle] Texture: ${o} -> ${n.substring(0,60)}...`)),console.log("[Particle] Loading texture...");const s=await Be(i,n);console.log("[Particle] ✅ Texture loaded:",s.name),console.log("[Particle] Creating entity...");const a=Oe(e);console.log("[Particle] ✅ Entity created:",a.name),a.particlesystem?(a.particlesystem.colorMap=s,console.log("[Particle] ✅ Texture applied to particle system"),console.log("[Particle] Particle system properties:",{numParticles:a.particlesystem.numParticles,lifetime:a.particlesystem.lifetime,rate:a.particlesystem.rate,loop:a.particlesystem.loop,autoPlay:a.particlesystem.autoPlay})):console.warn("[Particle] ⚠️ Entity has no particlesystem component!"),D.root.addChild(a),console.log("[Particle] ✅ Entity added to scene");const r=a.getPosition();console.log(`[Particle] Position: (${r.x.toFixed(2)}, ${r.y.toFixed(2)}, ${r.z.toFixed(2)})`);const l=(e.id||e.name||`particle-${Ue.size}`).replace(/[^a-zA-Z0-9]/g,"_");Ue.set(l,a),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||l}"`)}catch(t){const o=t instanceof Error?t:{message:String(t),stack:""};console.error(`[Particle] ❌ FAILED to create particle system: ${e.name}`),console.error(`[Particle] Error message: ${o.message||"No message"}`),console.error(`[Particle] Error stack: ${o.stack||"No stack"}`),console.error("[Particle] Error object:",t)}}console.log("═══════════════════════════════════════"),console.log(`[Particle] 🎉 COMPLETE: ${Ue.size}/${v.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from(Ue.keys())),console.log("═══════════════════════════════════════")}(),async function(){v.customMeshes&&0!==v.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${v.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",v.customMeshes),setTimeout(()=>{let t=0,e=0;v.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{Ne(o,n)?t++:(e++,console.warn(`[CustomMesh] Mesh ${o.name} returned null (likely missing modelUrl)`))}catch(t){console.error("[CustomMesh] Error loading mesh",o.name,":",t),e++}else console.log("[CustomMesh] Skipping disabled mesh:",o.name,"(enabled =",o.enabled,")"),e++}),console.log(`[CustomMesh] Summary: ${t} loaded, ${e} skipped`)},100),console.log(`[StorySplat Viewer] ${v.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),to(),v.lights&&0!==v.lights.length?(console.log(`[StorySplat Viewer] Creating ${v.lights.length} custom lights...`),v.lights.forEach((t,e)=>{let o=null;switch(t.type){case"point":o=no(t);break;case"directional":o=io(t);break;case"hemispheric":o=so(t);break;case"ambient":ao(t);break;case"spot":o=ro(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}o&&(D.root.addChild(o),eo.push(o),console.log(`[StorySplat Viewer] Created ${t.type} light:`,t.name||`Light ${e}`))}),console.log("[StorySplat Viewer] Lighting setup complete")):console.log("[StorySplat Viewer] No custom lights to create"),W&&(W.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),setTimeout(()=>{R.preloader&&d(R.preloader)},200),function(){if(!v.includeXR)return;if(!D.xr)return void console.warn("[StorySplat Viewer] WebXR not supported in this browser");const e=D.xr,o=v.xrMode||"both";"vr"!==o&&"both"!==o||(e.isAvailable(t.XRTYPE_VR)&&(R.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?R.vrButton?.classList.add("available"):R.vrButton?.classList.remove("available")})),"ar"!==o&&"both"!==o||(e.isAvailable(t.XRTYPE_AR)&&(R.arButton?.classList.add("available"),console.log("[StorySplat Viewer] AR is available")),e.on("available:"+t.XRTYPE_AR,t=>{t?R.arButton?.classList.add("available"):R.arButton?.classList.remove("available")})),e.on("start",()=>{xt=!0,console.log("[StorySplat Viewer] XR session started"),"vr"===wt?(R.vrButton?.classList.add("active"),R.vrButton.textContent=l(M,"exitVr")):"ar"===wt&&(R.arButton?.classList.add("active"),R.arButton.textContent=l(M,"exitAr")),yt.disable(),gt&>.disable(),i.emit("xrStart",{type:wt})}),e.on("end",()=>{xt=!1,console.log("[StorySplat Viewer] XR session ended"),Mt(),R.vrButton?.classList.remove("active"),R.arButton?.classList.remove("active"),R.vrButton&&(R.vrButton.textContent=l(M,"vr")),R.arButton&&(R.arButton.textContent=l(M,"ar")),wt=null,"explore"===ft?yt.enable():"walk"===ft&>&>.enable(),i.emit("xrEnd",{})}),R.vrButton&&R.vrButton.addEventListener("click",()=>{xt&&"vr"===wt?e.end():!xt&&e.isAvailable(t.XRTYPE_VR)&&(wt="vr",pt.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),wt=null)}}))}),R.arButton&&R.arButton.addEventListener("click",()=>{xt&&"ar"===wt?e.end():!xt&&e.isAvailable(t.XRTYPE_AR)&&(wt="ar",pt.camera.startXr(t.XRTYPE_AR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start AR:",t),wt=null)}}))})}(),v.htmlMeshes&&v.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",v.htmlMeshes.length);const t=Y(D,v.htmlMeshes);D.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*Ht,o=v.waypoints?.length||1,n=Math.round(Ht*Math.max(1,o-1));t.updateVisibility(e,n)})}if(v.customScript&&""!==v.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const e=function(e,o,n,i,s,a,r,l,c){if(!i||""===i.trim())return console.log("[Custom Script] No custom script provided"),null;console.log("[Custom Script] Initializing custom script system..."),console.log("[Custom Script] Script length:",i.length);const p=new X(i);return p.initialize({app:e,camera:o,pc:t,canvas:n,getScrollPercentage:s,getCurrentWaypointIndex:a,getHotspots:r,getSplats:l,getHTMLMeshes:c}),p.execute(),p}(D,pt,I,v.customScript,()=>Ht,()=>B,()=>Ce,()=>H?[H]:[],()=>v.htmlMeshes||[]);e&&(D.__customScriptSystem=e,console.log("[StorySplat Viewer] Custom script system initialized"))}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),o=t.get("autoplay");if(null!==e&&v.waypoints&&v.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<v.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),ye(t))}"true"!==o||n.autoPlay||v.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),we())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}if(i.emit("ready"),console.log("[StorySplat Viewer] Ready"),At(z),x){h(R,{nextWaypoint:ge,prevWaypoint:fe,play:we,pause:Se,isPlaying:()=>O,getCurrentWaypointIndex:()=>B,getWaypointCount:()=>v.waypoints?.length||0,getWaypoints:()=>v.waypoints||[],setCameraMode:At,on:(t,e)=>i.on(t,e)},z,M);const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{De(),Fe(e)}),R.waypointListContainer&&v.waypoints&&v.waypoints.length>0&&(!function(t,e){if(!t.waypointListContainer)return;const o=t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item"),n=t.waypointListContainer.querySelector(".storysplat-waypoint-list-toggle"),i=t.waypointListContainer.querySelector(".storysplat-waypoint-list-dropdown");o.forEach(t=>{t.addEventListener("click",()=>{const o=parseInt(t.getAttribute("data-waypoint-index")||"0",10);e(o),n?.classList.remove("open"),i?.classList.remove("open")})})}(R,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),ye(t)}),i.on("waypointChange",({index:t})=>{f(R,t)}),f(R,0)),i.emit("progressUpdate",{progress:Ht,index:B}),v.waypoints&&v.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:v.waypoints[0],prevIndex:-1})}(n.autoPlay||v.autoPlay)&&we()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),R.preloader&&d(R.preloader),i.emit("error",t)});const Uo=()=>{D.resizeCanvas()};window.addEventListener("resize",Uo);const $o=new Map,Vo=new Map,Bo=new Map,Oo=new Map,Ho=new Map;b&&(Ce.forEach(t=>{const e=t.hotspotData?.id;e&&$o.set(e,t)}),eo.forEach((t,e)=>{const o=v.lights?.[e],n=o?.id||o?.name||`light-${e}`;Vo.set(n,t)}),Ue.forEach((t,e)=>{Bo.set(e,t)}),Me.forEach(t=>{const e=t.portalData?.id;e&&Ho.set(e,t)}));const Wo={app:D,canvas:I,goToWaypoint:t=>{if("explore"===ft)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');ye(t)},nextWaypoint:()=>{if("explore"===ft)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');ge()},prevWaypoint:()=>{if("explore"===ft)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');fe()},getCurrentWaypointIndex:()=>B,getWaypointCount:()=>v.waypoints?.length||0,setPosition:(t,e,o)=>{if("explore"!==ft)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(O)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");yt.disable(),pt.setPosition(t,e,o),yt.syncFromCamera(),yt.enable(),requestAnimationFrame(()=>{pt.setPosition(t,e,o),yt.syncFromCamera()})},setRotation:(t,e,o)=>{if("explore"!==ft)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(O)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");yt.disable(),pt.setEulerAngles(t,e,o),yt.syncFromCamera(),yt.enable(),requestAnimationFrame(()=>{pt.setEulerAngles(t,e,o),yt.syncFromCamera()})},getPosition:()=>{const t=pt.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=pt.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if("explore"===ft)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');we()},pause:()=>{if("explore"===ft)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');Se()},stop:()=>{if("explore"===ft)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(N)return N.stop(),void i.emit("playbackStop");Se(),he(0)}()},isPlaying:()=>O,setFrame:t=>N?.setFrame(t),getCurrentFrame:()=>N?.getCurrentFrame()??0,getTotalFrames:()=>N?.getTotalFrames()??0,setFps:t=>N?.setFps(t),getFps:()=>N?.getFps()??24,getFrameProgress:()=>N?.getProgress()??0,setFrameProgress:t=>N?.setProgress(t),goToOriginalSplat:Ot,goToSplat:async t=>{const e=Vt();if(t===e)return void Ot();const o=j.find(e=>e.url===t);o||rt.has(t)||await Ft(t);const n=st||e;if(n===e&&H)Rt(H);else if(n){const t=rt.get(n);t&&(t.enabled=!1)}if(!Dt(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);o&&Ut(o.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return st||Vt()},isShowingOriginalSplat:function(){return st===Vt()||null===st},getAdditionalSplats:()=>j.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),destroy:()=>{G=!0,Se(),N&&(N.destroy(),N=null),window.removeEventListener("resize",Uo),R.preloader&&R.preloader.remove(),R.scrollControls&&R.scrollControls.remove(),R.fullscreenButton&&R.fullscreenButton.remove(),R.helpButton&&R.helpButton.remove(),R.helpPanel&&R.helpPanel.remove(),R.waypointInfo&&R.waypointInfo.remove(),R.watermark&&R.watermark.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),ho.forEach(t=>t.destroy()),ho.length=0,Je(),yt.setCollisionEntities([]),gt&>.destroy();const o=D.__customScriptSystem;o&&o.dispose();const n=D.__htmlMeshManager;n&&n.destroy(),St&&(St.destroy(),St=null),Et&&(Et.destroy(),Et=null),D.destroy(),I.remove()},resize:Uo,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await wo(e)},setCameraMode:t=>At(t),getCameraMode:()=>ft,setExploreMode:t=>{if("explore"!==ft)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');yt.setMode(t),kt(t)},setProgress:t=>{if("explore"===ft)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');he(t)},getProgress:()=>Ht,muteAll:()=>{ze||(Te.forEach(t=>{Re.set(t,t.volume),t.volume=0}),Ie.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),co.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),document.querySelectorAll("audio, video").forEach(t=>{t.muted=!0}),ze=!0,console.log("[Audio] All audio muted"))},unmuteAll:()=>{ze&&(Te.forEach(t=>{const e=Re.get(t);t.volume=void 0!==e?e:1}),Ie.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),co.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),document.querySelectorAll("audio, video").forEach(t=>{t.muted=!1}),ze=!1,console.log("[Audio] All audio unmuted"))},isMuted:()=>ze,getHotspots:()=>Ce.map(t=>{const e=t.hotspotData,o=t.getPosition();return{id:e?.id||"",title:e?.title||e?.information?.substring(0,30)||"",type:e?.type||"sphere",position:{x:o.x,y:o.y,z:o.z}}}),triggerHotspot:t=>{const o=Ce.find(e=>e.hotspotData?.id===t);if(!o)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const n=o.hotspotData;n&&u(e,n,M)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),o=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),o&&o.classList.remove("visible"),De(),Fe(e)},setButtonLabels:t=>{M={...M,...t};const o=t=>l(M,t),n=(t,n)=>{const i=e.querySelector(`.storysplat-mode-btn[data-mode="${t}"]`);i&&(i.textContent=o(n))};n("tour","tour"),n("explore","explore"),n("walk","walk");const i=e.querySelector('.storysplat-explore-btn[data-explore-mode="orbit"]');i&&(i.textContent=o("orbit"));const s=e.querySelector('.storysplat-explore-btn[data-explore-mode="fly"]');s&&(s.textContent=o("fly"));const a=e.querySelector(".storysplat-btn-prev");a&&(a.textContent=o("previous"));const r=e.querySelector(".storysplat-btn-next");r&&(r.textContent=o("next"));const c=e.querySelector(".storysplat-waypoint-list-toggle");if(c){const t=c.querySelector("svg");c.textContent="",c.append(o("waypoints")),t&&c.appendChild(t),c.setAttribute("aria-label",o("waypoints"))}const p=e.querySelector(".storysplat-vr-btn");p&&!p.classList.contains("active")&&(p.textContent=o("vr"),p.setAttribute("aria-label",o("vr")));const d=e.querySelector(".storysplat-ar-btn");d&&!d.classList.contains("active")&&(d.textContent=o("ar"),d.setAttribute("aria-label",o("ar")));const h=e.querySelector(".storysplat-fullscreen-btn");h&&h.setAttribute("aria-label",o("fullscreen"));const u=e.querySelector(".storysplat-hotspot-popup-close");u&&(u.textContent=o("close"));const m=e.querySelector(".storysplat-portal-popup-confirm");m&&(m.textContent=o("yes"));const y=e.querySelector(".storysplat-portal-popup-cancel");y&&(y.textContent=o("cancel"));const g=e.querySelector(".storysplat-help-panel");if(g){g.textContent="";const t=(t,e,o)=>{const n=document.createElement(t);if(o){const t=document.createElement("strong");t.textContent=e,n.appendChild(t)}else n.textContent=e;return g.appendChild(n),n},e=()=>g.appendChild(document.createElement("br"));t("h3",o("helpTitle")),t("p",o("helpCameraModes"),!0),t("p",`• ${o("tour")} - ${o("helpTourDesc")}`),t("p",`• ${o("explore")} - ${o("helpExploreDesc")}`),t("p",`• ${o("walk")} - ${o("helpWalkDesc")}`),e(),t("p",`${o("tour")} Mode:`,!0),t("p","• Scroll - Move along path"),t("p","• Drag - Look around"),e(),t("p",`${o("explore")} Mode:`,!0),t("p","• LMB Drag - Orbit camera"),t("p","• RMB Drag - Fly/look"),t("p","• WASD/QE - Move camera"),t("p","• Shift - Move fast"),t("p","• Scroll/Pinch - Zoom"),t("p","• Double-click - Focus"),e(),t("p",`${o("walk")} Mode:`,!0),t("p","• Click to lock mouse"),t("p","• WASD/Arrows - Move"),t("p","• Mouse - Look around"),t("p","• Shift - Sprint"),t("p","• Space - Jump")}const f=e.querySelector(".storysplat-help-btn");f&&f.setAttribute("title",o("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e)};if(b){const e=Wo;return e.setCameraMode=t=>At(t),e.getCameraMode=()=>ft,e.getCameraControls=()=>yt,e.getApp=()=>D,e.getSplatEntity=()=>H,e.getAllHotspotEntities=()=>$o,e.getAllLightEntities=()=>Vo,e.getCamera=()=>pt,e.setProgress=t=>he(t),e.getProgress=()=>Ht,e.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,o=mo(t,Ce.length);return $o.set(e,o),e},e.removeHotspot=t=>{const e=$o.get(t);if(e){e.destroy(),$o.delete(t);const o=Ce.indexOf(e);o>=0&&Ce.splice(o,1)}},e.updateHotspot=(t,e)=>{const o=$o.get(t);if(!o)return;e.position&&o.setPosition(e.position.x,e.position.y,-e.position.z);const n=o.hotspotData||{};o.hotspotData={...n,...e}},e.addLight=t=>{const e=t.id||t.name||`light-${Date.now()}`;let o=null;switch(t.type){case"point":default:o=no(t);break;case"directional":o=io(t);break;case"hemispheric":o=so(t);break;case"ambient":ao(t);break;case"spot":o=ro(t)}return o&&(D.root.addChild(o),eo.push(o),Vo.set(e,o)),e},e.removeLight=t=>{const e=Vo.get(t);if(e){e.destroy(),Vo.delete(t);const o=eo.indexOf(e);o>=0&&eo.splice(o,1)}},e.updateLight=(t,e)=>{const o=Vo.get(t);o&&(e.position&&o.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),o.light&&(void 0!==e.intensity&&(o.light.intensity=e.intensity),void 0!==e.range&&(o.light.range=e.range),void 0!==e.castShadows&&(o.light.castShadows=e.castShadows),e.color&&(o.light.color=oo(e.color))))},e.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,o=Ne(t,He.size);return o&&Oo.set(e,o),e},e.removeCustomMesh=t=>{const e=Oo.get(t);e&&(He.forEach((t,o)=>{t.entity===e&&He.delete(o)}),e.destroy(),Oo.delete(t))},e.addParticleSystem=t=>{const e=t.id||t.name||`particle-${Date.now()}`,o=Oe(t),n=t.particleTexture||"flare";let i,s;"custom"===n&&t.customTextureUrl?(i=t.customTextureUrl,s=`custom_${e}`):(i=Ve[n]||Ve.flare,s=n),Be(s,i).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load particle texture:",t)}),D.root.addChild(o);const a=e.replace(/[^a-zA-Z0-9]/g,"_");return Ue.set(a,o),Bo.set(e,o),e},e.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),o=Bo.get(t)||Ue.get(e);o&&(o.destroy(),Bo.delete(t),Ue.delete(e))},e.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,o=vo(t,Me.length);return Ho.set(e,o),e},e.removePortal=t=>{const e=Ho.get(t);if(e){const o=e.videoElement;o&&(o.pause(),o.src=""),e.destroy(),Ho.delete(t);const n=Me.indexOf(e);n>=0&&Me.splice(n,1)}},e.updatePortal=(t,e)=>{const o=Ho.get(t);if(!o)return;e.position&&o.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0));const n=o.portalData||{};o.portalData={...n,...e}},e.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let o=D.__htmlMeshManager;return o||(o=Y(D,[]),D.__htmlMeshManager=o),o.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},e.removeHtmlMesh=t=>{const e=D.__htmlMeshManager;e&&e.destroyMesh(t)},e.addCollisionMesh=e=>{const o=e.id||`collision-${Date.now()}`,n=new t.Entity(`collision-mesh-${o}`),i=e.position,s=Array.isArray(i)?i[0]:i.x,a=Array.isArray(i)?i[1]:i.y,r=Array.isArray(i)?i[2]:i.z;if(n.setPosition(s,a,-(r??0)),e.rotation){const t=e.rotation,o=Array.isArray(t)?t[0]:t.x,i=Array.isArray(t)?t[1]:t.y,s=Array.isArray(t)?t[2]:t.z;n.setEulerAngles(o??0,i??0,s??0)}if(e.scaling){const t=e.scaling,o=Array.isArray(t)?t[0]:t.x,i=Array.isArray(t)?t[1]:t.y,s=Array.isArray(t)?t[2]:t.z;n.setLocalScale(o??1,i??1,s??1)}const l=e.meshType||"cube";if("custom"!==l){const e="cube"===l?"box":"floor"===l?"plane":l;n.addComponent("render",{type:e,castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial;o.diffuse=new t.Color(0,1,0),o.opacity=.3,o.blendType=t.BLEND_NORMAL,o.depthWrite=!1,o.update(),n.render&&n.render.meshInstances.forEach(t=>{t.material=o})}return n.addComponent("collision",{type:"sphere"===l?"sphere":"box"}),n.enabled=!1!==e.visible,n._collisionMeshId=o,D.root.addChild(n),o},e.removeCollisionMesh=t=>{const e=D.root.children;for(let o=e.length-1;o>=0;o--)if(e[o]._collisionMeshId===t){e[o].destroy();break}},e.addAudioEmitter=e=>{const o=e.id||`emitter-${Date.now()}`,n=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${o}`);if(i.setPosition(n.x,n.y,n.z),i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||100,rollOffFactor:e.rolloffFactor||1,volume:e.volume??.5}),i.sound?.addSlot(o,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1}),e.url){const n=new t.Asset(`audio-emitter-${o}`,"audio",{url:e.url});n.on("load",()=>{if(G)return;const t=i.sound?.slot(o);t&&(t.asset=n.id,!1!==e.autoplay&&t.play())}),D.assets.add(n),D.assets.load(n)}return D.root.addChild(i),co.set(o,{entity:i,config:e,slotId:o,playing:!1,assetReady:!1}),o},e.removeAudioEmitter=t=>{const e=co.get(t);if(e){const o=e.entity.sound?.slot(e.slotId);o&&o.stop(),e.entity.destroy(),co.delete(t)}},e.updateAudioEmitter=(t,e)=>{const o=co.get(t);o&&(e.position&&o.entity.setPosition(e.position.x,e.position.y,e.position.z),void 0!==e.volume&&o.entity.sound&&(o.entity.sound.volume=e.volume),o.config={...o.config,...e})},e.setSkybox=(t,e)=>{!function(t,e){Ke&&(Ke.destroy(),Ke=null),Qe&&(D.off("update",Qe),Qe=null),t&&(v.skybox={url:t,rotation:e??0},v.skyboxUrl=t,v.skyboxRotation=e??0,to())}(t,e)},e.setBackgroundColor=e=>{const o=new t.Color(e.r/255,e.g/255,e.b/255);pt.camera&&(pt.camera.clearColor=o)},e.setFOV=t=>{pt.camera&&(pt.camera.fov=t)},e.addWaypoint=t=>{v.waypoints||(v.waypoints=[]),v.waypoints.push(t)},e.removeWaypoint=t=>{v.waypoints&&t>=0&&t<v.waypoints.length&&v.waypoints.splice(t,1)},e.updateWaypoint=(t,e)=>{v.waypoints&&t>=0&&t<v.waypoints.length&&Object.assign(v.waypoints[t],e)},e.rebuildTourPath=()=>{console.log("[Editor] Tour path will use updated waypoints on next progress update")},e}return Wo}async function ot(t,e,o){console.log("[StorySplat Viewer] Fetching scene from:",e);const n=await fetch(e);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),et(t,i,o)}function nt(t,e,o){return t+(e-t)*o}async function it(t,e,o,n,i){try{const s=await fetch(`${t}/api/track-embed`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sceneId:e,ownerId:o,type:n,..."bandwidth"===n&&i?{bytes:i}:{}})});s.ok?console.log(`[StorySplat] Tracked ${n}${"bandwidth"===n?` (${(i/1024/1024).toFixed(2)}MB)`:""}`):console.warn(`[StorySplat] Failed to track ${n}:`,s.status)}catch(t){console.warn(`[StorySplat] Error tracking ${n}:`,t)}}class st extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class at extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const rt=function(){if("undefined"!=typeof process&&process.env?.STORYSPLAT_API_URL)return process.env.STORYSPLAT_API_URL;const t="undefined"!=typeof window?window:void 0;return t?.__STORYSPLAT_API_URL__?t.__STORYSPLAT_API_URL__:"https://discover.storysplat.com"}();async function lt(t,e,o={}){const n=o.baseUrl||rt;console.log(`[StorySplat Viewer] Fetching scene: ${e}`);const i=`${n}/api/scene/${encodeURIComponent(e)}`,s={"Content-Type":"application/json"};o.apiKey&&(s.Authorization=`Bearer ${o.apiKey}`);const a=await fetch(i,{method:"GET",headers:s});if(!a.ok){if(404===a.status)throw new st(e);const t=await a.text();let o;try{o=JSON.parse(t).error||`API error: ${a.status}`}catch{o=`API error: ${a.status}`}throw new at(o,a.status)}const r=await a.json();if(!r.success||!r.data)throw new at("Invalid API response format",500);console.log(`[StorySplat Viewer] Scene loaded: "${r.meta.name}"`);const l=r.meta.ownerId,c={...r.data,name:r.data.name||r.meta.name,thumbnailUrl:r.data.thumbnailUrl||r.meta.thumbnailUrl},{baseUrl:p,apiKey:d,...h}=o,u=et(t,c,h);it(n,e,l,"view");let m=!1;return u.on("loaded",t=>{m||(m=!0,t&&t.bandwidthUsed>0&&t.isStorySplatHosted?it(n,e,l,"bandwidth",t.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))}),u}async function ct(t,e={}){const o=`${e.baseUrl||rt}/api/scene/${encodeURIComponent(t)}/meta`,n={"Content-Type":"application/json"};e.apiKey&&(n.Authorization=`Bearer ${e.apiKey}`);const i=await fetch(o,{method:"GET",headers:n});if(!i.ok){if(404===i.status)throw new st(t);throw new at(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class pt{constructor(e,o,n={}){this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null,this._mode="none",this.app=e,this.camera=o,this.gizmoLayer=t.Gizmo.createLayer(e),this.translateGizmo=new t.TranslateGizmo(o,this.gizmoLayer),this.rotateGizmo=new t.RotateGizmo(o,this.gizmoLayer),this.scaleGizmo=new t.ScaleGizmo(o,this.gizmoLayer),this.setSnap(n.snap??!1,n.snapIncrement??1),this.setCoordSpace(n.coordSpace??"world"),this.setupEvents(this.translateGizmo),this.setupEvents(this.rotateGizmo),this.setupEvents(this.scaleGizmo)}setupEvents(t){t.on("transform:start",()=>{this.onTransformStart?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})}),t.on("transform:move",()=>{this.onTransformMove?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})}),t.on("transform:end",()=>{this.onTransformEnd?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})})}get mode(){return this._mode}setMode(t){switch(this._mode=t,this.activeGizmo?.detach(),this.activeGizmo=null,t){case"translate":this.activeGizmo=this.translateGizmo;break;case"rotate":this.activeGizmo=this.rotateGizmo;break;case"scale":this.activeGizmo=this.scaleGizmo}this.activeGizmo&&this.attachedEntity&&this.activeGizmo.attach([this.attachedEntity])}attach(t){this.attachedEntity=t,this.activeGizmo&&(t?this.activeGizmo.attach([t]):this.activeGizmo.detach())}attachToMesh(t){this.attach(t)}detach(){this.attachedEntity=null,this.activeGizmo?.detach()}set positionGizmoEnabled(t){t&&"translate"!==this._mode?this.setMode("translate"):t||"translate"!==this._mode||this.setMode("none")}get positionGizmoEnabled(){return"translate"===this._mode}set rotationGizmoEnabled(t){t&&"rotate"!==this._mode?this.setMode("rotate"):t||"rotate"!==this._mode||this.setMode("none")}get rotationGizmoEnabled(){return"rotate"===this._mode}set scaleGizmoEnabled(t){t&&"scale"!==this._mode?this.setMode("scale"):t||"scale"!==this._mode||this.setMode("none")}get scaleGizmoEnabled(){return"scale"===this._mode}setSnap(t,e){this.translateGizmo&&(this.translateGizmo.snap=t,void 0!==e&&(this.translateGizmo.snapIncrement=e)),this.rotateGizmo&&(this.rotateGizmo.snap=t,void 0!==e&&(this.rotateGizmo.snapIncrement=e)),this.scaleGizmo&&(this.scaleGizmo.snap=t,void 0!==e&&(this.scaleGizmo.snapIncrement=e))}setCoordSpace(t){this.translateGizmo&&(this.translateGizmo.coordSpace=t),this.rotateGizmo&&(this.rotateGizmo.coordSpace=t),this.scaleGizmo&&(this.scaleGizmo.coordSpace=t)}onTransform(t){this.onTransformStart=t.start,this.onTransformMove=t.move,this.onTransformEnd=t.end}update(){this.activeGizmo?.update()}destroy(){this.translateGizmo?.destroy(),this.rotateGizmo?.destroy(),this.scaleGizmo?.destroy(),this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null}}class dt{constructor(e,o,n={}){this.selectedEntities=new Set,this.highlightedEntity=null,this.originalMaterials=new Map,this.app=e,this.camera=o,this.config={highlightColor:n.highlightColor??new t.Color(.2,.5,1,1),multiSelect:n.multiSelect??!1},this.picker=new t.Picker(e,1,1,!0)}async pickAtScreenPosition(t,e){const o=this.app.graphicsDevice.canvas,n=this.camera.camera;if(!n||!o)return null;const i=.25,s=Math.max(1,Math.floor(o.clientWidth*i)),a=Math.max(1,Math.floor(o.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(n,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i),p=this.picker.getSelection(l,c);if(p&&p.length>0){const t=p[0];if(t&&t.node){let e=t.node;for(;e.parent&&e.parent!==this.app.root;){const t=e.parent;if(t.tags?.has("selectable")||t.tags?.has("hotspot")||t.tags?.has("waypoint")||t.tags?.has("light")){e=t;break}e=t}return e}}return null}async getWorldPointAtScreen(t,e){const o=this.app.graphicsDevice.canvas,n=this.camera.camera;if(!n||!o)return null;const i=.25,s=Math.max(1,Math.floor(o.clientWidth*i)),a=Math.max(1,Math.floor(o.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(n,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i);try{return await this.picker.getWorldPointAsync(l,c)||null}catch(t){return null}}select(t,e=!1){const o=this.getSelectedEntity();e||this.deselectAll(),t&&(this.selectedEntities.add(t),this.applyHighlight(t)),this.onSelectionChange?.({entity:t,previousEntity:o})}deselect(t){this.selectedEntities.has(t)&&(this.selectedEntities.delete(t),this.removeHighlight(t),this.onSelectionChange?.({entity:this.getSelectedEntity(),previousEntity:t}))}deselectAll(){const t=this.getSelectedEntity();this.selectedEntities.forEach(t=>{this.removeHighlight(t)}),this.selectedEntities.clear(),t&&this.onSelectionChange?.({entity:null,previousEntity:t})}isSelected(t){return this.selectedEntities.has(t)}getSelectedEntity(){const t=Array.from(this.selectedEntities);return t.length>0?t[0]:null}getSelectedEntities(){return Array.from(this.selectedEntities)}applyHighlight(e){if(!e.render)return;const o=new Map;e.render.meshInstances.forEach((e,n)=>{if(e.material){o.set(n,e.material);const i=e.material.clone();i instanceof t.StandardMaterial&&(i.emissive=this.config.highlightColor,i.emissiveIntensity=.3,i.update()),e.material=i}}),this.originalMaterials.set(e,o)}removeHighlight(t){const e=this.originalMaterials.get(t);e&&t.render&&(t.render.meshInstances.forEach((t,o)=>{const n=e.get(o);n&&(t.material=n)}),this.originalMaterials.delete(t))}onSelect(t){this.onSelectionChange=t}async handlePointerDown(t,e=!1){let o,n;if(t instanceof MouseEvent)o=t.clientX,n=t.clientY;else{const e=t.touches[0];o=e.clientX,n=e.clientY}const i=await this.pickAtScreenPosition(o,n);return i?this.select(i,e):e||this.deselectAll(),i}destroy(){this.deselectAll(),this.selectedEntities.clear(),this.originalMaterials.clear()}}class ht{constructor(e,o,n,i={}){this.fpvEntity=null,this._enabled=!1,this.app=e,this.tourCamera=o,this.cameraControls=n,this.config=i,this.editCamera=new t.Entity("editCamera");const s=o.camera;this.editCamera.addComponent("camera",{clearColor:s?.clearColor||new t.Color(.1,.1,.1),fov:s?.fov||60,nearClip:s?.nearClip||.1,farClip:s?.farClip||1e3});const a=o.getPosition();this.editCamera.setPosition(a.x,a.y+5,a.z+10),this.editCamera.lookAt(a),this.editCamera.enabled=!1,e.root.addChild(this.editCamera),!1!==i.showFPVVisualization&&this.createFPVVisualization()}createFPVVisualization(){this.fpvEntity=new t.Entity("fpv-camera-viz"),this.fpvEntity.addComponent("render",{type:"cone",castShadows:!1,receiveShadows:!1}),this.fpvEntity.setLocalScale(.3,.5,.3),this.fpvEntity.enabled=!1,this.app.root.addChild(this.fpvEntity),this.app.on("update",()=>{if(this.fpvEntity&&this._enabled){const t=this.tourCamera.getPosition(),e=this.tourCamera.getRotation();this.fpvEntity.setPosition(t),this.fpvEntity.setRotation(e),this.fpvEntity.rotateLocal(90,0,0)}})}enable(){this._enabled=!0,this.tourCamera.enabled=!1,this.editCamera.enabled=!0,this.cameraControls.disable();const t=this.tourCamera.getPosition();this.editCamera.setPosition(t.x,t.y+5,t.z+10),this.editCamera.lookAt(t),this.fpvEntity&&(this.fpvEntity.enabled=!0)}disable(){this._enabled=!1,this.editCamera.enabled=!1,this.tourCamera.enabled=!0,this.fpvEntity&&(this.fpvEntity.enabled=!1)}get enabled(){return this._enabled}getCamera(){return this.editCamera}focusOn(t){const e=this.config.orbitDistance||10;this.editCamera.setPosition(t.x,t.y+.5*e,t.z+e),this.editCamera.lookAt(t)}syncClearColor(){this.editCamera.camera&&this.tourCamera.camera&&(this.editCamera.camera.clearColor=this.tourCamera.camera.clearColor)}setFOV(t){this.editCamera.camera&&(this.editCamera.camera.fov=t)}destroy(){this.disable(),this.editCamera.destroy(),this.fpvEntity&&(this.fpvEntity.destroy(),this.fpvEntity=null)}}export{C as CameraControls,r as DEFAULT_BUTTON_LABELS,ht as EditorCameraController,Z as FrameSequencePlayer,pt as GizmoManager,A as REVEAL_PRESETS,at as SceneApiError,st as SceneNotFoundError,dt as SelectionManager,et as createViewer,lt as createViewerFromSceneId,ot as createViewerFromUrl,a as exportPropsToViewerConfig,ct as fetchSceneMeta,n as generateHTML,i as generateHTMLFromUrl,P as getRevealPreset,s as transformSceneToExportProps};
|
|
1
|
+
import*as t from"playcanvas";function e(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function o(t){return t.replace(/<\/script/gi,"<\\/script")}function n(t,n={}){const{cdnUrl:i="https://unpkg.com/storysplat-viewer@2/dist/storysplat-viewer.umd.js",title:s=t.name||"StorySplat Scene",description:a=`Interactive 3D scene: ${t.name||"StorySplat"}`,faviconUrl:r,customCSS:l="",lazyLoad:c,lazyLoadButtonText:p}=n,d=c??t.uiOptions?.lazyLoad??!1,h=p??t.uiOptions?.lazyLoadButtonText,u=r?`<link rel="icon" href="${e(r)}" />`:"",m=l?`<style>${l}</style>`:"",y=o(JSON.stringify(t,(t,e)=>{if(!("undefined"!=typeof HTMLElement&&e instanceof HTMLElement||"function"==typeof e||e&&"object"==typeof e&&"nodeType"in e))return e},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="${e(a)}">\n <title>${e(s)}</title>\n ${u}\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n overflow: hidden;\n background: #111;\n }\n #app {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\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 ${m}\n</head>\n<body>\n <div id="app"></div>\n\n <script src="https://cdn.jsdelivr.net/npm/playcanvas@2.14.3/build/playcanvas.min.js"><\/script>\n <script src="${e(i)}"><\/script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${y};\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 lazyLoad: ${d},\n lazyLoadButtonText: ${h?`'${o(h)}'`:"undefined"}\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 i(t,e={}){const o=await fetch(t);if(!o.ok)throw new Error(`Failed to fetch scene: ${o.statusText}`);return n(await o.json(),e)}function s(t){const e=t.loadedModelUrl||t.splatUrl||"",o=t.sogModelUrl||t.sogUrl,n=t.compressedPlyUrl,i=t.lodMetaUrl,s="string"==typeof t.activeSkyboxUrl&&t.activeSkyboxUrl?t.activeSkyboxUrl:"string"==typeof t.skyboxUrl&&t.skyboxUrl?t.skyboxUrl:void 0,a=t.skybox||(s?{url:s,rotation:t.skyboxRotation}:void 0),r=e.split("?")[0].split(".").pop()?.toLowerCase(),l="ply"===r||e.includes(".compressed.ply");let c=e;o?c=o:n?c=n:l||console.warn("[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:",r),console.log("[StorySplat Viewer] URL selection:",{original:e,lodMetaUrl:i,sogUrl:o,compressedPlyUrl:n,selected:c});const p=(t.waypoints||[]).map(t=>{let e=t.fov||60;e<3.5&&(e*=180/Math.PI);let o=t.info||t.description||"";if(!o&&t.interactions){const e=t.interactions.find(t=>"info"===t.type),n=function(t){if(!t||"object"!=typeof t)return;const e=t;return"string"==typeof e.text?e.text:void 0}(e?.data);n&&(o=n)}return{position:t.position||{x:t.x||0,y:t.y||0,z:t.z||0},rotation:t.rotation||{x:0,y:0,z:0},fov:e,duration:t.duration||2e3,name:t.name||t.title||"",info:o,interactions:t.interactions||[],triggerDistance:t.triggerDistance??1}}),d=Array.isArray(t.particleSystems)&&t.particleSystems.length>0?t.particleSystems:t.particles||[];return{name:t.name||"StorySplat Scene",sceneId:t.sceneId,userId:t.userId,userName:t.userName||"Unknown",thumbnailUrl:t.thumbnailUrl,splatUrl:c,sogUrl:o,lodMetaUrl:i,fallbackUrls:[...t.fallbackUrls||[],o!==c?o:null,n!==c?n:null,e!==c?e:null].filter(t=>null!=t&&""!==t),scale:t.scale||(null!=t.splatScale?{x:t.splatScale,y:t.splatScale,z:t.splatScale}:{x:1,y:1,z:1}),splatPosition:t.splatPosition||t.position||t.cameraPosition||[0,0,0],splatRotation:t.splatRotation||t.rotation||t.cameraRotation||[0,0,0],invertXScale:t.invertXScale,invertYScale:t.invertYScale,waypoints:p,hotspots:t.hotspots||[],portals:t.portals||[],skybox:a,skyboxUrl:a?.url,skyboxRotation:a?.rotation,customMeshes:t.customMeshes||[],htmlMeshes:t.htmlMeshes||[],lights:t.lights||[],particles:d,collisionMeshesData:t.collisionMeshesData||[],playerHeight:t.playerHeight,uiColor:t.uiColor||"#ffffff",uiOptions:{showStartExperience:t.uiOptions?.showStartExperience??!0,showWatermark:t.uiOptions?.showWatermark??!0,hideFullscreenButton:t.uiOptions?.hideFullscreenButton??!1,hideInfoButton:t.uiOptions?.hideInfoButton??!1,hideMuteButton:t.uiOptions?.hideMuteButton??!1,hideHelpButton:t.uiOptions?.hideHelpButton??!1,hideNavigator:t.uiOptions?.hideNavigator??!1,showWaypointList:t.uiOptions?.showWaypointList??!0,hideWatermark:t.uiOptions?.hideWatermark??!1,watermarkText:t.uiOptions?.watermarkText,watermarkLink:t.uiOptions?.watermarkLink,buttonPosition:t.uiOptions?.buttonPosition||"inline",buttonLabels:t.uiOptions?.buttonLabels,customPreloaderLogoUrl:t.uiOptions?.customPreloaderLogoUrl,lazyLoad:t.uiOptions?.lazyLoad,lazyLoadButtonText:t.uiOptions?.lazyLoadButtonText,lazyLoadThumbnailUrl:t.uiOptions?.lazyLoadThumbnailUrl,lazyLoadThumbnailType:t.uiOptions?.lazyLoadThumbnailType,uiType:t.uiOptions?.uiType,debugMode:t.uiOptions?.debugMode},defaultCameraMode:t.defaultCameraMode||"orbit",allowedCameraModes:t.allowedCameraModes||["orbit","first-person","drone"],cameraMovementSpeed:t.cameraMovementSpeed,cameraRotationSensitivity:t.cameraRotationSensitivity,cameraDamping:t.cameraDamping,invertCameraRotation:t.invertCameraRotation,includeScrollControls:t.includeScrollControls??!0,scrollButtonMode:t.scrollButtonMode||"continuous",scrollAmount:t.scrollAmount||100,scrollSpeed:t.scrollSpeed,transitionSpeed:t.transitionSpeed,autoPlayEnabled:t.autoPlayEnabled??!1,autoplaySpeed:t.autoplaySpeed,loopMode:t.loopMode,includeXR:t.includeXR,xrMode:t.xrMode,customScript:t.customScript,templateType:t.uiOptions?.uiType||"minimal",additionalSplats:t.additionalSplats||[],keepMeshesInMemory:t.keepMeshesInMemory??!1,initialSplatExploreMode:t.initialSplatExploreMode,audioEmitters:t.audioEmitters||[],frameSequence:t.frameSequence}}function a(t){const e=t.waypoints?.[0]?.fov||60;return{splatUrl:t.splatUrl,sogUrl:t.sogUrl,lodMetaUrl:t.lodMetaUrl,fallbackUrls:t.fallbackUrls,scale:t.scale,position:t.splatPosition||[0,0,0],rotation:t.splatRotation||[0,0,0],invertXScale:t.invertXScale,invertYScale:t.invertYScale,waypoints:t.waypoints,hotspots:t.hotspots,portals:t.portals,skybox:t.skybox,skyboxUrl:t.skyboxUrl,skyboxRotation:t.skyboxRotation,customMeshes:t.customMeshes,htmlMeshes:t.htmlMeshes,lights:t.lights,particles:t.particles,collisionMeshesData:t.collisionMeshesData,cameraMode:t.defaultCameraMode,defaultCameraMode:t.defaultCameraMode,allowedCameraModes:t.allowedCameraModes,autoPlay:t.autoPlayEnabled,uiColor:t.uiColor,uiOptions:t.uiOptions,fov:e,nearClip:t.minClipPlane||.1,farClip:t.maxClipPlane||1e3,playerHeight:t.playerHeight||1.6,cameraMovementSpeed:t.cameraMovementSpeed||1,cameraRotationSensitivity:t.cameraRotationSensitivity||.2,cameraDamping:t.cameraDamping||.75,invertCameraRotation:t.invertCameraRotation,scrollSpeed:t.scrollSpeed,scrollAmount:t.scrollAmount,scrollButtonMode:t.scrollButtonMode,transitionSpeed:t.transitionSpeed,autoplaySpeed:t.autoplaySpeed,loopMode:t.loopMode,additionalSplats:t.additionalSplats,keepMeshesInMemory:t.keepMeshesInMemory??!1,initialSplatExploreMode:t.initialSplatExploreMode,includeXR:t.includeXR,xrMode:t.xrMode,customScript:t.customScript,audioEmitters:t.audioEmitters||[],frameSequence:t.frameSequence}}const r={tour:"Tour",explore:"Explore",hybrid:"Hybrid",walk:"Walk",orbit:"Orbit",fly:"Fly",next:"Next",previous:"Prev",startExperience:"Start Experience",fullscreen:"Fullscreen",mute:"Mute",unmute:"Unmute",waypoints:"Waypoints",close:"Close",yes:"Yes",cancel:"Cancel",switchScenes:"Switch scenes?",hotspotDefaultTitle:"Hotspot",openExternalLink:"Open External Link",vr:"VR",ar:"AR",exitVr:"Exit VR",exitAr:"Exit AR",loading:"Loading...",loadingScene:"Loading {name}...",helpTitle:"Controls & Help",helpCameraModes:"Camera Modes:",helpTourDesc:"Follow predefined path",helpExploreDesc:"Free movement",helpWalkDesc:"First-person walking",errorWebGLTitle:"Unable to Initialize 3D Graphics",errorWebGLMessage:"Your browser or device may not support WebGL. Please try a different browser or device.",percentageFormat:"{n}%"};function l(t,e){return t?.[e]||r[e]}function c(t="#4CAF50",e="minimal"){const o=document.getElementById("storysplat-viewer-styles");o&&o.remove();const n=document.createElement("style");return n.id="storysplat-viewer-styles",n.textContent=function(t="#4CAF50",e="minimal"){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 /* Style isolation: reset common elements to prevent parent page styles from leaking in.\n Uses :where() for zero specificity so individual class rules always win. */\n .storysplat-viewer-container :where(button, a) {\n all: unset;\n box-sizing: border-box;\n display: inline-block;\n font-family: inherit;\n font-size: inherit;\n line-height: normal;\n cursor: pointer;\n }\n\n .storysplat-viewer-container button:focus-visible,\n .storysplat-viewer-container a:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\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: 100000;\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: ${t};\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: ${t};\n transition: width 0.3s ease-out;\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 text-align: center;\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 /* Explore Controls (Orbit/Fly toggle) */\n .storysplat-explore-controls {\n display: none;\n gap: 5px;\n width: 100%;\n justify-content: center;\n }\n\n .storysplat-explore-controls.visible {\n display: flex;\n }\n\n .storysplat-explore-btn {\n flex: 1;\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 6px 12px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-explore-btn.selected {\n background: ${t} !important;\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.5);\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 text-align: center;\n }\n\n .storysplat-mode-btn.selected {\n background: ${t} !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 /* Mute Button - Minimal */\n .storysplat-mute-btn {\n position: absolute;\n top: 10px;\n right: 40px;\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-mute-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 /* XR (VR/AR) Buttons - Minimal */\n .storysplat-xr-btn {\n position: absolute;\n top: 10px;\n right: 45px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n z-index: 1000;\n color: white;\n font-size: 11px;\n font-weight: bold;\n text-transform: uppercase;\n transition: all 0.2s ease;\n display: none;\n }\n\n .storysplat-xr-btn.available {\n display: block;\n }\n\n .storysplat-xr-btn.active {\n background: ${t};\n }\n\n .storysplat-xr-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-xr-btn.active:hover {\n background: ${t};\n opacity: 0.9;\n }\n\n .storysplat-ar-btn {\n right: 85px;\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 styles */\n /* Uses position: absolute so popup stays within container when embedded */\n .storysplat-hotspot-popup {\n position: absolute;\n background-color: rgba(0, 0, 0, 0.75);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100001;\n box-shadow: 0 0 10px rgba(0,0,0,0.5);\n display: none;\n font-size: 14px;\n flex-direction: column;\n font-family: system-ui, -apple-system, sans-serif;\n /* Default centered positioning */\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 80%;\n max-height: 80%;\n overflow: auto;\n }\n\n .storysplat-hotspot-popup.visible {\n display: flex;\n }\n\n /* Fullscreen mode for click activation with image/iframe content */\n .storysplat-hotspot-popup.fullscreen {\n top: 20px !important;\n left: 20px !important;\n right: 20px !important;\n bottom: 20px !important;\n transform: none !important;\n width: auto !important;\n height: auto !important;\n max-width: none !important;\n max-height: none !important;\n margin: 0 !important;\n border-radius: 10px !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n flex-direction: column !important;\n padding: 0 !important;\n overflow: hidden !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: 18px;\n font-weight: 600;\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: 1.5;\n font-size: 14px;\n white-space: pre-wrap;\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup-content {\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-title {\n text-align: center;\n width: 100%;\n padding: 20px 20px 10px 20px;\n padding-right: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n width: 100%;\n max-width: none;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n\n .storysplat-hotspot-popup.fullscreen p {\n text-align: center;\n max-width: 80%;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-link {\n margin: 4px auto;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-close {\n display: block;\n margin: 4px auto 20px auto;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup img {\n max-width: 100%;\n height: auto;\n border-radius: 5px;\n margin-bottom: 10px;\n display: block;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n width: auto !important;\n height: auto !important;\n max-width: 90% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n margin: 10px 0;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup iframe {\n width: 100%;\n max-width: 100%;\n aspect-ratio: 16 / 9;\n height: auto;\n border: none;\n border-radius: 5px;\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 90% !important;\n max-width: 800px !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 100% !important;\n margin: 10px 0;\n border-radius: 8px;\n flex-shrink: 1;\n min-height: 0;\n }\n\n /* Portal confirmation popup */\n .storysplat-portal-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100002;\n display: none;\n flex-direction: column;\n gap: 15px;\n min-width: 240px;\n max-width: 80%;\n text-align: center;\n box-shadow: 0 0 12px rgba(0, 0, 0, 0.6);\n }\n\n .storysplat-portal-popup.visible {\n display: flex;\n }\n\n .storysplat-portal-popup-title {\n font-size: 16px;\n font-weight: 600;\n }\n\n .storysplat-portal-popup-actions {\n display: flex;\n justify-content: center;\n gap: 10px;\n }\n\n .storysplat-portal-popup-btn {\n padding: 8px 14px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n }\n\n .storysplat-portal-popup-confirm {\n background: ${t};\n color: white;\n }\n\n .storysplat-portal-popup-cancel {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n }\n\n /* Video container - matches HTML export sizing */\n .storysplat-hotspot-popup .video-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 300px;\n max-width: 100%;\n height: auto;\n margin: 10px 0;\n }\n\n .storysplat-hotspot-popup video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 5px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n }\n\n /* Fullscreen popup video - constrained so title+description+buttons always fit */\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 90% !important;\n max-width: 800px !important;\n max-height: 100% !important;\n height: auto !important;\n margin: 10px 0 !important;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen video {\n max-width: 100% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n }\n\n /* Close button - matching BabylonJS export (green button at bottom) */\n .storysplat-hotspot-popup-close {\n display: inline-block;\n width: auto;\n padding: 10px 20px;\n background-color: #4CAF50;\n border: none;\n color: white;\n cursor: pointer;\n border-radius: 5px;\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: system-ui, -apple-system, sans-serif;\n text-align: center;\n transition: background-color 0.2s;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup-close:hover {\n background-color: #45a049;\n }\n\n /* Error Popup - for outdated/broken scenes */\n .storysplat-error-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.95);\n border-radius: 12px;\n padding: 30px 40px;\n max-width: 450px;\n width: 90%;\n text-align: center;\n z-index: 10001;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 100, 100, 0.3);\n }\n\n .storysplat-error-popup-icon {\n font-size: 48px;\n margin-bottom: 15px;\n }\n\n .storysplat-error-popup-title {\n color: #ff6b6b;\n font-size: 20px;\n font-weight: 600;\n margin: 0 0 15px 0;\n }\n\n .storysplat-error-popup-message {\n color: #ccc;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 20px 0;\n }\n\n .storysplat-error-popup-action {\n display: inline-block;\n padding: 12px 24px;\n background: linear-gradient(135deg, #4CAF50, #45a049);\n color: white;\n text-decoration: none;\n border-radius: 6px;\n font-weight: 500;\n font-size: 14px;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .storysplat-error-popup-action:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);\n }\n\n .storysplat-hotspot-popup-link {\n display: block;\n padding: 8px 16px;\n background: #007bff;\n color: white;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n font-weight: 500;\n margin: 0 0 4px 0;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .storysplat-hotspot-popup-link:hover {\n background: #0056b3;\n }\n\n /* Mobile Responsive */\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen {\n top: 10px !important;\n left: 10px !important;\n right: 10px !important;\n bottom: 10px !important;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n padding: 10px 15px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 98% !important;\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 98% !important;\n max-width: 98% !important;\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 will-change: transform;\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 /* Look Zone Active State */\n .storysplat-look-zone.active .storysplat-look-zone-icon {\n background: rgba(255, 255, 255, 0.25);\n border-color: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-look-zone.active .storysplat-look-zone-icon svg {\n fill: rgba(255, 255, 255, 0.8);\n }\n\n /* Lazy Load Container */\n .storysplat-lazy-load-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #1a1a1a;\n z-index: 10001;\n }\n\n .storysplat-lazy-load-thumbnail {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-thumbnail-video {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.3) 0%,\n rgba(0, 0, 0, 0.5) 50%,\n rgba(0, 0, 0, 0.7) 100%\n );\n }\n\n .storysplat-lazy-load-content {\n position: relative;\n z-index: 1;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n }\n\n .storysplat-lazy-load-start-btn {\n padding: 16px 32px;\n background: ${t};\n border: none;\n border-radius: 50px;\n color: white;\n font-size: 18px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n transition: all 0.3s ease;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n }\n\n .storysplat-lazy-load-start-btn:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 30px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-lazy-load-start-btn:active {\n transform: scale(0.98);\n }\n\n .storysplat-lazy-load-start-btn svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n @media (max-width: 768px) {\n .storysplat-lazy-load-start-btn {\n padding: 12px 24px;\n font-size: 16px;\n }\n }\n\n /* Waypoint List Dropdown - Glassmorphic style */\n .storysplat-waypoint-list-container {\n position: absolute;\n top: 10px;\n right: 45px;\n z-index: 1000;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n transition: background-color 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-waypoint-list-toggle:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: white;\n transition: transform 0.3s ease;\n }\n\n .storysplat-waypoint-list-toggle.open svg {\n transform: rotate(180deg);\n }\n\n .storysplat-waypoint-list-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n min-width: 200px;\n max-height: 300px;\n overflow-y: auto;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n display: none;\n flex-direction: column;\n }\n\n .storysplat-waypoint-list-dropdown.open {\n display: flex;\n }\n\n .storysplat-waypoint-item {\n padding: 12px 16px;\n color: white;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n transition: background-color 0.2s ease;\n font-size: 14px;\n }\n\n .storysplat-waypoint-item:last-child {\n border-bottom: none;\n }\n\n .storysplat-waypoint-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-waypoint-item.active {\n background: rgba(76, 175, 80, 0.3);\n }\n\n /* Adjust position when fullscreen button is present */\n .storysplat-waypoint-list-container.with-fullscreen {\n right: 45px;\n }\n\n .storysplat-waypoint-list-container.no-fullscreen {\n right: 10px;\n }\n\n @media (max-width: 768px) {\n .storysplat-waypoint-list-container {\n right: 40px;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 6px 12px;\n font-size: 12px;\n }\n\n .storysplat-waypoint-list-dropdown {\n min-width: 160px;\n max-height: 250px;\n }\n\n .storysplat-waypoint-item {\n padding: 10px 12px;\n font-size: 12px;\n }\n }\n\n /* Watermark - matches BabylonJS HTML export */\n .storysplat-watermark {\n position: absolute;\n bottom: 10px;\n right: 10px;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-size: 12px;\n z-index: 1000;\n pointer-events: auto; /* Allow pointer events for the entire watermark so links work */\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n }\n\n .storysplat-watermark a {\n color: ${t};\n text-decoration: none;\n cursor: pointer;\n }\n\n .storysplat-watermark a:hover {\n text-decoration: underline;\n }\n\n .storysplat-fps-counter {\n position: absolute;\n top: 8px;\n left: 8px;\n background: rgba(0, 0, 0, 0.6);\n color: #0f0;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-family: monospace;\n z-index: 1000;\n pointer-events: none;\n line-height: 1;\n }\n `+function(t,e){return"standard"===e?`\n /* ===== STANDARD (Centered) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: auto;\n max-width: 350px;\n padding: 15px 20px;\n background: rgba(0, 0, 0, 0.7);\n border-radius: 10px;\n bottom: 20px;\n gap: 8px;\n }\n\n .storysplat-scroll-content {\n gap: 8px;\n }\n\n .storysplat-progress-container {\n max-width: 300px;\n height: 10px;\n border-radius: 5px;\n }\n\n .storysplat-progress-bar {\n border-radius: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 16px;\n }\n\n .storysplat-scroll-buttons {\n gap: 8px;\n }\n\n .storysplat-btn {\n background: ${t};\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n }\n\n .storysplat-btn:hover {\n opacity: 0.85;\n background: ${t};\n }\n\n .storysplat-btn-play {\n padding: 10px 12px;\n background: ${t};\n border-radius: 5px;\n }\n\n .storysplat-btn-play svg {\n width: 16px;\n height: 16px;\n }\n\n .storysplat-btn-play:hover {\n opacity: 0.85;\n background: ${t};\n }\n\n .storysplat-mode-container {\n margin-top: 8px;\n }\n\n .storysplat-mode-toggle {\n gap: 8px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 14px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-btn {\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n max-width: 280px;\n padding: 12px 15px;\n }\n\n .storysplat-btn {\n padding: 8px 16px;\n font-size: 14px;\n }\n\n .storysplat-progress-container {\n height: 8px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n }\n }\n `:"pro"===e?"\n /* ===== PRO (Dark) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: 80%;\n max-width: 600px;\n padding: 12px 20px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 8px;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n gap: 6px;\n }\n\n .storysplat-scroll-content {\n gap: 6px;\n }\n\n .storysplat-progress-container {\n max-width: 100%;\n height: 2px;\n border-radius: 1px;\n }\n\n .storysplat-progress-bar {\n border-radius: 1px;\n }\n\n .storysplat-progress-text {\n font-size: 13px;\n opacity: 0.8;\n }\n\n .storysplat-scroll-buttons {\n gap: 6px;\n }\n\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.5);\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-btn-play {\n padding: 8px 10px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn-play svg {\n width: 14px;\n height: 14px;\n }\n\n .storysplat-btn-play:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n /* Pro: mode toggle on the left side, vertical stack */\n .storysplat-mode-container {\n position: absolute;\n bottom: 20px;\n left: 20px;\n margin-top: 0;\n z-index: 1000;\n }\n\n .storysplat-mode-toggle {\n flex-direction: column;\n gap: 4px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 12px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-controls {\n flex-direction: column;\n }\n\n .storysplat-explore-btn {\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n width: 70%;\n max-width: none;\n padding: 10px 15px;\n bottom: 10px;\n }\n\n .storysplat-mode-container {\n bottom: 10px;\n left: 10px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n ":""}(t,e)}(t,e),document.head.appendChild(n),n}function p(t,e,o){const n=document.createElement("div");n.className="storysplat-preloader";const i=!!e;return n.innerHTML=`\n <div class="storysplat-preloader-content">\n <div class="storysplat-preloader-media">\n ${i?`<img class="storysplat-preloader-image" src="${e}" 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 ${i?"":'<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">${l(o,"loading")} 0%</div>\n <div class="storysplat-preloader-bar"></div>\n </div>\n </div>\n `,t.appendChild(n),i||new Promise(t=>{if(customElements.get("lottie-player"))return void t();const e=document.querySelector('script[src*="lottie-player"]');if(e)return void e.addEventListener("load",()=>t());const o=document.createElement("script");o.src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js",o.onload=()=>t(),document.head.appendChild(o)}),n}function d(t){t.classList.add("hidden"),setTimeout(()=>t.remove(),500)}function h(t,e,o="tour",n){const i=t.scrollControls?.querySelector(".storysplat-btn-prev"),s=t.scrollControls?.querySelector(".storysplat-btn-next"),a=t.scrollControls?.querySelector(".storysplat-btn-play");if(i&&i.addEventListener("click",()=>e.prevWaypoint()),s&&s.addEventListener("click",()=>e.nextWaypoint()),a){const t=t=>{a.innerHTML=t?'<svg viewBox="0 0 24 24"><path d="M6 4h4v16H6zm8 0h4v16h-4z"/></svg>':'<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>'};a.addEventListener("click",()=>{e.isPlaying()?e.pause():e.play()}),e.on("playbackStart",()=>t(!0)),e.on("playbackStop",()=>t(!1)),t(e.isPlaying())}if(t.helpButton&&t.helpPanel&&t.helpButton.addEventListener("click",()=>{t.helpPanel.classList.toggle("visible")}),t.fullscreenButton){if(/iPad|iPhone|iPod/.test(navigator.userAgent)||"MacIntel"===navigator.platform&&navigator.maxTouchPoints>1)t.fullscreenButton.style.display="none",console.log("[StorySplat Viewer] Fullscreen button hidden on iOS (API not supported)");else{const e=t.fullscreenButton.parentElement;t.fullscreenButton.addEventListener("click",()=>{const o=document;if(o.fullscreenElement||o.webkitFullscreenElement){o.exitFullscreen?o.exitFullscreen():o.webkitExitFullscreen&&o.webkitExitFullscreen();const e=t.fullscreenButton.querySelector(".storysplat-expand-icon"),n=t.fullscreenButton.querySelector(".storysplat-compress-icon");e&&(e.style.display="block"),n&&(n.style.display="none")}else{e?.requestFullscreen?e.requestFullscreen():e?.webkitRequestFullscreen&&e.webkitRequestFullscreen?.();const o=t.fullscreenButton.querySelector(".storysplat-expand-icon"),n=t.fullscreenButton.querySelector(".storysplat-compress-icon");o&&(o.style.display="none"),n&&(n.style.display="block")}});const o=()=>{const e=document,o=e.fullscreenElement||e.webkitFullscreenElement,n=t.fullscreenButton.querySelector(".storysplat-expand-icon"),i=t.fullscreenButton.querySelector(".storysplat-compress-icon");n&&(n.style.display=o?"none":"block"),i&&(i.style.display=o?"block":"none")};document.addEventListener("fullscreenchange",o),document.addEventListener("webkitfullscreenchange",o)}}const l=e=>{const o=t.scrollControls?.querySelector(".storysplat-progress-text"),n=t.scrollControls?.querySelector(".storysplat-progress-container"),i=t.scrollControls?.querySelector(".storysplat-scroll-buttons"),s=e?"":"none";o&&(o.style.display=s),n&&(n.style.display=s),i&&(i.style.display=s)},c=e=>{const o=t.scrollControls?.querySelector(".storysplat-explore-controls");o&&(e?o.classList.add("visible"):o.classList.remove("visible"))};if(l("tour"===o),c("explore"===o),e.setCameraMode){const o=t.scrollControls?.parentElement||t.scrollControls,n=o?.querySelectorAll(".storysplat-mode-btn");n?.forEach(t=>{t.addEventListener("click",()=>{const o=t.getAttribute("data-mode");o&&e.setCameraMode&&(e.setCameraMode(o),n.forEach(t=>t.classList.remove("selected")),t.classList.add("selected"),l("tour"===o),c("explore"===o))})}),e.on("modeChange",({mode:t})=>{l("tour"===t),c("explore"===t),n?.forEach(e=>{const o=e.getAttribute("data-mode");e.classList.toggle("selected",o===t)})})}let p=0,d=-1;e.on("progressUpdate",({progress:e})=>{const o=100*Math.max(0,Math.min(1,e)),i=Math.round(o),s=performance.now();var a,l;s-p<33||(p=s,t.progressBar&&(t.progressBar.style.width=`${o}%`),t.progressText&&i!==d&&(d=i,t.progressText.innerHTML="",t.progressText.textContent=(a=n,l=i,(a?.percentageFormat||r.percentageFormat).replace("{n}",String(l)))))});let h=-1;e.on("waypointChange",({index:o,waypoint:n})=>{if(o===h)return;if(h=o,!t.waypointInfo)return;const i=t.waypointInfo.querySelector(".storysplat-waypoint-title"),s=t.waypointInfo.querySelector(".storysplat-waypoint-description");if(!i||!s)return;const a=n||e.getWaypoints?.()[o];a&&(a.name||a.info)?(i.textContent=a.name||"",s.textContent=a.info||"",a.name||a.info?t.waypointInfo.classList.add("hasContent"):t.waypointInfo.classList.remove("hasContent")):(i.textContent="",s.textContent="",t.waypointInfo.classList.remove("hasContent"))})}function u(t,e,o){const n=t.querySelector(".storysplat-hotspot-popup");if(!n)return;const i=n.querySelector(".storysplat-hotspot-popup-title"),s=n.querySelector(".storysplat-hotspot-popup-content"),a=n.querySelector(".storysplat-hotspot-popup-close");n.style.cssText="",n.classList.remove("fullscreen");const r=e.activationMode||"click",c=e.photoUrl||e.popupVideoUrl||"iframe"===e.contentType&&e.iframeUrl;"click"===r&&c&&n.classList.add("fullscreen");const p=e.backgroundAlpha??.75;if(e.backgroundColor){const t=e.backgroundColor.replace("#","");if(6===t.length){const e=parseInt(t.substring(0,2),16),o=parseInt(t.substring(2,4),16),i=parseInt(t.substring(4,6),16);n.style.backgroundColor=`rgba(${e}, ${o}, ${i}, ${p})`}else n.style.backgroundColor=e.backgroundColor}else n.style.backgroundColor=`rgba(0, 0, 0, ${p})`;e.textColor&&(n.style.color=e.textColor,i&&(i.style.color=e.textColor)),e.fontFamily&&(n.style.fontFamily=e.fontFamily),e.fontSize&&(n.style.fontSize=`${e.fontSize}px`),a&&e.closeButtonColor&&(a.style.backgroundColor=e.closeButtonColor),i&&(i.textContent=e.title||l(o,"hotspotDefaultTitle"));let d="";if("iframe"===e.contentType&&e.iframeUrl&&(d+=`<iframe src="${e.iframeUrl}" title="${e.title||"Embedded content"}"></iframe>`),e.popupVideoUrl&&(d+=`<div class="video-container"><video src="${e.popupVideoUrl}" controls playsinline webkit-playsinline preload="metadata"></video></div>`),e.photoUrl&&(d+=`<img src="${e.photoUrl}" alt="${e.title||"Hotspot image"}" />`),e.information&&(d+=`<p>${e.information}</p>`),e.externalLinkUrl){const t=e.externalLinkButtonColor||"#007bff";d+=`\n <div onclick="window.open('${e.externalLinkUrl}', '_blank', 'noopener,noreferrer')"\n class="storysplat-hotspot-popup-link" style="background-color: ${t}">\n ${e.externalLinkText||l(o,"openExternalLink")}\n </div>\n `}s&&(s.innerHTML=d),n.classList.add("visible")}function m(t,e){t.joystick&&(e?(t.joystick.classList.add("visible"),t.joystickThumb&&(t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)")):t.joystick.classList.remove("visible")),t.lookZone&&(e?t.lookZone.classList.add("visible"):t.lookZone.classList.remove("visible"))}function y(t,e,o,n,i){if(t.joystickThumb)if(e){t.joystickThumb.classList.add("active");const e=Math.sqrt(o*o+n*n),s=Math.min(e,i),a=e>0?s/e:0,r=o*a,l=n*a;t.joystickThumb.style.transform=`translate(${r}px, ${l}px)`}else t.joystickThumb.classList.remove("active"),t.joystickThumb.style.transform="translate(0, 0)"}function g(t,e){t.lookZone&&(e?t.lookZone.classList.add("active"):t.lookZone.classList.remove("active"))}function f(t,e){if(!t.waypointListContainer)return;t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item").forEach((t,o)=>{o===e?t.classList.add("active"):t.classList.remove("active")})}const v=new t.Vec3,b=new t.Vec3,x=new t.Pose,w=new t.InputFrame({move:[0,0,0],rotate:[0,0,0]}),S=(t,e,o)=>{const n=Math.sqrt(t[0]*t[0]+t[1]*t[1]);if(n<e)return void t.fill(0);const i=(n-e)/(o-e);t[0]*=i/n,t[1]*=i/n},E=(e,o,n,i,s=new t.Vec3)=>{const{fov:a,aspectRatio:r,horizontalFov:l,projection:c,orthoHeight:p}=e,d=e.system?.app,{width:h,height:u}=d?.graphicsDevice?.clientRect||{width:1920,height:1080};s.set(-o/h*2,n/u*2,0);const m=b.set(0,0,0);if(c===t.PROJECTION_PERSPECTIVE){const e=i*Math.tan(.5*a*t.math.DEG_TO_RAD);l?m.set(e,e/r,0):m.set(e*r,e,0)}else m.set(p*r,p,0);return s.mul(m),s};class C{constructor(e,o,n={}){this.enabled=!0,this._mode="orbit",this._enableOrbit=!0,this._enableFly=!0,this.enablePan=!0,this._pose=new t.Pose,this._preFocusMode="orbit",this._startZoomDist=0,this._pitchRange=new t.Vec2(-89,89),this._yawRange=new t.Vec2(-1/0,1/0),this._lastFocusPoint=new t.Vec3(0,0,0),this._zoomRange=new t.Vec2(.01,1/0),this._state={axis:new t.Vec3,shift:0,ctrl:0,mouse:[0,0,0],touches:0},this.moveSpeed=25,this.moveFastSpeed=50,this.moveSlowSpeed=10,this.rotateSpeed=.05,this.rotateTouchSens=1,this.rotateJoystickSens=1,this.zoomSpeed=.001,this.zoomPinchSens=5,this.keyboardSpeedMultiplier=1.5,this.gamepadDeadZone=new t.Vec2(.3,.6),this.invertRotation=!1,this.joystickEventName="joystick",this._collisionEntities=[],this._collisionRadius=.3,this._prevPosition=new t.Vec3,this._prevPositionValid=!1,this._collisionTestVec=new t.Vec3,this._destroyHandler=null,this.camera=e,this.app=o;const i=e.camera;if(!i)throw new Error("CameraControls: camera component not found on entity");this.cameraComponent=i,this._flyController=new t.FlyController,this._orbitController=new t.OrbitController,this._focusController=new t.FocusController,this._flyController.moveDamping=.75,this._flyController.rotateDamping=.75,this._orbitController.rotateDamping=.75,this._orbitController.zoomDamping=.8,this._orbitController.zoomRange=new t.Vec2(.01,1/0);const s=o.graphicsDevice.canvas;this._desktopInput=new t.KeyboardMouseSource,this._orbitMobileInput=new t.MultiTouchSource,this._flyMobileInput=new t.DualGestureSource,this._gamepadInput=new t.GamepadSource,this._desktopInput.attach(s),this._orbitMobileInput.attach(s),this._flyMobileInput.attach(s),this._gamepadInput.attach(s),this._flyMobileInput.on("joystick:position:left",([t,e,o,n])=>{(t<0||"fly"===this._mode)&&this.app.fire(`${this.joystickEventName}:left`,t,e,o,n)}),this._flyMobileInput.on("joystick:position:right",([t,e,o,n])=>{(t<0||"fly"===this._mode)&&this.app.fire(`${this.joystickEventName}:right`,t,e,o,n)}),this._pose.look(this.camera.getPosition(),t.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.rotateTouchSens&&(this.rotateTouchSens=n.rotateTouchSens),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),void 0!==n.invertRotation&&(this.invertRotation=n.invertRotation)}set enableFly(t){this._enableFly=t,this._enableFly||"fly"!==this._mode||this._setMode("orbit")}get enableFly(){return this._enableFly}set enableOrbit(t){this._enableOrbit=t,this._enableOrbit||"orbit"!==this._mode||this._setMode("fly")}get enableOrbit(){return this._enableOrbit}set focusDamping(t){this._focusController.focusDamping=t}get focusDamping(){return this._focusController.focusDamping}set moveDamping(t){this._flyController.moveDamping=t}get moveDamping(){return this._flyController.moveDamping}set rotateDamping(t){this._flyController.rotateDamping=t,this._orbitController.rotateDamping=t}get rotateDamping(){return this._orbitController.rotateDamping}set zoomDamping(t){this._orbitController.zoomDamping=t}get zoomDamping(){return this._orbitController.zoomDamping}set focusPoint(t){const e=this.camera.getPosition();this._startZoomDist=e.distance(t),this._controller.attach(this._pose.look(e,t),!1)}get focusPoint(){return this._pose.getFocus(v)}set pitchRange(t){this._pitchRange.copy(t),this._flyController.pitchRange=this._pitchRange,this._orbitController.pitchRange=this._pitchRange}get pitchRange(){return this._pitchRange}set yawRange(e){this._yawRange.x=t.math.clamp(e.x,-360,360),this._yawRange.y=t.math.clamp(e.y,-360,360),this._flyController.yawRange=this._yawRange,this._orbitController.yawRange=this._yawRange}get yawRange(){return this._yawRange}set zoomRange(t){this._zoomRange.x=t.x,this._zoomRange.y=t.y<=t.x?1/0:t.y,this._orbitController.zoomRange=this._zoomRange}get zoomRange(){return this._zoomRange}set mobileInputLayout(t){/(?:joystick|touch)-(?:joystick|touch)/.test(t)?this._flyMobileInput.layout=t:console.warn(`CameraControls: invalid mobile input layout: ${t}`)}get mobileInputLayout(){return this._flyMobileInput.layout}get mode(){return this._mode}_setMode(t){if(this._enableFly&&!this._enableOrbit)t="fly";else if(!this._enableFly&&this._enableOrbit)t="orbit";else if(!this._enableFly&&!this._enableOrbit)return void console.warn("CameraControls: both fly and orbit modes are disabled");const e=this._mode;if(e!==t){switch(this._mode=t,this._controller&&this._controller.detach(),this._mode){case"orbit":if(this._controller=this._orbitController,"focus"===e){const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint)}this._lastYaw=this._pose.angles.y;break;case"fly":if(this._controller=this._flyController,"focus"===e){const t=this.camera.getPosition();this._pose.look(t,this._lastFocusPoint)}break;case"focus":this._controller=this._focusController}this._controller.attach(this._pose,!1),this.app.fire("cameracontrols:modechange",this._mode)}}setMode(t){this._setMode(t)}focus(t,e=!1){this._lastFocusPoint.copy(t),"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const o=e?this._startZoomDist:this.camera.getPosition().distance(t);this._startZoomDist=o;const n=v.copy(this.camera.forward).mulScalar(-o).add(t);this._controller.attach(x.look(n,t))}flyTo(t,e=2){this._lastFocusPoint.copy(t),"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const o=this.camera.getPosition(),n=o.distance(t),i=Math.min(e,.1*n),s=v.copy(t).sub(o).normalize(),a=b.copy(t).sub(s.mulScalar(i));this._controller.attach(x.look(a,t))}look(t,e=!1){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus");const o=e?v.copy(this.camera.getPosition()).sub(t).normalize().mulScalar(this._startZoomDist).add(t):this.camera.getPosition();this._controller.attach(x.look(o,t))}reset(t,e){"focus"!==this._mode&&(this._preFocusMode=this._mode),this._setMode("focus"),this._controller.attach(x.look(e,t))}syncFromCamera(t){const e=this.camera.getPosition().clone();if(this._prevPositionValid=!1,t){this._lastFocusPoint.copy(t);const o=e.distance(t);this._startZoomDist=o,this._pose.distance=o,this._pose.look(e,t);let n=this._pose.angles.y;for(;n>180;)n-=360;for(;n<-180;)n+=360;this._pose.angles.y=n,this._lastYaw=n,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),this._pose.angles.z=0}else{const t=this.camera.getEulerAngles();this._pose.position.copy(e),this._pose.angles.x=t.x,this._pose.angles.y=t.y,this._pose.angles.z=0;let o=this._pose.angles.y;for(;o>180;)o-=360;for(;o<-180;)o+=360;this._pose.angles.y=o,this._lastYaw=o,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x));const n=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(n.mulScalar(10));const i=10;this._startZoomDist=i,this._pose.distance=i}this._controller.attach(this._pose,!1)}syncFromPose(e,o,n){this._prevPositionValid=!1,this.camera.setPosition(e),this.camera.setRotation(o);const i=new t.Entity;i.setRotation(o);const s=i.getEulerAngles();this._pose.position.copy(e),this._pose.angles.x=s.x,this._pose.angles.y=s.y,this._pose.angles.z=0;let a=this._pose.angles.y;for(;a>180;)a-=360;for(;a<-180;)a+=360;if(this._pose.angles.y=a,this._lastYaw=a,this._pose.angles.x=Math.max(-89,Math.min(89,this._pose.angles.x)),n)this._lastFocusPoint.copy(n);else{const t=this.camera.forward.clone();this._lastFocusPoint.copy(e).add(t.mulScalar(10))}const r=e.distance(this._lastFocusPoint);this._startZoomDist=r,this._pose.distance=r,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(e){if(!this.enabled)return;const{keyCode:o}=t.KeyboardMouseSource,{key:n,button:i,mouse:s,wheel:a}=this._desktopInput.read(),{touch:r,pinch:l,count:c}=this._orbitMobileInput.read(),{leftInput:p,rightInput:d}=this._flyMobileInput.read(),{leftStick:h,rightStick:u}=this._gamepadInput.read();S(h,this.gamepadDeadZone.x,this.gamepadDeadZone.y),S(u,this.gamepadDeadZone.x,this.gamepadDeadZone.y),this._state.axis.add(v.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 t=0;t<this._state.mouse.length;t++)this._state.mouse[t]+=i[t];this._state.shift+=n[o.SHIFT],this._state.ctrl+=n[o.CTRL],this._state.touches+=c[0];const m=+("orbit"===this._mode),y=+("fly"===this._mode),g=+(this._state.touches>1),f=+(this._state.shift||this._state.mouse[1]),x=+this._flyMobileInput.layout.endsWith("joystick"),C=(this._state.shift?this.moveFastSpeed:this._state.ctrl?this.moveSlowSpeed:this.moveSpeed)*e,M=60*this.zoomSpeed*e,_=M*this.zoomPinchSens,L=60*this.rotateSpeed*e,k=L*this.rotateTouchSens,A=this.rotateSpeed*this.rotateJoystickSens*60*e,{deltas:P}=w,T=v.set(0,0,0),z=this._state.axis.clone().normalize();T.add(z.mulScalar(y*C*this.keyboardSpeedMultiplier));const R=E(this.cameraComponent,s[0],s[1],this._pose.distance);T.add(R.mulScalar(m*f*+this.enablePan));const D=b.set(0,0,a[0]);T.add(D.mulScalar(m*M)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const I=b.set(s[0],s[1],0);T.add(I.mulScalar((1-m*f)*L)),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),T.set(0,0,0);const F=b.set(p[0],0,-p[1]);T.add(F.mulScalar(y*C));const U=E(this.cameraComponent,r[0],r[1],this._pose.distance);T.add(U.mulScalar(m*g*+this.enablePan));const $=b.set(0,0,l[0]);T.add($.mulScalar(m*g*_)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const V=b.set(r[0],r[1],0);T.add(V.mulScalar(m*(1-g)*k));const B=b.set(d[0],d[1],0);T.add(B.mulScalar(y*(x?A:k))),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),T.set(0,0,0);const O=b.set(h[0],0,-h[1]);T.add(O.mulScalar(y*C)),P.move.append([T.x,T.y,T.z]),T.set(0,0,0);const H=b.set(u[0],u[1],0);if(T.add(H.mulScalar(y*A)),this.invertRotation&&(T.y=-T.y),P.rotate.append([T.x,T.y,T.z]),this.app.xr?.active)w.read();else{if("focus"===this._mode){const t=P.move.length()+P.rotate.length()>0,e=this._focusController.complete?.()??!1;(t||e)&&this._setMode(this._preFocusMode)}if(this._pose.copy(this._controller.update(w,e)),("fly"===this._mode||"orbit"===this._mode)&&this._collisionEntities.length>0){if(this._prevPositionValid){const t=this._pose.position,e=this._prevPosition,o=this._collisionTestVec;let n=!1;o.set(t.x,e.y,e.z),this.checkCollision(o)&&(t.x=e.x,n=!0),o.set(t.x,t.y,e.z),this.checkCollision(o)&&(t.y=e.y,n=!0),o.set(t.x,t.y,t.z),this.checkCollision(o)&&(t.z=e.z,n=!0),n&&this._controller.attach(this._pose,!1)}this._prevPosition.copy(this._pose.position),this._prevPositionValid=!0}else"fly"!==this._mode&&"orbit"!==this._mode&&(this._prevPositionValid=!1);if("orbit"===this._mode){let t=this._pose.angles.y;for(;t>180;)t-=360;for(;t<-180;)t+=360;if(void 0!==this._lastYaw){const e=t-this._lastYaw;e>180?t-=360:e<-180&&(t+=360)}this._pose.angles.y=t,this._lastYaw=t}this.camera.setPosition(this._pose.position),this.camera.setEulerAngles(this._pose.angles)}}setCollisionEntities(t,e){this._collisionEntities=t,void 0!==e&&(this._collisionRadius=e),this._prevPositionValid=!1}checkCollision(t){const e=this._collisionRadius;for(const o of this._collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):"floor"===s||"plane"===s?(c=n.x,p=n.y,d=n.z,a=i.x/2,r=.05,l=i.z/2):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=Math.abs(t.x-c),m=Math.abs(t.y-p),y=Math.abs(t.z-d);if(u<a+e&&m<r+e&&y<l+e)return!0}return!1}destroy(){this._desktopInput.destroy(),this._orbitMobileInput.destroy(),this._flyMobileInput.destroy(),this._gamepadInput.destroy(),this._flyController.destroy(),this._orbitController.destroy()}}function M(t,e){return t?Array.isArray(t)?[t[0]??e[0],t[1]??e[1],t[2]??e[2]]:[t.x??e[0],t.y??e[1],t.z??e[2]]:e}class _{constructor(e,o,n={}){this.enabled=!1,this.velocity=new t.Vec3,this.isGrounded=!1,this.yaw=0,this.pitch=0,this.keys={},this.mouseLocked=!1,this.moveSpeed=8,this.sprintMultiplier=2,this.lookSensitivity=.002,this.playerHeight=1.6,this.gravity=20,this.maxFallSpeed=50,this.jumpVelocity=8,this.collisionRadius=.3,this.stepHeight=.3,this.groundCheckDistance=.1,this.moveDamping=.9,this.horizontalVelocity=new t.Vec3,this.targetVelocity=new t.Vec3,this.collisionEntities=[],this.floorEntity=null,this.keydownHandler=null,this.keyupHandler=null,this.mousemoveHandler=null,this.clickHandler=null,this.pointerlockchangeHandler=null,this.tmpVec=new t.Vec3,this.tmpVec2=new t.Vec3,this.forward=new t.Vec3,this.right=new t.Vec3,this.camera=e,this.app=o,void 0!==n.moveSpeed&&(this.moveSpeed=n.moveSpeed),void 0!==n.sprintMultiplier&&(this.sprintMultiplier=n.sprintMultiplier),void 0!==n.lookSensitivity&&(this.lookSensitivity=n.lookSensitivity),void 0!==n.playerHeight&&(this.playerHeight=n.playerHeight),void 0!==n.gravity&&(this.gravity=n.gravity),void 0!==n.maxFallSpeed&&(this.maxFallSpeed=n.maxFallSpeed),void 0!==n.jumpVelocity&&(this.jumpVelocity=n.jumpVelocity),void 0!==n.collisionRadius&&(this.collisionRadius=n.collisionRadius),void 0!==n.stepHeight&&(this.stepHeight=n.stepHeight),void 0!==n.groundCheckDistance&&(this.groundCheckDistance=n.groundCheckDistance),void 0!==n.moveDamping&&(this.moveDamping=n.moveDamping);const i=this.camera.getEulerAngles();this.pitch=i.x,this.yaw=i.y}async createCollisionMeshes(e){if(!e||0===e.length)return;console.log("[CharacterController] Creating collision meshes:",e.length);const o=[];e.forEach((e,n)=>{if("custom"===e.meshType&&e.customMeshUrl){const t=this.loadCustomCollisionMesh(e,n);return void o.push(t)}let i=null;switch(e.meshType){case"cube":i=new t.Entity(`collision-cube-${n}`),i.addComponent("render",{type:"box"});break;case"sphere":i=new t.Entity(`collision-sphere-${n}`),i.addComponent("render",{type:"sphere"});break;case"floor":i=new t.Entity(`collision-floor-${n}`),i.addComponent("render",{type:"plane"}),this.floorEntity=i;break;default:i=new t.Entity(`collision-plane-${n}`),i.addComponent("render",{type:"plane"})}i&&this.configureCollisionEntity(i,e)}),o.length>0&&await Promise.all(o),console.log("[CharacterController] Created",this.collisionEntities.length,"collision entities")}async loadCustomCollisionMesh(e,o){const n=e.customMeshUrl;if(n){console.log("[CharacterController] Loading custom collision mesh:",n);try{const i=n.split("?")[0].split(".").pop()?.toLowerCase()||"glb",s="gltf"===i||"glb"===i?"container":"model",a=new t.Asset(`collision-custom-${o}`,s,{url:n});await new Promise((i,r)=>{a.ready(()=>{try{const n=new t.Entity(`collision-custom-${o}`);if("container"===s){const t=a.resource;if(t&&t.instantiateRenderEntity){const e=t.instantiateRenderEntity();for(;e.children.length>0;)n.addChild(e.children[0]);e.destroy()}}else n.addComponent("model",{asset:a});this.configureCollisionEntity(n,e),this.computeAndStoreBounds(n),i()}catch(t){console.error("[CharacterController] Error setting up custom mesh:",t),r(t)}}),a.on("error",t=>{console.error("[CharacterController] Error loading custom mesh:",n,t),r(t)}),this.app.assets.add(a),this.app.assets.load(a)})}catch(t){console.error("[CharacterController] Failed to load custom collision mesh:",n,t)}}}computeAndStoreBounds(e){const o=new t.BoundingBox;let n=!1;const i=e=>{if(e.render&&e.render.meshInstances)for(const t of e.render.meshInstances)t.aabb&&(n?o.add(t.aabb):(o.copy(t.aabb),n=!0));for(const o of e.children)o instanceof t.Entity&&i(o)};i(e),n&&(e._collisionBounds=o)}configureCollisionEntity(t,e){const o=M(e.position,[0,0,0]);t.setPosition(o[0],o[1],-o[2]);const n=M(e.rotation,[0,0,0]);t.setEulerAngles(n[0]*(180/Math.PI),n[1]*(180/Math.PI),-n[2]*(180/Math.PI));const i=M(e.scaling,[1,1,1]);t.setLocalScale(i[0],i[1],i[2]),this.setEntityVisibility(t,!1),t._collisionMeshType=e.meshType,this.app.root.addChild(t),this.collisionEntities.push(t)}setEntityVisibility(e,o){e.render&&(e.render.enabled=o);for(const n of e.children)n instanceof t.Entity&&this.setEntityVisibility(n,o)}enable(){if(this.enabled)return;this.enabled=!0;const t=this.camera.getEulerAngles();this.pitch=t.x,this.yaw=t.y,this.velocity.set(0,0,0),this.horizontalVelocity.set(0,0,0),this.targetVelocity.set(0,0,0),this.setupInputHandlers(),console.log("[CharacterController] Enabled")}disable(){this.enabled&&(this.enabled=!1,this.removeInputHandlers(),document.pointerLockElement&&document.exitPointerLock(),console.log("[CharacterController] Disabled"))}setupInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler=t=>{this.keys[t.code]=!0,"Space"===t.code&&this.isGrounded&&(this.velocity.y=this.jumpVelocity,this.isGrounded=!1)},this.keyupHandler=t=>{this.keys[t.code]=!1},this.mousemoveHandler=t=>{this.mouseLocked&&(this.yaw-=t.movementX*this.lookSensitivity*100,this.pitch-=t.movementY*this.lookSensitivity*100,this.pitch=Math.max(-89,Math.min(89,this.pitch)))},this.clickHandler=()=>{this.mouseLocked||t.requestPointerLock()},this.pointerlockchangeHandler=()=>{this.mouseLocked=document.pointerLockElement===t},document.addEventListener("keydown",this.keydownHandler),document.addEventListener("keyup",this.keyupHandler),document.addEventListener("mousemove",this.mousemoveHandler),t.addEventListener("click",this.clickHandler),document.addEventListener("pointerlockchange",this.pointerlockchangeHandler)}removeInputHandlers(){const t=this.app.graphicsDevice.canvas;this.keydownHandler&&document.removeEventListener("keydown",this.keydownHandler),this.keyupHandler&&document.removeEventListener("keyup",this.keyupHandler),this.mousemoveHandler&&document.removeEventListener("mousemove",this.mousemoveHandler),this.clickHandler&&t.removeEventListener("click",this.clickHandler),this.pointerlockchangeHandler&&document.removeEventListener("pointerlockchange",this.pointerlockchangeHandler),this.keys={}}checkCollision(t,e){for(const o of this.collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;if("floor"===s||"plane"===s)continue;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=Math.abs(t.x-c),m=Math.abs(t.y-p),y=Math.abs(t.z-d);if(u<a+e&&m<r+this.playerHeight/2&&y<l+e)return!0}return!1}checkGround(t){if(this.floorEntity){const e=this.floorEntity.getPosition(),o=this.floorEntity.getLocalScale(),n=o.x/2*100,i=o.z/2*100;if(Math.abs(t.x-e.x)<n&&Math.abs(t.z-e.z)<i)return e.y}let e=null;for(const o of this.collisionEntities){const n=o.getPosition(),i=o.getLocalScale(),s=o._collisionMeshType;if("floor"===s||"plane"===s||"sphere"===s)continue;let a,r,l,c,p,d;const h=o._collisionBounds;h?(c=h.center.x,p=h.center.y,d=h.center.z,a=h.halfExtents.x,r=h.halfExtents.y,l=h.halfExtents.z):(c=n.x,p=n.y,d=n.z,a=i.x/2,r=i.y/2,l=i.z/2);const u=p+r;Math.abs(t.x-c)<a+this.collisionRadius&&Math.abs(t.z-d)<l+this.collisionRadius&&t.y>=u-this.stepHeight&&(null===e||u>e)&&(e=u)}return e}update(e){if(!this.enabled)return;const o=(this.keys.KeyD||this.keys.ArrowRight?1:0)-(this.keys.KeyA||this.keys.ArrowLeft?1:0),n=(this.keys.KeyW||this.keys.ArrowUp?1:0)-(this.keys.KeyS||this.keys.ArrowDown?1:0),i=this.keys.ShiftLeft||this.keys.ShiftRight,s=this.yaw*(Math.PI/180);this.forward.set(-Math.sin(s),0,-Math.cos(s)),this.right.set(Math.cos(s),0,-Math.sin(s));const a=this.moveSpeed*(i?this.sprintMultiplier:1);this.targetVelocity.set(0,0,0),this.targetVelocity.add(this.tmpVec2.copy(this.forward).mulScalar(n*a)),this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(o*a));const r=((t,e)=>1-Math.pow(t,1e3*e))(this.moveDamping,e);this.horizontalVelocity.lerp(this.horizontalVelocity,this.targetVelocity,r),this.isGrounded||(this.velocity.y-=this.gravity*e,this.velocity.y=Math.max(-this.maxFallSpeed,this.velocity.y));const l=this.camera.getPosition().clone();l.y,this.playerHeight;const c=new t.Vec3;c.x=l.x+this.horizontalVelocity.x*e,c.y=l.y+this.velocity.y*e,c.z=l.z+this.horizontalVelocity.z*e,this.tmpVec2.set(c.x,l.y,l.z),this.checkCollision(this.tmpVec2,this.collisionRadius)&&(c.x=l.x),this.tmpVec2.set(c.x,l.y,c.z),this.checkCollision(this.tmpVec2,this.collisionRadius)&&(c.z=l.z);const p=this.checkGround(c),d=c.y-this.playerHeight;null!==p&&d<=p+this.groundCheckDistance?(c.y=p+this.playerHeight,this.velocity.y=0,this.isGrounded=!0):(null===p||d>p+this.stepHeight)&&(this.isGrounded=!1),this.camera.setPosition(c.x,c.y,c.z),this.camera.setEulerAngles(this.pitch,this.yaw,0)}destroy(){this.disable();for(const t of this.collisionEntities)t.destroy();this.collisionEntities=[],this.floorEntity=null}get collisionMeshEntities(){return this.collisionEntities}get grounded(){return this.isGrounded}getVelocity(){return this.velocity.clone()}setPosition(t,e,o){this.camera.setPosition(t,e,o)}setRotation(t,e){this.pitch=t,this.yaw=e,this.camera.setEulerAngles(t,e,0)}}let L=null;function k(){if(console.log("[RevealEffect] getGsplatRevealRadialClass called, cached:",!!L),L)return L;console.log("[RevealEffect] Creating new script class via pc.createScript");const e=t.createScript("gsplatRevealRadial");return console.log("[RevealEffect] Script class created:",e),Object.assign(e.prototype,{effectTime:0,_materialsApplied:null,_shadersApplied:!1,_retryCount:0,_maxRetries:100,_materialCreatedHandler:null,_systemMaterialHandler: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 t.Vec3(0,0,0)),this.dotTint||(this.dotTint=new t.Color(0,1,1)),this.waveTint||(this.waveTint=new t.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(t){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+=t,Math.floor(this.effectTime)!==Math.floor(this.effectTime-t)&&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 t=this.delay;if(0===this.acceleration)return t+this.endRadius/this.speed;const e=this.speed*this.speed+2*this.acceleration*this.endRadius;if(e<0)return 1/0;return t+(-this.speed+Math.sqrt(e))/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 t=this.entity.gsplat;if(!t)return void console.log("[RevealEffect] _applyShaders: No gsplat component found on entity");const e=t,o=!0===e.unified,n=this.app;if(o){console.log("[RevealEffect] Unified mode detected, using GSplatComponentSystem");const t=n?.systems?.gsplat;if(t){this._systemMaterialHandler||(this._systemMaterialHandler=(t,e,o)=>{console.log("[RevealEffect] material:created event from GSplatComponentSystem"),this._applyShaderToMaterial(t),this._shadersApplied=!0},t.on?.("material:created",this._systemMaterialHandler),console.log("[RevealEffect] Subscribed to GSplatComponentSystem material:created event"));const o=n.root?.findComponents("camera")||[],i=e.layers||[0];for(const e of o)for(const o of i){const i=n.scene?.layers?.getLayerById(o);if(i){const o=t.getGSplatMaterial?.(e.camera,i);o&&(console.log("[RevealEffect] Found unified material via getGSplatMaterial:",o),this._applyShaderToMaterial(o),this._shadersApplied=!0)}}}return}const i=e.instance||e._instance;if(i)return console.log("[RevealEffect] Instance found via component:",i),void this._applyToInstance(i);if(n&&n.scene){const t=n.scene.layers?.layerList||[];for(const e of t)if(e.meshInstances)for(const t of e.meshInstances){const e=t;if(t.gsplatInstance||e._gsplatInstance){const o=t.gsplatInstance||e._gsplatInstance;return console.log("[RevealEffect] Found gsplat instance via mesh instance:",o),void this._applyToInstance(o)}const o=t.material;if(o&&o.gsplat)return console.log("[RevealEffect] Found gsplat material via mesh instance:",o),this._applyShaderToMaterial(o),void(this._shadersApplied=!0)}const e=t=>{const o=t;if(o.gsplat){const t=o.gsplat.instance||o.gsplat._instance;if(t)return console.log("[RevealEffect] Found gsplat instance via entity search:",t),this._applyToInstance(t),!0}for(const o of t.children||[])if(e(o))return!0;return!1};n.root&&e(n.root)}this._retryCount%50==0&&(console.log("[RevealEffect] Still searching for gsplat materials..."),console.log("[RevealEffect] gsplatComponent.unified:",e.unified),console.log("[RevealEffect] gsplatComponent.asset:",t.asset))},_applyToInstance(t){if(this._shadersApplied)return;console.log("[RevealEffect] Applying shaders to instance"),console.log("[RevealEffect] Instance type:",t.constructor?.name),console.log("[RevealEffect] Instance keys:",Object.keys(t));const e=t.materials||t._materials;if(console.log("[RevealEffect] Instance materials:",e),e){const t=e;e 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(t=>{this._applyShaderToMaterial(t)}),this._shadersApplied=!0,console.log("[RevealEffect] SUCCESS: Shaders applied to",t.size,"materials"))):Array.isArray(e)&&(console.log("[RevealEffect] Materials is array with length:",e.length),e.forEach(t=>{this._applyShaderToMaterial(t)}),this._shadersApplied=!0)}if(!this._shadersApplied){const e=t.material||t._material;e&&(console.log("[RevealEffect] Found single material on instance"),this._applyShaderToMaterial(e),this._shadersApplied=!0)}t.on&&!this._materialCreatedHandler&&(this._materialCreatedHandler=t=>{console.log("[RevealEffect] material:created event received"),this._applyShaderToMaterial(t),this._shadersApplied=!0},t.on("material:created",this._materialCreatedHandler),console.log("[RevealEffect] Subscribed to material:created event"))},_applyShaderToMaterial(t){if(this._materialsApplied?.has(t))return void console.log("[RevealEffect] Material already has shader applied, skipping");console.log("[RevealEffect] Applying shader to material:",t),console.log("[RevealEffect] Material constructor:",t.constructor?.name),console.log("[RevealEffect] Material keys:",Object.keys(t));const e=[];let o=t;for(;o&&o!==Object.prototype;){const t=Object.getOwnPropertyNames(o);for(const n of t)try{"function"!=typeof o[n]||e.includes(n)||e.push(n)}catch(t){}o=Object.getPrototypeOf(o)}console.log("[RevealEffect] Material methods:",e.filter(t=>!t.startsWith("_")).join(", "));const n=this.getShaderGLSL(),i=this.getShaderWGSL(),s=t,a="function"==typeof s.setShaderChunk;console.log("[RevealEffect] material.setShaderChunk exists:",a),a?(n&&(s.setShaderChunk?.("gsplatEffectGLSL",n),console.log("[RevealEffect] GLSL shader chunk set via setShaderChunk")),i&&(s.setShaderChunk?.("gsplatEffectWGSL",i),console.log("[RevealEffect] WGSL shader chunk set via setShaderChunk"))):(s.chunks&&(console.log("[RevealEffect] Material has chunks property"),s.chunks.gsplatEffectGLSL=n,s.chunks.gsplatEffectWGSL=i,console.log("[RevealEffect] Set chunks directly")),s.options&&console.log("[RevealEffect] Material has options:",s.options),s.shader&&console.log("[RevealEffect] Material has shader:",s.shader),"function"==typeof t.setParameter&&console.log("[RevealEffect] material.setParameter exists - will use for uniforms")),t.update?.(),this._materialsApplied?.add(t),console.log("[RevealEffect] Material added to applied set, total:",this._materialsApplied?.size)},_removeShaders(){if(this._materialsApplied&&(this._materialsApplied.forEach(t=>{const e=t;e.setShaderChunk?.("gsplatEffectGLSL",""),e.setShaderChunk?.("gsplatEffectWGSL",""),t.update?.()}),this._materialsApplied.clear()),this._shadersApplied=!1,this._materialCreatedHandler){const t=this.entity.gsplat,e=t?.instance;e?.off&&e.off("material:created",this._materialCreatedHandler),this._materialCreatedHandler=null}if(this._systemMaterialHandler){const t=this.app?.systems?.gsplat;t?.off&&t.off("material:created",this._systemMaterialHandler),this._systemMaterialHandler=null}},_setUniform(t,e){this._materialsApplied&&this._materialsApplied.forEach(o=>{o.setParameter?.(t,e)})},destroy(){this._removeShaders()}}),L=e,e}const A={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 P(t){if("none"!==t)return A[t]}var T,z={},R={},D={};function I(){if(T)return D;T=1,Object.defineProperty(D,"__esModule",{value:!0}),D.loop=D.conditional=D.parse=void 0;D.parse=function t(e,o){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:n;if(Array.isArray(o))o.forEach(function(o){return t(e,o,n,i)});else if("function"==typeof o)o(e,n,i,t);else{var s=Object.keys(o)[0];Array.isArray(o[s])?(i[s]={},t(e,o[s],n,i[s])):i[s]=o[s](e,n,i,t)}return n};D.conditional=function(t,e){return function(o,n,i,s){e(o,n,i)&&s(o,t,n,i)}};return D.loop=function(t,e){return function(o,n,i,s){for(var a=[],r=o.pos;e(o,n,i);){var l={};if(s(o,t,n,l),o.pos===r)break;r=o.pos,a.push(l)}return a}},D}var F,U,$={};function V(){if(F)return $;F=1,Object.defineProperty($,"__esModule",{value:!0}),$.readBits=$.readArray=$.readUnsigned=$.readString=$.peekBytes=$.readBytes=$.peekByte=$.readByte=$.buildStream=void 0;$.buildStream=function(t){return{data:t,pos:0}};var t=function(){return function(t){return t.data[t.pos++]}};$.readByte=t;$.peekByte=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;return function(e){return e.data[e.pos+t]}};var e=function(t){return function(e){return e.data.subarray(e.pos,e.pos+=t)}};$.readBytes=e;$.peekBytes=function(t){return function(e){return e.data.subarray(e.pos,e.pos+t)}};$.readString=function(t){return function(o){return Array.from(e(t)(o)).map(function(t){return String.fromCharCode(t)}).join("")}};$.readUnsigned=function(t){return function(o){var n=e(2)(o);return t?(n[1]<<8)+n[0]:(n[0]<<8)+n[1]}};$.readArray=function(t,o){return function(n,i,s){for(var a="function"==typeof o?o(n,i,s):o,r=e(t),l=new Array(a),c=0;c<a;c++)l[c]=r(n);return l}};return $.readBits=function(t){return function(e){for(var o=function(t){return t.data[t.pos++]}(e),n=new Array(8),i=0;i<8;i++)n[7-i]=!!(o&1<<i);return Object.keys(t).reduce(function(e,o){var i=t[o];return i.length?e[o]=function(t,e,o){for(var n=0,i=0;i<o;i++)n+=t[e+i]&&Math.pow(2,o-i-1);return n}(n,i.index,i.length):e[o]=n[i.index],e},{})}},$}var B,O={};var H,W,G={};var N=function(){if(W)return z;W=1,Object.defineProperty(z,"__esModule",{value:!0}),z.decompressFrames=z.decompressFrame=z.parseGIF=void 0;var t,e=(U||(U=1,function(t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var e=I(),o=V(),n={blocks:function(t){for(var e=[],n=t.data.length,i=0,s=(0,o.readByte)()(t);0!==s&&s;s=(0,o.readByte)()(t)){if(t.pos+s>=n){var a=n-t.pos;e.push((0,o.readBytes)(a)(t)),i+=a;break}e.push((0,o.readBytes)(s)(t)),i+=s}for(var r=new Uint8Array(i),l=0,c=0;c<e.length;c++)r.set(e[c],l),l+=e[c].length;return r}},i=(0,e.conditional)({gce:[{codes:(0,o.readBytes)(2)},{byteSize:(0,o.readByte)()},{extras:(0,o.readBits)({future:{index:0,length:3},disposal:{index:3,length:3},userInput:{index:6},transparentColorGiven:{index:7}})},{delay:(0,o.readUnsigned)(!0)},{transparentColorIndex:(0,o.readByte)()},{terminator:(0,o.readByte)()}]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&249===e[1]}),s=(0,e.conditional)({image:[{code:(0,o.readByte)()},{descriptor:[{left:(0,o.readUnsigned)(!0)},{top:(0,o.readUnsigned)(!0)},{width:(0,o.readUnsigned)(!0)},{height:(0,o.readUnsigned)(!0)},{lct:(0,o.readBits)({exists:{index:0},interlaced:{index:1},sort:{index:2},future:{index:3,length:2},size:{index:5,length:3}})}]},(0,e.conditional)({lct:(0,o.readArray)(3,function(t,e,o){return Math.pow(2,o.descriptor.lct.size+1)})},function(t,e,o){return o.descriptor.lct.exists}),{data:[{minCodeSize:(0,o.readByte)()},n]}]},function(t){return 44===(0,o.peekByte)()(t)}),a=(0,e.conditional)({text:[{codes:(0,o.readBytes)(2)},{blockSize:(0,o.readByte)()},{preData:function(t,e,n){return(0,o.readBytes)(n.text.blockSize)(t)}},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&1===e[1]}),r=(0,e.conditional)({application:[{codes:(0,o.readBytes)(2)},{blockSize:(0,o.readByte)()},{id:function(t,e,n){return(0,o.readString)(n.blockSize)(t)}},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&255===e[1]}),l=(0,e.conditional)({comment:[{codes:(0,o.readBytes)(2)},n]},function(t){var e=(0,o.peekBytes)(2)(t);return 33===e[0]&&254===e[1]}),c=[{header:[{signature:(0,o.readString)(3)},{version:(0,o.readString)(3)}]},{lsd:[{width:(0,o.readUnsigned)(!0)},{height:(0,o.readUnsigned)(!0)},{gct:(0,o.readBits)({exists:{index:0},resolution:{index:1,length:3},sort:{index:4},size:{index:5,length:3}})},{backgroundColorIndex:(0,o.readByte)()},{pixelAspectRatio:(0,o.readByte)()}]},(0,e.conditional)({gct:(0,o.readArray)(3,function(t,e){return Math.pow(2,e.lsd.gct.size+1)})},function(t,e){return e.lsd.gct.exists}),{frames:(0,e.loop)([i,r,l,s,a],function(t){var e=(0,o.peekByte)()(t);return 33===e||44===e})}];t.default=c}(R)),(t=R)&&t.__esModule?t:{default:t}),o=I(),n=V(),i=(B||(B=1,Object.defineProperty(O,"__esModule",{value:!0}),O.deinterlace=void 0,O.deinterlace=function(t,e){for(var o=new Array(t.length),n=t.length/e,i=function(n,i){var s=t.slice(i*e,(i+1)*e);o.splice.apply(o,[n*e,e].concat(s))},s=[0,4,2,1],a=[8,8,4,2],r=0,l=0;l<4;l++)for(var c=s[l];c<n;c+=a[l])i(c,r),r++;return o}),O),s=(H||(H=1,Object.defineProperty(G,"__esModule",{value:!0}),G.lzw=void 0,G.lzw=function(t,e,o){var n,i,s,a,r,l,c,p,d,h,u,m,y,g,f,v,b=4096,x=o,w=new Array(o),S=new Array(b),E=new Array(b),C=new Array(4097);for(r=1+(i=1<<(h=t)),n=i+2,c=-1,s=(1<<(a=h+1))-1,p=0;p<i;p++)S[p]=0,E[p]=p;for(u=m=y=g=f=v=0,d=0;d<x;){if(0===g){if(m<a){u+=e[v]<<m,m+=8,v++;continue}if(p=u&s,u>>=a,m-=a,p>n||p==r)break;if(p==i){s=(1<<(a=h+1))-1,n=i+2,c=-1;continue}if(-1==c){C[g++]=E[p],c=p,y=p;continue}for(l=p,p==n&&(C[g++]=y,p=c);p>i;)C[g++]=E[p],p=S[p];y=255&E[p],C[g++]=y,n<b&&(S[n]=c,E[n]=y,0===(++n&s)&&n<b&&(a++,s+=n)),c=l}g--,w[f++]=C[g],d++}for(d=f;d<x;d++)w[d]=0;return w}),G);z.parseGIF=function(t){var i=new Uint8Array(t);return(0,o.parse)((0,n.buildStream)(i),e.default)};var a=function(t,e,o){if(t.image){var n=t.image,a=n.descriptor.width*n.descriptor.height,r=(0,s.lzw)(n.data.minCodeSize,n.data.blocks,a);n.descriptor.lct.interlaced&&(r=(0,i.deinterlace)(r,n.descriptor.width));var l={pixels:r,dims:{top:t.image.descriptor.top,left:t.image.descriptor.left,width:t.image.descriptor.width,height:t.image.descriptor.height}};return n.descriptor.lct&&n.descriptor.lct.exists?l.colorTable=n.lct:l.colorTable=e,t.gce&&(l.delay=10*(t.gce.delay||10),l.disposalType=t.gce.extras.disposal,t.gce.extras.transparentColorGiven&&(l.transparentIndex=t.gce.transparentColorIndex)),o&&(l.patch=function(t){for(var e=t.pixels.length,o=new Uint8ClampedArray(4*e),n=0;n<e;n++){var i=4*n,s=t.pixels[n],a=t.colorTable[s]||[0,0,0];o[i]=a[0],o[i+1]=a[1],o[i+2]=a[2],o[i+3]=s!==t.transparentIndex?255:0}return o}(l)),l}console.warn("gif frame does not have associated image.")};return z.decompressFrame=a,z.decompressFrames=function(t,e){return t.frames.filter(function(t){return t.image}).map(function(o){return a(o,t.gct,e)})},z}();class q{constructor(t,e,o={}){this.frames=[],this.currentFrameIndex=0,this.isPlaying=!1,this.isLoaded=!1,this.lastFrameTime=0,this.updateHandler=null,this.texture=null,this.gifWidth=0,this.gifHeight=0,this.update=()=>{if(!this.isPlaying||!this.isLoaded||this.frames.length<=1)return;const t=performance.now(),e=this.frames[this.currentFrameIndex].delay||100;t-this.lastFrameTime>=e&&(this.currentFrameIndex=(this.currentFrameIndex+1)%this.frames.length,this.drawFrame(this.currentFrameIndex),this.lastFrameTime=t)},this.app=t,this.url=e,this.options=o,this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d",{willReadFrequently:!0}),this.load()}async load(){try{const e=await fetch(this.url);if(!e.ok)throw new Error(`Failed to fetch GIF: ${e.statusText}`);const o=await e.arrayBuffer(),n=N.parseGIF(o);if(this.frames=N.decompressFrames(n,!0),0===this.frames.length)throw new Error("GIF has no frames");this.gifWidth=n.lsd.width,this.gifHeight=n.lsd.height,this.canvas.width=this.gifWidth,this.canvas.height=this.gifHeight,this.texture=new t.Texture(this.app.graphicsDevice,{width:this.gifWidth,height:this.gifHeight,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE}),this.drawFrame(0),this.isLoaded=!0,console.log(`[AnimatedGif] Loaded GIF: ${this.url}, ${this.frames.length} frames, ${this.gifWidth}x${this.gifHeight}`),this.options.onReady&&this.options.onReady(),this.options.autoPlay&&this.play()}catch(t){console.error("[AnimatedGif] Error loading GIF:",t),this.options.onError&&this.options.onError(t instanceof Error?t:new Error(String(t)))}}drawFrame(t){if(!this.texture||t>=this.frames.length)return;const e=this.frames[t],o=t>0?this.frames[t-1]:null;o&&2===o.disposalType&&this.ctx.clearRect(o.dims.left,o.dims.top,o.dims.width,o.dims.height);const n=new ImageData(new Uint8ClampedArray(e.patch),e.dims.width,e.dims.height),i=document.createElement("canvas");i.width=e.dims.width,i.height=e.dims.height;i.getContext("2d").putImageData(n,0,0),this.ctx.drawImage(i,e.dims.left,e.dims.top),this.updateTexture()}updateTexture(){if(!this.texture)return;const t=this.ctx.getImageData(0,0,this.gifWidth,this.gifHeight),e=this.texture.lock();e&&e.set(t.data),this.texture.unlock(),this.texture.upload()}play(){this.isPlaying||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.updateHandler||(this.updateHandler=this.update,this.app.on("update",this.updateHandler)))}pause(){this.isPlaying&&(this.isPlaying=!1,this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null))}stop(){this.pause(),this.currentFrameIndex=0,this.isLoaded&&(this.ctx.clearRect(0,0,this.gifWidth,this.gifHeight),this.drawFrame(0))}get playing(){return this.isPlaying}get loaded(){return this.isLoaded}destroy(){this.pause(),this.texture&&(this.texture.destroy(),this.texture=null),this.frames=[],this.isLoaded=!1,this.canvas=null,this.ctx=null}}class j{constructor(e){this.meshes=new Map,this.updateHandler=null,this.useTexElement2D=!1,this.app=e,this.canvas=e.graphicsDevice.canvas,function(){const e=t.GraphicsDevice;if(!e)return void console.warn("[HtmlMeshHelper] Could not find pc.GraphicsDevice to patch");if("function"==typeof e.prototype._isHTMLElementInterface)return;e.prototype._isHTMLElementInterface=function(t){return!(!("undefined"!=typeof HTMLElement&&t instanceof HTMLElement)||t instanceof HTMLImageElement||t instanceof HTMLCanvasElement||t instanceof HTMLVideoElement)};const o=e.prototype._isBrowserInterface;o&&(e.prototype._isBrowserInterface=function(t){return o.call(this,t)||this._isHTMLElementInterface(t)}),console.log("[HtmlMeshHelper] Patched PlayCanvas GraphicsDevice for HTML-in-Canvas support")}();const o=e.graphicsDevice;this.useTexElement2D=!0===o.supportsTexElement2D,console.log(`[HtmlMeshManager] texElement2D support: ${this.useTexElement2D}`)}createMesh(t){let e=t.width||512,o=t.height||512;e<4&&(e=512),o<4&&(o=512),e=Math.round(e),o=Math.round(o);const n=this.createHtmlElement(t,e,o),i=this.createTexture(n,e,o,t),s=this.createMaterial(i,t),a={entity:this.createEntity(t,s,e,o),texture:i,material:s,htmlElement:n,config:t,destroy:()=>this.destroyMesh(t.id),update:()=>this.updateMeshTexture(t.id)};return this.meshes.set(t.id,a),t.animated&&!this.updateHandler&&this.startUpdateLoop(),a}createHtmlElement(t,e,o){const n=document.createElement("div");if(n.id=`html-mesh-${t.id}`,n.style.width=`${e}px`,n.style.height=`${o}px`,n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.pointerEvents="none",n.style.zIndex="-1",n.style.overflow="hidden",t.css){const e=document.createElement("style");e.textContent=t.css,n.appendChild(e)}return n.innerHTML+=t.html,this.useTexElement2D?(this.canvas.setAttribute("layoutsubtree",""),this.canvas.setAttribute("data-layoutsubtree",""),this.canvas.appendChild(n)):(n.style.visibility="hidden",document.body.appendChild(n)),n}createTexture(e,o,n,i){const s=new t.Texture(this.app.graphicsDevice,{width:o,height:n,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE,name:`htmlMesh-${i.id}`});if(this.useTexElement2D)try{s.setSource(e),console.log(`[HtmlMeshManager] Using texElement2D for mesh ${i.id}`)}catch(t){console.warn(`[HtmlMeshManager] texElement2D failed, falling back to canvas: ${t}`),this.renderToCanvas(s,e,o,n)}else this.renderToCanvas(s,e,o,n);return s}renderToCanvas(t,e,o,n){const i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d",{willReadFrequently:!0}),a=`\n <svg xmlns="http://www.w3.org/2000/svg" width="${o}" height="${n}">\n <foreignObject width="100%" height="100%">\n <div xmlns="http://www.w3.org/1999/xhtml" style="width:${o}px;height:${n}px;">\n ${e.innerHTML}\n </div>\n </foreignObject>\n </svg>\n `,r=new Image,l=new Blob([a],{type:"image/svg+xml;charset=utf-8"}),c=URL.createObjectURL(l);r.onload=()=>{s.drawImage(r,0,0),URL.revokeObjectURL(c);let e=!1;try{i.toDataURL()}catch{e=!0}e&&console.warn("[HtmlMeshManager] Canvas tainted by cross-origin content, using clean fallback");const a=document.createElement("canvas");a.width=o,a.height=n;const l=a.getContext("2d");if(e)l.fillStyle="#333",l.fillRect(0,0,o,n),l.fillStyle="#fff",l.font="20px Arial",l.textAlign="center",l.fillText("HTML Mesh",o/2,n/2),t.setSource(a);else{l.drawImage(i,0,0);try{a.toDataURL(),t.setSource(a)}catch{console.warn("[HtmlMeshManager] Fallback canvas also tainted, using placeholder"),l.fillStyle="#333",l.fillRect(0,0,o,n),l.fillStyle="#fff",l.font="20px Arial",l.textAlign="center",l.fillText("HTML Mesh",o/2,n/2),t.setSource(a)}}},r.onerror=()=>{s.fillStyle="#333",s.fillRect(0,0,o,n),s.fillStyle="#fff",s.font="20px Arial",s.textAlign="center",s.fillText("HTML Mesh",o/2,n/2),URL.revokeObjectURL(c),t.setSource(i)},r.src=c}createMaterial(e,o){const n=new t.StandardMaterial;return n.diffuseMap=e,n.emissiveMap=e,n.emissive=new t.Color(.5,.5,.5),n.opacity=o.opacity??1,n.blendType=void 0!==o.opacity&&o.opacity<1?t.BLEND_NORMAL:t.BLEND_NONE,n.cull=o.doubleSided?t.CULLFACE_NONE:t.CULLFACE_BACK,n.update(),n}createEntity(e,o,n,i){const s=new t.Entity(`htmlMesh-${e.id}`),a=n/i;s.addComponent("render",{type:"plane",material:o,castShadows:e.castShadows??!1,receiveShadows:e.receiveShadows??!1}),s.setPosition(e.position.x,e.position.y,e.position.z);const r=e.rotation||{x:0,y:0,z:0};s.setEulerAngles(r.x,r.y,r.z);const l=e.scale||{x:1,y:1};return s.setLocalScale(l.x*a,1,l.y),this.app.root.addChild(s),e.billboard&&this.app.on("update",()=>{if(!s.enabled)return;const t=this.app.root.findComponent("camera")?.entity;t&&s.lookAt(t.getPosition())}),s}updateMeshTexture(t){const e=this.meshes.get(t);if(e)if(this.useTexElement2D)e.texture.upload();else{let t=e.config.width||512,o=e.config.height||512;t<4&&(t=512),o<4&&(o=512),this.renderToCanvas(e.texture,e.htmlElement,Math.round(t),Math.round(o))}}startUpdateLoop(){let t={};this.updateHandler=()=>{const e=performance.now();this.meshes.forEach((o,n)=>{if(!o.config.animated)return;const i=o.config.updateRate||100,s=t[n]||0;e-s>=i&&(this.updateMeshTexture(n),t[n]=e)})},this.app.on("update",this.updateHandler)}destroyMesh(t){const e=this.meshes.get(t);if(!e)return;e.entity.destroy(),e.texture.destroy(),e.htmlElement.remove(),this.meshes.delete(t);!Array.from(this.meshes.values()).some(t=>t.config.animated)&&this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null)}getMesh(t){return this.meshes.get(t)}updateVisibility(t,e){this.meshes.forEach(o=>{const n=o.config;if(n.visibilityRange){const i=n.visibilityRange;let s=!0;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),o.entity.enabled=s}if(n.billboard&&n.billboardRange){const i=n.billboardRange;let s=!1;"percentage"===i.type?s=t>=i.start&&t<=i.end:"waypoint"===i.type&&(s=e>=i.start&&e<=i.end),o.entity._billboardActive=s}else n.billboard&&(o.entity._billboardActive=!0)})}getAllMeshes(){return this.meshes}destroy(){this.meshes.forEach((t,e)=>this.destroyMesh(e)),this.updateHandler&&(this.app.off("update",this.updateHandler),this.updateHandler=null)}}function X(t,e){const o=new j(t);for(const t of e)o.createMesh(t);return o}class Y{constructor(t){this.isInitialized=!1,this.scriptCleanup=[],this.lastError=null,this.updateCallbacks=[],this.customScript=t,this.api={}}initialize(t){this.api={...t,registerCleanup:t=>this.addCleanup(t)},this.isInitialized=!0}updateScript(t){t!==this.customScript&&(this.customScript=t,this.execute())}addCleanup(t){"function"==typeof t&&this.scriptCleanup.push(t)}sanitizeScript(t){if(!t)return"";let e=t.normalize("NFC");return e=e.replace(/\uFEFF/g,""),e=e.replace(/\u00A0/g," "),e=e.replace(/[\u2028\u2029]/g,"\n"),e=e.replace(/[\u200B-\u200D\u2060]/g,""),e=e.replace(/[\u2018\u2019\u201B]/g,"'").replace(/[\u201C\u201D\u201E]/g,'"'),e}preprocessScript(t){if(!t)return"";let e=this.sanitizeScript(t).trim().replace(/\r\n/g,"\n");return/console\/log\s*\(/.test(e)&&(console.warn("[Custom Script] Detected 'console/log(...)'. Did you mean 'console.log(...)'?"),e=e.replace(/console\/log\s*\(/g,"console.log(")),e="try {\n"+e+"\n} catch (error) {\n console.error('[Custom Script] Runtime error:', error);\n}",e}execute(){if(!this.isInitialized||!this.customScript)return;if(this.customScript.length>2e5)return void console.warn("[Custom Script] Script too large, aborting execution.");this.cleanup();const t=this.preprocessScript(this.customScript);try{const e=this.api.app;let o=!1;const n=()=>{o||(this.updateCallbacks.length>200?(console.warn("[Custom Script] Too many update callbacks; further callbacks ignored."),o=!0):requestAnimationFrame(n))};requestAnimationFrame(n);const i=Object.create(e);i.registerUpdate=t=>{if("function"!=typeof t||o)return;const n=()=>{try{t()}catch(t){console.error("[Custom Script] Error in update callback:",t)}};this.updateCallbacks.push(n),e.on("update",n),this.addCleanup(()=>{e.off("update",n);const t=this.updateCallbacks.indexOf(n);-1!==t&&this.updateCallbacks.splice(t,1)})},i.registerBeforeRender=i.registerUpdate;const{camera:s,pc:a,canvas:r,getScrollPercentage:l,getCurrentWaypointIndex:c,getHotspots:p,getSplats:d,getHTMLMeshes:h}=this.api,u=new Function("app","camera","pc","canvas","getScrollPercentage","getCurrentWaypointIndex","getHotspots","getSplats","getHTMLMeshes","registerCleanup","registerUpdate","exports","module","require","globalThis","window","document","self","'use strict';\n"+t),m={},y={exports:m},g=()=>{throw new Error("require not available in custom script")},f=new Proxy({},{get:()=>{},set:()=>!1}),v=u(i,s,a,r,l,c,p,d,h,t=>this.addCleanup(t),i.registerUpdate.bind(i),m,y,g,f,void 0,void 0,void 0)||y.exports||m.default||m.cleanup;"function"==typeof v&&this.addCleanup(v),console.log("[Custom Script] Executed successfully")}catch(t){this.lastError=t,console.error("[Custom Script] Execution error:",t)}}cleanup(){this.scriptCleanup.forEach(t=>{try{t()}catch(t){console.error("[Custom Script] Cleanup error:",t)}}),this.scriptCleanup=[],this.updateCallbacks=[]}getLastError(){return this.lastError}dispose(){this.cleanup(),this.isInitialized=!1}}class Z{constructor(t,e,o={}){this.options=o,this.frameAssets=new Map,this.activeEntityIndex=0,this.currentFrame=0,this.isPlaying=!1,this.lastFrameTime=0,this.loadingFrames=new Set,this.destroyed=!1,this.isDisplaying=!1,this.listeners=new Map,this.app=t,this.frameUrls=e.frameUrls,this.fps=e.fps||24,this.loop=!1!==e.loop,this.preloadCount=e.preloadCount||5,this.frameInterval=1e3/this.fps,this.entities=[this.createSplatEntity("frameEntityA"),this.createSplatEntity("frameEntityB")],this.entities[0].enabled=!1,this.entities[1].enabled=!1,this.preloadInitialFrames(),e.autoplay&&this.preloadFrame(0).then(()=>{this.destroyed||this.play()})}createSplatEntity(e){const o=new t.Entity(e);return o.addComponent("gsplat",{}),this.app.root.addChild(o),o}async preloadInitialFrames(){const t=Math.min(this.preloadCount,this.frameUrls.length),e=[];for(let o=0;o<t;o++)e.push(this.preloadFrame(o));await Promise.all(e),this.options.onLoadProgress?.(t,this.frameUrls.length)}async preloadFrame(e){return this.destroyed||e<0||e>=this.frameUrls.length?null:this.frameAssets.has(e)?this.frameAssets.get(e):this.loadingFrames.has(e)?null:(this.loadingFrames.add(e),new Promise(o=>{const n=this.frameUrls[e],i=new t.Asset(`frame_${e}`,"gsplat",{url:n});i.on("load",()=>{this.destroyed||(this.frameAssets.set(e,i),this.loadingFrames.delete(e)),o(i)}),i.on("error",t=>{console.error(`Failed to load frame ${e}:`,t),this.loadingFrames.delete(e),this.options.onError?.(`Failed to load frame ${e}: ${t}`),o(null)}),this.app.assets.add(i),this.app.assets.load(i)}))}unloadFrame(t){const e=this.frameAssets.get(t);e&&(this.app.assets.remove(e),e.unload(),this.frameAssets.delete(t))}updatePreloadWindow(){for(let t=1;t<=this.preloadCount;t++){const e=this.loop?(this.currentFrame+t)%this.frameUrls.length:this.currentFrame+t;e<this.frameUrls.length&&this.preloadFrame(e)}for(const[t]of this.frameAssets){const e=this.currentFrame-t;e>2&&e<this.frameUrls.length-this.preloadCount&&this.unloadFrame(t)}}async displayFrame(t){if(!this.destroyed&&!this.isDisplaying){this.isDisplaying=!0;try{let e=this.frameAssets.get(t);if(!e&&(e=await this.preloadFrame(t),!e||this.destroyed))return;const o=(this.activeEntityIndex+1)%2,n=this.entities[this.activeEntityIndex],i=this.entities[o],s=i.gsplat;s&&(s.asset=e),i.enabled=!0,n.enabled=!1,this.activeEntityIndex=o,this.currentFrame=t,this.emit("frameChange",t,this.frameUrls.length),this.options.onFrameChange?.(t,this.frameUrls.length),this.updatePreloadWindow()}finally{this.isDisplaying=!1}}}update(t){if(!this.isPlaying||this.destroyed)return;const e=performance.now(),o=e-this.lastFrameTime;if(o>=this.frameInterval){this.lastFrameTime=e-o%this.frameInterval;let t=this.currentFrame+1;if(t>=this.frameUrls.length){if(!this.loop)return this.pause(),void this.emit("complete");t=0}this.displayFrame(t)}}play(){this.isPlaying||this.destroyed||(this.isPlaying=!0,this.lastFrameTime=performance.now(),this.entities[this.activeEntityIndex].enabled||this.displayFrame(this.currentFrame),this.emit("play"))}pause(){this.isPlaying=!1,this.emit("pause")}stop(){this.isPlaying=!1,this.currentFrame=0,this.displayFrame(0),this.emit("stop")}setFrame(t){t<0&&(t=0),t>=this.frameUrls.length&&(t=this.frameUrls.length-1),this.displayFrame(t)}nextFrame(){let t=this.currentFrame+1;t>=this.frameUrls.length&&(t=this.loop?0:this.frameUrls.length-1),this.setFrame(t)}previousFrame(){let t=this.currentFrame-1;t<0&&(t=this.loop?this.frameUrls.length-1:0),this.setFrame(t)}getCurrentFrame(){return this.currentFrame}getTotalFrames(){return this.frameUrls.length}getProgress(){return this.frameUrls.length>1?this.currentFrame/(this.frameUrls.length-1):0}setProgress(t){const e=Math.round(t*(this.frameUrls.length-1));this.setFrame(e)}getFps(){return this.fps}setFps(t){this.fps=t,this.frameInterval=1e3/t}getIsPlaying(){return this.isPlaying}setLoop(t){this.loop=t}getLoop(){return this.loop}setPosition(t,e,o){this.entities[0].setPosition(t,e,o),this.entities[1].setPosition(t,e,o)}setRotation(t,e,o){this.entities[0].setEulerAngles(t,e,o),this.entities[1].setEulerAngles(t,e,o)}setScale(t,e,o){this.entities[0].setLocalScale(t,e,o),this.entities[1].setLocalScale(t,e,o)}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e))}destroy(){this.destroyed=!0,this.isPlaying=!1;for(const[t]of this.frameAssets)this.unloadFrame(t);this.entities[0].destroy(),this.entities[1].destroy(),this.listeners.clear()}}class J{constructor(){this.listeners=new Map}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){this.listeners.get(t)?.delete(e)}emit(t,...e){this.listeners.get(t)?.forEach(t=>t(...e))}}function Q(){const t=navigator.userAgent||navigator.vendor||window.opera||"";return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(t)}const K={"desktop-max":{range:[0,5],lodDistances:[15,30,80,250,300]},desktop:{range:[0,2],lodDistances:[15,30,80,250,300]},"mobile-max":{range:[1,2],lodDistances:[15,30,80,250,300]},mobile:{range:[2,5],lodDistances:[15,30,80,250,300]}};function tt(t){const e=t.includes("lod-meta.json");return e&&console.log("[SPLAT] Detected LOD streaming format (lod-meta.json)"),e}function et(e,o,n={}){if(console.log("[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3"),n.lazyLoad){const t=new J;let i,s=null;const a=n.lazyLoadThumbnail||o.uiOptions?.lazyLoadThumbnailUrl||o.thumbnailUrl,r=n.lazyLoadButtonText||o.uiOptions?.lazyLoadButtonText||l(o.uiOptions?.buttonLabels,"startExperience"),p=o.uiColor||"#4CAF50";console.log("[StorySplat Viewer] Lazy loading enabled, showing start button"),function(t,e){const{thumbnailUrl:o,thumbnailType:n,buttonText:i="Start Experience",uiColor:s="#4CAF50",onStart:a}=e;c(s),t.classList.add("storysplat-viewer-container");const r=document.createElement("div");r.className="storysplat-lazy-load-container";const l=n||(o?function(t){const e=t.toLowerCase();return e.includes(".mp4")||e.includes(".webm")||e.includes(".mov")||e.includes(".ogg")?"video":e.includes(".gif")?"gif":"image"}(o):"image");let p="";o&&(p+="video"===l?`<video class="storysplat-lazy-load-thumbnail-video" src="${o}" autoplay muted loop playsinline webkit-playsinline></video>`:`<img class="storysplat-lazy-load-thumbnail" src="${o}" alt="Scene preview" />`),p+='<div class="storysplat-lazy-load-overlay"></div>',p+=`\n <div class="storysplat-lazy-load-content">\n <button class="storysplat-lazy-load-start-btn" style="background: ${s}">\n <svg viewBox="0 0 24 24">\n <path d="M8 5v14l11-7z"/>\n </svg>\n ${i}\n </button>\n </div>\n `,r.innerHTML=p,t.appendChild(r);const d=r.querySelector(".storysplat-lazy-load-start-btn");d?.addEventListener("click",()=>{const t=r.querySelector("video");t&&(t.pause(),t.src=""),r.style.transition="opacity 0.3s ease-out",r.style.opacity="0",setTimeout(()=>{r.remove(),a()},300)})}(e,{thumbnailUrl:a,thumbnailType:n.lazyLoadThumbnailType||o.uiOptions?.lazyLoadThumbnailType,buttonText:r,uiColor:p,onStart:()=>{console.log("[StorySplat Viewer] User clicked start, initializing viewer..."),s=et(e,o,{...n,lazyLoad:!1}),i&&(s.setButtonLabels(i),i=void 0),s.on("ready",()=>t.emit("ready")),s.on("error",e=>t.emit("error",e)),s.on("waypointChange",e=>t.emit("waypointChange",e)),s.on("playbackStart",()=>t.emit("playbackStart")),s.on("playbackStop",()=>t.emit("playbackStop")),s.on("loaded",()=>t.emit("loaded")),s.on("progress",e=>t.emit("progress",e))}});return{app:null,canvas:null,goToWaypoint:t=>s?.goToWaypoint(t),nextWaypoint:()=>s?.nextWaypoint(),prevWaypoint:()=>s?.prevWaypoint(),getCurrentWaypointIndex:()=>s?.getCurrentWaypointIndex()??0,getWaypointCount:()=>s?.getWaypointCount()??0,setPosition:(t,e,o)=>s?.setPosition(t,e,o),setRotation:(t,e,o)=>s?.setRotation(t,e,o),getPosition:()=>s?.getPosition()??{x:0,y:0,z:0},getRotation:()=>s?.getRotation()??{x:0,y:0,z:0},play:()=>s?.play(),pause:()=>s?.pause(),stop:()=>s?.stop(),isPlaying:()=>s?.isPlaying()??!1,setCameraMode:t=>s?.setCameraMode(t),getCameraMode:()=>s?.getCameraMode()??"tour",setExploreMode:t=>s?.setExploreMode(t),goToOriginalSplat:()=>s?.goToOriginalSplat(),goToSplat:async t=>s?.goToSplat(t),getCurrentSplatUrl:()=>s?.getCurrentSplatUrl()??"",isShowingOriginalSplat:()=>s?.isShowingOriginalSplat()??!0,getAdditionalSplats:()=>s?.getAdditionalSplats()??[],setProgress:t=>s?.setProgress(t),getProgress:()=>s?.getProgress()??0,muteAll:()=>s?.muteAll(),unmuteAll:()=>s?.unmuteAll(),isMuted:()=>s?.isMuted()??!1,getHotspots:()=>s?.getHotspots()??[],triggerHotspot:t=>s?.triggerHotspot(t),closeHotspot:()=>s?.closeHotspot(),destroy:()=>{s?s.destroy():(e.querySelectorAll(".storysplat-lazy-load-container, .storysplat-viewer-container").forEach(t=>t.remove()),e.classList.remove("storysplat-viewer-container"))},resize:()=>s?.resize(),navigateToScene:async t=>{if(s)return s.navigateToScene(t)},setButtonLabels:t=>{s?s.setButtonLabels(t):i={...i,...t}},on:(e,o)=>t.on(e,o),off:(e,o)=>t.off(e,o)}}const i=new J;if(!n.allowParentStyles){const t=e.style.width,o=e.style.height;e.style.all="initial",e.style.position="relative",e.style.display="block",e.style.width=t||"100%",e.style.height=o||"100%",e.style.overflow="hidden",e.style.fontFamily="system-ui, -apple-system, sans-serif"}i.on("error",t=>{console.error("[StorySplat Viewer] Error event:",t.message),function(t,e){t.querySelector(".storysplat-error-popup")?.remove();const o=t.querySelector(".storysplat-preloader");o&&d(o);const n=e.includes("Failed to load splat from any URL"),i=document.createElement("div");i.className="storysplat-error-popup",i.innerHTML=n?'\n <div class="storysplat-error-popup-icon">⚠️</div>\n <h3 class="storysplat-error-popup-title">Scene Needs Update</h3>\n <p class="storysplat-error-popup-message">\n This scene was created with an older version of StorySplat and needs to be re-exported to work with the latest viewer.\n <br><br>\n Please ask the scene creator to re-export it from the StorySplat editor.\n </p>\n <a href="https://storysplat.com" target="_blank" class="storysplat-error-popup-action">\n Visit StorySplat\n </a>\n ':`\n <div class="storysplat-error-popup-icon">❌</div>\n <h3 class="storysplat-error-popup-title">Failed to Load Scene</h3>\n <p class="storysplat-error-popup-message">\n ${e||"An error occurred while loading this scene. Please try refreshing the page."}\n </p>\n `,t.appendChild(i)}(e,t.message)});const v=a(s(o));console.log("[StorySplat Viewer] Creating viewer with config:",v),console.log("[StorySplat Viewer] Scale config:",v.scale),console.log("[StorySplat Viewer] Raw scene data:",{splatScale:o.splatScale,scale:o.scale});const b=!0===n.editor,x=b?!n.editorSkipUI&&!1!==n.showUI:!1!==n.showUI,w=v.uiColor||"#4CAF50",S=v.uiOptions||{},E=n.template||S.uiType||"minimal";let M=S.buttonLabels;const L=t=>"first-person"===t?"tour":"drone"===t?"explore":t,A=v.collisionMeshesData&&v.collisionMeshesData.length>0;let T=(v.allowedCameraModes||["orbit","first-person","drone"]).map(L).filter((t,e,o)=>o.indexOf(t)===e);A&&!T.includes("walk")&&T.push("walk"),!A&&T.includes("walk")&&(T=T.filter(t=>"walk"!==t));const z=L(v.defaultCameraMode||"orbit"),R=!!(v.audioEmitters?.length||v.hotspots?.some(t=>t.audioUrl)||v.hotspots?.some(t=>"video"===t.type&&!0!==t.videoMuted)||v.waypoints?.some(t=>t.interactions?.some(t=>"audio"===t.type)));let D={};x&&(D=function(t,e,o={}){const{uiColor:n="#4CAF50",showScrollControls:i=!0,showModeToggle:s=!0,showFullscreenButton:a=!0,showHelpButton:r=!1,showMuteButton:d=!1,showPreloader:h=!0,allowedCameraModes:u=["tour","explore"],defaultCameraMode:m="tour",customPreloaderLogoUrl:y,buttonLabels:g,hideWatermark:f=!1,watermarkText:v,watermarkLink:b,sceneId:x,showWaypointList:w=!0,template:S="minimal"}=o,E={tour:l(g,"tour"),explore:l(g,"explore"),walk:l(g,"walk"),orbit:l(g,"orbit"),fly:l(g,"fly"),previous:l(g,"previous"),next:l(g,"next"),fullscreen:l(g,"fullscreen"),waypoints:l(g,"waypoints"),close:l(g,"close"),yes:l(g,"yes"),cancel:l(g,"cancel"),vr:l(g,"vr"),ar:l(g,"ar"),loading:l(g,"loading"),helpTitle:l(g,"helpTitle"),helpCameraModes:l(g,"helpCameraModes"),helpTourDesc:l(g,"helpTourDesc"),helpExploreDesc:l(g,"helpExploreDesc"),helpWalkDesc:l(g,"helpWalkDesc"),hotspotDefaultTitle:l(g,"hotspotDefaultTitle"),openExternalLink:l(g,"openExternalLink"),mute:l(g,"mute"),unmute:l(g,"unmute")},C={};t.querySelectorAll(".storysplat-preloader, .storysplat-scroll-controls, .storysplat-waypoint-info, .storysplat-fullscreen-btn, .storysplat-mute-btn, .storysplat-help-btn, .storysplat-help-panel, .storysplat-watermark, .storysplat-waypoint-list-container, .storysplat-hotspot-popup, .storysplat-portal-popup").forEach(t=>t.remove()),c(n,S),t.classList.add("storysplat-viewer-container"),h&&(C.preloader=p(t,y,g));const M=document.createElement("div");if(M.className="storysplat-waypoint-info",M.innerHTML='\n <h2 class="storysplat-waypoint-title"></h2>\n <p class="storysplat-waypoint-description"></p>\n ',t.appendChild(M),C.waypointInfo=M,i&&e.waypoints&&e.waypoints.length>0){const e=document.createElement("div");if(e.className="storysplat-scroll-controls",e.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">${E.previous}</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">${E.next}</button>\n </div>\n <div class="storysplat-explore-controls">\n <button class="storysplat-explore-btn" data-explore-mode="orbit">${E.orbit}</button>\n <button class="storysplat-explore-btn" data-explore-mode="fly">${E.fly}</button>\n </div>\n ${s&&u.length>1?`\n <div class="storysplat-mode-container">\n <div class="storysplat-mode-toggle">\n ${u.includes("tour")?`<button class="storysplat-mode-btn ${"tour"===m?"selected":""}" data-mode="tour">${E.tour}</button>`:""}\n ${u.includes("explore")?`<button class="storysplat-mode-btn ${"explore"===m?"selected":""}" data-mode="explore">${E.explore}</button>`:""}\n ${u.includes("walk")?`<button class="storysplat-mode-btn ${"walk"===m?"selected":""}" data-mode="walk">${E.walk}</button>`:""}\n </div>\n </div>\n `:""}\n </div>\n `,t.appendChild(e),C.scrollControls=e,C.progressBar=e.querySelector(".storysplat-progress-bar"),C.progressText=e.querySelector(".storysplat-progress-text"),"pro"===S){const o=e.querySelector(".storysplat-mode-container");o&&t.appendChild(o)}}if(a){const e=document.createElement("button");e.className="storysplat-fullscreen-btn",e.setAttribute("aria-label",E.fullscreen),e.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 ',t.appendChild(e),C.fullscreenButton=e}if(d){const e=document.createElement("button");e.className="storysplat-mute-btn",e.setAttribute("aria-label",E.mute);const o=document.createElementNS("http://www.w3.org/2000/svg","svg");o.classList.add("storysplat-unmuted-icon"),o.setAttribute("viewBox","0 0 24 24");const n=document.createElementNS("http://www.w3.org/2000/svg","path");n.setAttribute("d","M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"),o.appendChild(n),e.appendChild(o);const i=document.createElementNS("http://www.w3.org/2000/svg","svg");i.classList.add("storysplat-muted-icon"),i.setAttribute("viewBox","0 0 24 24"),i.style.display="none";const s=document.createElementNS("http://www.w3.org/2000/svg","path");s.setAttribute("d","M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"),i.appendChild(s),e.appendChild(i),t.appendChild(e),C.muteButton=e}if(w&&e.waypoints&&e.waypoints.length>0){const o=document.createElement("div");o.className="storysplat-waypoint-list-container "+(a?"with-fullscreen":"no-fullscreen");const n=e.waypoints.map((t,e)=>`<div class="storysplat-waypoint-item" data-waypoint-index="${e}">${t.name||`Waypoint ${e+1}`}</div>`).join("");o.innerHTML=`\n <button class="storysplat-waypoint-list-toggle" aria-label="${E.waypoints}">\n ${E.waypoints}\n <svg viewBox="0 0 24 24">\n <path d="M7 10l5 5 5-5z"/>\n </svg>\n </button>\n <div class="storysplat-waypoint-list-dropdown">\n ${n}\n </div>\n `,t.appendChild(o),C.waypointListContainer=o;const i=o.querySelector(".storysplat-waypoint-list-toggle"),s=o.querySelector(".storysplat-waypoint-list-dropdown");i?.addEventListener("click",t=>{t.stopPropagation(),i.classList.toggle("open"),s?.classList.toggle("open")}),document.addEventListener("click",t=>{o.contains(t.target)||(i?.classList.remove("open"),s?.classList.remove("open"))})}const _=document.createElement("button");_.className="storysplat-xr-btn storysplat-vr-btn",_.setAttribute("aria-label",E.vr),_.textContent=E.vr,t.appendChild(_),C.vrButton=_;const L=document.createElement("button");if(L.className="storysplat-xr-btn storysplat-ar-btn",L.setAttribute("aria-label",E.ar),L.textContent=E.ar,t.appendChild(L),C.arButton=L,r){const e=document.createElement("button");e.className="storysplat-help-btn",e.setAttribute("title",E.helpTitle),e.textContent="?",t.appendChild(e),C.helpButton=e;const o=document.createElement("div");o.className="storysplat-help-panel",o.innerHTML=`\n <h3>${E.helpTitle}</h3>\n <p><strong>${E.helpCameraModes}</strong></p>\n <p>• ${E.tour} - ${E.helpTourDesc}</p>\n <p>• ${E.explore} - ${E.helpExploreDesc}</p>\n <p>• ${E.walk} - ${E.helpWalkDesc}</p>\n <br/>\n <p><strong>${E.tour} Mode:</strong></p>\n <p>• Scroll - Move along path</p>\n <p>• Drag - Look around</p>\n <br/>\n <p><strong>${E.explore} Mode:</strong></p>\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 <br/>\n <p><strong>${E.walk} Mode:</strong></p>\n <p>• Click to lock mouse</p>\n <p>• WASD/Arrows - Move</p>\n <p>• Mouse - Look around</p>\n <p>• Shift - Sprint</p>\n <p>• Space - Jump</p>\n `,t.appendChild(o),C.helpPanel=o}const k=document.createElement("div");k.className="storysplat-hotspot-popup",k.id="hotspotContent",k.innerHTML=`\n <h2 class="storysplat-hotspot-popup-title"></h2>\n <div class="storysplat-hotspot-popup-content"></div>\n <button class="storysplat-hotspot-popup-close">${E.close}</button>\n `,t.appendChild(k),C.hotspotPopup=k;const A=k.querySelector(".storysplat-hotspot-popup-close");A?.addEventListener("click",()=>{k.classList.remove("visible","fullscreen")});const P=document.createElement("div");P.className="storysplat-portal-popup",P.innerHTML=`\n <div class="storysplat-portal-popup-title"></div>\n <div class="storysplat-portal-popup-actions">\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-confirm">${E.yes}</button>\n <button class="storysplat-portal-popup-btn storysplat-portal-popup-cancel">${E.cancel}</button>\n </div>\n `,t.appendChild(P),C.portalPopup=P;const T=document.createElement("div");T.className="storysplat-joystick-container",T.innerHTML='\n <div class="storysplat-joystick-base"></div>\n <div class="storysplat-joystick-thumb"></div>\n ',t.appendChild(T),C.joystick=T,C.joystickThumb=T.querySelector(".storysplat-joystick-thumb");const z=document.createElement("div");if(z.className="storysplat-look-zone",z.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 ',t.appendChild(z),C.lookZone=z,!f){const e=document.createElement("div");e.className="storysplat-watermark";const o=b||(x?`https://storysplat.com?ref=${x}`:"https://storysplat.com");e.innerHTML=v?`<a href="${o}" target="_blank">${v}</a>`:`Created with <a href="${o}" target="_blank">StorySplat</a>`,t.appendChild(e),C.watermark=e}if(o.debugMode){const e=document.createElement("div");e.className="storysplat-fps-counter",e.textContent="-- FPS",t.appendChild(e),C.fpsCounter=e}return C}(e,v,{uiColor:w,showScrollControls:v.waypoints&&v.waypoints.length>0,showModeToggle:T.length>1,showFullscreenButton:!S.hideFullscreenButton,showHelpButton:!S.hideHelpButton&&!S.hideInfoButton,showMuteButton:!S.hideMuteButton&&R,showPreloader:!0,allowedCameraModes:T,defaultCameraMode:z,buttonLabels:M,customPreloaderLogoUrl:S.customPreloaderLogoUrl,hideWatermark:S.hideWatermark,watermarkText:S.watermarkText,watermarkLink:S.watermarkLink,sceneId:o.sceneId,showWaypointList:S.showWaypointList,template:E,debugMode:S.debugMode}));const I=document.createElement("canvas");let F;I.id="storysplat-viewer-canvas",I.style.width="100%",I.style.height="100%",I.style.display="block",e.appendChild(I);const U={antialias:!1,alpha:!1,powerPreference:"high-performance"};try{F=new t.Application(I,{graphicsDeviceOptions:U,mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] Graphics initialized successfully")}catch(o){console.warn("[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:",o);try{F=new t.Application(I,{graphicsDeviceOptions:{...U,preferWebGl2:!1},mouse:new t.Mouse(I),touch:new t.TouchDevice(I),keyboard:new t.Keyboard(window)}),console.log("[StorySplat Viewer] WebGL1 fallback successful")}catch(t){console.error("[StorySplat Viewer] WebGL initialization failed completely:",t);const o=document.createElement("div");o.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:rgba(0,0,0,0.8);padding:20px;border-radius:10px;text-align:center;font-family:sans-serif;";const n=document.createElement("h3");n.style.cssText="margin:0 0 10px 0;",n.textContent=l(M,"errorWebGLTitle");const i=document.createElement("p");throw i.style.cssText="margin:0;",i.textContent=l(M,"errorWebGLMessage"),o.appendChild(n),o.appendChild(i),e.appendChild(o),new Error("WebGL initialization failed - browser may not support WebGL")}}I.addEventListener("webglcontextlost",t=>{t.preventDefault(),console.error("[StorySplat Viewer] WebGL context lost"),i.emit("error",new Error("WebGL context lost"))},!1),I.addEventListener("webglcontextrestored",()=>{console.log("[StorySplat Viewer] WebGL context restored")},!1),F.setCanvasFillMode(t.FILLMODE_FILL_WINDOW),F.setCanvasResolution(t.RESOLUTION_AUTO),F.start(),console.log("[StorySplat Viewer] App started");try{const t=F.scene.layers.getLayerByName("World"),e=t&&Object.getPrototypeOf(t);if(e&&!e._splitLightsPatched){const t=Object.getOwnPropertyDescriptor(e,"splitLights");if(t?.get){const o=t.get;Object.defineProperty(e,"splitLights",{get(){return this._splitLightsDirty&&(this._lights=this._lights.filter(t=>void 0!==t._type&&t._type>=0&&t._type<=2)),o.call(this)},configurable:!0}),e._splitLightsPatched=!0}}}catch(t){console.warn("[StorySplat Viewer] Could not patch Layer.splitLights:",t)}const $=Q(),V=$?"mobile":"desktop",B=K[V];console.log("[SPLAT] Initializing LOD system for device:",$?"mobile":"desktop"),F.scene.gsplat?(F.scene.gsplat.lodUpdateAngle=90,F.scene.gsplat.lodBehindPenalty=2,F.scene.gsplat.radialSorting=!0,F.scene.gsplat.lodUpdateDistance=1,F.scene.gsplat.lodUnderfillLimit=10,F.scene.gsplat.lodRangeMin=B.range[0],F.scene.gsplat.lodRangeMax=B.range[1],F.scene.gsplat.colorUpdateDistance=1,F.scene.gsplat.colorUpdateAngle=4,F.scene.gsplat.colorUpdateDistanceLodScale=2,F.scene.gsplat.colorUpdateAngleLodScale=2,console.log("[SPLAT] LOD system configured:",{preset:V,lodRangeMin:B.range[0],lodRangeMax:B.range[1],lodDistances:B.lodDistances,lodUpdateAngle:90,lodUpdateDistance:1,isMobile:$})):console.warn("[SPLAT] GSplat scene settings not available - LOD may not work optimally");let O=0,H=!1,W=null,G=null,N=!1,j=null;const ot=v.additionalSplats||[],it=v.keepMeshesInMemory??!1,st=v.initialSplatExploreMode||"fly";let at=null,rt=!1;const lt=new Map;let ct=-1,pt=-1;const dt=new t.Entity("camera");let ht=new t.Color(.1,.1,.1);if(v.backgroundColor){const e=v.backgroundColor.replace("#","");if(6===e.length){const o=parseInt(e.substring(0,2),16)/255,n=parseInt(e.substring(2,4),16)/255,i=parseInt(e.substring(4,6),16)/255;ht=new t.Color(o,n,i),console.log("[StorySplat Viewer] Background color set from config:",v.backgroundColor)}}dt.addComponent("camera",{clearColor:ht,fov:v.fov||60,nearClip:v.nearClip||.1,farClip:v.farClip||1e3}),dt.addComponent("audiolistener"),console.log("[StorySplat Viewer] Camera settings:",{fov:v.fov,nearClip:v.nearClip,farClip:v.farClip,playerHeight:v.playerHeight});const ut=v.playerHeight||1.6;if(v.waypoints&&v.waypoints.length>0){const t=v.waypoints[0];if(console.log("[StorySplat Viewer] First waypoint raw:",t),t.position){const e=t.position;dt.setPosition(e.x,e.y,-e.z),console.log("[StorySplat Viewer] Camera position (Z negated):",{x:e.x,y:e.y,z:-e.z})}else dt.setPosition(0,ut,5);if(t.rotation){const e=Rt(t.rotation);dt.setRotation(e),console.log("[StorySplat Viewer] Camera rotation set from waypoint")}}else dt.setPosition(0,ut,5),dt.lookAt(new t.Vec3(0,0,0)),console.log("[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)");F.root.addChild(dt);const mt=new t.Entity("light");mt.addComponent("light",{type:"directional",color:new t.Color(1,1,1),intensity:1,castShadows:!1}),mt.setEulerAngles(45,45,0),F.root.addChild(mt),console.log("[StorySplat Viewer] Light added");const yt=5*(v.cameraMovementSpeed||1),gt=new C(dt,F,{moveSpeed:yt,moveFastSpeed:2.5*yt,moveSlowSpeed:.5*yt,rotateSpeed:800/(v.cameraRotationSensitivity||4e3),enableOrbit:!0,enableFly:!0,enablePan:!0,invertRotation:v.invertCameraRotation,moveDamping:.75,rotateDamping:.75,zoomDamping:.8});let ft=null;v.collisionMeshesData&&v.collisionMeshesData.length>0&&(ft=new _(dt,F,{moveSpeed:yt,sprintMultiplier:2,lookSensitivity:1/(v.cameraRotationSensitivity||4e3)*10,playerHeight:v.playerHeight||1.6,gravity:20,jumpVelocity:8,collisionRadius:.3,stepHeight:.3}),ft.createCollisionMeshes(v.collisionMeshesData).then(()=>{console.log("[StorySplat Viewer] Collision meshes loaded for walk mode"),gt.setCollisionEntities(ft.collisionMeshEntities)}).catch(t=>{console.error("[StorySplat Viewer] Failed to load collision meshes:",t)}));let vt=z,bt=!0;"tour"===z&>.disable();const xt=new t.Picker(F,1,1,!0);let wt=!1,St=null;let Et=null,Ct=null,Mt=!1;function _t(){Et&&(Et.enabled=!1,Mt=!1)}let Lt=0;F.on("update",e=>{var o,n;D.fpsCounter&&(Lt+=e,Lt>=.5&&(Lt=0,n=1/e,(o=D).fpsCounter&&(o.fpsCounter.textContent=`${Math.round(n)} FPS`))),"walk"===vt&&ft?ft.update(e):gt.update(e),bt||function(){const t=dt.getPosition();Me.forEach(e=>{const o=e.hotspotData;if(!o)return;if("proximity"!==(e.mediaTriggerMode||"click"))return;const n=e.getPosition(),i=t.distance(n)<=(e.proximityDistance||5),s=e.wasInProximity||!1;if(i&&!s){if("video"===o.type&&e.videoElement)wo(e,o);else if("gif"===o.type)e.enabled=!0;else if(("sphere"===o.type||"image"===o.type)&&e.audioElements){const t=e.audioElements,n=t.audio;n&&n.paused&&(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),n.play().catch(t=>console.error("[Audio] Hotspot audio play failed (proximity):",t)),console.log("[Audio] Hotspot audio started (proximity):",o.title))}e.wasInProximity=!0}else if(!i&&s){if("video"===o.type&&e.videoElement)So(e);else if("gif"===o.type)e.enabled=!1;else if(("sphere"===o.type||"image"===o.type)&&e.audioElements){const t=e.audioElements.audio;t&&!t.paused&&(t.pause(),console.log("[Audio] Hotspot audio stopped (proximity):",o.title))}e.wasInProximity=!1}})}(),function(){if(0===Ie.size)return;const t=dt.getPosition();Ie.forEach((e,o)=>{const{entity:n,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=n.sound?.slot(s);if(l&&i.spatialSound){const s=n.getPosition(),a=t.distance(s),c=i.maxDistance||20;a<=c&&!r?(console.log(`[Audio] Proximity play: ${o}, distance=${a.toFixed(2)}, maxDistance=${c}`),l.play(),e.playing=!0):a>c&&r&&i.stopOnExit&&(console.log(`[Audio] Proximity stop: ${o}, distance=${a.toFixed(2)}`),l.stop(),e.playing=!1)}})}(),function(){if(0===po.size)return;const t=dt.getPosition();po.forEach((e,o)=>{const{entity:n,config:i,slotId:s,assetReady:a,playing:r}=e;if(!a)return;const l=n.sound?.slot(s);if(l&&!1!==i.spatialSound){const o=n.getPosition();t.distance(o)<=(i.maxDistance||100)&&!r&&!1!==i.autoplay&&(l.isPlaying||(l.play(),e.playing=!0))}})}(),function(){if(!v.waypoints?.length)return;const e=dt.getPosition();v.waypoints.forEach((o,n)=>{const i=new t.Vec3(o.position?.x??0,o.position?.y??0,-(o.position?.z??0)),s=e.distance(i),a=o.triggerDistance??1;s<=a?ho.has(n)||(ho.add(n),console.log(`[StorySplat] Waypoint ${n} triggered (distance: ${s.toFixed(2)}, threshold: ${a})`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{if("audio"===t.type){const e=t.id;if(!e)return;const o=Ie.get(e);if(o&&o.assetReady&&!o.playing){const t=o.entity.sound?.slot(o.slotId);t&&(t.play(),o.playing=!0)}}})}(o)):ho.has(n)&&(ho.delete(n),console.log(`[StorySplat] Waypoint ${n} exited`),function(t){if(!t.interactions?.length)return;t.interactions.forEach(t=>{if("audio"===t.type){if(t.data?.stopOnExit??!1){const e=t.id;if(!e)return;const o=Ie.get(e);if(o&&o.playing){const t=o.entity.sound?.slot(o.slotId);t&&(t.stop(),o.playing=!1)}}}})}(o))})}(),wt&&Mt&&function(){if(Et?.enabled&&dt){const t=dt.forward.clone(),e=dt.getPosition(),o=e.clone().add(t.mulScalar(1.5));o.y-=.1,Et.setPosition(o),Et.lookAt(e),Et.rotateLocal(90,0,0)}}()}),F.on("joystick:left",(t,e,o,n)=>{if(t<0||e<0)y(D,!1,0,0,60);else{y(D,!0,o-t,n-e,60)}}),F.on("joystick:right",(t,e,o,n)=>{g(D,!(t<0||e<0))});const kt=D.scrollControls?.querySelectorAll(".storysplat-explore-btn"),At=t=>{kt?.forEach(e=>{const o=e.getAttribute("data-explore-mode");e.classList.toggle("selected",o===t)})};function Pt(t){"walk"===vt&&ft?ft.disable():"explore"===vt&>.disable(),vt=t,console.log("[StorySplat Viewer] Switching to mode:",t);const e=Q();if("walk"===t&&ft)bt=!1,gt.disable(),ft.enable(),m(D,!1);else if("explore"===t){ft&&ft.disable(),oe=0,ne=0,ie=!1,bt=!1,gt.disable(),gt.enable(),gt.enableOrbit=!0,gt.enableFly=!0,gt.enablePan=!0;const t=st;Kt&&te?(gt.syncFromPose(Kt,te),console.log("[StorySplat Viewer] Synced camera from waypoint pose for explore mode")):gt.syncFromCamera();(async()=>{try{const t=.25;xt.resize(Math.floor(ko.clientWidth*t),Math.floor(ko.clientHeight*t));const e=F.scene.layers.getLayerByName("World");if(e){xt.prepare(dt.camera,F.scene,[e]);const o=Math.floor(.5*ko.clientWidth*t),n=Math.floor(.5*ko.clientHeight*t),i=await xt.getWorldPointAsync(o,n);if(i){const t=dt.getPosition().distance(i);t>.5&&t<500&&(gt.syncFromCamera(i),console.log("[StorySplat Viewer] Updated focus target at distance:",t.toFixed(2)))}}}catch(t){}})(),gt.setMode(t),At(t),e&&m(D,"fly"===t)}else gt.disable(),ft&&ft.disable(),bt=!0,m(D,!1);i.emit("modeChange",{mode:t})}kt?.forEach(t=>{t.addEventListener("click",()=>{if("explore"!==vt)return;"orbit"===t.getAttribute("data-explore-mode")?(gt.setMode("orbit"),At("orbit"),$&&m(D,!1)):(gt.setMode("fly"),At("fly"),$&&m(D,!0))})}),F.on("cameracontrols:modechange",t=>{"orbit"!==t&&"fly"!==t||(At(t),$&&"explore"===vt&&m(D,"fly"===t))});const Tt=(t,e)=>{D.preloader&&function(t,e,o,n){const i=t.querySelector(".storysplat-preloader-bar"),s=t.querySelector(".storysplat-preloader-text"),a=Math.max(0,Math.min(100,100*e));i&&(i.style.width=`${a}%`),s&&(s.textContent=o||`${n||r.loading} ${Math.round(a)}%`)}(D.preloader,t,e,l(M,"loading")),i.emit("progress",{progress:t,text:e})};function zt(t){return["firebasestorage.googleapis.com/v0/b/story-splat","storage.googleapis.com/story-splat","storysplat.com","discover.storysplat.com"].some(e=>t.includes(e))}function Rt(e){if("_w"in e||"w"in e){const o=e._x??e.x??0,n=e._y??e.y??0,i=e._z??e.z??0,s=e._w??e.w??1;return new t.Quat(-o,-n,i,s)}if("x"in e&&"y"in e&&"z"in e){const o=new t.Quat;return o.setFromEulerAngles(e.x||0,e.y||0,e.z||0),o}return new t.Quat}function Dt(t){t.enabled=!1,console.log("[SplatSwap] Splat hidden")}function It(t){const e=lt.get(t);e&&(e.destroy(),lt.delete(t),console.log("[SplatSwap] Splat disposed:",t))}function Ft(t){const e=lt.get(t);return!!e&&(e.enabled=!0,at=t,console.log("[SplatSwap] Splat shown:",t),!0)}async function Ut(e){if(!lt.has(e)&&e!==at&&!N){console.log("[SplatSwap] Preloading splat:",e);try{const o=new t.Asset("splat-preload-"+Date.now(),"gsplat",{url:e});await new Promise((n,i)=>{o.ready(()=>{if(N)return void i(new Error("Viewer destroyed"));const s=new t.Entity("splat-preload");s.addComponent("gsplat",{asset:o,unified:!0});const a=v.scale||{x:1,y:1,z:1},r=v.invertXScale||!1,l=v.invertYScale||!1,c={x:r?-a.x:a.x,y:l?-a.y:a.y,z:r!==l?-a.z:a.z};s.setLocalScale(c.x,c.y,c.z);const p=v.position||[0,0,0];s.setPosition(p[0],p[1],-p[2]);const d=v.rotation||[0,0,0],h=[180+d[0]*(180/Math.PI),d[1]*(180/Math.PI),-d[2]*(180/Math.PI)];s.setEulerAngles(h[0],h[1],h[2]),s.enabled=!1,F.root.addChild(s),lt.set(e,s),console.log("[SplatSwap] Preload complete:",e),n()}),o.on("error",t=>{console.error("[SplatSwap] Preload error:",t),i(t)}),F.assets.add(o),F.assets.load(o)})}catch(t){console.error("[SplatSwap] Error preloading:",e,t)}}}function $t(t,e=!1){if("explore"!==vt)return;const o=e?st:t||st;if(gt){gt.mode!==o&&(gt.setMode(o),console.log(`[SplatSwap] Switching explore sub-mode to: ${o}`),At(o),$&&m(D,"fly"===o))}}async function Vt(e){if(e!==at&&!rt&&!N){rt=!0,console.log("[SplatSwap] Switching to splat:",e);try{if(at&<.has(at))if(it){const t=lt.get(at);t&&Dt(t)}else It(at);else W&&(W.enabled=!1);if(lt.has(e))Ft(e),i.emit("splatChange",{url:e,isOriginal:!1});else{const o=new t.Asset("splat-swap-"+Date.now(),"gsplat",{url:e});await new Promise((n,s)=>{o.ready(()=>{if(N)return void s(new Error("Viewer destroyed"));const a=new t.Entity("splat-swap");a.addComponent("gsplat",{asset:o,unified:!0});const r=v.scale||{x:1,y:1,z:1},l=v.invertXScale||!1,c=v.invertYScale||!1,p={x:l?-r.x:r.x,y:c?-r.y:r.y,z:l!==c?-r.z:r.z};a.setLocalScale(p.x,p.y,p.z);const d=v.position||[0,0,0];a.setPosition(d[0],d[1],-d[2]);const h=v.rotation||[0,0,0],u=[180+h[0]*(180/Math.PI),h[1]*(180/Math.PI),-h[2]*(180/Math.PI)];a.setEulerAngles(u[0],u[1],u[2]),F.root.addChild(a),lt.set(e,a),at=e,i.emit("splatChange",{url:e,isOriginal:!1}),console.log("[SplatSwap] New splat loaded and shown:",e),n()}),o.on("error",t=>{console.error("[SplatSwap] Load error:",t),s(t)}),F.assets.add(o),F.assets.load(o)})}const o=ot.findIndex(t=>t.url===e);-1!==o&&async function(t){if(!ot||0===ot.length)return;const e=(t+1)%ot.length,o=ot[e];o&&o.url&&await Ut(o.url)}(o)}catch(t){console.error("[SplatSwap] Error switching splat:",t)}finally{rt=!1}}}function Bt(){return v.sogUrl?v.sogUrl:v.splatUrl?v.splatUrl:v.fallbackUrls&&v.fallbackUrls.length>0?v.fallbackUrls[0]:""}function Ot(){if(!ot||0===ot.length)return;const e=v.waypoints?.length||1,o=100*Wt,n=Math.round(Wt*Math.max(1,e-1));if(Math.abs(o-ct)<.1&&n===pt)return;ct=o,pt=n;let s=null,a=null,r=-1/0,l=-1/0;for(const t of ot)-1!==t.waypointIndex?n>=t.waypointIndex&&t.waypointIndex>r&&(r=t.waypointIndex,s=t):-1!==t.percentage&&o>=t.percentage&&t.percentage>l&&(l=t.percentage,a=t);const c=a||s,p=c&&"__ORIGINAL__"===c.url,d=Bt(),h=c?p?d:c.url:d;h&&h!==at&&(h===d&&W&&!at?at=d:h===d&&W?(lt.forEach((t,e)=>{e!==d&&(it?Dt(t):It(e))}),W&&(W.enabled=!0),at=d,i.emit("splatChange",{url:d,isOriginal:!0}),console.log("[SplatSwap] Returned to primary splat"),$t(void 0,!0)):(Vt(h),c&&$t(c.defaultExploreMode,!1)),c&&c.skyboxUrl&&!p&&function(e,o=0){console.log("[SplatSwap] Applying skybox:",e,"rotation:",o);const n=new t.Asset("skybox-swap-"+Date.now(),"cubemap",{url:e},{type:t.TEXTURETYPE_RGBM,mipmaps:!0});n.ready(()=>{if(!N)try{if(F.scene.skybox=n.resource,F.scene.skyboxMip=0,0!==o){const e=new t.Quat;e.setFromEulerAngles(0,o*(180/Math.PI),0),F.scene.skyboxRotation=e}console.log("[SplatSwap] Skybox applied successfully")}catch(t){console.error("[SplatSwap] Error applying skybox:",t)}}),n.on("error",t=>{console.error("[SplatSwap] Skybox load error:",t)}),F.assets.add(n),F.assets.load(n)}(c.skyboxUrl,c.skyboxRotation||0))}function Ht(){const t=Bt();at!==t&&(lt.forEach((e,o)=>{o!==t&&(it?Dt(e):It(o))}),W&&(W.enabled=!0),at=t,i.emit("splatChange",{url:t,isOriginal:!0}),console.log("[SplatSwap] Manually returned to original splat"),$t(void 0,!0))}let Wt=0,Gt=0,Nt=!1;const qt=v.waypoints?.reduce((t,e)=>t+(e.duration||2e3),0)||1,jt=v.waypoints?.length||1,Xt=Math.max(1,20*(jt-1)),Yt=void 0!==v.autoplaySpeed?60*v.autoplaySpeed/Xt:1e3/qt,Zt=v.loopMode;let Jt;Jt=!0===Zt?"loop":!1===Zt?"none":"loop"===Zt||"pingpong"===Zt||"none"===Zt?Zt:"loop";let Qt=1;console.log("[StorySplat Viewer] Playback config:",{loopMode:Jt,playbackSpeed:Yt,totalDuration:qt,autoPlay:v.autoPlay,rawLoopMode:v.loopMode});let Kt=null,te=null;const ee=.01+.1*(v.transitionSpeed||1);let oe=0,ne=0;let ie=!1,se=0,ae=0;const re=[],le=[],ce=[],pe=v.fov||60;let de=pe;function he(o){if(!bt||re.length<2)return;o=Math.max(0,Math.min(1,o));const n=re.length,s=o*(n-1),a=Math.min(Math.floor(s),n-2),r=s-a,l=re[a],c=re[a+1];Kt=new t.Vec3(nt(l.x,c.x,r),nt(l.y,c.y,r),nt(l.z,c.z,r));const p=le[a],d=le[a+1];te=new t.Quat,te.slerp(p,d,r);const h=ce[a],u=ce[a+1];de=nt(h,u,r);const m=Math.round(o*(n-1));if(m!==O){const o=O;O=m;const n=v.waypoints[m],s=n.cameraMode||"first-person";"orbit"===s&&(n.orbitTarget?new t.Vec3(n.orbitTarget.x,n.orbitTarget.y,-(n.orbitTarget.z||0)):new t.Vec3(re[m].x,re[m].y,re[m].z)),i.emit("waypointChange",{index:m,waypoint:n,prevIndex:o,cameraMode:s}),y=m,g=o,Ie.forEach((t,e)=>{const{entity:o,waypointIndex:n,config:i,slotId:s,autoplayTriggered:a}=t,r=o.sound?.slot(s);if(!r)return;const l=g===n&&y!==n;y===n&&g!==n&&i.autoplay&&!a&&(console.log(`[Audio] Autoplay waypoint audio: ${e} at waypoint ${n}`),r.isPlaying||(r.play(),t.playing=!0,t.autoplayTriggered=!0)),l&&i.stopOnExit&&t.playing&&(console.log(`[Audio] Stopping waypoint audio on exit: ${e}`),r.isPlaying&&(r.stop(),t.playing=!1,t.autoplayTriggered=!1))}),Fe(),Ue(e);const a=e.querySelector(".storysplat-hotspot-popup");a&&a.classList.remove("visible")}var y,g;i.emit("progressUpdate",{progress:Math.max(0,Math.min(1,o)),index:O}),Ot()}function ue(t,e=!1){const o=Math.max(0,Math.min(1,t));Gt=o,e?ye(o):(Wt=o,he(Wt))}v.waypoints&&v.waypoints.length>0&&(v.waypoints.forEach(e=>{const o=e.position?new t.Vec3(e.position.x,e.position.y,-e.position.z):new t.Vec3(0,v.playerHeight||1.6,0);re.push(o);const n=e.rotation?Rt(e.rotation):new t.Quat;le.push(n);let i=e.fov||pe;i<3.5&&(i*=180/Math.PI),ce.push(i)}),re.length>0&&(Kt=re[0].clone(),te=le[0].clone(),de=ce[0])),F.on("update",function(){if(!bt)return;if(!Nt){const t=Gt-Wt;Math.abs(t)>1e-4&&(Wt+=t*ee,he(Wt))}if(!Kt||!te)return;const e=dt.getPosition(),o=new t.Vec3;o.lerp(e,Kt,ee),dt.setPosition(o.x,o.y,o.z);const n=dt.camera;if(n&&ce.length>0){const t=nt(n.fov,de,ee);n.fov=t}ie||(oe*=.95,ne*=.95,Math.abs(oe)<.01&&(oe=0),Math.abs(ne)<.01&&(ne=0));const i=new t.Quat;i.setFromEulerAngles(ne,oe,0);const s=new t.Quat;s.mul2(te,i);const a=dt.getRotation(),r=new t.Quat;r.slerp(a,s,ee),dt.setRotation(r)});const me=500*(v.transitionSpeed||1);function ye(t,e=me){const o=Wt,n=performance.now();Nt=!0;const i=()=>{const s=performance.now()-n,a=Math.min(s/e,1);Wt=o+(t-o)*(a<.5?2*a*a:(4-2*a)*a-1),he(Wt),a<1?requestAnimationFrame(i):Nt=!1};requestAnimationFrame(i)}function ge(t){if(!v.waypoints||t<0||t>=v.waypoints.length)return;if(!bt)return;const e=t/Math.max(1,v.waypoints.length-1);Gt=e,ye(e)}function fe(){if(!v.waypoints||0===v.waypoints.length)return;const t=v.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=v.scrollAmount||10;let e=Wt+t/100;e>1&&(e="loop"===Jt?0:1),Gt=e,ye(e)}else{let t=O+1;t>=v.waypoints.length&&(t="loop"===Jt?0:v.waypoints.length-1),ge(t)}}function ve(){if(!v.waypoints||0===v.waypoints.length)return;const t=v.scrollButtonMode||"waypoint";if("percentage"===t||"continuous"===t||"incremental"===t){const t=v.scrollAmount||10;let e=Wt-t/100;e<0&&(e="loop"===Jt?1:0),Gt=e,ye(e)}else{let t=O-1;t<0&&(t="loop"===Jt?v.waypoints.length-1:0),ge(t)}}let be=0,xe=null;function we(t){if(!H)return;0===be&&(be=t);const e=(t-be)/1e3;be=t;const o=Yt*e*Qt;if(Wt+=o,Gt+=o,Wt>=1)switch(console.log("[StorySplat Viewer] End of tour reached, loopMode:",Jt),Jt){case"loop":console.log("[StorySplat Viewer] Looping - restarting at beginning"),Wt=0,Gt=0;break;case"pingpong":console.log("[StorySplat Viewer] Pingpong - reversing direction"),Wt=1,Gt=1,Qt=-1;break;case"none":return console.log("[StorySplat Viewer] No loop - stopping playback"),Wt=1,Gt=1,Ee(),void i.emit("playbackComplete");default:return console.log("[StorySplat Viewer] Unknown loopMode, stopping:",Jt),Wt=1,Gt=1,Ee(),void i.emit("playbackComplete")}else if(Wt<=0)if("pingpong"===Jt)console.log("[StorySplat Viewer] Pingpong - reversing to forward"),Wt=0,Gt=0,Qt=1;else Wt=0,Gt=0;he(Wt),xe=requestAnimationFrame(we)}function Se(){if(j)return j.play(),void i.emit("playbackStart");H||!v.waypoints||v.waypoints.length<2||(H=!0,be=0,Qt=1,i.emit("playbackStart"),xe=requestAnimationFrame(we))}function Ee(){if(j)return j.pause(),void i.emit("playbackStop");H=!1,xe&&(cancelAnimationFrame(xe),xe=null),i.emit("playbackStop")}const Ce=F.graphicsDevice.canvas;Ce.addEventListener("wheel",t=>{if(!bt)return;t.preventDefault();const e=v.scrollSpeed||.1,o=v.scrollAmount||100,n=v.waypoints?.length||2,i=Math.max(20,20*(n-1)),s=100*(Math.abs(t.deltaY)/100)*e*(o/100)/i,a=t.deltaY>0?s:-s;Gt=Math.max(0,Math.min(1,Gt+a))},{passive:!1}),Ce.addEventListener("pointerdown",t=>{bt&&(ie=!0,se=t.clientX,ae=t.clientY)},{capture:!0}),Ce.addEventListener("pointermove",t=>{if(!bt||!ie)return;const e=t.clientX-se,o=t.clientY-ae;se=t.clientX,ae=t.clientY;oe+=.3*-e,ne+=.3*-o,ne=Math.max(-60,Math.min(60,ne))},{capture:!0}),Ce.addEventListener("pointerup",()=>{ie=!1},{capture:!0}),Ce.addEventListener("pointerleave",()=>{ie=!1},{capture:!0});const Me=[],_e=[],Le=D.portalPopup;let ke=null;const Ae=()=>{Le&&(Le.classList.remove("visible"),ke=null)};if(Le){const t=Le.querySelector(".storysplat-portal-popup-confirm"),e=Le.querySelector(".storysplat-portal-popup-cancel");t?.addEventListener("click",()=>{if(ke){const t=ke;Ae(),_o(t)}}),e?.addEventListener("click",()=>{Ae()})}function Pe(e){const o=parseInt(e.slice(1,3),16)/255,n=parseInt(e.slice(3,5),16)/255,i=parseInt(e.slice(5,7),16)/255;return new t.Color(o,n,i)}const Te=[],ze=[];let Re=!1;const De=new Map,Ie=new Map;function Fe(){Me.forEach(t=>{if(t.audioElements){const e=t.audioElements.audio;e&&!e.paused&&(e.pause(),e.currentTime=0)}if(t.videoElement){const e=t.videoElement;e.paused||e.pause()}t.wasInProximity=!1})}function Ue(t){const e=t.querySelector(".storysplat-hotspot-popup");e&&(e.querySelectorAll("video").forEach(t=>{t.pause(),t.currentTime=0}),e.querySelectorAll("iframe").forEach(t=>{t.src=""}))}const $e=new Map,Ve=new Map,Be={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%2Fsparkle.png?alt=media&token=466739a4-ccd7-4295-88c2-dedd681a34f0",rain:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Frain.png?alt=media&token=13478487-6259-4906-838c-ad592db893e4",smoke:"https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsmoke.png?alt=media&token=6cece4f8-87cf-47f9-974d-c4dd6a649f0f"};function Oe(e,o){return new Promise((n,i)=>{if(Ve.has(e))return void n(Ve.get(e));const s=new t.Asset(e,"texture",{url:o});s.on("load",()=>{if(N)return console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${e}`),void i(new Error("Viewer destroyed"));const t=s.resource;Ve.set(e,t),n(t)}),s.on("error",t=>{console.error(`[Particle] Failed to load texture: ${e}`,t),i(t)}),F.assets.add(s),F.assets.load(s)})}function He(e){const o=new t.Entity(e.name||"particle-system"),n=(t,e=0)=>({x:t?._x??t?.x??e,y:t?._y??t?.y??e,z:t?._z??t?.z??e}),i=t=>({r:t?.r??1,g:t?.g??1,b:t?.b??1,a:t?.a??1}),s=n(e.emitterPosition,0),a=n(e.gravity,0),r=i(e.color1),l=i(e.color2),c=i(e.colorDead),p=n(e.direction1,0),d=n(e.direction2,0),h=((e.minLifeTime??1)+(e.maxLifeTime??3))/2;let u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.1,.1,.1),y=new t.Vec3(0,0,0);if("sphere"===e.emitterType||"sphere"===e.emitterShape){u=t.EMITTERSHAPE_SPHERE;const o=e.emitterRadius||.1;m=new t.Vec3(o,o,o)}else if("box"===e.emitterShape&&e.emitBoxMin&&e.emitBoxMax){u=t.EMITTERSHAPE_BOX;const o=n(e.emitBoxMin,0),i=n(e.emitBoxMax,0);m=new t.Vec3(Math.abs(i.x-o.x)/2,Math.abs(i.y-o.y)/2,Math.abs(i.z-o.z)/2),y=new t.Vec3((o.x+i.x)/2,(o.y+i.y)/2,-(o.z+i.z)/2)}else"point"===e.emitterShape?(u=t.EMITTERSHAPE_BOX,m=new t.Vec3(.01,.01,.01)):m=new t.Vec3(e.emitterExtents?.x||e.emitterRadius||.1,e.emitterExtents?.y||e.emitterRadius||.1,e.emitterExtents?.z||e.emitterRadius||.1);let g=t.BLEND_ADDITIVEALPHA;const f=e.blendMode||"";"BLENDMODE_STANDARD"===f||"normal"===f||"alpha"===f?g=t.BLEND_NORMAL:"BLENDMODE_MULTIPLY"===f||"multiply"===f?g=t.BLEND_MULTIPLICATIVE:"BLENDMODE_ONEONE"===f?g=t.BLEND_ADDITIVE:"BLENDMODE_ADD"!==f&&"BLENDMODE_MULTIPLYADD"!==f||(g=t.BLEND_ADDITIVEALPHA);const v=a.x||0,b=a.y||0,x=-(a.z||0),w=v*h,S=b*h,E=x*h,C=e.minAngularSpeed??0,M=e.maxAngularSpeed??e.angularSpeed??0,_=e.minInitialRotation??0,L=e.maxInitialRotation??0,k=e.minSize??.1,A=e.maxSize??.3,P=e.minScaleX??1,T=e.maxScaleX??1,z=e.minScaleY??1,R=e.maxScaleY??1,D=e.minEmitPower??1,I=e.maxEmitPower??2,F=(p.x+d.x)/2,U=(p.y+d.y)/2,$=-(p.z+d.z)/2;o.addComponent("particlesystem",{numParticles:e.numParticles||500,lifetime:h,rate:e.emitRate||50,emitterShape:u,emitterExtents:m,emitterRadius:e.emitterRadius||.1,startAngle:_,startAngle2:0!==L?L:360,radialSpeedGraph:new t.Curve([0,D,1,I]),localVelocityGraph:new t.CurveSet([[0,F*D],[0,U*D],[0,$*D]]),localVelocityGraph2:new t.CurveSet([[0,F*I],[0,U*I],[0,$*I]]),velocityGraph:new t.CurveSet([[0,0,1,w],[0,0,1,S],[0,0,1,E]]),scaleGraph:new t.Curve([0,k*P,1,A*T]),scaleGraph2:new t.Curve([0,k*z,1,A*R]),rotationSpeedGraph:new t.Curve([0,C]),rotationSpeedGraph2:M!==C?new t.Curve([0,M]):void 0,colorGraph:new t.CurveSet([[0,r.r,.5,l.r,1,c.r],[0,r.g,.5,l.g,1,c.g],[0,r.b,.5,l.b,1,c.b]]),alphaGraph:new t.Curve([0,r.a??1,.5,l.a??.8,1,c.a??0]),blend:g,depthWrite:e.depthWrite??!1,depthSoftening:e.softParticles??0,lighting:e.lighting??!1,halfLambert:e.halfLambert??!1,alignToMotion:e.alignToMotion??!1,stretch:e.stretch||0,preWarm:e.preWarm??!1,loop:e.loop??!0,autoPlay:e.autoPlay??!0,sort:e.sort??0,orientation:e.orientation??0}),o.particlesystem&&(o.particlesystem.localSpace=e.localSpace??!1);const V=e.translationPivot?.x??0,B=e.translationPivot?.y??0;o.setPosition(s.x+y.x+V,s.y+y.y+B,-s.z+y.z);const O=e.renderingGroupId??3;return o.particlesystem&&void 0!==O&&(o.particlesystem.drawOrder=O),console.log(`[Particle] Entity configured at position: (${s.x+y.x+V}, ${s.y+y.y+B}, ${-s.z+y.z})`),console.log(`[Particle] Emitter shape: ${e.emitterShape||"box"}, extents: ${m.x}, ${m.y}, ${m.z}`),console.log(`[Particle] Gravity: (${v}, ${b}, ${x})`),console.log(`[Particle] Colors: start=${JSON.stringify(r)}, mid=${JSON.stringify(l)}, dead=${JSON.stringify(c)}`),console.log(`[Particle] Angular speed: ${C} - ${M}, Initial rotation: ${_} - ${L}`),console.log(`[Particle] TranslationPivot: (${V}, ${B}), RenderingGroupId: ${O}`),o}const We=new Map;function Ge(t){const{type:e,component:o,node:n,animations:i}=t;console.log("[CustomMesh] Playing animation, type:",e,"node:",n?.name,"animations:",i?.length);try{if("pc-anim"===e){if(console.log("[CustomMesh] Using simple pc-anim approach (like HTML export)"),!o)return;o.playing=!0,t.isPlaying=!0,console.log("[CustomMesh] Animation started - playing:",o.playing)}else if("animation"===e){if(!o)return;if(o.playing=!0,o.loop=!0,"function"==typeof o.play){const t=Object.keys(o.animations||{});t.length>0?(o.play(t[0],1),console.log("[CustomMesh] Playing legacy animation clip:",t[0])):o.play()}}else if("glb-skeleton"===e){console.log("[CustomMesh] Starting GLB skeleton animation playback");const{modelEntity:e,animations:o}=t;if(!e)return;if(o&&o.length>0)try{if(e.anim||e.addComponent("anim",{activate:!0,speed:1}),e.anim){const n=e.anim;o.forEach((t,e)=>{const o=t.resource||t,i=o._name||o.name||t._name||t.name||`Anim_${e}`;console.log("[CustomMesh] Assigning animation track:",i,"type:",typeof o);try{n.assignAnimation?.(i,o)}catch(t){console.warn("[CustomMesh] assignAnimation failed for",i,t)}}),e.anim.playing=!0,e.anim.speed=1,t.animComponent=e.anim,t.isPlaying=!0,console.log("[CustomMesh] GLB animation playing via AnimComponent")}}catch(t){console.error("[CustomMesh] Error setting up GLB animation:",t)}}else if("anim"===e&&o&&(o.playing=!0,o.speed=1,o.baseLayer)){const t=(o.baseLayer.states||[]).find(t=>"START"!==t&&"END"!==t&&"ANY"!==t);t&&(o.baseLayer.play?.(t),console.log("[CustomMesh] Playing anim state:",t))}o&&console.log("[CustomMesh] Animation component state - playing:",o.playing,"speed:",o.speed)}catch(t){console.error("[CustomMesh] Error in playAnimComponentV2:",t)}}function Ne(t){const{type:e,component:o}=t;if(o||"glb-skeleton"===e)try{"pc-anim"===e&&o?(o.playing=!1,t.isPlaying=!1,console.log("[CustomMesh] pc-anim animation paused")):"glb-skeleton"===e?(t.isPlaying=!1,t.animComponent&&(t.animComponent.playing=!1,console.log("[CustomMesh] GLB skeleton animation paused via AnimComponent")),t.updateHandler&&(F.off("update",t.updateHandler),t.updateHandler=null,console.log("[CustomMesh] GLB manual animation paused"))):o&&(o.playing=!1,o.speed=0,"animation"===e&&"function"==typeof o.pause&&o.pause())}catch(t){console.error("[CustomMesh] Error pausing animation:",t)}}function qe(e,o){if(console.log("[CustomMesh] Loading mesh",o,":",e.name,e),!e.modelUrl||"string"!=typeof e.modelUrl||""===e.modelUrl.trim())return console.warn("[CustomMesh] Skipping mesh",e.name,"- no valid modelUrl provided. Config:",e),null;const n=new t.Entity("custom-mesh-"+o),i=e.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)),e.rotation){const t=e.rotation;n.setRotation(fo(t._x??t.x??0,t._y??t.y??0,t._z??t.z??0))}if(e.scale){const t=e.scale;n.setLocalScale(t._x??t.x??1,t._y??t.y??1,t._z??t.z??1)}const s=e.modelUrl.trim();console.log("[CustomMesh] Loading model from URL:",s);const a=new t.Asset("mesh-model-"+o,"container",{url:s});F.assets.add(a);const r=e.id||`mesh-${o}`,l={entity:n,config:e,isAnimPlaying:!1,audioPlaying:!1};return We.set(r,l),a.ready(o=>{try{if(console.log("[CustomMesh] Model loaded:",e.name),!o||!o.resource)return void console.error("[CustomMesh] Invalid asset resource for mesh:",e.name);const i=o.resource,s=i?.instantiateRenderEntity();if(!s)return void console.error("[CustomMesh] Failed to instantiate render entity for mesh:",e.name);function a(e){e.enabled=!0,e.children&&e.children.forEach(e=>{e instanceof t.Entity&&a(e)})}n.addChild(s),n.modelEntity=s,a(s);let r=0;function c(e){const o=e.render;if(o&&o.meshInstances)for(const t of o.meshInstances){const e=t.material;e&&"function"==typeof e.update&&(e.specular&&e.specular.mulScalar(.3),void 0!==e.gloss&&(e.gloss=Math.min(e.gloss,60)),e.update())}e.children&&e.children.forEach(e=>{e instanceof t.Entity&&c(e)})}if(s.forEach(()=>{r++}),console.log("[CustomMesh] Enabled all children for:",e.name,"- Total nodes:",r),c(s),"animated"!==e.opacityMode&&void 0!==e.opacity&&e.opacity<1&&(Je(n,e.opacity),console.log("[CustomMesh] Applied static opacity:",e.opacity,"to",e.name)),e.billboard){const d=n.getEulerAngles().clone();n._billboardActive=!e.billboardRange,F.on("update",()=>{if(!n.enabled)return;if(!n._billboardActive)return void n.setEulerAngles(d.x,d.y,d.z);const t=dt.getPosition(),e=n.getPosition(),o=t.x-e.x,i=t.z-e.z,s=Math.atan2(o,i)*(180/Math.PI),a=n.getEulerAngles();n.setEulerAngles(a.x,s,a.z)}),console.log("[CustomMesh] Billboard enabled for:",e.name,e.billboardRange?"(with range control)":"(always active)")}const p=(i?.animations?.length??0)>0;if(console.log("[CustomMesh] Asset resource for",e.name,":",{hasAnimations:p,animationCount:i?.animations?.length||0,animations:i?.animations?.map(t=>t?.name||t?.resource?.name||"unnamed")||[],interactionConfig:e.interaction}),p||e.interaction&&e.interaction.playModelAnimation){const h=[],u=s.anim;if(u&&(console.log("[CustomMesh] Found modelEntity.anim component - using simple approach"),h.push({type:"pc-anim",component:u,modelEntity:s})),0===h.length){function m(e,o=0){e.anim&&!h.find(t=>t.component===e.anim)&&(h.push({type:"anim",component:e.anim,node:e}),console.log("[CustomMesh] Found existing ANIM component on:",e.name)),e.animation&&(h.push({type:"animation",component:e.animation,node:e}),console.log("[CustomMesh] Found ANIMATION (legacy) component on:",e.name)),e.children&&e.children.forEach(e=>{e instanceof t.Entity&&m(e,o+1)})}m(s)}if(0===h.length&&p){const y=i?.animations||[];console.log("[CustomMesh] No anim component found - falling back to GLB skeleton approach"),console.log("[CustomMesh] GLB has",y.length,"embedded animations");try{h.push({type:"glb-skeleton",modelEntity:s,asset:o,animations:y,animationNames:y.map(t=>t.resource?.name||t.name||"Animation"),isPlaying:!1,currentTime:0}),console.log("[CustomMesh] GLB skeleton animations stored:",y.map(t=>t.resource?.name||t.name))}catch(g){console.error("[CustomMesh] Error setting up GLB animations:",g)}}if(h.length>0){l.allAnimComponents=h,l.animComponent=h[0].component,console.log("[CustomMesh] Total animation components for",e.name,":",h.length,"- Types:",h.map(t=>t.type).join(", "));const f=e.interaction?.animationAutoPlay;f&&(h.forEach(t=>{Ge(t)}),l.isAnimPlaying=!0,console.log("[CustomMesh] Auto-playing all animations for:",e.name))}else console.warn("[CustomMesh] No animation components could be set up for mesh:",e.name)}else console.log("[CustomMesh] No animations to setup for:",e.name,"(no GLB animations and playModelAnimation not enabled)");e.interaction&&e.interaction.playAudio&&e.interaction.audioUrl&&function(e,o,n){const i=o.interaction;if(!i)return;const s=o.id||o.name,a=`mesh-audio-${s}`;e.addComponent("sound",{positional:i.audioSpatial||!1,distanceModel:i.audioDistanceModel||"exponential",refDistance:i.audioRefDistance||1,maxDistance:i.audioMaxDistance||100,rollOffFactor:i.audioRolloffFactor||1,slots:{[a]:{name:a,loop:i.audioLoop||!1,autoPlay:!1,volume:void 0!==i.audioVolume?i.audioVolume:1,pitch:1}}}),n.audioSlotId=a;const r=new t.Asset(`mesh-audio-asset-${s}`,"audio",{url:i.audioUrl});F.assets.add(r),r.ready(()=>{const t=e.sound?.slot(a);t&&(t.asset=r.id),console.log("[CustomMesh] Audio loaded for mesh:",o.name,"Spatial:",i.audioSpatial)}),r.on("error",t=>{console.error("[CustomMesh] Failed to load mesh audio:",o.name,t)}),F.assets.load(r)}(n,e,l),e.interaction&&function(e,o,n){if(!function(t){const e=t.interaction;return!!e&&!!(e.triggerUIPopup||e.playAudio||e.playModelAnimation||e.triggerDirectLink)}(o))return void console.log("[CustomMesh] Skipping interaction setup - no active interactions for:",o.name);const i=o.interaction?.activationMode||"click",s=F.graphicsDevice.canvas,a=(o,n)=>{const i=s.getBoundingClientRect(),a=o-i.left,r=n-i.top,l=dt.camera.screenToWorld(a,r,dt.camera.nearClip),c=dt.camera.screenToWorld(a,r,dt.camera.farClip),p=(new t.Vec3).sub2(c,l).normalize(),d=e.modelEntity;if(d&&je(d,l,p))return!0;if(je(e,l,p))return!0;const h=e.getPosition(),u=(new t.Vec3).sub2(h,l).dot(p);if(u>0){const o=(new t.Vec3).add2(l,p.clone().mulScalar(u)).distance(h),n=e.getLocalScale();if(o<1.5*Math.max(n.x,n.y,n.z))return!0}return!1},r=o.interaction?.popupTriggerMode||i,l=o.interaction?.audioTriggerMode||i,c=o.interaction?.animationTriggerMode||i,p=o.interaction?.directLinkTriggerMode||i;let d=!1;const h=()=>{if(s.style.cursor="pointer","hover"===r&&o.interaction?.triggerUIPopup&&Ye(o),"hover"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&!t.isPlaying&&(t.play(),n.audioPlaying=!0,console.log("[CustomMesh] Playing audio on hover for:",o.name))}if("hover"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&!n.isAnimPlaying&&(t.forEach(t=>Ge(t)),n.isAnimPlaying=!0,console.log("[CustomMesh] Playing animation on hover for:",o.name))}"hover"===p&&o.interaction?.triggerDirectLink&&Ze(o)},u=()=>{if(s.style.cursor="","hover"===r&&o.interaction?.triggerUIPopup&&function(){const t=document.querySelector(".storysplat-hotspot-popup");t&&t.remove();const e=document.querySelector(".storysplat-mesh-popup");e&&e.remove()}(),"hover"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&t.isPlaying&&(t.stop(),n.audioPlaying=!1,console.log("[CustomMesh] Stopped audio on hover out for:",o.name))}if("hover"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&n.isAnimPlaying&&(t.forEach(t=>Ne(t)),n.isAnimPlaying=!1,console.log("[CustomMesh] Paused animation on hover out for:",o.name))}},m=()=>{if(console.log("[CustomMesh] Clicked mesh:",o.name),"click"===r&&o.interaction?.triggerUIPopup&&Ye(o),"click"===c&&o.interaction?.playModelAnimation){const t=n.allAnimComponents;t&&t.length>0&&(n.isAnimPlaying?(t.forEach(t=>Ne(t)),n.isAnimPlaying=!1,console.log("[CustomMesh] Paused",t.length,"animations on click for:",o.name)):(t.forEach(t=>Ge(t)),n.isAnimPlaying=!0,console.log("[CustomMesh] Playing",t.length,"animations on click for:",o.name)))}if("click"===l&&o.interaction?.playAudio&&n.audioSlotId){const t=e.sound?.slot(n.audioSlotId);t&&(t.isPlaying?(t.pause(),n.audioPlaying=!1,console.log("[CustomMesh] Paused audio on click for:",o.name)):(t.play(),n.audioPlaying=!0,console.log("[CustomMesh] Playing audio on click for:",o.name)))}"click"===p&&o.interaction?.triggerDirectLink&&Ze(o)},y=t=>{a(t.clientX,t.clientY)&&m()},g=t=>{const e=a(t.clientX,t.clientY);e&&!d?(d=!0,h()):!e&&d&&(d=!1,u())},f=()=>{d&&(d=!1,u())};s.addEventListener("click",y),s.addEventListener("mousemove",g),s.addEventListener("mouseleave",f),e.meshClickHandler=y,e.meshHoverHandler=g,e.meshLeaveHandler=f}(n,e,l),console.log("[CustomMesh] Mesh fully initialized:",e.name)}catch(v){console.error("[CustomMesh] Error processing loaded mesh:",e.name,v)}}),a.on("error",t=>{console.error("[CustomMesh] Failed to load mesh:",e.name,t),F.assets.remove(a)}),F.assets.load(a),e.visibilityRange?(n.visibilityRange=e.visibilityRange,n.enabled=!1!==e.enabled):n.enabled=!1!==e.enabled,n.opacityConfig={mode:e.opacityMode||"static",value:void 0!==e.opacity?e.opacity:1},F.root.addChild(n),n}function je(e,o,n){const i=e.render;if(i&&i.meshInstances)for(const t of i.meshInstances)if(t.aabb){if(Xe(o,n,t.aabb))return!0}const s=e.model;if(s&&s.meshInstances)for(const t of s.meshInstances)if(t.aabb){if(Xe(o,n,t.aabb))return!0}const a=e.children;if(a)for(const e of a)if(e instanceof t.Entity&&je(e,o,n))return!0;return!1}function Xe(t,e,o){const n=o.getMin?o.getMin():o.min,i=o.getMax?o.getMax():o.max;if(!n||!i)return!1;let s=-1/0,a=1/0;const r=["x","y","z"],l=(t,e,o)=>{const n=t[e];if("number"==typeof n)return n;const i=t[`_${e}`];return"number"==typeof i?i:t.data?.[o]??0};for(let o=0;o<3;o++){const c=r[o],p=t[c],d=e[c],h=l(n,c,o),u=l(i,c,o);if(Math.abs(d)<1e-8){if(p<h||p>u)return!1}else{let t=(h-p)/d,e=(u-p)/d;if(t>e&&([t,e]=[e,t]),s=Math.max(s,t),a=Math.min(a,e),s>a)return!1}}return a>=0}function Ye(t){if(!t.interaction)return;const e={id:`mesh-content-${t.id}`,title:t.interaction.title||t.name,information:t.interaction.information,photoUrl:t.interaction.photoUrl,iframeUrl:t.interaction.iframeUrl,externalLinkUrl:t.interaction.externalLinkUrl,externalLinkText:t.interaction.externalLinkText,backgroundColor:t.interaction.backgroundColor||"#000000",textColor:t.interaction.textColor||"#ffffff"};D.showHotspotPopup?D.showHotspotPopup(e):function(t){const e=document.querySelector(".storysplat-mesh-popup");e&&e.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: 100001;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ";const n=document.createElement("h2");if(n.textContent=t.name||"Custom Mesh",n.style.cssText="margin-top: 0; margin-bottom: 16px;",o.appendChild(n),t.interaction?.popupContent){const e=document.createElement("p");e.textContent=t.interaction.popupContent,e.style.cssText="margin: 0; line-height: 1.6;",o.appendChild(e)}const i=document.createElement("button");i.textContent=`× ${l(M,"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)}(t)}function Ze(t){t.interaction?.triggerDirectLink&&t.interaction.directLinkUrl&&window.open(t.interaction.directLinkUrl,"_blank")}function Je(e,o){!function e(n){n.render&&n.render.meshInstances&&n.render.meshInstances.forEach(e=>{if(e.material){if(!e.material._isCloned){const t=e.material.clone();t._isCloned=!0,e.material=t}e.material.opacity=o,e.material.blendType=t.BLEND_PREMULTIPLIED,e.material.depthTest=!0,e.material.depthWrite=!0,e.material.alphaTest=.01,e.material.update()}}),n.children.forEach(o=>{o instanceof t.Entity&&e(o)})}(e)}function Qe(){const t=F.graphicsDevice.canvas;We.forEach(e=>{const o=e.entity,n=o.meshClickHandler;n&&t.removeEventListener("click",n),o.destroy()}),We.clear()}i.on("progressUpdate",()=>{!function(){const t=100*Wt,e=v.waypoints?.length||1,o=Math.round(Wt*Math.max(1,e-1));We.forEach(e=>{const{entity:n,config:i}=e,s=n.visibilityRange;if(s){let e=!0;"waypoint"===s.type?e=o>=s.start&&o<=s.end:"percentage"===s.type&&(e=t>=s.start&&t<=s.end),n.enabled=e}if("animated"===i.opacityMode&&i.opacityAnimation){const e=i.opacityAnimation;if(t>=e.startPercent&&t<=e.endPercent){const o=(t-e.startPercent)/(e.endPercent-e.startPercent);Je(n,e.startOpacity+(e.endOpacity-e.startOpacity)*o)}}if(i.billboard&&i.billboardRange){const e=i.billboardRange;let s=!1;"percentage"===e.type?s=t>=e.start&&t<=e.end:"waypoint"===e.type&&(s=o>=e.start&&o<=e.end),n._billboardActive=s}else i.billboard&&(n._billboardActive=!0)})}()});let Ke=null,to=null;function eo(){const e=v.skybox?.url||v.skyboxUrl;if(!e)return void console.log("[StorySplat Viewer] No skybox configured");const o=v.skybox?.rotation??v.skyboxRotation??0,n=v.skybox?.intensity??1,i=v.skybox?.enableIBL??!0;console.log("[StorySplat Viewer] Creating skybox:",e,"rotation:",o,"rad =",o*(180/Math.PI),"deg","IBL:",i);const s=e.toLowerCase().includes(".hdr")||e.toLowerCase().includes(".exr");Ke&&(Ke.destroy(),Ke=null),to&&(F.off("update",to),to=null);const a=new t.Entity("skybox"),r=new t.StandardMaterial;r.useLighting=!1,r.cull=t.CULLFACE_BACK,r.depthTest=!1,r.depthWrite=!1;const l=new t.Asset(`skybox-texture-${Date.now()}`,"texture",{url:e},s?{type:t.TEXTURETYPE_RGBM,mipmaps:!0}:{mipmaps:!0});if(F.assets.add(l),l.ready(e=>{const o=e.resource;try{o.minFilter=t.FILTER_LINEAR_MIPMAP_LINEAR,o.magFilter=t.FILTER_LINEAR}catch{}if(r.emissiveMap=o,r.emissive=new t.Color(n,n,n),r.update(),console.log("[StorySplat Viewer] Skybox texture loaded"),i)try{s&&(F.scene.exposure=n,F.scene.toneMapping=t.TONEMAP_ACES,console.log("[StorySplat Viewer] HDR tone mapping enabled")),o&&(F.scene.ambientLight=new t.Color(.3*n,.3*n,.35*n),console.log("[StorySplat Viewer] IBL ambient lighting applied with intensity:",n))}catch(t){console.warn("[StorySplat Viewer] Failed to apply IBL:",t)}}),l.on("error",t=>{console.error("[StorySplat Viewer] Failed to load skybox texture:",t)}),F.assets.load(l),a.addComponent("render",{type:"sphere",material:r,castShadows:!1,receiveShadows:!1}),a.render&&a.render.meshInstances.length>0&&(a.render.meshInstances[0].drawOrder=-1e4),a.setLocalScale(-500,500,500),0!==o){const t=o*(180/Math.PI);a.setEulerAngles(0,t,0)}to=()=>{const t=dt.getPosition();a.setPosition(t.x,t.y,t.z)},F.on("update",to),F.root.addChild(a),Ke=a,console.log("[StorySplat Viewer] Skybox created with rotation:",o,"intensity:",n)}const oo=[];function no(e){const o=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return o?new t.Color(parseInt(o[1],16)/255,parseInt(o[2],16)/255,parseInt(o[3],16)/255):new t.Color(1,1,1)}function io(e){const o=new t.Entity(e.name||"Point Light");o.addComponent("light",{type:"point",color:no(e.color||"#ffffff"),intensity:e.intensity||1,range:e.range||10,castShadows:e.castShadows||!1});const n=e.position;return n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0)),o.enabled=!1!==e.enabled,o}function so(e){const o=new t.Entity(e.name||"Directional Light");o.addComponent("light",{type:"directional",color:no(e.color||"#ffffff"),intensity:e.intensity||1,castShadows:e.castShadows||!1});const n=e.position;n&&o.setPosition(n._x??n.x??0,n._y??n.y??0,-(n._z??n.z??0));const i=e.rotation;return i?o.setRotation(fo(i._x??i.x??0,i._y??i.y??0,i._z??i.z??0)):o.setEulerAngles(45,0,0),o.enabled=!1!==e.enabled,o}function ao(e){const o=new t.Entity(e.name||"Hemispheric Light");o.addComponent("light",{type:"directional",color:no(e.color||"#ffffff"),intensity:e.intensity||1,castShadows:!1});const n=e.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!==e.enabled,e.groundColor){const o=no(e.groundColor),n=no(e.color||"#ffffff");F.scene.ambientLight=new t.Color((n.r+.3*o.r)/1.3,(n.g+.3*o.g)/1.3,(n.b+.3*o.b)/1.3)}return o}function ro(e){const o=no(e.color||"#404040"),n=e.intensity||.4;return F.scene.ambientLight=new t.Color(o.r*n,o.g*n,o.b*n),console.log("[StorySplat Viewer] Set ambient light:",e.name||"Ambient Light"),null}function lo(e){const o=new t.Entity(e.name||"Spot Light"),n=180/Math.PI,i=(e.angle??45*Math.PI/180)*n;e.exponent;const s=e.innerConeAngle??e.innerAngle??.8*i,a=e.outerConeAngle??e.outerAngle??i;o.addComponent("light",{type:"spot",color:no(e.color||"#ffffff"),intensity:e.intensity||1,range:e.range||10,innerConeAngle:s,outerConeAngle:a,castShadows:e.castShadows||!1,shadowBias:e.shadowBias??.05,normalOffsetBias:e.normalOffsetBias??.05});const r=e.position;r&&o.setPosition(r._x??r.x??0,r._y??r.y??0,-(r._z??r.z??0));const l=e.rotation;if(l)o.setRotation(fo(l._x??l.x??0,l._y??l.y??0,l._z??l.z??0));else if(e.direction){const n=e.direction,i=new t.Vec3(n._x??n.x??0,n._y??n.y??-1,-(n._z??n.z??0)).normalize();o.lookAt(o.getPosition().x+i.x,o.getPosition().y+i.y,o.getPosition().z+i.z)}else o.setEulerAngles(90,0,0);return o.enabled=!1!==e.enabled,o}function co(t){switch(t){case"linear":return"linear";case"inverse":return"inverse";default:return"exponential"}}const po=new Map;const ho=new Set;function uo(){Re||(ze.forEach(t=>{De.set(t,t.volume),t.volume=0}),Ie.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),po.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);e&&(e._storedVolume=e.volume,e.volume=0)}),document.querySelectorAll("audio, video").forEach(t=>{t.muted=!0}),Re=!0,console.log("[Audio] All audio muted"))}function mo(){Re&&(ze.forEach(t=>{const e=De.get(t);t.volume=void 0!==e?e:1}),Ie.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),po.forEach(t=>{const e=t.entity.sound?.slot(t.slotId);if(e){const t=e._storedVolume;e.volume=void 0!==t?t:1}}),document.querySelectorAll("audio, video").forEach(t=>{t.muted=!1}),Re=!1,console.log("[Audio] All audio unmuted"))}const yo=[];function go(e,o=!1,n=1,i){const s=new t.StandardMaterial;if(s.blendType=t.BLEND_PREMULTIPLIED,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.01,o?s.diffuse=new t.Color(1,1,1):(s.diffuse=new t.Color(0,0,0),s.emissive=new t.Color(1,1,1),s.specular=new t.Color(0,0,0),s.useLighting=!1),s.opacity=n,s.update(),function(t){const e=t.toLowerCase();return e.endsWith(".gif")||e.includes("image/gif")||e.includes("format=gif")}(e)){console.log(`[Hotspot] Loading animated GIF: ${e}`);const n=new q(F,e,{autoPlay:!0,onReady:()=>{if(N)return console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${e}`),void n.destroy();n.texture&&(n.texture.premultiplyAlpha=!0,o?(s.diffuseMap=n.texture,s.opacityMap=n.texture):(s.emissiveMap=n.texture,s.opacityMap=n.texture),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] GIF texture loaded: ${e}, useLighting=${o}`),i&&i())},onError:o=>{console.error(`[Hotspot] Failed to load GIF: ${e}`,o),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()}});yo.push(n)}else{const n=new Image;n.crossOrigin="anonymous",n.onload=()=>{if(N)return void console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${e}`);const a=new t.Texture(F.graphicsDevice,{width:n.width,height:n.height,format:t.PIXELFORMAT_RGBA8,mipmaps:!0,minFilter:t.FILTER_LINEAR_MIPMAP_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});a.setSource(n),a.premultiplyAlpha=!0,o?(s.diffuseMap=a,s.opacityMap=a):(s.emissiveMap=a,s.opacityMap=a),s.opacityMapChannel="a",s.update(),console.log(`[Hotspot] Texture loaded for: ${e}, useLighting=${o}`),i&&i()},n.onerror=o=>{console.error(`[Hotspot] Failed to load texture: ${e}`,o),s.opacity=.3,s.emissive=new t.Color(1,0,0),s.update(),i&&i()},n.src=e}return s}function fo(e,o,n){const i=o/2,s=e/2,a=n/2,r=Math.cos(i),l=Math.sin(i),c=Math.cos(s),p=Math.sin(s),d=Math.cos(a),h=Math.sin(a),u=r*c*d+l*p*h,m=r*p*d+l*c*h,y=l*c*d-r*p*h,g=r*c*h-l*p*d;return new t.Quat(-m,-y,g,u)}function vo(e,o,n,i){const s=(new t.Quat).setFromEulerAngles(90,0,0),a=fo(o,n,i);e.setRotation((new t.Quat).mul2(a,s))}function bo(e,o){const n=new t.Entity(`hotspot-${e.id||o}`),i=e.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 s=e.scale||{_x:1,_y:1,_z:1},a="number"==typeof s?{x:s,y:s,z:s}:s,r=Math.abs(a._x??a.x??1),l=Math.abs(a._y??a.y??1),c=a._z??a.z??1,p=e.rotation||{_x:0,_y:0,_z:0},d=p._x??p.x??0,h=p._y??p.y??0,u=p._z??p.z??0;if(n.setRotation(fo(d,h,u)),"sphere"===e.type){n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Pe(e.color||"#ffffff");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(o.opacity=1,o.blendType=t.BLEND_NONE,o.depthTest=!0,o.depthWrite=!0):(o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0),o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let t=1;"animated"===e.opacityMode&&e.opacityAnimation?t=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(t=e.opacity),n.targetOpacity=t,n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const o=!0===e.useLighting,i=go(e.imageUrl,o,t,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,c,l),vo(n,d,h,u),n.hotspotMaterial=i,console.log(`[Hotspot] Created image hotspot: ${e.title}, opacity=${t}, opacityMode=${e.opacityMode}, useLighting=${o}`)}else if("video"===e.type&&e.videoUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const i=/iPad|iPhone|iPod/.test(navigator.userAgent)&&e.useIOSVideoAlphaMethod||e.forceIOSVideoAlphaMethodForAllDevices,s=i&&e.iosMainVideoUrl?e.iosMainVideoUrl:e.videoUrl,a=i&&e.alphaMaskVideoUrl||null,p=(t=>{const e=t.toLowerCase();return e.endsWith(".webm")||e.includes("format=webm")||e.includes("video/webm")})(s)&&!1!==e.webmHasAlpha,m=(o,n,i=!1)=>{const s=document.createElement("video");s.src=o,s.loop=!1!==e.videoLoop,s.crossOrigin="anonymous",s.playsInline=!0,s.preload="metadata",s.muted=!!n||!1!==e.videoMuted,"autoplay"===e.mediaTriggerMode&&(s.autoplay=!0,s.muted=!0);const a=new t.Texture(F.graphicsDevice,{format:i?t.PIXELFORMAT_R8_G8_B8_A8:t.PIXELFORMAT_R8_G8_B8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});return a.setSource(s),{video:s,texture:a}},y=m(s,!1,p),g=y.video,f=y.texture;if(e.videoBackupUrl){const t=e.videoBackupUrl;g.onerror=()=>{console.log(`[Hotspot] Main video failed, trying backup: ${t}`),g.src=t,g.load()}}const v=new t.StandardMaterial;v.useLighting=!1,v.emissiveMap=f,v.emissive=new t.Color(1,1,1),v.diffuse=new t.Color(0,0,0),v.depthTest=!0,v.depthWrite=!0,v.cull=t.CULLFACE_NONE,v.twoSidedLighting=!0,v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,p&&(v.opacityMap=f,v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,console.log(`[Hotspot] WebM video with alpha enabled: ${e.title}`));let b=null,x=null;if(a){const o=m(a,!0,!1);b=o.video,x=o.texture,v.opacityMap=x,v.opacityMapChannel="r",v.blendType=t.BLEND_PREMULTIPLIED,v.alphaTest=.01,g.addEventListener("play",()=>{b&&b.paused&&(b.currentTime=g.currentTime,b.play().catch(console.warn))}),g.addEventListener("pause",()=>{b&&!b.paused&&b.pause()}),g.addEventListener("seeked",()=>{b&&(b.currentTime=g.currentTime)}),console.log(`[Hotspot] iOS alpha mask video enabled: ${e.title}, alphaUrl=${a.substring(0,50)}...`)}v.update(),F.on("update",()=>{g.readyState===g.HAVE_ENOUGH_DATA&&f.upload(),b&&x&&b.readyState===b.HAVE_ENOUGH_DATA&&x.upload()});const w={x:r,y:l,z:c};if(g.addEventListener("loadedmetadata",()=>{const t=g.videoWidth,e=g.videoHeight;if(t>0&&e>0){const o=t/e;1===w.x&&1===w.y&&n.setLocalScale(o*w.y,w.z,w.y)}}),n.render.material=v,n.setLocalScale(r,c,l),vo(n,d,h,u),n.videoElement=g,n.alphaVideoElement=b,n.hotspotMaterial=v,n.mediaTriggerMode=e.mediaTriggerMode||"click",n.proximityDistance=e.proximityDistance||5,n.isVideoPlaying=!1,"click"!==e.mediaTriggerMode&&e.mediaTriggerMode||(g.addEventListener("loadeddata",()=>{g.currentTime=0,f.upload(),b&&x&&(b.currentTime=0,x.upload()),console.log(`[Hotspot] First frame loaded for: ${e.title}`)},{once:!0}),g.load(),b&&b.load()),!0!==e.videoMuted){const t=function(t,e,o){if(!o.videoSpatialAudio&&!1!==o.videoMuted)return null;try{const n=new(window.AudioContext||window.webkitAudioContext);Te.push(n);const i=n.createMediaElementSource(e),s=n.createPanner();s.panningModel="HRTF",s.distanceModel=o.videoDistanceModel||"linear",s.refDistance=void 0!==o.videoRefDistance?o.videoRefDistance:1,s.maxDistance=void 0!==o.videoMaxDistance?o.videoMaxDistance:100,s.rolloffFactor=void 0!==o.videoRolloffFactor?o.videoRolloffFactor:1;const a=t.getPosition();return s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(n.destination),F.on("update",()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),dt&&dt.getPosition){const t=dt.getPosition(),e=dt.forward,o=dt.up;n.listener.positionX?(n.listener.positionX.value=t.x,n.listener.positionY.value=t.y,n.listener.positionZ.value=t.z,n.listener.forwardX.value=e.x,n.listener.forwardY.value=e.y,n.listener.forwardZ.value=e.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(t.x,t.y,t.z),n.listener.setOrientation(e.x,e.y,e.z,o.x,o.y,o.z))}}),console.log(`[Audio] Video spatial audio setup for hotspot: ${o.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audioCtx:n,source:i,panner:s}}catch(t){return console.warn("[Audio] Failed to setup video spatial audio:",t),null}}(n,g,e);t&&(n.videoSpatialAudio=t)}if("click"===e.mediaTriggerMode||!e.mediaTriggerMode||"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted){const i=new t.Entity("video-overlay-"+(e.id||o));i.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1}),i.setLocalPosition(0,0,-.01),i.setLocalScale(1,1,1);const s=document.createElement("canvas");s.width=512,s.height=512;const a=s.getContext("2d");a.clearRect(0,0,512,512),a.fillStyle="rgba(0, 0, 0, 0.4)",a.fillRect(0,0,512,512),a.fillStyle="rgba(255, 255, 255, 0.9)",a.beginPath(),a.moveTo(220,180),a.lineTo(220,300),a.lineTo(320,240),a.closePath(),a.fill(),a.font="bold 36px sans-serif",a.textAlign="center",a.textBaseline="middle",a.shadowColor="rgba(0, 0, 0, 0.8)",a.shadowBlur=8,a.shadowOffsetX=2,a.shadowOffsetY=2,a.fillStyle="white";const r="autoplay"===e.mediaTriggerMode&&!1===e.videoMuted?"Tap for Audio":"Tap to Start";a.fillText(r,256,350);const l=new t.Texture(F.graphicsDevice,{format:t.PIXELFORMAT_R8_G8_B8_A8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});l.setSource(s);const c=new t.StandardMaterial;c.useLighting=!1,c.emissive=new t.Color(1,1,1),c.emissiveMap=l,c.diffuse=new t.Color(0,0,0),c.opacityMap=l,c.blendType=t.BLEND_PREMULTIPLIED,c.alphaTest=.01,c.depthTest=!0,c.depthWrite=!1,c.cull=t.CULLFACE_NONE,c.update(),i.render.material=c,n.addChild(i);const p=()=>{"autoplay"===e.mediaTriggerMode&&!1===e.videoMuted?i.enabled=g.muted:i.enabled=g.paused};g.addEventListener("play",p),g.addEventListener("pause",p),g.addEventListener("volumechange",p),i.enabled=!0,n.videoOverlay=i,console.log(`[Hotspot] Created tap-to-play overlay for: ${e.title}`)}console.log(`[Hotspot] Created video hotspot: ${e.title}, mode=${e.mediaTriggerMode}, useAlpha=${!!a}, spatialAudio=${!!n.videoSpatialAudio}`)}else if("gif"===e.type&&e.gifUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});let o=1;"animated"===e.opacityMode&&e.opacityAnimation?o=void 0!==e.opacityAnimation.startOpacity?e.opacityAnimation.startOpacity:1:void 0!==e.opacity&&(o=e.opacity),n.targetOpacity=o,n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const i=!0===e.useLighting,s=new t.StandardMaterial;s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=o,s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.twoSidedLighting=!0,s.alphaTest=.01,i||(s.emissive=new t.Color(1,1,1),s.diffuse=new t.Color(0,0,0));const a=async a=>{try{const p=document.createElement("canvas"),m=p.getContext("2d"),y=new Image;y.crossOrigin="anonymous",y.onload=()=>{p.width=y.width||256,p.height=y.height||256,m.drawImage(y,0,0);const g=new t.Texture(F.graphicsDevice,{format:t.PIXELFORMAT_R8_G8_B8_A8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});g.setSource(p),s.diffuseMap=g,i||(s.emissiveMap=g),s.opacityMap=g,s.alphaTest=.01,s.update(),n.render.material=s;const f=p.width/p.height;1===r&&1===l?n.setLocalScale(f,c,1):n.setLocalScale(r,c,l),vo(n,d,h,u),n.textureLoaded=!0,n.gifCanvas=p,n.gifTexture=g,n.hotspotMaterial=s,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1,fetch(a).then(t=>t.arrayBuffer()).then(t=>{const e=()=>{n.enabled&&n.gifTexture&&(m.clearRect(0,0,p.width,p.height),m.drawImage(y,0,0),n.gifTexture?.upload(),requestAnimationFrame(e))};n.gifAnimationFrame=requestAnimationFrame(e)}).catch(t=>{console.warn("[Hotspot] GIF animation load failed, using static frame:",t)}),console.log(`[Hotspot] Created GIF hotspot: ${e.title}, opacity=${o}, useLighting=${i}`)},y.onerror=o=>{console.error("[Hotspot] Failed to load GIF:",e.gifUrl,o),n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const i=new t.StandardMaterial;i.diffuse=Pe(e.color||"#FF00FF"),i.opacity=.8,i.blendType=t.BLEND_NORMAL,i.update(),n.render.material=i,n.setLocalScale(.2*r,.2*l,.2*c),n.enabled=!0,n.hiddenUntilTextureLoaded=!1},y.src=a}catch(t){console.error("[Hotspot] GIF hotspot creation failed:",t)}};a(e.gifUrl)}else{n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Pe(e.color||"#4CAF50");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0,o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}n.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),n.hotspotData=e;const m=function(t,e){if(!e.audioUrl)return null;const o=document.createElement("audio");if(o.src=e.audioUrl,o.loop=e.audioLoop||!1,o.volume=void 0!==e.audioVolume?e.audioVolume:1,o.crossOrigin="anonymous",ze.push(o),e.audioSpatial){const n=new(window.AudioContext||window.webkitAudioContext);Te.push(n);const i=n.createMediaElementSource(o),s=n.createPanner();s.panningModel="HRTF",s.distanceModel=e.audioDistanceModel||"linear",s.refDistance=void 0!==e.audioRefDistance?e.audioRefDistance:1,s.maxDistance=void 0!==e.audioMaxDistance?e.audioMaxDistance:100,s.rolloffFactor=void 0!==e.audioRolloffFactor?e.audioRolloffFactor:1;const a=t.getPosition();s.setPosition(a.x,a.y,a.z),i.connect(s),s.connect(n.destination);const r=()=>{if(!t||!t.getPosition)return;const e=t.getPosition();if(s.setPosition(e.x,e.y,e.z),dt&&dt.getPosition){const t=dt.getPosition(),e=dt.forward,o=dt.up;n.listener.positionX?(n.listener.positionX.value=t.x,n.listener.positionY.value=t.y,n.listener.positionZ.value=t.z,n.listener.forwardX.value=e.x,n.listener.forwardY.value=e.y,n.listener.forwardZ.value=e.z,n.listener.upX.value=o.x,n.listener.upY.value=o.y,n.listener.upZ.value=o.z):(n.listener.setPosition(t.x,t.y,t.z),n.listener.setOrientation(e.x,e.y,e.z,o.x,o.y,o.z))}};return F.on("update",r),console.log(`[Audio] Spatial audio setup for hotspot: ${e.title}, refDist=${s.refDistance}, maxDist=${s.maxDistance}`),{audio:o,audioCtx:n,source:i,panner:s,updateAudioPosition:r}}return console.log(`[Audio] Non-spatial audio setup for hotspot: ${e.title}`),{audio:o}}(n,e);if(m&&(n.audioElements=m,console.log(`[StorySplat Viewer] Audio setup for hotspot: ${e.title||"Untitled"}`)),e.billboard){const t=n.getEulerAngles().clone(),o=void 0!==e.billboardRangeStart||void 0!==e.billboardRangeEnd;n._billboardActive=!o,n._billboardOriginalRotation=t,F.on("update",()=>{n._billboardActive&&(n.lookAt(dt.getPosition()),n.rotateLocal(90,0,0))})}return e.visibilityRange&&(n.visibilityRange=e.visibilityRange,n.enabled=!1),F.root.addChild(n),Me.push(n),console.log(`[StorySplat Viewer] Created hotspot: ${e.title||"Untitled"}`),n}function xo(){const t=100*Wt,e=v.waypoints?.length||1,o=Math.round(Wt*Math.max(1,e-1)),n=dt.getPosition();Me.forEach(e=>{const i=e.hotspotData;if(!i)return;let s=!0;if(i.alwaysVisible)s=!0;else{const n=e.visibilityRange;n&&("waypoint"===n.type?s=o>=n.start&&o<=n.end:"percentage"===n.type&&(s=t>=n.start&&t<=n.end))}if(e.shouldBeVisible=s,i.billboard&&(void 0!==i.billboardRangeStart||void 0!==i.billboardRangeEnd)){const o=i.billboardRangeStart??0,n=i.billboardRangeEnd??100,s=t>=o&&t<=n;if(e._billboardActive=s,!s&&e._billboardOriginalRotation){const t=e._billboardOriginalRotation;e.setEulerAngles(t.x,t.y,t.z)}}if(e.hiddenUntilTextureLoaded?e.enabled=!1:e.enabled=s,e.videoElement&&"video"===i.type){const o=e.mediaTriggerMode||"click";if("proximity"===o){const t=e.getPosition(),o=n.distance(t),s=e.proximityDistance||5;o<=s&&!e.isVideoPlaying?(wo(e,i),console.log(`[Hotspot] Proximity play: ${i.title}, distance=${o.toFixed(2)}`)):o>s&&e.isVideoPlaying&&(So(e),console.log(`[Hotspot] Proximity pause: ${i.title}, distance=${o.toFixed(2)}`))}"scroll"===o&&(s&&!e.isVideoPlaying?(wo(e,i),console.log(`[Hotspot] Scroll play: ${i.title}, scroll=${t.toFixed(1)}%`)):!s&&e.isVideoPlaying&&(So(e),console.log(`[Hotspot] Scroll pause: ${i.title}, scroll=${t.toFixed(1)}%`)))}if("animated"===i.opacityMode&&i.opacityAnimation){if("image"===i.type&&!e.textureLoaded)return;const o=i.opacityAnimation,n=o.startPercent??0,s=o.endPercent??100,a=void 0!==o.startOpacity?o.startOpacity:1,r=void 0!==o.endOpacity?o.endOpacity:1;let l;if(t<=n)l=a;else if(t>=s)l=r;else{l=a+(r-a)*((t-n)/(s-n))}if(l=Math.max(0,Math.min(1,l)),e.hotspotMaterial)e.hotspotMaterial.opacity=l,e.hotspotMaterial.update();else if(e.render&&e.render.material){const t=e.render.material;t.opacity=l,t.update?.()}}})}function wo(t,e){const o=t.videoElement,n=t.alphaVideoElement;if(o){if("autoplay"!==t.mediaTriggerMode&&(o.muted=!1!==e.videoMuted),t.videoSpatialAudio&&t.videoSpatialAudio.audioCtx){const e=t.videoSpatialAudio.audioCtx;"suspended"===e.state&&e.resume().then(()=>{console.log("[Audio] Video spatial audio context resumed")}).catch(t=>console.warn("[Audio] Failed to resume video audio context:",t))}o.play().catch(t=>console.warn("Video play failed:",t)),n&&n.play().catch(t=>console.warn("Alpha video play failed:",t)),t.isVideoPlaying=!0}}function So(t){const e=t.videoElement,o=t.alphaVideoElement;e&&(e.pause(),o&&o.pause(),t.isVideoPlaying=!1)}function Eo(e,o){const n=new t.Entity(`portal-${o}`),i=e.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 s=e.scale||{_x:1,_y:1,_z:1},a="number"==typeof s?{x:s,y:s,z:s}:s,r=Math.abs(a._x??a.x??1),l=Math.abs(a._y??a.y??1),c=a._z??a.z??1,p=e.rotation||{_x:0,_y:0,_z:0},d=p._x??p.x??0,h=p._y??p.y??0,u=p._z??p.z??0;if(n.setRotation(fo(d,h,u)),"sphere"===e.type){n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Pe(e.color||"#9C27B0");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;s>=.95?(o.opacity=1,o.blendType=t.BLEND_NONE,o.depthTest=!0,o.depthWrite=!0):(o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0),o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}else if("image"===e.type&&e.imageUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,o=e.opacity??1;n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1;const i=go(e.imageUrl,t,o,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,c,l),vo(n,d,h,u),n.portalMaterial=i,console.log(`[Portal] Created image portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("video"===e.type&&e.videoUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const o=document.createElement("video");o.src=e.videoUrl,o.loop=!0,o.muted=!0,o.crossOrigin="anonymous",o.playsInline=!0,o.autoplay=!0;const i=new t.Texture(F.graphicsDevice,{format:t.PIXELFORMAT_R8_G8_B8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR,addressU:t.ADDRESS_CLAMP_TO_EDGE,addressV:t.ADDRESS_CLAMP_TO_EDGE});i.setSource(o);const s=new t.StandardMaterial;s.useLighting=!1,s.emissiveMap=i,s.emissive=new t.Color(1,1,1),s.diffuse=new t.Color(0,0,0),s.depthTest=!0,s.depthWrite=!0,s.cull=t.CULLFACE_NONE,s.blendType=t.BLEND_PREMULTIPLIED,s.opacity=e.opacity??1,s.update(),n.render.material=s,n.setLocalScale(r,c,l),vo(n,d,h,u),F.on("update",()=>{o.readyState>=o.HAVE_CURRENT_DATA&&i.setSource(o)}),o.play().catch(t=>console.log("[Portal] Video autoplay blocked:",t)),n.videoElement=o,n.portalMaterial=s,console.log(`[Portal] Created video portal: ${e.title||e.targetSceneName||"Untitled"}`)}else if("gif"===e.type&&e.gifUrl){n.addComponent("render",{type:"plane",castShadows:!1,receiveShadows:!1});const t=!0===e.useLighting,o=e.opacity??1,i=go(e.gifUrl,t,o,()=>{n.textureLoaded=!0,n.visibilityRange&&!n.shouldBeVisible||(n.enabled=!0),n.hiddenUntilTextureLoaded=!1});n.render.material=i,n.setLocalScale(r,c,l),vo(n,d,h,u),n.textureLoaded=!1,n.hiddenUntilTextureLoaded=!0,n.enabled=!1,n.portalMaterial=i,console.log(`[Portal] Created GIF portal: ${e.title||e.targetSceneName||"Untitled"}`)}else{n.addComponent("render",{type:"sphere",castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial,i=Pe(e.color||"#9C27B0");o.diffuse=i,o.emissive=i.clone(),o.emissive.mulScalar(.5);const s=e.opacity??.8;o.opacity=s,o.blendType=t.BLEND_ADDITIVEALPHA,o.depthTest=!0,o.depthWrite=!0,o.update(),n.render.material=o,n.setLocalScale(.2*r,.2*l,.2*c)}return n.addComponent("collision",{type:"sphere"===e.type?"sphere":"box",radius:.1,halfExtents:new t.Vec3(.5,.5,.05)}),n.portalData=e,e.billboard&&F.on("update",()=>{n.enabled&&(n.lookAt(dt.getPosition()),n.rotateLocal(90,0,0))}),e.visibilityRange&&(n.visibilityRange=e.visibilityRange,n.enabled=!1),F.root.addChild(n),_e.push(n),console.log(`[StorySplat Viewer] Created portal: ${e.title||e.targetSceneName||"Untitled"} -> ${e.targetSceneId}`),n}function Co(){const t=100*Wt,e=v.waypoints?.length||1,o=Math.round(Wt*Math.max(1,e-1)),n=dt.getPosition();_e.forEach(e=>{const i=e.portalData;if(!i)return;let s=!0;const a=e.visibilityRange;if(a&&("waypoint"===a.type?s=o>=a.start&&o<=a.end:"percentage"===a.type&&(s=t>=a.start&&t<=a.end)),e.shouldBeVisible=s,e.hiddenUntilTextureLoaded?e.enabled=!1:e.enabled=s,"proximity"===i.activationMode&&s){const t=e.getPosition(),o=n.distance(t),s=i.proximityDistance||2;o<=s&&!e.proximityTriggered?(e.proximityTriggered=!0,console.log(`[Portal] Proximity triggered: ${i.title||i.targetSceneName}, navigating to scene ${i.targetSceneId}`),_o(i)):o>s&&(e.proximityTriggered=!1)}})}function Mo(e,o){const n=dt.camera.screenToWorld(e,o,dt.camera.nearClip),i=dt.camera.screenToWorld(e,o,dt.camera.farClip);let s=null;_e.forEach(e=>{if(!e.enabled)return;const o=e.getPosition(),a=(new t.Vec3).sub2(i,n).normalize(),r=(new t.Vec3).sub2(o,n).dot(a);if(r<0)return;const l=(new t.Vec3).add2(n,a.clone().mulScalar(r)).distance(o),c=e.getLocalScale();l<.6*Math.max(c.x,c.y,.3)&&(!s||r<s.distance)&&(s={entity:e,distance:r})});if(null!==s){const t=s.entity;return{entity:t,portal:t.portalData}}return null}async function _o(t){if(t.targetSceneId){console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${t.targetSceneId}`),i.emit("portalActivated",{portalId:t.id,targetSceneId:t.targetSceneId,targetSceneName:t.targetSceneName}),function(t,e){const o=t.querySelector(".storysplat-portal-loading");o&&o.remove();try{"static"===window.getComputedStyle(t).position&&(t.style.position="relative")}catch{}const n=document.createElement("div");n.className="storysplat-portal-loading";const i=document.createElement("div");i.className="storysplat-portal-spinner";const s=document.createElement("div");if(s.className="storysplat-portal-loading-text",s.textContent=l(M,"loadingScene").replace("{name}",e),n.appendChild(i),n.appendChild(s),Object.assign(n.style,{position:"absolute",inset:"0",background:"rgba(0, 0, 0, 0.85)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",zIndex:"100003",fontFamily:"system-ui, sans-serif"}),Object.assign(i.style,{width:"50px",height:"50px",border:"4px solid rgba(255, 255, 255, 0.2)",borderTop:`4px solid ${w}`,borderRadius:"50%",animation:"storysplat-portal-spin 1s linear infinite",marginBottom:"20px"}),Object.assign(s.style,{color:"#ffffff",fontSize:"18px"}),!document.getElementById("storysplat-portal-styles")){const t=document.createElement("style");t.id="storysplat-portal-styles",t.textContent="\n @keyframes storysplat-portal-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n ",document.head.appendChild(t)}t.appendChild(n)}(e,t.targetSceneName||t.title||"scene");try{const o=`https://discover.storysplat.com/api/scene/${t.targetSceneId}`;console.log(`[Portal] Fetching scene from: ${o}`);const n=await fetch(o);if(!n.ok)throw new Error(`Failed to fetch scene: ${n.status} ${n.statusText}`);const i=await n.json(),s=i.data||i;s.name&&function(t,e){const o=t.querySelector(".storysplat-portal-loading-text");o&&(o.textContent=l(M,"loadingScene").replace("{name}",e))}(e,s.name);!function(){console.log("[Portal] Cleaning up current scene for navigation..."),Ee(),Me.forEach(t=>{t.videoElement&&(t.videoElement.pause(),t.videoElement.src=""),t.alphaVideoElement&&(t.alphaVideoElement.pause(),t.alphaVideoElement.src="")}),yo.forEach(t=>t.destroy()),yo.length=0,Qe(),Me.forEach(t=>{t.destroy()}),Me.length=0,_e.forEach(t=>{t.destroy()}),_e.length=0,W&&(W.destroy(),W=null);const t=F.__htmlMeshManager;t&&t.destroy();const e=F.__customScriptSystem;e&&e.dispose();console.log("[Portal] Cleanup complete")}(),await new Promise(t=>setTimeout(t,100)),I&&I.parentNode&&I.remove();await et(e,s,{});return Lo(e),void console.log(`[Portal] Successfully navigated to scene: ${t.targetSceneId}`)}catch(t){console.error("[Portal] Navigation failed:",t),Lo(e);const o=document.createElement("div");o.className="storysplat-portal-error",o.textContent=`Failed to load scene: ${t.message}`,Object.assign(o.style,{position:"absolute",top:"50%",left:"50%",transform:"translate(-50%, -50%)",background:"rgba(0,0,0,0.9)",color:"#ff6b6b",padding:"20px",borderRadius:"8px",zIndex:"100004",fontFamily:"system-ui, sans-serif"}),e.appendChild(o),setTimeout(()=>o.remove(),3e3)}}else console.warn("[Portal] No target scene ID specified")}function Lo(t){const e=t.querySelector(".storysplat-portal-loading");e&&e.remove()}i.on("progressUpdate",()=>{xo()}),setTimeout(()=>{xo()},100),i.on("progressUpdate",()=>{Co()}),setTimeout(()=>{Co()},100);const ko=F.graphicsDevice.canvas;let Ao=!1,Po=!1,To=0,zo=0;ko.addEventListener("pointerdown",t=>{0===t.button&&(Po=!0,Ao=!1,To=t.clientX,zo=t.clientY)}),ko.addEventListener("pointermove",t=>{if(!Po)return;const e=t.clientX-To,o=t.clientY-zo;e*e+o*o>25&&(Ao=!0)});const Ro=()=>{Po=!1};function Do(e,o){const n=dt.camera.screenToWorld(e,o,dt.camera.nearClip),i=dt.camera.screenToWorld(e,o,dt.camera.farClip);let s=null;Me.forEach(e=>{if(!e.enabled)return;const o=e.getPosition(),a=(new t.Vec3).sub2(i,n).normalize(),r=(new t.Vec3).sub2(o,n).dot(a);if(r<0)return;const l=(new t.Vec3).add2(n,a.clone().mulScalar(r)).distance(o),c=e.getLocalScale();l<.6*Math.max(c.x,c.y,.3)&&(!s||r<s.distance)&&(s={entity:e,distance:r})});if(null!==s){const t=s.entity;return{entity:t,hotspot:t.hotspotData}}return null}ko.addEventListener("pointerup",Ro),ko.addEventListener("pointercancel",Ro);let Io=null,Fo=!1;const Uo=e.querySelector(".storysplat-hotspot-popup"),$o=e.querySelector(".storysplat-hotspot-overlay");async function Vo(t,e){if("explore"!==vt)return;console.log("[StorySplat Viewer] Double-click focus at:",t,e);const o=Do(t,e);if(o){const t=o.entity.getPosition();return console.log("[StorySplat Viewer] Focusing on hotspot at:",t.x,t.y,t.z),void("fly"===gt.mode?gt.flyTo(t):gt.focus(t,!1))}try{const o=.25;xt.resize(Math.floor(ko.clientWidth*o),Math.floor(ko.clientHeight*o));const n=F.scene.layers.getLayerByName("World");if(!n)return void console.warn("[StorySplat Viewer] World layer not found");xt.prepare(dt.camera,F.scene,[n]);const i=Math.floor(t*o),s=Math.floor(e*o),a=await xt.getWorldPointAsync(i,s);if(a){const t=dt.getPosition().distance(a);if(t>.1&&t<1e3)return console.log("[StorySplat Viewer] Focusing on GSplat at:",a.x.toFixed(2),a.y.toFixed(2),a.z.toFixed(2),"distance:",t.toFixed(2)),void("fly"===gt.mode?gt.flyTo(a):gt.focus(a,!1))}const r=await xt.getSelectionAsync(i,s,1,1);if(r.length>0){const t=r[0];if(!t.aabb)return void console.log("[StorySplat Viewer] No AABB available for picked object");const e=t.aabb.center.clone();console.log("[StorySplat Viewer] Focusing on mesh AABB center:",e.x.toFixed(2),e.y.toFixed(2),e.z.toFixed(2)),"fly"===gt.mode?gt.flyTo(e):gt.focus(e,!1)}else console.log("[StorySplat Viewer] No pick result at click point")}catch(t){console.warn("[StorySplat Viewer] Picking failed:",t)}}Uo&&(Uo.addEventListener("mouseenter",()=>{Fo=!0}),Uo.addEventListener("mouseleave",()=>{Fo=!1,Io&&"hover"===Io.activationMode&&(Uo.classList.remove("visible"),$o&&$o.classList.remove("visible"),Io=null)})),ko.addEventListener("mousemove",t=>{const o=ko.getBoundingClientRect(),n=t.clientX-o.left,i=t.clientY-o.top,s=Mo(n,i);if(s&&s.portal){const t=s.portal.activationMode||"click";return void(ko.style.cursor="click"===t?"pointer":"default")}const a=Do(n,i);if(a&&a.hotspot){const t=a.hotspot;"click"===t.activationMode||"hover"===t.activationMode||"video"===t.type?ko.style.cursor="pointer":ko.style.cursor="default","hover"===t.activationMode&&Io!==t&&(Io=t,(t.information||t.photoUrl||t.iframeUrl||t.externalLinkUrl)&&u(e,t,M))}else if(ko.style.cursor="default",Io&&"hover"===Io.activationMode&&!Fo){const t=e.querySelector(".storysplat-hotspot-popup"),o=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),o&&o.classList.remove("visible"),Io=null}}),ko.addEventListener("click",o=>{if(Ao)return void(Ao=!1);const n=ko.getBoundingClientRect(),i=o.clientX-n.left,s=o.clientY-n.top,a=Mo(i,s);if(null!==a&&a.portal){const t=a.portal;console.log("[StorySplat Viewer] Portal clicked:",t.title||t.targetSceneName);if("click"===(t.activationMode||"click")){!1!==t.confirmNavigation&&(t.title||t.targetSceneName||t.targetSceneId)&&Le?(t=>{if(!Le)return;const e=Le.querySelector(".storysplat-portal-popup-title");e&&(t.title?e.textContent=t.title:t.targetSceneName?e.textContent=`${l(M,"switchScenes").replace("?","")} ${t.targetSceneName}?`:e.textContent=l(M,"switchScenes")),ke=t,Le.classList.add("visible")})(t):_o(t)}return}const r=Do(i,s);if(null!==r){const o=r.entity,n=r.hotspot;if(console.log("[StorySplat Viewer] Hotspot clicked:",n.title),o.audioElements&&o.audioElements.audio){const t=o.audioElements,e=t.audio;e.paused?(t.audioCtx&&"suspended"===t.audioCtx.state&&t.audioCtx.resume(),e.play().catch(t=>console.error("[Audio] Hotspot audio play failed:",t)),console.log("[Audio] Hotspot audio started:",n.title)):(e.pause(),console.log("[Audio] Hotspot audio paused:",n.title))}if(o.videoElement&&("click"===o.mediaTriggerMode||"autoplay"===o.mediaTriggerMode)){const t=o.videoElement;o.alphaVideoElement,"autoplay"===o.mediaTriggerMode&&!t.paused&&t.muted?(t.muted=(n.videoMuted,!1),console.log("[Hotspot] Video unmuted by click")):t.paused?(wo(o,n),console.log("[Hotspot] Video started")):(So(o),console.log("[Hotspot] Video paused"))}const i=n.activationMode||"click",s=n.title||n.information||n.photoUrl||n.iframeUrl||n.externalLinkUrl;"click"===i&&s&&(wt?Mt?_t():function(e){if(!wt)return;Et||(Et=new t.Entity("arContentPlane"),Et.addComponent("render",{type:"plane"}),Et.setLocalScale(.8,1,.45),F.root.addChild(Et));const o=512,n=288,i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d"),a=e.backgroundColor||"rgba(20, 20, 20, 0.95)";s.fillStyle=a,s.fillRect(0,0,o,n),s.strokeStyle="rgba(255, 255, 255, 0.3)",s.lineWidth=2,s.strokeRect(1,1,510,286);const r=e.textColor||"#ffffff";let l=35;e.title&&(s.fillStyle=r,s.font="bold 28px Arial, sans-serif",s.fillText(e.title,20,l),l+=40),e.information&&(s.fillStyle=r,s.font="18px Arial, sans-serif",l=function(t,e,o,n,i,s){const a=e.split(" ");let r="",l=n;for(const e of a){const n=r+e+" ";t.measureText(n).width>i&&r?(t.fillText(r.trim(),o,l),r=e+" ",l+=s):r=n}return t.fillText(r.trim(),o,l),l+s}(s,e.information,20,l,472,24)),s.fillStyle="rgba(255, 255, 255, 0.5)",s.font="14px Arial, sans-serif",s.textAlign="center",s.fillText("Tap to close",256,273),s.textAlign="left",Ct||(Ct=new t.Texture(F.graphicsDevice,{width:o,height:n,format:t.PIXELFORMAT_RGBA8,mipmaps:!1,minFilter:t.FILTER_LINEAR,magFilter:t.FILTER_LINEAR})),Ct.setSource(i);const c=new t.StandardMaterial;c.diffuse=new t.Color(0,0,0),c.diffuseMap=Ct,c.emissive=new t.Color(1,1,1),c.emissiveMap=Ct,c.useLighting=!1,c.blendType=t.BLEND_NORMAL,c.cull=t.CULLFACE_NONE,c.depthWrite=!0,c.update(),Et.render&&Et.render.meshInstances[0]&&(Et.render.meshInstances[0].material=c),Et.enabled=!0,Mt=!0,console.log("[StorySplat Viewer] AR content displayed for hotspot:",e.title)}(n):u(e,n,M));const a=n.teleportWaypoint??n.teleportToWaypoint,l=n.teleportPercent??n.teleportToPercent,c=n.teleportMode||"animate";let p=null;if(void 0!==a&&-1!==a){const t=v.waypoints?.length||1,e=Math.max(0,Math.min(a,t-1));p=t>1?e/(t-1):0,console.log("[Hotspot] Teleporting to waypoint:",e,"(progress:",p,", mode:",c,")")}else void 0!==l&&-1!==l&&(p=Math.max(0,Math.min(l/100,1)),console.log("[Hotspot] Teleporting to percent:",l,"(progress:",p,", mode:",c,")"));null!==p&&("instant"===c?(Wt=p,Gt=p,he(Wt)):(Gt=p,ye(p,800)))}}),ko.addEventListener("dblclick",t=>{const e=ko.getBoundingClientRect();Vo(t.clientX-e.left,t.clientY-e.top)});let Bo=0;ko.addEventListener("touchend",t=>{if(1!==t.changedTouches.length)return;const e=Date.now();if(e-Bo<300){const e=t.changedTouches[0],o=ko.getBoundingClientRect();Vo(e.clientX-o.left,e.clientY-o.top),Bo=0}else Bo=e}),Tt(.2,"Initializing...");const Oo=o.frameSequence&&o.frameSequence.frameUrls&&o.frameSequence.frameUrls.length>0?async function(){if(!o.frameSequence||!o.frameSequence.frameUrls||0===o.frameSequence.frameUrls.length)throw new Error("No frame sequence URLs provided");console.log("[StorySplat Viewer] Loading 4DGS frame sequence:",o.frameSequence.frameUrls.length,"frames"),Tt(.3,"Loading 4DGS frames..."),j=new Z(F,{frameUrls:o.frameSequence.frameUrls,fps:o.frameSequence.fps||24,loop:!1!==o.frameSequence.loop,preloadCount:o.frameSequence.preloadCount||5,autoplay:o.frameSequence.autoplay||!1},{onFrameChange:(t,e)=>{i.emit("frameChange",t,e)},onLoadProgress:(t,e)=>{Tt(.3+t/e*.6,`Loading frames... ${t}/${e}`)},onError:t=>{console.error("[StorySplat Viewer] Frame sequence error:",t),i.emit("error",new Error(t))}}),j.on("complete",()=>{i.emit("frameComplete")}),F.on("update",t=>{j&&!N&&j.update(t)}),console.log("[StorySplat Viewer] 4DGS frame sequence player initialized"),i.emit("loaded",{bandwidthUsed:0,isStorySplatHosted:!1})}:async function(){let e=0,s="";const a=[];console.log("[SPLAT] Building URL priority list..."),console.log("[SPLAT] Available URLs:",{lodMetaUrl:v.lodMetaUrl||"(none)",sogUrl:v.sogUrl||"(none)",splatUrl:v.splatUrl||"(none)",fallbackUrls:v.fallbackUrls?.length||0}),v.lodMetaUrl&&(a.push(v.lodMetaUrl),console.log("[SPLAT] ✓ LOD streaming URL added (highest priority):",v.lodMetaUrl)),v.sogUrl&&(a.push(v.sogUrl),console.log("[SPLAT] ✓ SOG URL added (second priority):",v.sogUrl)),v.splatUrl&&(a.push(v.splatUrl),console.log("[SPLAT] ✓ Original splat URL added (third priority):",v.splatUrl)),v.fallbackUrls&&(a.push(...v.fallbackUrls),console.log("[SPLAT] ✓ Fallback URLs added:",v.fallbackUrls)),console.log("[SPLAT] Final URL priority order:",a),console.log("[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other"),Tt(.3,l(M,"loading"));for(const r of a)if(r)try{const a=r.split(".").pop()?.toLowerCase()||"splat",c="gsplat",p=r.includes("lod-meta.json"),d=r.includes(".sog")||p;console.log("[SPLAT] Attempting to load URL:",r),console.log("[SPLAT] Format detection:",{extension:a,isLodStreaming:p,isSogFormat:d,assetType:c});const h=new t.Asset("splat-"+Date.now(),c,{url:r});h.on("progress",(t,o)=>{if(o>0){zt(r)&&(e=t);const n=.3+t/o*.6,i=Math.round(t/o*100);i%25!=0&&100!==i||console.log(`[SPLAT] Loading progress: ${i}% (${(t/1024/1024).toFixed(2)}MB / ${(o/1024/1024).toFixed(2)}MB)`),Tt(n,`${l(M,"loading")} ${i}%`)}}),s=r,await new Promise((e,s)=>{let a=!1;const l=d?t=>{if(a)return;const e=t.reason?.message||String(t.reason);(e.includes("shape")||e.includes("upgradeMeta"))&&(console.warn("[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):",e),a=!0,t.preventDefault(),i.emit("warning",{type:"deprecated_format",message:"This scene uses an outdated SOG format. Please re-upload your scene with the latest tools for better performance.",details:"SOG v1 format is deprecated. Use splat-transform v2+ to convert your PLY files.",url:r}),console.log("[SPLAT] Will try fallback URL if available..."),s(new Error(`SOG parse error: ${e}`)))}:null;d&&l&&(console.log("[SPLAT] Registered error handler for SOG format parsing"),window.addEventListener("unhandledrejection",l));const u=()=>{d&&l&&window.removeEventListener("unhandledrejection",l)};if(h.ready(()=>{if(N)return console.log("[SPLAT] Ignoring load - viewer was destroyed during loading"),u(),void s(new Error("Viewer destroyed"));console.log("[SPLAT] ✓ Asset loaded and resources ready");try{W=new t.Entity("splat"),W.addComponent("gsplat",{asset:h,unified:!0});const i=W.gsplat;i&&tt(r)?(i.lodDistances=[...B.lodDistances],console.log("[SPLAT] ✓ LOD streaming enabled for this splat"),console.log("[SPLAT] LOD distances configured:",{distances:B.lodDistances,description:"Quality levels switch at these camera distances",preset:V})):d?console.log("[SPLAT] ✓ Single SOG file loaded (no LOD streaming)"):console.log("[SPLAT] Standard format loaded (PLY/SPLAT)");const s=v.scale||{x:1,y:1,z:1},l=v.invertXScale||!1,c=v.invertYScale||!1,p={x:l?-s.x:s.x,y:c?-s.y:s.y,z:l!==c?-s.z:s.z};W.setLocalScale(p.x,p.y,p.z);const m=v.position||[0,0,0];W.setPosition(m[0],m[1],-m[2]);const y=v.rotation||[0,0,0],g=[180+y[0]*(180/Math.PI),y[1]*(180/Math.PI),-y[2]*(180/Math.PI)];W.setEulerAngles(g[0],g[1],g[2]),console.log("[StorySplat Viewer] Splat transform applied:",{scale:p,position:[m[0],m[1],-m[2]],rotation:g,userRotation:y,invertXScale:l,invertYScale:c}),F.root.addChild(W),console.log("[StorySplat Viewer] Splat entity added to scene"),W.gsplat?.material&&(W.gsplat.material.setParameter("alphaClip",.01),console.log("[StorySplat Viewer] GSplat alphaClip set for picking support"));const f=n.revealEffect||o.revealEffect||"medium",b=P(f);if(b){W.addComponent("script");const t=k();G=W.script?.create?.(t)??null,G&&(G.enabled=!1,G.center.set(0,0,0),G.speed=b.speed,G.acceleration=b.acceleration,G.delay=b.delay,G.oscillationIntensity=b.oscillationIntensity,G.dotTint.set(b.dotTint.r,b.dotTint.g,b.dotTint.b),G.waveTint.set(b.waveTint.r,b.waveTint.g,b.waveTint.b),G.endRadius=b.endRadius,console.log("[StorySplat Viewer] Reveal effect configured (waiting to start):",f))}else console.log("[StorySplat Viewer] Reveal effect disabled");setTimeout(()=>{a||(u(),e())},100)}catch(t){console.error("[StorySplat Viewer] Error during gsplat setup:",t),u(),s(t)}}),h.on("error",t=>{const e=t;console.error("[SPLAT] ✗ Asset load error:",{url:r,assetType:c,isSogFormat:d,isLodFormat:p,error:t,message:e?.message||"Unknown error",status:e?.status||e?.statusCode||"N/A"}),u(),s(t)}),F.assets.add(h),p){const t=F.loader.getHandler("gsplat"),e=t?.parsers?.octree;if(e){console.log("[SPLAT] Using direct octree parser for LOD URL (bypassing basename routing)");const t={load:r,original:r};e.load(t,(t,e)=>{if(t)return console.error("[SPLAT] Octree parser error:",t),void h.fire("error",t);h.resource=e,h.loaded=!0,h.fire("load",h)},h)}else console.warn("[SPLAT] Could not access octree parser, falling back to standard load"),F.assets.load(h)}else F.assets.load(h)});const u=zt(s);return i.emit("loaded",{bandwidthUsed:u?e:0,isStorySplatHosted:u}),console.log("[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓"),void console.log("[SPLAT] Load summary:",{url:s,format:p?"LOD streaming (lod-meta.json)":d?"SOG (compressed)":"Standard (PLY/SPLAT)",lodEnabled:p,lodPreset:p?V:"N/A",bandwidthUsed:`${(e/1024/1024).toFixed(2)}MB`,isStorySplatHosted:u,bandwidthCounted:u?"Yes":"No (self-hosted)"})}catch(t){console.warn("[SPLAT] ✗ Failed to load URL, trying next fallback:",r),console.warn("[SPLAT] Error:",t)}console.error("[SPLAT] ✗✗✗ FAILED TO LOAD SPLAT FROM ANY URL ✗✗✗"),console.error("[SPLAT] Tried URLs:",a),i.emit("error",new Error("Failed to load splat from any URL"))};Oo().then(()=>{if(Tt(1,"Ready!"),v.hotspots&&0!==v.hotspots.length?(console.log(`[StorySplat Viewer] Creating ${v.hotspots.length} hotspots...`),v.hotspots.forEach((t,e)=>{bo(t,e)})):console.log("[StorySplat Viewer] No hotspots to create"),v.portals&&0!==v.portals.length?(console.log(`[StorySplat Viewer] Creating ${v.portals.length} portals...`),v.portals.forEach((t,e)=>{Eo(t,e)})):console.log("[StorySplat Viewer] No portals to create"),v.waypoints&&0!==v.waypoints.length&&(v.waypoints.forEach((e,o)=>{e.interactions&&Array.isArray(e.interactions)&&e.interactions.forEach(n=>{if("audio"===n.type&&n.data){const i=n.data;if(!i.url)return void console.warn(`[Audio] Waypoint ${o} audio interaction has no URL, skipping`);const s=String(n.id||`audio-${o}`),a=new t.Entity(`waypoint-audio-${s}`),r=e.position||{x:0,y:0,z:0};a.setPosition(r._x??r.x??e.x??0,r._y??r.y??e.y??1.6,-(r._z??r.z??e.z??0));const l={slots:{[s]:{name:s,loop:i.loop||!1,autoPlay:!1,volume:void 0!==i.volume?i.volume:1,pitch:1,positional:i.spatialSound||!1,distanceModel:co(i.distanceModel||"exponential"),maxDistance:i.maxDistance||1e4,refDistance:i.refDistance||1,rollOffFactor:i.rolloffFactor||1}}};a.addComponent("sound",l);const c=new t.Asset(`waypoint-audio-asset-${s}`,"audio",{url:i.url});F.assets.add(c),c.ready(()=>{const t=a.sound?.slot(s);t&&(t.asset=c.id);const e=Ie.get(s);e&&(e.assetReady=!0),console.log(`[Audio] Waypoint audio loaded: ${s}, spatialSound=${i.spatialSound}, maxDistance=${i.maxDistance}`)}),F.assets.load(c),F.root.addChild(a),Ie.set(s,{entity:a,waypointIndex:o,config:i,slotId:s,playing:!1,autoplayTriggered:!1,assetReady:!1}),console.log(`[Audio] Waypoint audio created: ${s} at waypoint ${o}, spatial=${i.spatialSound}`)}})}),Ie.size>0&&console.log(`[StorySplat Viewer] Setup ${Ie.size} waypoint audio sources`)),function(){const e=v.audioEmitters;e&&0!==e.length&&(console.log(`[Audio] Setting up ${e.length} standalone audio emitters`),e.forEach(e=>{if(!e.enabled)return void console.log(`[Audio] Skipping disabled emitter: ${e.name||e.id}`);if(!e.url)return void console.warn(`[Audio] Emitter has no URL: ${e.name||e.id}`);const o=e.id||`emitter-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,n=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${o}`);i.setPosition(n.x,n.y,n.z);const s=co(e.distanceModel||"linear");i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||100,rollOffFactor:e.rolloffFactor||1,distanceModel:s,volume:e.volume??.5}),i.sound?.addSlot(o,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1});const a=new t.Asset(`audio-emitter-${o}`,"audio",{url:e.url});a.on("load",()=>{if(N)return;const t=i.sound?.slot(o);t&&(t.asset=a.id);const n=po.get(o);n&&(n.assetReady=!0,!1!==e.autoplay&&t&&(console.log(`[Audio] Autoplay emitter: ${e.name||o}`),t.play(),n.playing=!0)),console.log(`[Audio] Emitter loaded: ${e.name||o}, spatial=${!1!==e.spatialSound}, maxDistance=${e.maxDistance||100}`)}),F.assets.add(a),F.assets.load(a),F.root.addChild(i),po.set(o,{entity:i,config:e,slotId:o,playing:!1,assetReady:!1}),console.log(`[Audio] Emitter created: ${e.name||o} at (${n.x}, ${n.y}, ${n.z})`)}),po.size>0&&console.log(`[StorySplat Viewer] Setup ${po.size} standalone audio emitters`))}(),console.log("[StorySplat Viewer] 🎆 Calling initParticleSystems..."),async function(){if(console.log("═══════════════════════════════════════"),console.log("🎆 PARTICLE SYSTEM INITIALIZATION"),console.log("═══════════════════════════════════════"),console.log("[Particle] config.particles:",v.particles),console.log("[Particle] Type:",typeof v.particles),console.log("[Particle] Is Array:",Array.isArray(v.particles)),!v.particles||0===v.particles.length)return console.log("[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)"),void console.log("═══════════════════════════════════════");console.log(`[Particle] 📋 Found ${v.particles.length} particle system(s) to create`);for(let t=0;t<v.particles.length;t++){const e=v.particles[t];console.log(`[Particle] --- Particle System ${t+1}/${v.particles.length} ---`),console.log("[Particle] Raw config:",JSON.stringify(e,null,2));try{const o=e.particleTexture||"flare";let n,i;"custom"===o&&e.customTextureUrl?(n=e.customTextureUrl,i=`custom_${e.id||e.name||t}`,console.log(`[Particle] Custom texture: ${n.substring(0,60)}...`)):(n=Be[o]||Be.flare,i=o,console.log(`[Particle] Texture: ${o} -> ${n.substring(0,60)}...`)),console.log("[Particle] Loading texture...");const s=await Oe(i,n);console.log("[Particle] ✅ Texture loaded:",s.name),console.log("[Particle] Creating entity...");const a=He(e);console.log("[Particle] ✅ Entity created:",a.name),a.particlesystem?(a.particlesystem.colorMap=s,console.log("[Particle] ✅ Texture applied to particle system"),console.log("[Particle] Particle system properties:",{numParticles:a.particlesystem.numParticles,lifetime:a.particlesystem.lifetime,rate:a.particlesystem.rate,loop:a.particlesystem.loop,autoPlay:a.particlesystem.autoPlay})):console.warn("[Particle] ⚠️ Entity has no particlesystem component!"),F.root.addChild(a),console.log("[Particle] ✅ Entity added to scene");const r=a.getPosition();console.log(`[Particle] Position: (${r.x.toFixed(2)}, ${r.y.toFixed(2)}, ${r.z.toFixed(2)})`);const l=(e.id||e.name||`particle-${$e.size}`).replace(/[^a-zA-Z0-9]/g,"_");$e.set(l,a),console.log(`[Particle] ✅ SUCCESS: Created "${e.name||l}"`)}catch(t){const o=t instanceof Error?t:{message:String(t),stack:""};console.error(`[Particle] ❌ FAILED to create particle system: ${e.name}`),console.error(`[Particle] Error message: ${o.message||"No message"}`),console.error(`[Particle] Error stack: ${o.stack||"No stack"}`),console.error("[Particle] Error object:",t)}}console.log("═══════════════════════════════════════"),console.log(`[Particle] 🎉 COMPLETE: ${$e.size}/${v.particles.length} particle systems created`),console.log("[Particle] Active particle systems:",Array.from($e.keys())),console.log("═══════════════════════════════════════")}(),async function(){v.customMeshes&&0!==v.customMeshes.length?(console.log(`[StorySplat Viewer] Creating ${v.customMeshes.length} custom meshes...`),console.log("[StorySplat Viewer] All custom mesh configs:",v.customMeshes),setTimeout(()=>{let t=0,e=0;v.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{qe(o,n)?t++:(e++,console.warn(`[CustomMesh] Mesh ${o.name} returned null (likely missing modelUrl)`))}catch(t){console.error("[CustomMesh] Error loading mesh",o.name,":",t),e++}else console.log("[CustomMesh] Skipping disabled mesh:",o.name,"(enabled =",o.enabled,")"),e++}),console.log(`[CustomMesh] Summary: ${t} loaded, ${e} skipped`)},100),console.log(`[StorySplat Viewer] ${v.customMeshes.length} custom meshes queued for loading`)):console.log("[StorySplat Viewer] No custom meshes to create")}(),eo(),v.lights&&0!==v.lights.length?(console.log(`[StorySplat Viewer] Creating ${v.lights.length} custom lights...`),v.lights.forEach((t,e)=>{let o=null;switch(t.type){case"point":o=io(t);break;case"directional":o=so(t);break;case"hemispheric":o=ao(t);break;case"ambient":ro(t);break;case"spot":o=lo(t);break;default:return void console.warn("[StorySplat Viewer] Unknown light type:",t.type)}o&&(F.root.addChild(o),oo.push(o),console.log(`[StorySplat Viewer] Created ${t.type} light:`,t.name||`Light ${e}`))}),console.log("[StorySplat Viewer] Lighting setup complete")):console.log("[StorySplat Viewer] No custom lights to create"),G&&(G.enabled=!0,console.log("[StorySplat Viewer] Reveal effect started immediately")),setTimeout(()=>{D.preloader&&d(D.preloader)},200),function(){if(!v.includeXR)return;if(!F.xr)return void console.warn("[StorySplat Viewer] WebXR not supported in this browser");const e=F.xr,o=v.xrMode||"both";"vr"!==o&&"both"!==o||(e.isAvailable(t.XRTYPE_VR)&&(D.vrButton?.classList.add("available"),console.log("[StorySplat Viewer] VR is available")),e.on("available:"+t.XRTYPE_VR,t=>{t?D.vrButton?.classList.add("available"):D.vrButton?.classList.remove("available")})),"ar"!==o&&"both"!==o||(e.isAvailable(t.XRTYPE_AR)&&(D.arButton?.classList.add("available"),console.log("[StorySplat Viewer] AR is available")),e.on("available:"+t.XRTYPE_AR,t=>{t?D.arButton?.classList.add("available"):D.arButton?.classList.remove("available")})),e.on("start",()=>{wt=!0,console.log("[StorySplat Viewer] XR session started"),"vr"===St?(D.vrButton?.classList.add("active"),D.vrButton.textContent=l(M,"exitVr")):"ar"===St&&(D.arButton?.classList.add("active"),D.arButton.textContent=l(M,"exitAr")),gt.disable(),ft&&ft.disable(),i.emit("xrStart",{type:St})}),e.on("end",()=>{wt=!1,console.log("[StorySplat Viewer] XR session ended"),_t(),D.vrButton?.classList.remove("active"),D.arButton?.classList.remove("active"),D.vrButton&&(D.vrButton.textContent=l(M,"vr")),D.arButton&&(D.arButton.textContent=l(M,"ar")),St=null,"explore"===vt?gt.enable():"walk"===vt&&ft&&ft.enable(),i.emit("xrEnd",{})}),D.vrButton&&D.vrButton.addEventListener("click",()=>{wt&&"vr"===St?e.end():!wt&&e.isAvailable(t.XRTYPE_VR)&&(St="vr",dt.camera.startXr(t.XRTYPE_VR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start VR:",t),St=null)}}))}),D.arButton&&D.arButton.addEventListener("click",()=>{wt&&"ar"===St?e.end():!wt&&e.isAvailable(t.XRTYPE_AR)&&(St="ar",dt.camera.startXr(t.XRTYPE_AR,t.XRSPACE_LOCALFLOOR,{callback:t=>{t&&(console.error("[StorySplat Viewer] Failed to start AR:",t),St=null)}}))})}(),v.htmlMeshes&&v.htmlMeshes.length>0){console.log("[StorySplat Viewer] Setting up HTML meshes:",v.htmlMeshes.length);const t=X(F,v.htmlMeshes);F.__htmlMeshManager=t,i.on("progressUpdate",()=>{const e=100*Wt,o=v.waypoints?.length||1,n=Math.round(Wt*Math.max(1,o-1));t.updateVisibility(e,n)})}if(v.customScript&&""!==v.customScript.trim()){console.log("[StorySplat Viewer] Initializing custom script system...");const e=function(e,o,n,i,s,a,r,l,c){if(!i||""===i.trim())return console.log("[Custom Script] No custom script provided"),null;console.log("[Custom Script] Initializing custom script system..."),console.log("[Custom Script] Script length:",i.length);const p=new Y(i);return p.initialize({app:e,camera:o,pc:t,canvas:n,getScrollPercentage:s,getCurrentWaypointIndex:a,getHotspots:r,getSplats:l,getHTMLMeshes:c}),p.execute(),p}(F,dt,I,v.customScript,()=>Wt,()=>O,()=>Me,()=>W?[W]:[],()=>v.htmlMeshes||[]);e&&(F.__customScriptSystem=e,console.log("[StorySplat Viewer] Custom script system initialized"))}try{const t=new URLSearchParams(window.location.search),e=t.get("waypoint"),o=t.get("autoplay");if(null!==e&&v.waypoints&&v.waypoints.length>0){const t=parseInt(e,10);!isNaN(t)&&t>=0&&t<v.waypoints.length&&(console.log("[StorySplat Viewer] URL param: navigating to waypoint",t),ge(t))}"true"!==o||n.autoPlay||v.autoPlay||(console.log("[StorySplat Viewer] URL param: starting autoplay"),Se())}catch(t){console.warn("[StorySplat Viewer] Could not parse URL parameters:",t)}if(i.emit("ready"),console.log("[StorySplat Viewer] Ready"),Pt(z),x){h(D,{nextWaypoint:fe,prevWaypoint:ve,play:Se,pause:Ee,isPlaying:()=>H,getCurrentWaypointIndex:()=>O,getWaypointCount:()=>v.waypoints?.length||0,getWaypoints:()=>v.waypoints||[],setCameraMode:Pt,on:(t,e)=>i.on(t,e)},z,M),D.muteButton&&D.muteButton.addEventListener("click",()=>{const t=(Re?mo():uo(),Re),e=D.muteButton.querySelector(".storysplat-unmuted-icon"),o=D.muteButton.querySelector(".storysplat-muted-icon");e&&(e.style.display=t?"none":""),o&&(o.style.display=t?"":"none"),D.muteButton.setAttribute("aria-label",l(M,t?"unmute":"mute"))});const t=e.querySelector(".storysplat-hotspot-popup-close");t&&t.addEventListener("click",()=>{Fe(),Ue(e)}),D.waypointListContainer&&v.waypoints&&v.waypoints.length>0&&(!function(t,e){if(!t.waypointListContainer)return;const o=t.waypointListContainer.querySelectorAll(".storysplat-waypoint-item"),n=t.waypointListContainer.querySelector(".storysplat-waypoint-list-toggle"),i=t.waypointListContainer.querySelector(".storysplat-waypoint-list-dropdown");o.forEach(t=>{t.addEventListener("click",()=>{const o=parseInt(t.getAttribute("data-waypoint-index")||"0",10);e(o),n?.classList.remove("open"),i?.classList.remove("open")})})}(D,t=>{console.log("[StorySplat Viewer] Waypoint list: jumping to waypoint",t),ge(t)}),i.on("waypointChange",({index:t})=>{f(D,t)}),f(D,0)),i.emit("progressUpdate",{progress:Wt,index:O}),v.waypoints&&v.waypoints.length>0&&i.emit("waypointChange",{index:0,waypoint:v.waypoints[0],prevIndex:-1})}(n.autoPlay||v.autoPlay)&&Se()}).catch(t=>{console.error("[StorySplat Viewer] Failed to initialize:",t),D.preloader&&d(D.preloader),i.emit("error",t)});const Ho=()=>{F.resizeCanvas()};window.addEventListener("resize",Ho);const Wo=new Map,Go=new Map,No=new Map,qo=new Map,jo=new Map;b&&(Me.forEach(t=>{const e=t.hotspotData?.id;e&&Wo.set(e,t)}),oo.forEach((t,e)=>{const o=v.lights?.[e],n=o?.id||o?.name||`light-${e}`;Go.set(n,t)}),$e.forEach((t,e)=>{No.set(e,t)}),_e.forEach(t=>{const e=t.portalData?.id;e&&jo.set(e,t)}));const Xo={app:F,canvas:I,goToWaypoint:t=>{if("explore"===vt)throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode("tour") first.');ge(t)},nextWaypoint:()=>{if("explore"===vt)throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode("tour") first.');fe()},prevWaypoint:()=>{if("explore"===vt)throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode("tour") first.');ve()},getCurrentWaypointIndex:()=>O,getWaypointCount:()=>v.waypoints?.length||0,setPosition:(t,e,o)=>{if("explore"!==vt)throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode("explore") first.');if(H)throw new Error("[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.");gt.disable(),dt.setPosition(t,e,o),gt.syncFromCamera(),gt.enable(),requestAnimationFrame(()=>{dt.setPosition(t,e,o),gt.syncFromCamera()})},setRotation:(t,e,o)=>{if("explore"!==vt)throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode("explore") first.');if(H)throw new Error("[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.");gt.disable(),dt.setEulerAngles(t,e,o),gt.syncFromCamera(),gt.enable(),requestAnimationFrame(()=>{dt.setEulerAngles(t,e,o),gt.syncFromCamera()})},getPosition:()=>{const t=dt.getPosition();return{x:t.x,y:t.y,z:t.z}},getRotation:()=>{const t=dt.getEulerAngles();return{x:t.x,y:t.y,z:t.z}},play:()=>{if("explore"===vt)throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode("tour") first.');Se()},pause:()=>{if("explore"===vt)throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode("tour") first.');Ee()},stop:()=>{if("explore"===vt)throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode("tour") first.');!function(){if(j)return j.stop(),void i.emit("playbackStop");Ee(),ue(0)}()},isPlaying:()=>H,setFrame:t=>j?.setFrame(t),getCurrentFrame:()=>j?.getCurrentFrame()??0,getTotalFrames:()=>j?.getTotalFrames()??0,setFps:t=>j?.setFps(t),getFps:()=>j?.getFps()??24,getFrameProgress:()=>j?.getProgress()??0,setFrameProgress:t=>j?.setProgress(t),goToOriginalSplat:Ht,goToSplat:async t=>{const e=Bt();if(t===e)return void Ht();const o=ot.find(e=>e.url===t);o||lt.has(t)||await Ut(t);const n=at||e;if(n===e&&W)Dt(W);else if(n){const t=lt.get(n);t&&(t.enabled=!1)}if(!Ft(t))throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${t}`);o&&$t(o.defaultExploreMode,!1),i.emit("splatChange",{url:t,isOriginal:!1})},getCurrentSplatUrl:function(){return at||Bt()},isShowingOriginalSplat:function(){return at===Bt()||null===at},getAdditionalSplats:()=>ot.map(t=>({url:t.url,name:t.name,waypointIndex:t.waypointIndex,percentage:t.percentage})),destroy:()=>{N=!0,Ee(),j&&(j.destroy(),j=null),window.removeEventListener("resize",Ho),D.preloader&&D.preloader.remove(),D.scrollControls&&D.scrollControls.remove(),D.fullscreenButton&&D.fullscreenButton.remove(),D.helpButton&&D.helpButton.remove(),D.helpPanel&&D.helpPanel.remove(),D.waypointInfo&&D.waypointInfo.remove(),D.watermark&&D.watermark.remove();const t=document.getElementById("storysplat-viewer-styles");t&&t.remove(),e.classList.remove("storysplat-viewer-container"),yo.forEach(t=>t.destroy()),yo.length=0,Qe(),gt.setCollisionEntities([]),ft&&ft.destroy();const o=F.__customScriptSystem;o&&o.dispose();const n=F.__htmlMeshManager;n&&n.destroy(),Et&&(Et.destroy(),Et=null),Ct&&(Ct.destroy(),Ct=null),F.destroy(),I.remove()},resize:Ho,navigateToScene:async t=>{const e={targetSceneId:t,activationMode:"click"};await _o(e)},setCameraMode:t=>Pt(t),getCameraMode:()=>vt,setExploreMode:t=>{if("explore"!==vt)throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode("explore") first.');gt.setMode(t),At(t)},setProgress:t=>{if("explore"===vt)throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode("tour") first.');ue(t)},getProgress:()=>Wt,muteAll:()=>uo(),unmuteAll:()=>mo(),isMuted:()=>Re,getHotspots:()=>Me.map(t=>{const e=t.hotspotData,o=t.getPosition();return{id:e?.id||"",title:e?.title||e?.information?.substring(0,30)||"",type:e?.type||"sphere",position:{x:o.x,y:o.y,z:o.z}}}),triggerHotspot:t=>{const o=Me.find(e=>e.hotspotData?.id===t);if(!o)throw new Error(`[StorySplat Viewer] Hotspot not found: ${t}`);const n=o.hotspotData;n&&u(e,n,M)},closeHotspot:()=>{const t=e.querySelector(".storysplat-hotspot-popup"),o=e.querySelector(".storysplat-hotspot-overlay");t&&t.classList.remove("visible"),o&&o.classList.remove("visible"),Fe(),Ue(e)},setButtonLabels:t=>{M={...M,...t};const o=t=>l(M,t),n=(t,n)=>{const i=e.querySelector(`.storysplat-mode-btn[data-mode="${t}"]`);i&&(i.textContent=o(n))};n("tour","tour"),n("explore","explore"),n("walk","walk");const i=e.querySelector('.storysplat-explore-btn[data-explore-mode="orbit"]');i&&(i.textContent=o("orbit"));const s=e.querySelector('.storysplat-explore-btn[data-explore-mode="fly"]');s&&(s.textContent=o("fly"));const a=e.querySelector(".storysplat-btn-prev");a&&(a.textContent=o("previous"));const r=e.querySelector(".storysplat-btn-next");r&&(r.textContent=o("next"));const c=e.querySelector(".storysplat-waypoint-list-toggle");if(c){const t=c.querySelector("svg");c.textContent="",c.append(o("waypoints")),t&&c.appendChild(t),c.setAttribute("aria-label",o("waypoints"))}const p=e.querySelector(".storysplat-vr-btn");p&&!p.classList.contains("active")&&(p.textContent=o("vr"),p.setAttribute("aria-label",o("vr")));const d=e.querySelector(".storysplat-ar-btn");d&&!d.classList.contains("active")&&(d.textContent=o("ar"),d.setAttribute("aria-label",o("ar")));const h=e.querySelector(".storysplat-fullscreen-btn");h&&h.setAttribute("aria-label",o("fullscreen"));const u=e.querySelector(".storysplat-hotspot-popup-close");u&&(u.textContent=o("close"));const m=e.querySelector(".storysplat-portal-popup-confirm");m&&(m.textContent=o("yes"));const y=e.querySelector(".storysplat-portal-popup-cancel");y&&(y.textContent=o("cancel"));const g=e.querySelector(".storysplat-help-panel");if(g){g.textContent="";const t=(t,e,o)=>{const n=document.createElement(t);if(o){const t=document.createElement("strong");t.textContent=e,n.appendChild(t)}else n.textContent=e;return g.appendChild(n),n},e=()=>g.appendChild(document.createElement("br"));t("h3",o("helpTitle")),t("p",o("helpCameraModes"),!0),t("p",`• ${o("tour")} - ${o("helpTourDesc")}`),t("p",`• ${o("explore")} - ${o("helpExploreDesc")}`),t("p",`• ${o("walk")} - ${o("helpWalkDesc")}`),e(),t("p",`${o("tour")} Mode:`,!0),t("p","• Scroll - Move along path"),t("p","• Drag - Look around"),e(),t("p",`${o("explore")} Mode:`,!0),t("p","• LMB Drag - Orbit camera"),t("p","• RMB Drag - Fly/look"),t("p","• WASD/QE - Move camera"),t("p","• Shift - Move fast"),t("p","• Scroll/Pinch - Zoom"),t("p","• Double-click - Focus"),e(),t("p",`${o("walk")} Mode:`,!0),t("p","• Click to lock mouse"),t("p","• WASD/Arrows - Move"),t("p","• Mouse - Look around"),t("p","• Shift - Sprint"),t("p","• Space - Jump")}const f=e.querySelector(".storysplat-help-btn");f&&f.setAttribute("title",o("helpTitle"))},on:(t,e)=>i.on(t,e),off:(t,e)=>i.off(t,e)};if(b){const e=Xo;return e.setCameraMode=t=>Pt(t),e.getCameraMode=()=>vt,e.getCameraControls=()=>gt,e.getApp=()=>F,e.getSplatEntity=()=>W,e.getAllHotspotEntities=()=>Wo,e.getAllLightEntities=()=>Go,e.getCamera=()=>dt,e.setProgress=t=>ue(t),e.getProgress=()=>Wt,e.addHotspot=t=>{const e=t.id||`hotspot-${Date.now()}`,o=bo(t,Me.length);return Wo.set(e,o),e},e.removeHotspot=t=>{const e=Wo.get(t);if(e){e.destroy(),Wo.delete(t);const o=Me.indexOf(e);o>=0&&Me.splice(o,1)}},e.updateHotspot=(t,e)=>{const o=Wo.get(t);if(!o)return;e.position&&o.setPosition(e.position.x,e.position.y,-e.position.z);const n=o.hotspotData||{};o.hotspotData={...n,...e}},e.addLight=t=>{const e=t.id||t.name||`light-${Date.now()}`;let o=null;switch(t.type){case"point":default:o=io(t);break;case"directional":o=so(t);break;case"hemispheric":o=ao(t);break;case"ambient":ro(t);break;case"spot":o=lo(t)}return o&&(F.root.addChild(o),oo.push(o),Go.set(e,o)),e},e.removeLight=t=>{const e=Go.get(t);if(e){e.destroy(),Go.delete(t);const o=oo.indexOf(e);o>=0&&oo.splice(o,1)}},e.updateLight=(t,e)=>{const o=Go.get(t);o&&(e.position&&o.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0)),o.light&&(void 0!==e.intensity&&(o.light.intensity=e.intensity),void 0!==e.range&&(o.light.range=e.range),void 0!==e.castShadows&&(o.light.castShadows=e.castShadows),e.color&&(o.light.color=no(e.color))))},e.addCustomMesh=t=>{const e=t.id||t.name||`mesh-${Date.now()}`,o=qe(t,We.size);return o&&qo.set(e,o),e},e.removeCustomMesh=t=>{const e=qo.get(t);e&&(We.forEach((t,o)=>{t.entity===e&&We.delete(o)}),e.destroy(),qo.delete(t))},e.addParticleSystem=t=>{const e=t.id||t.name||`particle-${Date.now()}`,o=He(t),n=t.particleTexture||"flare";let i,s;"custom"===n&&t.customTextureUrl?(i=t.customTextureUrl,s=`custom_${e}`):(i=Be[n]||Be.flare,s=n),Oe(s,i).then(t=>{o.particlesystem&&(o.particlesystem.colorMap=t)}).catch(t=>{console.warn("[Editor] Failed to load particle texture:",t)}),F.root.addChild(o);const a=e.replace(/[^a-zA-Z0-9]/g,"_");return $e.set(a,o),No.set(e,o),e},e.removeParticleSystem=t=>{const e=t.replace(/[^a-zA-Z0-9]/g,"_"),o=No.get(t)||$e.get(e);o&&(o.destroy(),No.delete(t),$e.delete(e))},e.addPortal=t=>{const e=t.id||`portal-${Date.now()}`,o=Eo(t,_e.length);return jo.set(e,o),e},e.removePortal=t=>{const e=jo.get(t);if(e){const o=e.videoElement;o&&(o.pause(),o.src=""),e.destroy(),jo.delete(t);const n=_e.indexOf(e);n>=0&&_e.splice(n,1)}},e.updatePortal=(t,e)=>{const o=jo.get(t);if(!o)return;e.position&&o.setPosition(e.position.x??0,e.position.y??0,-(e.position.z??0));const n=o.portalData||{};o.portalData={...n,...e}},e.addHtmlMesh=t=>{const e=t.id||`html-mesh-${Date.now()}`;let o=F.__htmlMeshManager;return o||(o=X(F,[]),F.__htmlMeshManager=o),o.createMesh({...t,id:e,html:t.html||t.htmlContent||""}),e},e.removeHtmlMesh=t=>{const e=F.__htmlMeshManager;e&&e.destroyMesh(t)},e.addCollisionMesh=e=>{const o=e.id||`collision-${Date.now()}`,n=new t.Entity(`collision-mesh-${o}`),i=e.position,s=Array.isArray(i)?i[0]:i.x,a=Array.isArray(i)?i[1]:i.y,r=Array.isArray(i)?i[2]:i.z;if(n.setPosition(s,a,-(r??0)),e.rotation){const t=e.rotation,o=Array.isArray(t)?t[0]:t.x,i=Array.isArray(t)?t[1]:t.y,s=Array.isArray(t)?t[2]:t.z;n.setEulerAngles(o??0,i??0,s??0)}if(e.scaling){const t=e.scaling,o=Array.isArray(t)?t[0]:t.x,i=Array.isArray(t)?t[1]:t.y,s=Array.isArray(t)?t[2]:t.z;n.setLocalScale(o??1,i??1,s??1)}const l=e.meshType||"cube";if("custom"!==l){const e="cube"===l?"box":"floor"===l?"plane":l;n.addComponent("render",{type:e,castShadows:!1,receiveShadows:!1});const o=new t.StandardMaterial;o.diffuse=new t.Color(0,1,0),o.opacity=.3,o.blendType=t.BLEND_NORMAL,o.depthWrite=!1,o.update(),n.render&&n.render.meshInstances.forEach(t=>{t.material=o})}return n.addComponent("collision",{type:"sphere"===l?"sphere":"box"}),n.enabled=!1!==e.visible,n._collisionMeshId=o,F.root.addChild(n),o},e.removeCollisionMesh=t=>{const e=F.root.children;for(let o=e.length-1;o>=0;o--)if(e[o]._collisionMeshId===t){e[o].destroy();break}},e.addAudioEmitter=e=>{const o=e.id||`emitter-${Date.now()}`,n=e.position||{x:0,y:0,z:0},i=new t.Entity(`audio-emitter-${o}`);if(i.setPosition(n.x,n.y,n.z),i.addComponent("sound",{positional:!1!==e.spatialSound,refDistance:e.refDistance||1,maxDistance:e.maxDistance||100,rollOffFactor:e.rolloffFactor||1,volume:e.volume??.5}),i.sound?.addSlot(o,{volume:e.volume??.5,loop:!1!==e.loop,autoPlay:!1,overlap:!1}),e.url){const n=new t.Asset(`audio-emitter-${o}`,"audio",{url:e.url});n.on("load",()=>{if(N)return;const t=i.sound?.slot(o);t&&(t.asset=n.id,!1!==e.autoplay&&t.play())}),F.assets.add(n),F.assets.load(n)}return F.root.addChild(i),po.set(o,{entity:i,config:e,slotId:o,playing:!1,assetReady:!1}),o},e.removeAudioEmitter=t=>{const e=po.get(t);if(e){const o=e.entity.sound?.slot(e.slotId);o&&o.stop(),e.entity.destroy(),po.delete(t)}},e.updateAudioEmitter=(t,e)=>{const o=po.get(t);o&&(e.position&&o.entity.setPosition(e.position.x,e.position.y,e.position.z),void 0!==e.volume&&o.entity.sound&&(o.entity.sound.volume=e.volume),o.config={...o.config,...e})},e.setSkybox=(t,e)=>{!function(t,e){Ke&&(Ke.destroy(),Ke=null),to&&(F.off("update",to),to=null),t&&(v.skybox={url:t,rotation:e??0},v.skyboxUrl=t,v.skyboxRotation=e??0,eo())}(t,e)},e.setBackgroundColor=e=>{const o=new t.Color(e.r/255,e.g/255,e.b/255);dt.camera&&(dt.camera.clearColor=o)},e.setFOV=t=>{dt.camera&&(dt.camera.fov=t)},e.addWaypoint=t=>{v.waypoints||(v.waypoints=[]),v.waypoints.push(t)},e.removeWaypoint=t=>{v.waypoints&&t>=0&&t<v.waypoints.length&&v.waypoints.splice(t,1)},e.updateWaypoint=(t,e)=>{v.waypoints&&t>=0&&t<v.waypoints.length&&Object.assign(v.waypoints[t],e)},e.rebuildTourPath=()=>{console.log("[Editor] Tour path will use updated waypoints on next progress update")},e}return Xo}async function ot(t,e,o){console.log("[StorySplat Viewer] Fetching scene from:",e);const n=await fetch(e);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),et(t,i,o)}function nt(t,e,o){return t+(e-t)*o}async function it(t,e,o,n,i){try{const s=await fetch(`${t}/api/track-embed`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sceneId:e,ownerId:o,type:n,..."bandwidth"===n&&i?{bytes:i}:{}})});s.ok?console.log(`[StorySplat] Tracked ${n}${"bandwidth"===n?` (${(i/1024/1024).toFixed(2)}MB)`:""}`):console.warn(`[StorySplat] Failed to track ${n}:`,s.status)}catch(t){console.warn(`[StorySplat] Error tracking ${n}:`,t)}}class st extends Error{constructor(t){super(`Scene not found: ${t}`),this.name="SceneNotFoundError"}}class at extends Error{constructor(t,e){super(t),this.name="SceneApiError",this.statusCode=e}}const rt=function(){if("undefined"!=typeof process&&process.env?.STORYSPLAT_API_URL)return process.env.STORYSPLAT_API_URL;const t="undefined"!=typeof window?window:void 0;return t?.__STORYSPLAT_API_URL__?t.__STORYSPLAT_API_URL__:"https://discover.storysplat.com"}();async function lt(t,e,o={}){const n=o.baseUrl||rt;console.log(`[StorySplat Viewer] Fetching scene: ${e}`);const i=`${n}/api/scene/${encodeURIComponent(e)}`,s={"Content-Type":"application/json"};o.apiKey&&(s.Authorization=`Bearer ${o.apiKey}`);const a=await fetch(i,{method:"GET",headers:s});if(!a.ok){if(404===a.status)throw new st(e);const t=await a.text();let o;try{o=JSON.parse(t).error||`API error: ${a.status}`}catch{o=`API error: ${a.status}`}throw new at(o,a.status)}const r=await a.json();if(!r.success||!r.data)throw new at("Invalid API response format",500);console.log(`[StorySplat Viewer] Scene loaded: "${r.meta.name}"`);const l=r.meta.ownerId,c={...r.data,name:r.data.name||r.meta.name,thumbnailUrl:r.data.thumbnailUrl||r.meta.thumbnailUrl},{baseUrl:p,apiKey:d,...h}=o,u=et(t,c,h);it(n,e,l,"view");let m=!1;return u.on("loaded",t=>{m||(m=!0,t&&t.bandwidthUsed>0&&t.isStorySplatHosted?it(n,e,l,"bandwidth",t.bandwidthUsed):console.log("[StorySplat] Bandwidth not tracked (self-hosted or no data)"))}),u}async function ct(t,e={}){const o=`${e.baseUrl||rt}/api/scene/${encodeURIComponent(t)}/meta`,n={"Content-Type":"application/json"};e.apiKey&&(n.Authorization=`Bearer ${e.apiKey}`);const i=await fetch(o,{method:"GET",headers:n});if(!i.ok){if(404===i.status)throw new st(t);throw new at(`API error: ${i.status}`,i.status)}const s=await i.json();return{...s,userName:s.userName||"Unknown",userSlug:s.userSlug||"unknown"}}class pt{constructor(e,o,n={}){this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null,this._mode="none",this.app=e,this.camera=o,this.gizmoLayer=t.Gizmo.createLayer(e),this.translateGizmo=new t.TranslateGizmo(o,this.gizmoLayer),this.rotateGizmo=new t.RotateGizmo(o,this.gizmoLayer),this.scaleGizmo=new t.ScaleGizmo(o,this.gizmoLayer),this.setSnap(n.snap??!1,n.snapIncrement??1),this.setCoordSpace(n.coordSpace??"world"),this.setupEvents(this.translateGizmo),this.setupEvents(this.rotateGizmo),this.setupEvents(this.scaleGizmo)}setupEvents(t){t.on("transform:start",()=>{this.onTransformStart?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})}),t.on("transform:move",()=>{this.onTransformMove?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})}),t.on("transform:end",()=>{this.onTransformEnd?.({entity:this.attachedEntity,position:this.attachedEntity?.getPosition().clone(),rotation:this.attachedEntity?.getRotation().clone(),scale:this.attachedEntity?.getLocalScale().clone()})})}get mode(){return this._mode}setMode(t){switch(this._mode=t,this.activeGizmo?.detach(),this.activeGizmo=null,t){case"translate":this.activeGizmo=this.translateGizmo;break;case"rotate":this.activeGizmo=this.rotateGizmo;break;case"scale":this.activeGizmo=this.scaleGizmo}this.activeGizmo&&this.attachedEntity&&this.activeGizmo.attach([this.attachedEntity])}attach(t){this.attachedEntity=t,this.activeGizmo&&(t?this.activeGizmo.attach([t]):this.activeGizmo.detach())}attachToMesh(t){this.attach(t)}detach(){this.attachedEntity=null,this.activeGizmo?.detach()}set positionGizmoEnabled(t){t&&"translate"!==this._mode?this.setMode("translate"):t||"translate"!==this._mode||this.setMode("none")}get positionGizmoEnabled(){return"translate"===this._mode}set rotationGizmoEnabled(t){t&&"rotate"!==this._mode?this.setMode("rotate"):t||"rotate"!==this._mode||this.setMode("none")}get rotationGizmoEnabled(){return"rotate"===this._mode}set scaleGizmoEnabled(t){t&&"scale"!==this._mode?this.setMode("scale"):t||"scale"!==this._mode||this.setMode("none")}get scaleGizmoEnabled(){return"scale"===this._mode}setSnap(t,e){this.translateGizmo&&(this.translateGizmo.snap=t,void 0!==e&&(this.translateGizmo.snapIncrement=e)),this.rotateGizmo&&(this.rotateGizmo.snap=t,void 0!==e&&(this.rotateGizmo.snapIncrement=e)),this.scaleGizmo&&(this.scaleGizmo.snap=t,void 0!==e&&(this.scaleGizmo.snapIncrement=e))}setCoordSpace(t){this.translateGizmo&&(this.translateGizmo.coordSpace=t),this.rotateGizmo&&(this.rotateGizmo.coordSpace=t),this.scaleGizmo&&(this.scaleGizmo.coordSpace=t)}onTransform(t){this.onTransformStart=t.start,this.onTransformMove=t.move,this.onTransformEnd=t.end}update(){this.activeGizmo?.update()}destroy(){this.translateGizmo?.destroy(),this.rotateGizmo?.destroy(),this.scaleGizmo?.destroy(),this.translateGizmo=null,this.rotateGizmo=null,this.scaleGizmo=null,this.activeGizmo=null,this.attachedEntity=null}}class dt{constructor(e,o,n={}){this.selectedEntities=new Set,this.highlightedEntity=null,this.originalMaterials=new Map,this.app=e,this.camera=o,this.config={highlightColor:n.highlightColor??new t.Color(.2,.5,1,1),multiSelect:n.multiSelect??!1},this.picker=new t.Picker(e,1,1,!0)}async pickAtScreenPosition(t,e){const o=this.app.graphicsDevice.canvas,n=this.camera.camera;if(!n||!o)return null;const i=.25,s=Math.max(1,Math.floor(o.clientWidth*i)),a=Math.max(1,Math.floor(o.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(n,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i),p=this.picker.getSelection(l,c);if(p&&p.length>0){const t=p[0];if(t&&t.node){let e=t.node;for(;e.parent&&e.parent!==this.app.root;){const t=e.parent;if(t.tags?.has("selectable")||t.tags?.has("hotspot")||t.tags?.has("waypoint")||t.tags?.has("light")){e=t;break}e=t}return e}}return null}async getWorldPointAtScreen(t,e){const o=this.app.graphicsDevice.canvas,n=this.camera.camera;if(!n||!o)return null;const i=.25,s=Math.max(1,Math.floor(o.clientWidth*i)),a=Math.max(1,Math.floor(o.clientHeight*i));this.picker.resize(s,a);const r=this.app.scene.layers.getLayerByName("World");if(!r)return null;this.picker.prepare(n,this.app.scene,[r]);const l=Math.floor(t*i),c=Math.floor(e*i);try{return await this.picker.getWorldPointAsync(l,c)||null}catch(t){return null}}select(t,e=!1){const o=this.getSelectedEntity();e||this.deselectAll(),t&&(this.selectedEntities.add(t),this.applyHighlight(t)),this.onSelectionChange?.({entity:t,previousEntity:o})}deselect(t){this.selectedEntities.has(t)&&(this.selectedEntities.delete(t),this.removeHighlight(t),this.onSelectionChange?.({entity:this.getSelectedEntity(),previousEntity:t}))}deselectAll(){const t=this.getSelectedEntity();this.selectedEntities.forEach(t=>{this.removeHighlight(t)}),this.selectedEntities.clear(),t&&this.onSelectionChange?.({entity:null,previousEntity:t})}isSelected(t){return this.selectedEntities.has(t)}getSelectedEntity(){const t=Array.from(this.selectedEntities);return t.length>0?t[0]:null}getSelectedEntities(){return Array.from(this.selectedEntities)}applyHighlight(e){if(!e.render)return;const o=new Map;e.render.meshInstances.forEach((e,n)=>{if(e.material){o.set(n,e.material);const i=e.material.clone();i instanceof t.StandardMaterial&&(i.emissive=this.config.highlightColor,i.emissiveIntensity=.3,i.update()),e.material=i}}),this.originalMaterials.set(e,o)}removeHighlight(t){const e=this.originalMaterials.get(t);e&&t.render&&(t.render.meshInstances.forEach((t,o)=>{const n=e.get(o);n&&(t.material=n)}),this.originalMaterials.delete(t))}onSelect(t){this.onSelectionChange=t}async handlePointerDown(t,e=!1){let o,n;if(t instanceof MouseEvent)o=t.clientX,n=t.clientY;else{const e=t.touches[0];o=e.clientX,n=e.clientY}const i=await this.pickAtScreenPosition(o,n);return i?this.select(i,e):e||this.deselectAll(),i}destroy(){this.deselectAll(),this.selectedEntities.clear(),this.originalMaterials.clear()}}class ht{constructor(e,o,n,i={}){this.fpvEntity=null,this._enabled=!1,this.app=e,this.tourCamera=o,this.cameraControls=n,this.config=i,this.editCamera=new t.Entity("editCamera");const s=o.camera;this.editCamera.addComponent("camera",{clearColor:s?.clearColor||new t.Color(.1,.1,.1),fov:s?.fov||60,nearClip:s?.nearClip||.1,farClip:s?.farClip||1e3});const a=o.getPosition();this.editCamera.setPosition(a.x,a.y+5,a.z+10),this.editCamera.lookAt(a),this.editCamera.enabled=!1,e.root.addChild(this.editCamera),!1!==i.showFPVVisualization&&this.createFPVVisualization()}createFPVVisualization(){this.fpvEntity=new t.Entity("fpv-camera-viz"),this.fpvEntity.addComponent("render",{type:"cone",castShadows:!1,receiveShadows:!1}),this.fpvEntity.setLocalScale(.3,.5,.3),this.fpvEntity.enabled=!1,this.app.root.addChild(this.fpvEntity),this.app.on("update",()=>{if(this.fpvEntity&&this._enabled){const t=this.tourCamera.getPosition(),e=this.tourCamera.getRotation();this.fpvEntity.setPosition(t),this.fpvEntity.setRotation(e),this.fpvEntity.rotateLocal(90,0,0)}})}enable(){this._enabled=!0,this.tourCamera.enabled=!1,this.editCamera.enabled=!0,this.cameraControls.disable();const t=this.tourCamera.getPosition();this.editCamera.setPosition(t.x,t.y+5,t.z+10),this.editCamera.lookAt(t),this.fpvEntity&&(this.fpvEntity.enabled=!0)}disable(){this._enabled=!1,this.editCamera.enabled=!1,this.tourCamera.enabled=!0,this.fpvEntity&&(this.fpvEntity.enabled=!1)}get enabled(){return this._enabled}getCamera(){return this.editCamera}focusOn(t){const e=this.config.orbitDistance||10;this.editCamera.setPosition(t.x,t.y+.5*e,t.z+e),this.editCamera.lookAt(t)}syncClearColor(){this.editCamera.camera&&this.tourCamera.camera&&(this.editCamera.camera.clearColor=this.tourCamera.camera.clearColor)}setFOV(t){this.editCamera.camera&&(this.editCamera.camera.fov=t)}destroy(){this.disable(),this.editCamera.destroy(),this.fpvEntity&&(this.fpvEntity.destroy(),this.fpvEntity=null)}}export{C as CameraControls,r as DEFAULT_BUTTON_LABELS,ht as EditorCameraController,Z as FrameSequencePlayer,pt as GizmoManager,A as REVEAL_PRESETS,at as SceneApiError,st as SceneNotFoundError,dt as SelectionManager,et as createViewer,lt as createViewerFromSceneId,ot as createViewerFromUrl,a as exportPropsToViewerConfig,ct as fetchSceneMeta,n as generateHTML,i as generateHTMLFromUrl,P as getRevealPreset,s as transformSceneToExportProps};
|
|
2
2
|
//# sourceMappingURL=index.esm.js.map
|